// Copyright (C) 2020-2021 Jakub Melka
//
// This file is part of PDF4QT.
//
// PDF4QT is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// with the written consent of the copyright owner, any later version.
//
// PDF4QT is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with PDF4QT. If not, see .
#include "dimensiontool.h"
#include "pdfwidgetutils.h"
#include "pdfdrawwidget.h"
#include
DimensionTool::DimensionTool(Style style, pdf::PDFDrawWidgetProxy* proxy, QAction* action, QObject* parent) :
BaseClass(proxy, action, parent),
m_style(style),
m_previewPointPixelSize(0),
m_pickTool(nullptr)
{
const bool isRectanglePicking = style == RectanglePerimeter || style == RectangleArea;
const pdf::PDFPickTool::Mode pickingMode = isRectanglePicking ? pdf::PDFPickTool::Mode::Rectangles : pdf::PDFPickTool::Mode::Points;
m_pickTool = new pdf::PDFPickTool(proxy, pickingMode, this);
addTool(m_pickTool);
connect(m_pickTool, &pdf::PDFPickTool::pointPicked, this, &DimensionTool::onPointPicked);
connect(m_pickTool, &pdf::PDFPickTool::rectanglePicked, this, &DimensionTool::onRectanglePicked);
m_previewPointPixelSize = pdf::PDFWidgetUtils::scaleDPI_x(proxy->getWidget(), 5);
}
void DimensionTool::drawPage(QPainter* painter,
pdf::PDFInteger pageIndex,
const pdf::PDFPrecompiledPage* compiledPage,
pdf::PDFTextLayoutGetter& layoutGetter,
const QMatrix& pagePointToDevicePointMatrix,
QList& errors) const
{
Q_UNUSED(compiledPage);
Q_UNUSED(layoutGetter);
Q_UNUSED(errors);
if (m_pickTool->getPageIndex() != pageIndex)
{
// Other page, nothing to draw
return;
}
if (m_style == RectanglePerimeter || m_style == RectangleArea)
{
// Nothing to draw, picking tool is already drawing picked rectangle
return;
}
painter->setPen(Qt::black);
const std::vector& points = m_pickTool->getPickedPoints();
for (size_t i = 1; i < points.size(); ++i)
{
painter->drawLine(pagePointToDevicePointMatrix.map(points[i - 1]), pagePointToDevicePointMatrix.map(points[i]));
}
if (!points.empty())
{
QMatrix inverted = pagePointToDevicePointMatrix.inverted();
QPointF adjustedPoint = adjustPagePoint(inverted.map(m_pickTool->getSnappedPoint()));
painter->drawLine(pagePointToDevicePointMatrix.map(points.back()), pagePointToDevicePointMatrix.map(adjustedPoint));
}
QPen pen = painter->pen();
pen.setWidth(m_previewPointPixelSize);
pen.setCapStyle(Qt::RoundCap);
painter->setPen(pen);
for (size_t i = 0; i < points.size(); ++i)
{
painter->drawPoint(pagePointToDevicePointMatrix.map(points[i]));
}
}
void DimensionTool::onPointPicked(pdf::PDFInteger pageIndex, QPointF pagePoint)
{
Q_UNUSED(pagePoint);
if (Dimension::isComplete(getDimensionType(), m_pickTool->getPickedPoints()))
{
// Create a new dimension...
std::vector points = m_pickTool->getPickedPoints();
for (QPointF& point : points)
{
point = adjustPagePoint(point);
}
pdf::PDFReal measuredValue = getMeasuredValue(pageIndex, points);
emit dimensionCreated(Dimension(getDimensionType(), pageIndex, measuredValue, qMove(points)));
m_pickTool->resetTool();
}
if ((m_style == Perimeter || m_style == Area) && m_pickTool->getPickedPoints().size() == 1)
{
m_pickTool->setCustomSnapPoints(pageIndex, m_pickTool->getPickedPoints());
}
}
void DimensionTool::onRectanglePicked(pdf::PDFInteger pageIndex, QRectF pageRectangle)
{
if (pageRectangle.isEmpty())
{
return;
}
std::vector points = { pageRectangle.topLeft(), pageRectangle.topRight(), pageRectangle.bottomRight(), pageRectangle.bottomLeft(), pageRectangle.topLeft() };
Q_ASSERT(Dimension::isComplete(getDimensionType(), points));
pdf::PDFReal measuredValue = getMeasuredValue(pageIndex, points);
emit dimensionCreated(Dimension(getDimensionType(), pageIndex, measuredValue, qMove(points)));
}
QPointF DimensionTool::adjustPagePoint(QPointF pagePoint) const
{
switch (m_style)
{
case Style::LinearHorizontal:
{
const std::vector& pickedPoints = m_pickTool->getPickedPoints();
if (!pickedPoints.empty())
{
const pdf::PDFPage* page = getDocument()->getCatalog()->getPage(m_pickTool->getPageIndex());
const bool rotated = page->getPageRotation() == pdf::PageRotation::Rotate90 || page->getPageRotation() == pdf::PageRotation::Rotate270;
if (rotated)
{
pagePoint.setX(pickedPoints.front().x());
}
else
{
pagePoint.setY(pickedPoints.front().y());
}
}
break;
}
case Style::LinearVertical:
{
const std::vector& pickedPoints = m_pickTool->getPickedPoints();
if (!pickedPoints.empty())
{
const pdf::PDFPage* page = getDocument()->getCatalog()->getPage(m_pickTool->getPageIndex());
const bool rotated = page->getPageRotation() == pdf::PageRotation::Rotate90 || page->getPageRotation() == pdf::PageRotation::Rotate270;
if (!rotated)
{
pagePoint.setX(pickedPoints.front().x());
}
else
{
pagePoint.setY(pickedPoints.front().y());
}
}
break;
}
default:
break;
}
return pagePoint;
}
Dimension::Type DimensionTool::getDimensionType() const
{
switch (m_style)
{
case DimensionTool::LinearHorizontal:
case DimensionTool::LinearVertical:
case DimensionTool::Linear:
return Dimension::Type::Linear;
case DimensionTool::Perimeter:
case DimensionTool::RectanglePerimeter:
return Dimension::Type::Perimeter;
case DimensionTool::Area:
case DimensionTool::RectangleArea:
return Dimension::Type::Area;
case DimensionTool::Angle:
return Dimension::Type::Angular;
}
Q_ASSERT(false);
return Dimension::Type::Linear;
}
pdf::PDFReal DimensionTool::getMeasuredValue(pdf::PDFInteger pageIndex, const std::vector& pickedPoints) const
{
const pdf::PDFPage* page = getDocument()->getCatalog()->getPage(pageIndex);
Q_ASSERT(page);
switch (getDimensionType())
{
case Dimension::Linear:
case Dimension::Perimeter:
{
pdf::PDFReal length = 0.0;
for (size_t i = 1; i < pickedPoints.size(); ++i)
{
QPointF vector = pickedPoints[i] - pickedPoints[i - 1];
length += qSqrt(QPointF::dotProduct(vector, vector));
}
return length * page->getUserUnit();
}
case Dimension::Area:
{
pdf::PDFReal area = 0.0;
// We calculate the area using standard formula for polygons.
// We determine integral over perimeter (for each edge of the polygon).
for (size_t i = 1; i < pickedPoints.size(); ++i)
{
const QPointF& p1 = pickedPoints[i - 1];
const QPointF& p2 = pickedPoints[i];
area += p1.x() * p2.y() - p1.y() * p2.x();
}
area = qAbs(area) * 0.5;
return area * page->getUserUnit() * page->getUserUnit();
}
case Dimension::Angular:
{
Q_ASSERT(pickedPoints.size() == 3);
QLineF line1(pickedPoints[1], pickedPoints.front());
QLineF line2(pickedPoints[1], pickedPoints.back());
return line1.angleTo(line2);
}
default:
Q_ASSERT(false);
break;
}
return 0.0;
}
bool Dimension::isComplete(Type type, const std::vector& polygon)
{
switch (type)
{
case Dimension::Linear:
return polygon.size() == 2;
case Dimension::Perimeter:
case Dimension::Area:
return polygon.size() > 2 && polygon.front() == polygon.back();
case Dimension::Angular:
return polygon.size() == 3;
default:
Q_ASSERT(false);
break;
}
return false;
}
DimensionUnits DimensionUnit::getLengthUnits()
{
DimensionUnits units;
units.emplace_back(1.0, DimensionTool::tr("pt"));
units.emplace_back(pdf::PDF_POINT_TO_INCH, DimensionTool::tr("in"));
units.emplace_back(pdf::PDF_POINT_TO_MM, DimensionTool::tr("mm"));
units.emplace_back(pdf::PDF_POINT_TO_MM * 0.1, DimensionTool::tr("cm"));
return units;
}
DimensionUnits DimensionUnit::getAreaUnits()
{
DimensionUnits units;
units.emplace_back(1.0, DimensionTool::tr("sq. pt"));
units.emplace_back(pdf::PDF_POINT_TO_INCH * pdf::PDF_POINT_TO_INCH, DimensionTool::tr("sq. in"));
units.emplace_back(pdf::PDF_POINT_TO_MM * pdf::PDF_POINT_TO_MM, DimensionTool::tr("sq. mm"));
units.emplace_back(pdf::PDF_POINT_TO_MM * 0.1 * pdf::PDF_POINT_TO_MM * 0.1, DimensionTool::tr("sq. cm"));
return units;
}
DimensionUnits DimensionUnit::getAngleUnits()
{
DimensionUnits units;
units.emplace_back(1.0, DimensionTool::tr("°"));
units.emplace_back(qDegreesToRadians(1.0), DimensionTool::tr("rad"));
return units;
}