From 5dd988890645b245777995d6f8893daa70635bec Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sat, 4 Apr 2020 17:33:10 +0200 Subject: [PATCH] Highlight and link annotations --- PdfForQtLib/sources/pdfannotation.cpp | 162 ++++++++++++++++++++++++-- PdfForQtLib/sources/pdfannotation.h | 15 ++- 2 files changed, 166 insertions(+), 11 deletions(-) diff --git a/PdfForQtLib/sources/pdfannotation.cpp b/PdfForQtLib/sources/pdfannotation.cpp index c63e2a3..51e4701 100644 --- a/PdfForQtLib/sources/pdfannotation.cpp +++ b/PdfForQtLib/sources/pdfannotation.cpp @@ -592,13 +592,13 @@ PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObject PDFAnnotationQuadrilaterals PDFAnnotation::parseQuadrilaterals(const PDFObjectStorage* storage, PDFObject quadrilateralsObject, const QRectF annotationRect) { QPainterPath path; - std::vector underlines; + PDFAnnotationQuadrilaterals::Quadrilaterals quadrilaterals; PDFDocumentDataLoaderDecorator loader(storage); std::vector points = loader.readNumberArray(quadrilateralsObject); const size_t quadrilateralCount = points.size() % 8; path.reserve(int(quadrilateralCount) + 5); - underlines.reserve(quadrilateralCount); + quadrilaterals.reserve(quadrilateralCount); for (size_t i = 0; i < quadrilateralCount; ++i) { const size_t offset = i * 8; @@ -609,12 +609,11 @@ PDFAnnotationQuadrilaterals PDFAnnotation::parseQuadrilaterals(const PDFObjectSt path.moveTo(p1); path.lineTo(p2); - path.lineTo(p3); path.lineTo(p4); - path.lineTo(p1); + path.lineTo(p3); path.closeSubpath(); - underlines.emplace_back(p1, p2); + quadrilaterals.emplace_back(PDFAnnotationQuadrilaterals::Quadrilateral{ p1, p2, p3, p4 }); } if (path.isEmpty() && annotationRect.isValid()) @@ -622,10 +621,10 @@ PDFAnnotationQuadrilaterals PDFAnnotation::parseQuadrilaterals(const PDFObjectSt // Jakub Melka: we are using points at the top, because PDF has inverted y axis // against the Qt's y axis. path.addRect(annotationRect); - underlines.emplace_back(annotationRect.topLeft(), annotationRect.topRight()); + quadrilaterals.emplace_back(PDFAnnotationQuadrilaterals::Quadrilateral{ annotationRect.topLeft(), annotationRect.topRight(), annotationRect.bottomLeft(), annotationRect.bottomRight() }); } - return PDFAnnotationQuadrilaterals(qMove(path), qMove(underlines)); + return PDFAnnotationQuadrilaterals(qMove(path), qMove(quadrilaterals)); } AnnotationLineEnding PDFAnnotation::convertNameToLineEnding(const QByteArray& name) @@ -1599,4 +1598,153 @@ void PDFAnnotation::drawLine(const PDFAnnotation::LineGeometryInfo& info, } } +void PDFHighlightAnnotation::draw(AnnotationDrawParameters& parameters) const +{ + if (m_highlightArea.isEmpty()) + { + // Jakub Melka: do not draw empty highlight area + return; + } + + QPainter& painter = *parameters.painter; + parameters.boundingRectangle = m_highlightArea.getPath().boundingRect(); + + painter.setPen(getPen()); + painter.setBrush(getBrush()); + switch (m_type) + { + case AnnotationType::Highlight: + { + painter.setCompositionMode(QPainter::CompositionMode_Multiply); + painter.fillPath(m_highlightArea.getPath(), QBrush(getStrokeColor(), Qt::SolidPattern)); + break; + } + + case AnnotationType::Underline: + { + for (const PDFAnnotationQuadrilaterals::Quadrilateral& quadrilateral : m_highlightArea.getQuadrilaterals()) + { + QPointF p1 = quadrilateral[0]; + QPointF p2 = quadrilateral[1]; + QLineF line(p1, p2); + painter.drawLine(line); + } + break; + } + + case AnnotationType::Squiggly: + { + // Jakub Melka: Squiggly underline + for (const PDFAnnotationQuadrilaterals::Quadrilateral& quadrilateral : m_highlightArea.getQuadrilaterals()) + { + QPointF p1 = quadrilateral[0]; + QPointF p2 = quadrilateral[1]; + + // Calculate length (height) of quadrilateral + const PDFReal height = (QLineF(quadrilateral[0], quadrilateral[2]).length() + QLineF(quadrilateral[1], quadrilateral[3]).length()) * 0.5; + const PDFReal markSize = height / 7.0; + + // We can't assume, that line is horizontal. For example, rotated text with 45° degrees + // counterclockwise, if it is highlighted with squiggly underline, it is not horizontal line. + // So, we must calculate line geometry and transform it. + QLineF line(p1, p2); + LineGeometryInfo lineGeometryInfo = LineGeometryInfo::create(line); + + bool leadingEdge = true; + for (PDFReal x = lineGeometryInfo.transformedLine.p1().x(); x < lineGeometryInfo.transformedLine.p2().x(); x+= markSize) + { + QLineF line; + if (leadingEdge) + { + line = QLineF(x, 0.0, x + markSize, markSize); + } + else + { + // Falling edge + line = QLineF(x, markSize, x + markSize, 0.0); + } + + QLineF transformedLine = lineGeometryInfo.LCStoGCS.map(line); + painter.drawLine(transformedLine); + leadingEdge = !leadingEdge; + } + } + break; + } + + case AnnotationType::StrikeOut: + { + for (const PDFAnnotationQuadrilaterals::Quadrilateral& quadrilateral : m_highlightArea.getQuadrilaterals()) + { + QPointF p1 = (quadrilateral[0] + quadrilateral[2]) * 0.5; + QPointF p2 = (quadrilateral[1] + quadrilateral[3]) * 0.5; + QLineF line(p1, p2); + painter.drawLine(line); + } + break; + } + + default: + break; + } + + const qreal penWidth = painter.pen().widthF(); + parameters.boundingRectangle.adjust(-penWidth, -penWidth, penWidth, penWidth); +} + +std::vector PDFLinkAnnotation::getDrawKeys() const +{ + return { PDFAppeareanceStreams::Key{ PDFAppeareanceStreams::Appearance::Down, QByteArray() } }; +} + +void PDFLinkAnnotation::draw(AnnotationDrawParameters& parameters) const +{ + if (parameters.key.first != PDFAppeareanceStreams::Appearance::Down || + m_activationRegion.isEmpty() || + m_highlightMode == LinkHighlightMode::None) + { + // Nothing to draw + return; + } + + QPainter& painter = *parameters.painter; + parameters.boundingRectangle = getRectangle(); + + switch (m_highlightMode) + { + case LinkHighlightMode::Invert: + { + // Invert all + painter.setCompositionMode(QPainter::CompositionMode_Difference); + painter.fillPath(m_activationRegion.getPath(), QBrush(Qt::white, Qt::SolidPattern)); + break; + } + + case LinkHighlightMode::Outline: + { + // Invert the border + painter.setCompositionMode(QPainter::CompositionMode_Difference); + QPen pen = getPen(); + pen.setColor(Qt::white); + painter.setPen(pen); + painter.setBrush(Qt::NoBrush); + painter.drawPath(m_activationRegion.getPath()); + break; + } + + case LinkHighlightMode::Push: + { + // Draw border + painter.setPen(getPen()); + painter.setBrush(Qt::NoBrush); + painter.drawPath(m_activationRegion.getPath()); + break; + } + + default: + Q_ASSERT(false); + break; + } +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfannotation.h b/PdfForQtLib/sources/pdfannotation.h index e065656..aab012c 100644 --- a/PdfForQtLib/sources/pdfannotation.h +++ b/PdfForQtLib/sources/pdfannotation.h @@ -203,20 +203,24 @@ private: class PDFAnnotationQuadrilaterals { public: + using Quadrilateral = std::array; + using Quadrilaterals = std::vector; + inline explicit PDFAnnotationQuadrilaterals() = default; - inline explicit PDFAnnotationQuadrilaterals(QPainterPath&& path, std::vector&& underLines) : + inline explicit PDFAnnotationQuadrilaterals(QPainterPath&& path, Quadrilaterals&& quadrilaterals) : m_path(qMove(path)), - m_underLines(qMove(underLines)) + m_quadrilaterals(qMove(quadrilaterals)) { } const QPainterPath& getPath() const { return m_path; } - const std::vector& getUnderlines() const { return m_underLines; } + const Quadrilaterals& getQuadrilaterals() const { return m_quadrilaterals; } + bool isEmpty() const { return m_path.isEmpty(); } private: QPainterPath m_path; - std::vector m_underLines; + Quadrilaterals m_quadrilaterals; }; /// Represents callout line (line from annotation to some point) @@ -657,6 +661,8 @@ public: inline explicit PDFLinkAnnotation() = default; virtual AnnotationType getType() const override { return AnnotationType::Link; } + virtual std::vector getDrawKeys() const; + virtual void draw(AnnotationDrawParameters& parameters) const override; const PDFAction* getAction() const { return m_action.data(); } LinkHighlightMode getHighlightMode() const { return m_highlightMode; } @@ -866,6 +872,7 @@ public: } virtual AnnotationType getType() const override { return m_type; } + virtual void draw(AnnotationDrawParameters& parameters) const override; const PDFAnnotationQuadrilaterals& getHiglightArea() const { return m_highlightArea; }