Line annotation appearance

This commit is contained in:
Jakub Melka 2020-04-02 19:55:44 +02:00
parent 8d242e752f
commit 52e4312bed
2 changed files with 305 additions and 0 deletions

View File

@ -1196,4 +1196,263 @@ void PDFTextAnnotation::draw(AnnotationDrawParameters& parameters) const
parameters.boundingRectangle = rectangle;
}
void PDFLineAnnotation::draw(AnnotationDrawParameters& parameters) const
{
QLineF line = getLine();
if (line.isNull())
{
// Jakub Melka: do not draw empty lines
return;
}
QPainter& painter = *parameters.painter;
painter.setPen(getPen());
painter.setBrush(getBrush());
QPainterPath boundingPath;
boundingPath.moveTo(line.p1());
boundingPath.lineTo(line.p2());
LineGeometryInfo info = LineGeometryInfo::create(line);
const PDFReal leaderLineLength = getLeaderLineLength();
const PDFReal coefficientSigned = (leaderLineLength >= 0.0) ? 1.0 : -1.0;
const PDFReal leaderLineOffset = getLeaderLineOffset() * coefficientSigned;
const PDFReal leaderLineExtension = getLeaderLineExtension() * coefficientSigned;
const PDFReal lineEndingSize = qMin(painter.pen().widthF() * 5.0, line.length() * 0.5);
const bool hasLeaderLine = !qFuzzyIsNull(leaderLineLength) || !qFuzzyIsNull(leaderLineExtension);
QLineF normalLine = info.transformedLine.normalVector().unitVector();
QPointF normalVector = normalLine.p1() - normalLine.p2();
QLineF lineToPaint = info.transformedLine;
if (hasLeaderLine)
{
// We will draw leader lines at both start/end
QPointF p1llStart = info.transformedLine.p1() + normalVector * leaderLineOffset;
QPointF p1llEnd = info.transformedLine.p1() + normalVector * (leaderLineOffset + leaderLineLength + leaderLineExtension);
QLineF llStart(p1llStart, p1llEnd);
llStart = info.LCStoGCS.map(llStart);
boundingPath.moveTo(llStart.p1());
boundingPath.lineTo(llStart.p2());
painter.drawLine(llStart);
QPointF p2llStart = info.transformedLine.p2() + normalVector * leaderLineOffset;
QPointF p2llEnd = info.transformedLine.p2() + normalVector * (leaderLineOffset + leaderLineLength + leaderLineExtension);
QLineF llEnd(p2llStart, p2llEnd);
llEnd = info.LCStoGCS.map(llEnd);
boundingPath.moveTo(llEnd.p1());
boundingPath.lineTo(llEnd.p2());
painter.drawLine(llEnd);
lineToPaint.translate(normalVector * (leaderLineOffset + leaderLineLength));
}
QLineF lineToPaintOrig = info.LCStoGCS.map(lineToPaint);
info = LineGeometryInfo::create(lineToPaintOrig);
drawLine(info, painter, lineEndingSize, getStartLineEnding(), getEndLineEnding(), boundingPath);
parameters.boundingRectangle = boundingPath.boundingRect();
parameters.boundingRectangle.adjust(-lineEndingSize, -lineEndingSize, lineEndingSize, lineEndingSize);
}
QColor PDFLineAnnotation::getFillColor() const
{
QColor color = getDrawColorFromAnnotationColor(getInteriorColor());
if (color.isValid())
{
color.setAlphaF(getOpacity());
}
return color;
}
QColor PDFPolygonalGeometryAnnotation::getFillColor() const
{
QColor color = getDrawColorFromAnnotationColor(getInteriorColor());
if (color.isValid())
{
color.setAlphaF(getOpacity());
}
return color;
}
PDFAnnotation::LineGeometryInfo PDFAnnotation::LineGeometryInfo::create(QLineF line)
{
LineGeometryInfo result;
result.originalLine = line;
result.transformedLine = QLineF(QPointF(0, 0), QPointF(line.length(), 0));
// Strategy: for simplification, we rotate the line clockwise so we will
// get the line axis equal to the x-axis.
const double angle = line.angleTo(QLineF(0, 0, 1, 0));
QPointF p1 = line.p1();
// Matrix LCStoGCS is local coordinate system of line line. It transforms
// points on the line to the global coordinate system. So, point (0, 0) will
// map onto p1 and point (length(p1-p2), 0) will map onto p2.
result.LCStoGCS = QMatrix();
result.LCStoGCS.translate(p1.x(), p1.y());
result.LCStoGCS.rotate(angle);
result.GCStoLCS = result.LCStoGCS.inverted();
return result;
}
void PDFAnnotation::drawLine(const PDFAnnotation::LineGeometryInfo& info,
QPainter& painter,
PDFReal lineEndingSize,
AnnotationLineEnding p1Ending,
AnnotationLineEnding p2Ending,
QPainterPath& boundingPath) const
{
const PDFReal angle = 30;
const PDFReal lineEndingHalfSize = lineEndingSize * 0.5;
const PDFReal arrowAxisLength = lineEndingHalfSize / qTan(qDegreesToRadians(angle));
auto getOffsetFromLineEnding = [lineEndingHalfSize, arrowAxisLength](AnnotationLineEnding ending)
{
switch (ending)
{
case AnnotationLineEnding::Square:
case AnnotationLineEnding::Circle:
case AnnotationLineEnding::Diamond:
return lineEndingHalfSize;
case AnnotationLineEnding::ClosedArrow:
return arrowAxisLength;
default:
break;
}
return 0.0;
};
auto drawLineEnding = [&](QPointF point, AnnotationLineEnding ending, bool flipAxis)
{
QPainterPath path;
switch (ending)
{
case AnnotationLineEnding::None:
break;
case AnnotationLineEnding::Square:
{
path.addRect(-lineEndingHalfSize, -lineEndingHalfSize, lineEndingSize, lineEndingSize);
break;
}
case AnnotationLineEnding::Circle:
{
path.addEllipse(QPointF(0, 0), lineEndingHalfSize, lineEndingHalfSize);
break;
}
case AnnotationLineEnding::Diamond:
{
path.moveTo(0.0, -lineEndingHalfSize);
path.lineTo(lineEndingHalfSize, 0.0);
path.lineTo(0.0, +lineEndingHalfSize);
path.lineTo(-lineEndingHalfSize, 0.0);
path.closeSubpath();
break;
}
case AnnotationLineEnding::OpenArrow:
{
path.moveTo(0.0, 0.0);
path.lineTo(arrowAxisLength, lineEndingHalfSize);
path.moveTo(0.0, 0.0);
path.lineTo(arrowAxisLength, -lineEndingHalfSize);
break;
}
case AnnotationLineEnding::ClosedArrow:
{
path.moveTo(0.0, 0.0);
path.lineTo(arrowAxisLength, lineEndingHalfSize);
path.lineTo(arrowAxisLength, -lineEndingHalfSize);
path.closeSubpath();
break;
}
case AnnotationLineEnding::Butt:
{
path.moveTo(0.0, -lineEndingHalfSize);
path.lineTo(0.0, lineEndingHalfSize);
break;
}
case AnnotationLineEnding::ROpenArrow:
{
path.moveTo(0.0, 0.0);
path.lineTo(-arrowAxisLength, lineEndingHalfSize);
path.moveTo(0.0, 0.0);
path.lineTo(-arrowAxisLength, -lineEndingHalfSize);
break;
}
case AnnotationLineEnding::RClosedArrow:
{
path.moveTo(0.0, 0.0);
path.lineTo(-arrowAxisLength, lineEndingHalfSize);
path.lineTo(-arrowAxisLength, -lineEndingHalfSize);
path.closeSubpath();
break;
}
case AnnotationLineEnding::Slash:
{
const PDFReal angle = 60;
const PDFReal lineEndingHalfSize = lineEndingSize * 0.5;
const PDFReal slashAxisLength = lineEndingHalfSize / qTan(qDegreesToRadians(angle));
path.moveTo(-slashAxisLength, -lineEndingHalfSize);
path.lineTo(slashAxisLength, lineEndingHalfSize);
break;
}
default:
break;
}
if (!path.isEmpty())
{
// Flip the x-axis (we are drawing endpoint)
if (flipAxis && ending != AnnotationLineEnding::Slash)
{
QMatrix matrix;
matrix.scale(-1.0, 1.0);
path = matrix.map(path);
}
path.translate(point);
path = info.LCStoGCS.map(path);
painter.drawPath(path);
boundingPath.addPath(path);
}
};
// Remove the offset from start/end
const PDFReal startOffset = getOffsetFromLineEnding(p1Ending);
const PDFReal endOffset = getOffsetFromLineEnding(p2Ending);
QLineF adjustedLine = info.transformedLine;
adjustedLine.setP1(adjustedLine.p1() + QPointF(startOffset, 0));
adjustedLine.setP2(adjustedLine.p2() - QPointF(endOffset, 0));
adjustedLine = info.LCStoGCS.map(adjustedLine);
drawLineEnding(info.transformedLine.p1(), p1Ending, false);
drawLineEnding(info.transformedLine.p2(), p2Ending, true);
painter.drawLine(adjustedLine);
boundingPath.moveTo(adjustedLine.p1());
boundingPath.lineTo(adjustedLine.p2());
}
} // namespace pdf

View File

@ -484,6 +484,26 @@ protected:
virtual QColor getStrokeColor() const;
virtual QColor getFillColor() const;
struct LineGeometryInfo
{
/// Original line
QLineF originalLine;
/// Transformed line
QLineF transformedLine;
/// Matrix LCStoGCS is local coordinate system of line originalLine. It transforms
/// points on the line to the global coordinate system. So, point (0, 0) will
/// map onto p1 and point (originalLine.length(), 0) will map onto p2.
QMatrix LCStoGCS;
/// Inverted matrix of LCStoGCS. It maps global coordinate system to local
/// coordinate system of the original line.
QMatrix GCStoLCS;
static LineGeometryInfo create(QLineF line);
};
/// Returns draw color from defined annotation color
QColor getDrawColorFromAnnotationColor(const std::vector<PDFReal>& color) const;
@ -494,6 +514,25 @@ protected:
/// a brush, then empty brush is returned.
QBrush getBrush() const;
/// Draw line using given parameters and painter. Line is specified
/// by its geometry information. Painter must be set to global coordinates.
/// Bounding path is also updated, it is specified in global coordinates,
/// not in line local coordinate system. We consider p1 as start point of
/// the line, and p2 as the end point. The painter must have proper QPen
/// and QBrush setted, this function uses current pen/brush to paint the line.
/// \param info Line geometry info
/// \param painter Painter
/// \param lineEndingSize Line ending size
/// \param p1Ending Line endpoint graphics at p1
/// \param p2Ending Line endpoint graphics at p2
/// \param boundingPath Bounding path in global coordinate system
void drawLine(const LineGeometryInfo& info,
QPainter& painter,
PDFReal lineEndingSize,
AnnotationLineEnding p1Ending,
AnnotationLineEnding p2Ending,
QPainterPath& boundingPath) const;
private:
QRectF m_rectangle; ///< Annotation rectangle, in page coordinates, "Rect" entry
@ -683,6 +722,7 @@ public:
inline explicit PDFLineAnnotation() = default;
virtual AnnotationType getType() const override { return AnnotationType::Line; }
virtual void draw(AnnotationDrawParameters& parameters) const override;
enum class Intent
{
@ -709,6 +749,9 @@ public:
const PDFObject& getMeasureDictionary() const { return m_measureDictionary; }
const QPointF& getCaptionOffset() const { return m_captionOffset; }
protected:
virtual QColor getFillColor() const override;
private:
friend static PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObject object);
@ -788,6 +831,9 @@ public:
Intent getIntent() const { return m_intent; }
const PDFObject& getMeasure() const { return m_measure; }
protected:
virtual QColor getFillColor() const override;
private:
friend static PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObject object);