mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-06-05 21:59:17 +02:00
Highlight and link annotations
This commit is contained in:
@ -592,13 +592,13 @@ PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObject
|
|||||||
PDFAnnotationQuadrilaterals PDFAnnotation::parseQuadrilaterals(const PDFObjectStorage* storage, PDFObject quadrilateralsObject, const QRectF annotationRect)
|
PDFAnnotationQuadrilaterals PDFAnnotation::parseQuadrilaterals(const PDFObjectStorage* storage, PDFObject quadrilateralsObject, const QRectF annotationRect)
|
||||||
{
|
{
|
||||||
QPainterPath path;
|
QPainterPath path;
|
||||||
std::vector<QLineF> underlines;
|
PDFAnnotationQuadrilaterals::Quadrilaterals quadrilaterals;
|
||||||
|
|
||||||
PDFDocumentDataLoaderDecorator loader(storage);
|
PDFDocumentDataLoaderDecorator loader(storage);
|
||||||
std::vector<PDFReal> points = loader.readNumberArray(quadrilateralsObject);
|
std::vector<PDFReal> points = loader.readNumberArray(quadrilateralsObject);
|
||||||
const size_t quadrilateralCount = points.size() % 8;
|
const size_t quadrilateralCount = points.size() % 8;
|
||||||
path.reserve(int(quadrilateralCount) + 5);
|
path.reserve(int(quadrilateralCount) + 5);
|
||||||
underlines.reserve(quadrilateralCount);
|
quadrilaterals.reserve(quadrilateralCount);
|
||||||
for (size_t i = 0; i < quadrilateralCount; ++i)
|
for (size_t i = 0; i < quadrilateralCount; ++i)
|
||||||
{
|
{
|
||||||
const size_t offset = i * 8;
|
const size_t offset = i * 8;
|
||||||
@ -609,12 +609,11 @@ PDFAnnotationQuadrilaterals PDFAnnotation::parseQuadrilaterals(const PDFObjectSt
|
|||||||
|
|
||||||
path.moveTo(p1);
|
path.moveTo(p1);
|
||||||
path.lineTo(p2);
|
path.lineTo(p2);
|
||||||
path.lineTo(p3);
|
|
||||||
path.lineTo(p4);
|
path.lineTo(p4);
|
||||||
path.lineTo(p1);
|
path.lineTo(p3);
|
||||||
path.closeSubpath();
|
path.closeSubpath();
|
||||||
|
|
||||||
underlines.emplace_back(p1, p2);
|
quadrilaterals.emplace_back(PDFAnnotationQuadrilaterals::Quadrilateral{ p1, p2, p3, p4 });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.isEmpty() && annotationRect.isValid())
|
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
|
// Jakub Melka: we are using points at the top, because PDF has inverted y axis
|
||||||
// against the Qt's y axis.
|
// against the Qt's y axis.
|
||||||
path.addRect(annotationRect);
|
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)
|
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
|
} // namespace pdf
|
||||||
|
@ -203,20 +203,24 @@ private:
|
|||||||
class PDFAnnotationQuadrilaterals
|
class PDFAnnotationQuadrilaterals
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
using Quadrilateral = std::array<QPointF, 4>;
|
||||||
|
using Quadrilaterals = std::vector<Quadrilateral>;
|
||||||
|
|
||||||
inline explicit PDFAnnotationQuadrilaterals() = default;
|
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_path(qMove(path)),
|
||||||
m_underLines(qMove(underLines))
|
m_quadrilaterals(qMove(quadrilaterals))
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const QPainterPath& getPath() const { return m_path; }
|
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:
|
private:
|
||||||
QPainterPath m_path;
|
QPainterPath m_path;
|
||||||
std::vector<QLineF> m_underLines;
|
Quadrilaterals m_quadrilaterals;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Represents callout line (line from annotation to some point)
|
/// Represents callout line (line from annotation to some point)
|
||||||
@ -657,6 +661,8 @@ public:
|
|||||||
inline explicit PDFLinkAnnotation() = default;
|
inline explicit PDFLinkAnnotation() = default;
|
||||||
|
|
||||||
virtual AnnotationType getType() const override { return AnnotationType::Link; }
|
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(); }
|
const PDFAction* getAction() const { return m_action.data(); }
|
||||||
LinkHighlightMode getHighlightMode() const { return m_highlightMode; }
|
LinkHighlightMode getHighlightMode() const { return m_highlightMode; }
|
||||||
@ -866,6 +872,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
virtual AnnotationType getType() const override { return m_type; }
|
virtual AnnotationType getType() const override { return m_type; }
|
||||||
|
virtual void draw(AnnotationDrawParameters& parameters) const override;
|
||||||
|
|
||||||
const PDFAnnotationQuadrilaterals& getHiglightArea() const { return m_highlightArea; }
|
const PDFAnnotationQuadrilaterals& getHiglightArea() const { return m_highlightArea; }
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user