Highlight and link annotations

This commit is contained in:
Jakub Melka 2020-04-04 17:33:10 +02:00
parent aea7e44800
commit 5dd9888906
2 changed files with 166 additions and 11 deletions

View File

@ -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<QLineF> underlines;
PDFAnnotationQuadrilaterals::Quadrilaterals quadrilaterals;
PDFDocumentDataLoaderDecorator loader(storage);
std::vector<PDFReal> 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<PDFAppeareanceStreams::Key> 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

View File

@ -203,20 +203,24 @@ private:
class PDFAnnotationQuadrilaterals
{
public:
using Quadrilateral = std::array<QPointF, 4>;
using Quadrilaterals = std::vector<Quadrilateral>;
inline explicit PDFAnnotationQuadrilaterals() = default;
inline explicit PDFAnnotationQuadrilaterals(QPainterPath&& path, std::vector<QLineF>&& 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<QLineF>& 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<QLineF> 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<PDFAppeareanceStreams::Key> 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; }