// Copyright (C) 2020 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 // (at your option) 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 "dimensionsplugin.h" #include "pdfdrawwidget.h" #include "pdfwidgetutils.h" #include "settingsdialog.h" #include #include namespace pdfplugin { DimensionsPlugin::DimensionsPlugin() : pdf::PDFPlugin(nullptr), m_dimensionTools(), m_showDimensionsAction(nullptr), m_clearDimensionsAction(nullptr), m_settingsAction(nullptr) { } void DimensionsPlugin::setWidget(pdf::PDFWidget* widget) { Q_ASSERT(!m_widget); BaseClass::setWidget(widget); QAction* horizontalDimensionAction = new QAction(QIcon(":/pdfplugins/dimensiontool/linear-horizontal.svg"), tr("Horizontal Dimension"), this); QAction* verticalDimensionAction = new QAction(QIcon(":/pdfplugins/dimensiontool/linear-vertical.svg"), tr("Vertical Dimension"), this); QAction* linearDimensionAction = new QAction(QIcon(":/pdfplugins/dimensiontool/linear.svg"), tr("Linear Dimension"), this); QAction* perimeterDimensionAction = new QAction(QIcon(":/pdfplugins/dimensiontool/perimeter.svg"), tr("Perimeter"), this); QAction* rectanglePerimeterDimensionAction = new QAction(QIcon(":/pdfplugins/dimensiontool/rectangle-perimeter.svg"), tr("Rectangle Perimeter"), this); QAction* areaDimensionAction = new QAction(QIcon(":/pdfplugins/dimensiontool/area.svg"), tr("Area"), this); QAction* rectangleAreaDimensionAction = new QAction(QIcon(":/pdfplugins/dimensiontool/rectangle-area.svg"), tr("Rectangle Area"), this); QAction* angleAction = new QAction(QIcon(":/pdfplugins/dimensiontool/angle.svg"), tr("Angle"), this); horizontalDimensionAction->setObjectName("dimensiontool_LinearHorizontalAction"); verticalDimensionAction->setObjectName("dimensiontool_LinearVerticalAction"); linearDimensionAction->setObjectName("dimensiontool_LinearAction"); perimeterDimensionAction->setObjectName("dimensiontool_PerimeterAction"); rectanglePerimeterDimensionAction->setObjectName("dimensiontool_RectanglePerimeterAction"); areaDimensionAction->setObjectName("dimensiontool_AreaAction"); rectangleAreaDimensionAction->setObjectName("dimensiontool_RectangleAreaAction"); angleAction->setObjectName("dimensiontool_AngleAction"); horizontalDimensionAction->setCheckable(true); verticalDimensionAction->setCheckable(true); linearDimensionAction->setCheckable(true); perimeterDimensionAction->setCheckable(true); rectanglePerimeterDimensionAction->setCheckable(true); areaDimensionAction->setCheckable(true); rectangleAreaDimensionAction->setCheckable(true); angleAction->setCheckable(true); m_dimensionTools[DimensionTool::LinearHorizontal] = new DimensionTool(DimensionTool::LinearHorizontal, widget->getDrawWidgetProxy(), horizontalDimensionAction, this); m_dimensionTools[DimensionTool::LinearVertical] = new DimensionTool(DimensionTool::LinearVertical, widget->getDrawWidgetProxy(), verticalDimensionAction, this); m_dimensionTools[DimensionTool::Linear] = new DimensionTool(DimensionTool::Linear, widget->getDrawWidgetProxy(), linearDimensionAction, this); m_dimensionTools[DimensionTool::Perimeter] = new DimensionTool(DimensionTool::Perimeter, widget->getDrawWidgetProxy(), perimeterDimensionAction, this); m_dimensionTools[DimensionTool::RectanglePerimeter] = new DimensionTool(DimensionTool::RectanglePerimeter, widget->getDrawWidgetProxy(), rectanglePerimeterDimensionAction, this); m_dimensionTools[DimensionTool::Area] = new DimensionTool(DimensionTool::Area, widget->getDrawWidgetProxy(), areaDimensionAction, this); m_dimensionTools[DimensionTool::RectangleArea] = new DimensionTool(DimensionTool::RectangleArea, widget->getDrawWidgetProxy(), rectangleAreaDimensionAction, this); m_dimensionTools[DimensionTool::Angle] = new DimensionTool(DimensionTool::Angle, widget->getDrawWidgetProxy(), angleAction, this); pdf::PDFToolManager* toolManager = widget->getToolManager(); for (DimensionTool* tool : m_dimensionTools) { toolManager->addTool(tool); connect(tool, &DimensionTool::dimensionCreated, this, &DimensionsPlugin::onDimensionCreated); } m_showDimensionsAction = new QAction(QIcon(":/pdfplugins/dimensiontool/show-dimensions.svg"), tr("Show Dimensions"), this); m_clearDimensionsAction = new QAction(QIcon(":/pdfplugins/dimensiontool/clear-dimensions.svg"), tr("Clear Dimensions"), this); m_settingsAction = new QAction(QIcon(":/pdfplugins/dimensiontool/settings.svg"), tr("Settings"), this); m_showDimensionsAction->setCheckable(true); m_showDimensionsAction->setChecked(true); connect(m_showDimensionsAction, &QAction::triggered, this, &DimensionsPlugin::onShowDimensionsTriggered); connect(m_clearDimensionsAction, &QAction::triggered, this, &DimensionsPlugin::onClearDimensionsTriggered); connect(m_settingsAction, &QAction::triggered, this, &DimensionsPlugin::onSettingsTriggered); m_lengthUnit = DimensionUnit::getLengthUnits().front(); m_areaUnit = DimensionUnit::getAreaUnits().front(); m_angleUnit = DimensionUnit::getAngleUnits().front(); m_widget->getDrawWidgetProxy()->registerDrawInterface(this); updateActions(); } void DimensionsPlugin::setDocument(const pdf::PDFModifiedDocument& document) { BaseClass::setDocument(document); if (document.hasReset()) { m_dimensions.clear(); updateActions(); } } std::vector DimensionsPlugin::getActions() const { std::vector result; for (DimensionTool* tool : m_dimensionTools) { if (tool) { result.push_back(tool->getAction()); } } if (!result.empty()) { result.push_back(nullptr); result.push_back(m_showDimensionsAction); result.push_back(m_clearDimensionsAction); result.push_back(m_settingsAction); } return result; } void DimensionsPlugin::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_showDimensionsAction || !m_showDimensionsAction->isChecked() || m_dimensions.empty()) { // Nothing to draw return; } QLocale locale; painter->setRenderHint(QPainter::Antialiasing, true); for (const Dimension& dimension : m_dimensions) { if (pageIndex != dimension.getPageIndex()) { continue; } switch (dimension.getType()) { case Dimension::Linear: { QPointF p1 = pagePointToDevicePointMatrix.map(dimension.getPolygon().front()); QPointF p2 = pagePointToDevicePointMatrix.map(dimension.getPolygon().back()); // Swap so p1 is to the left of the page, before p2 (for correct determination of angle) if (p1.x() > p2.x()) { qSwap(p1, p2); } QLineF line(p1, p2); if (qFuzzyIsNull(line.length())) { // If we have zero line, then do nothing continue; } QLineF unitVectorLine = line.normalVector().unitVector(); QPointF unitVector = unitVectorLine.p2() - unitVectorLine.p1(); qreal extensionLineSize = pdf::PDFWidgetUtils::scaleDPI_y(painter->device(), 5); painter->setPen(Qt::black); painter->drawLine(line); QLineF extensionLineLeft(p1 - unitVector * extensionLineSize, p1 + unitVector * extensionLineSize); QLineF extensionLineRight(p2 - unitVector * extensionLineSize, p2 + unitVector * extensionLineSize); painter->drawLine(extensionLineLeft); painter->drawLine(extensionLineRight); QPointF textPoint = line.center(); qreal angle = line.angle(); QFontMetricsF fontMetrics(painter->font()); QRectF textRect(-line.length() * 0.5, -fontMetrics.lineSpacing(), line.length(), fontMetrics.lineSpacing()); QString text = QString("%1 %2").arg(locale.toString(dimension.getMeasuredValue() * m_lengthUnit.scale, 'f', 2), m_lengthUnit.symbol); painter->save(); painter->translate(textPoint); painter->rotate(-angle); painter->drawText(textRect, Qt::AlignCenter | Qt::TextDontClip, text); painter->restore(); break; } case Dimension::Perimeter: case Dimension::Area: { const bool isArea = dimension.getType() == Dimension::Type::Area; const std::vector& polygon = dimension.getPolygon(); qreal lineSize = pdf::PDFWidgetUtils::scaleDPI_x(painter->device(), 1); qreal pointSize = pdf::PDFWidgetUtils::scaleDPI_x(painter->device(), 5); painter->save(); QPen pen(Qt::black); pen.setWidthF(lineSize); pen.setCosmetic(true); QColor brushColor = Qt::black; brushColor.setAlphaF(0.1); painter->setPen(qMove(pen)); painter->setBrush(QBrush(brushColor, isArea ? Qt::SolidPattern : Qt::DiagCrossPattern)); painter->setMatrix(pagePointToDevicePointMatrix, true); painter->drawPolygon(polygon.data(), int(polygon.size()), Qt::OddEvenFill); painter->restore(); QPen penPoint(Qt::black); penPoint.setCapStyle(Qt::RoundCap); penPoint.setWidthF(pointSize); painter->setPen(penPoint); QPointF centerPoint(0, 0); for (const QPointF& point : polygon) { QPointF mappedPoint = pagePointToDevicePointMatrix.map(point); centerPoint += mappedPoint; painter->drawPoint(mappedPoint); } centerPoint *= 1.0 / qreal(polygon.size()); QString text; if (isArea) { text = tr("A = %1 %2").arg(locale.toString(dimension.getMeasuredValue() * m_areaUnit.scale, 'f', 2), m_areaUnit.symbol); } else { text = tr("p = %1 %2").arg(locale.toString(dimension.getMeasuredValue() * m_lengthUnit.scale, 'f', 2), m_lengthUnit.symbol); } painter->drawText(centerPoint, text); break; } case Dimension::Angular: { const std::vector& polygon = dimension.getPolygon(); QLineF line1(pagePointToDevicePointMatrix.map(polygon[1]), pagePointToDevicePointMatrix.map(polygon.front())); QLineF line2(pagePointToDevicePointMatrix.map(polygon[1]), pagePointToDevicePointMatrix.map(polygon.back())); qreal lineSize = pdf::PDFWidgetUtils::scaleDPI_x(painter->device(), 1); qreal pointSize = pdf::PDFWidgetUtils::scaleDPI_x(painter->device(), 5); qreal maxLength = qMax(line1.length(), line2.length()); line1.setLength(maxLength); line2.setLength(maxLength); QPen pen(Qt::black); pen.setWidthF(lineSize); painter->setPen(qMove(pen)); painter->drawLine(line1); painter->drawLine(line2); qreal startAngle = line1.angle() * 16; qreal angleLength = dimension.getMeasuredValue() * 16; QRectF rect(-maxLength * 0.5, -maxLength * 0.5, maxLength, maxLength); rect.translate(line1.p1()); painter->drawArc(rect, startAngle - angleLength, angleLength); QPen penPoint(Qt::black); penPoint.setCapStyle(Qt::RoundCap); penPoint.setWidthF(pointSize); painter->setPen(penPoint); painter->drawPoint(line1.p1()); painter->drawPoint(line1.p2()); painter->drawPoint(line2.p2()); QMatrix textMatrix; textMatrix.translate(line1.x1(), line1.y1()); textMatrix.rotate(-line1.angle() + dimension.getMeasuredValue() * 0.5); QPointF textPoint = textMatrix.map(QPointF(maxLength * 0.25, 0.0)); QString text = QString("%1 %2").arg(locale.toString(dimension.getMeasuredValue() * m_angleUnit.scale, 'f', 2), m_angleUnit.symbol); painter->drawText(textPoint, text); break; } default: Q_ASSERT(false); break; } } } void DimensionsPlugin::onShowDimensionsTriggered() { updateGraphics(); } void DimensionsPlugin::onClearDimensionsTriggered() { m_dimensions.clear(); updateActions(); updateGraphics(); } void DimensionsPlugin::onSettingsTriggered() { SettingsDialog dialog(m_widget, m_lengthUnit, m_areaUnit, m_angleUnit); dialog.exec(); updateGraphics(); } void DimensionsPlugin::onDimensionCreated(Dimension dimension) { m_dimensions.emplace_back(qMove(dimension)); updateActions(); updateGraphics(); } void DimensionsPlugin::updateActions() { if (m_showDimensionsAction) { m_showDimensionsAction->setEnabled(!m_dimensions.empty()); } if (m_clearDimensionsAction) { m_clearDimensionsAction->setEnabled(!m_dimensions.empty()); } } void DimensionsPlugin::updateGraphics() { if (m_widget) { m_widget->getDrawWidget()->getWidget()->update(); } } }