Ink annotation graphics

This commit is contained in:
Jakub Melka
2020-04-05 16:01:09 +02:00
parent 992e4b32f3
commit cc11073d70
7 changed files with 839 additions and 159 deletions

View File

@ -430,7 +430,6 @@ PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObject
annotation->m_inkPath.lineTo(point);
}
}
annotation->m_inkPath.closeSubpath();
}
}
}
@ -1938,4 +1937,78 @@ void PDFCaretAnnotation::draw(AnnotationDrawParameters& parameters) const
painter.fillPath(path, QBrush(getStrokeColor(), Qt::SolidPattern));
}
void PDFInkAnnotation::draw(AnnotationDrawParameters& parameters) const
{
QPainter& painter = *parameters.painter;
QPainterPath path = getInkPath();
painter.setPen(getPen());
painter.setBrush(getBrush());
QPainterPath boundingPath;
QPainterPath currentPath;
const int elementCount = path.elementCount();
for (int i = 0; i < elementCount; ++i)
{
QPainterPath::Element element = path.elementAt(i);
switch (element.type)
{
case QPainterPath::MoveToElement:
{
// Reset the path
if (!currentPath.isEmpty())
{
boundingPath.addPath(currentPath);
painter.drawPath(currentPath);
currentPath.clear();
}
currentPath.moveTo(element.x, element.y);
break;
}
case QPainterPath::LineToElement:
{
const QPointF p0 = currentPath.currentPosition();
const QPointF p1(element.x, element.y);
QLineF line(p0, p1);
QPointF normal = line.normalVector().p2() - p0;
// Jakub Melka: This computation should be clarified. We use second order Bezier curves.
// We must calculate single control point. Let B(t) is bezier curve of second order.
// Then second derivation is B''(t) = 2(p0 - 2*Pc + p1). Second derivation curve is its
// normal. So, we calculate normal vector of the line (which has norm equal to line length),
// then we get following formula:
//
// Pc = (p0 + p1) / 2 - B''(t) / 4
//
// So, for bezier curves of second order, second derivative is constant (which is not surprising,
// because second derivative of polynomial of order 2 is also a constant). Control point is then
// used to paint the path.
QPointF controlPoint = -normal * 0.25 + (p0 + p1) * 0.5;
currentPath.quadTo(controlPoint, p1);
break;
}
case QPainterPath::CurveToElement:
case QPainterPath::CurveToDataElement:
default:
Q_ASSERT(false);
break;
}
}
// Reset the path
if (!currentPath.isEmpty())
{
boundingPath.addPath(currentPath);
painter.drawPath(currentPath);
currentPath.clear();
}
const qreal penWidth = painter.pen().widthF();
parameters.boundingRectangle = boundingPath.boundingRect();
parameters.boundingRectangle.adjust(-penWidth, -penWidth, penWidth, penWidth);
}
} // namespace pdf

View File

@ -35,6 +35,7 @@ class PDFObjectStorage;
class PDFDrawWidgetProxy;
using TextAlignment = Qt::Alignment;
using Polygons = std::vector<QPolygonF>;
enum class AnnotationType
{
@ -971,6 +972,7 @@ public:
inline explicit PDFInkAnnotation() = default;
virtual AnnotationType getType() const override { return AnnotationType::Ink; }
virtual void draw(AnnotationDrawParameters& parameters) const override;
const QPainterPath& getInkPath() const { return m_inkPath; }

View File

@ -897,6 +897,18 @@ void PDFDocumentBuilder::updateDocumentInfo(PDFObject info)
mergeTo(infoReference, info);
}
QRectF PDFDocumentBuilder::getPolygonsBoundingRect(const Polygons& polygons) const
{
QRectF rect;
for (const QPolygonF& polygon : polygons)
{
rect = rect.united(polygon.boundingRect());
}
return rect;
}
std::vector<PDFObject> PDFDocumentBuilder::copyFrom(const std::vector<PDFObject>& objects, const PDFObjectStorage& storage, bool createReferences)
{
// 1) Collect all references, which we must copy. If object is referenced, then
@ -1149,8 +1161,6 @@ PDFObjectReference PDFDocumentBuilder::createAnnotationCaret(PDFObjectReference
objectBuilder.endDictionaryItem();
objectBuilder.endDictionary();
PDFObjectReference annotationObject = addObject(objectBuilder.takeObject());
PDFObjectReference popupAnnotation = createAnnotationPopup(page, annotationObject, getPopupWindowRect(rectangle), false);
objectBuilder.beginDictionary();
objectBuilder.beginDictionaryItem("Annots");
objectBuilder.beginArray();
@ -1573,6 +1583,140 @@ PDFObjectReference PDFDocumentBuilder::createAnnotationHighlight(PDFObjectRefere
}
PDFObjectReference PDFDocumentBuilder::createAnnotationInk(PDFObjectReference page,
QPolygonF inkPoints,
PDFReal borderWidth,
QColor strokeColor,
QString title,
QString subject,
QString contents)
{
PDFObjectFactory objectBuilder;
objectBuilder.beginDictionary();
objectBuilder.beginDictionaryItem("Type");
objectBuilder << WrapName("Annot");
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("Subtype");
objectBuilder << WrapName("Ink");
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("Rect");
objectBuilder << inkPoints.boundingRect();
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("F");
objectBuilder << 4;
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("P");
objectBuilder << page;
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("InkList");
objectBuilder.beginArray();
objectBuilder << inkPoints;
objectBuilder.endArray();
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("M");
objectBuilder << WrapCurrentDateTime();
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("CreationDate");
objectBuilder << WrapCurrentDateTime();
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("Border");
objectBuilder << std::initializer_list<PDFReal>{ 0.0, 0.0, borderWidth };
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("C");
objectBuilder << WrapAnnotationColor(strokeColor);
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("T");
objectBuilder << title;
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("Contents");
objectBuilder << contents;
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("Subj");
objectBuilder << subject;
objectBuilder.endDictionaryItem();
objectBuilder.endDictionary();
PDFObjectReference annotationObject = addObject(objectBuilder.takeObject());
objectBuilder.beginDictionary();
objectBuilder.beginDictionaryItem("Annots");
objectBuilder.beginArray();
objectBuilder << annotationObject;
objectBuilder.endArray();
objectBuilder.endDictionaryItem();
objectBuilder.endDictionary();
PDFObject pageAnnots = objectBuilder.takeObject();
appendTo(page, pageAnnots);
updateAnnotationAppearanceStreams(annotationObject);
return annotationObject;
}
PDFObjectReference PDFDocumentBuilder::createAnnotationInk(PDFObjectReference page,
Polygons inkPoints,
PDFReal borderWidth,
QColor strokeColor,
QString title,
QString subject,
QString contents)
{
PDFObjectFactory objectBuilder;
objectBuilder.beginDictionary();
objectBuilder.beginDictionaryItem("Type");
objectBuilder << WrapName("Annot");
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("Subtype");
objectBuilder << WrapName("Ink");
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("Rect");
objectBuilder << getPolygonsBoundingRect(inkPoints);
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("F");
objectBuilder << 4;
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("P");
objectBuilder << page;
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("InkList");
objectBuilder << inkPoints;
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("M");
objectBuilder << WrapCurrentDateTime();
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("CreationDate");
objectBuilder << WrapCurrentDateTime();
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("Border");
objectBuilder << std::initializer_list<PDFReal>{ 0.0, 0.0, borderWidth };
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("C");
objectBuilder << WrapAnnotationColor(strokeColor);
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("T");
objectBuilder << title;
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("Contents");
objectBuilder << contents;
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("Subj");
objectBuilder << subject;
objectBuilder.endDictionaryItem();
objectBuilder.endDictionary();
PDFObjectReference annotationObject = addObject(objectBuilder.takeObject());
objectBuilder.beginDictionary();
objectBuilder.beginDictionaryItem("Annots");
objectBuilder.beginArray();
objectBuilder << annotationObject;
objectBuilder.endArray();
objectBuilder.endDictionaryItem();
objectBuilder.endDictionary();
PDFObject pageAnnots = objectBuilder.takeObject();
appendTo(page, pageAnnots);
updateAnnotationAppearanceStreams(annotationObject);
return annotationObject;
}
PDFObjectReference PDFDocumentBuilder::createAnnotationLine(PDFObjectReference page,
QRectF boundingRect,
QPointF startPoint,
@ -1644,8 +1788,6 @@ PDFObjectReference PDFDocumentBuilder::createAnnotationLine(PDFObjectReference p
objectBuilder.endDictionaryItem();
objectBuilder.endDictionary();
PDFObjectReference annotationObject = addObject(objectBuilder.takeObject());
PDFObjectReference popupAnnotation = createAnnotationPopup(page, annotationObject, getPopupWindowRect(boundingRect), false);
objectBuilder.beginDictionary();
objectBuilder.beginDictionaryItem("Annots");
objectBuilder.beginArray();
@ -1751,8 +1893,6 @@ PDFObjectReference PDFDocumentBuilder::createAnnotationLine(PDFObjectReference p
objectBuilder.endDictionaryItem();
objectBuilder.endDictionary();
PDFObjectReference annotationObject = addObject(objectBuilder.takeObject());
PDFObjectReference popupAnnotation = createAnnotationPopup(page, annotationObject, getPopupWindowRect(boundingRect), false);
objectBuilder.beginDictionary();
objectBuilder.beginDictionaryItem("Annots");
objectBuilder.beginArray();
@ -1876,8 +2016,6 @@ PDFObjectReference PDFDocumentBuilder::createAnnotationPolygon(PDFObjectReferenc
objectBuilder.endDictionaryItem();
objectBuilder.endDictionary();
PDFObjectReference annotationObject = addObject(objectBuilder.takeObject());
PDFObjectReference popupAnnotation = createAnnotationPopup(page, annotationObject, getPopupWindowRect(polygon.boundingRect()), false);
objectBuilder.beginDictionary();
objectBuilder.beginDictionaryItem("Annots");
objectBuilder.beginArray();
@ -1956,8 +2094,6 @@ PDFObjectReference PDFDocumentBuilder::createAnnotationPolyline(PDFObjectReferen
objectBuilder.endDictionaryItem();
objectBuilder.endDictionary();
PDFObjectReference annotationObject = addObject(objectBuilder.takeObject());
PDFObjectReference popupAnnotation = createAnnotationPopup(page, annotationObject, getPopupWindowRect(polyline.boundingRect()), false);
objectBuilder.beginDictionary();
objectBuilder.beginDictionaryItem("Annots");
objectBuilder.beginArray();
@ -2380,6 +2516,55 @@ PDFObjectReference PDFDocumentBuilder::createAnnotationText(PDFObjectReference p
}
PDFObjectReference PDFDocumentBuilder::createAnnotationUnderline(PDFObjectReference page,
QRectF rectangle,
QColor color)
{
PDFObjectFactory objectBuilder;
objectBuilder.beginDictionary();
objectBuilder.beginDictionaryItem("Type");
objectBuilder << WrapName("Annot");
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("Subtype");
objectBuilder << WrapName("Underline");
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("Rect");
objectBuilder << rectangle;
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("P");
objectBuilder << page;
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("CreationDate");
objectBuilder << WrapCurrentDateTime();
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("C");
objectBuilder << color;
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("QuadPoints");
objectBuilder.beginArray();
objectBuilder << rectangle.bottomLeft();
objectBuilder << rectangle.bottomRight();
objectBuilder << rectangle.topLeft();
objectBuilder << rectangle.topRight();
objectBuilder.endArray();
objectBuilder.endDictionaryItem();
objectBuilder.endDictionary();
PDFObjectReference annotationObject = addObject(objectBuilder.takeObject());
objectBuilder.beginDictionary();
objectBuilder.beginDictionaryItem("Annots");
objectBuilder.beginArray();
objectBuilder << annotationObject;
objectBuilder.endArray();
objectBuilder.endDictionaryItem();
objectBuilder.endDictionary();
PDFObject pageAnnots = objectBuilder.takeObject();
appendTo(page, pageAnnots);
updateAnnotationAppearanceStreams(annotationObject);
return annotationObject;
}
PDFObjectReference PDFDocumentBuilder::createAnnotationUnderline(PDFObjectReference page,
QRectF rectangle,
QColor color,
@ -2444,55 +2629,6 @@ PDFObjectReference PDFDocumentBuilder::createAnnotationUnderline(PDFObjectRefere
}
PDFObjectReference PDFDocumentBuilder::createAnnotationUnderline(PDFObjectReference page,
QRectF rectangle,
QColor color)
{
PDFObjectFactory objectBuilder;
objectBuilder.beginDictionary();
objectBuilder.beginDictionaryItem("Type");
objectBuilder << WrapName("Annot");
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("Subtype");
objectBuilder << WrapName("Underline");
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("Rect");
objectBuilder << rectangle;
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("P");
objectBuilder << page;
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("CreationDate");
objectBuilder << WrapCurrentDateTime();
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("C");
objectBuilder << color;
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("QuadPoints");
objectBuilder.beginArray();
objectBuilder << rectangle.bottomLeft();
objectBuilder << rectangle.bottomRight();
objectBuilder << rectangle.topLeft();
objectBuilder << rectangle.topRight();
objectBuilder.endArray();
objectBuilder.endDictionaryItem();
objectBuilder.endDictionary();
PDFObjectReference annotationObject = addObject(objectBuilder.takeObject());
objectBuilder.beginDictionary();
objectBuilder.beginDictionaryItem("Annots");
objectBuilder.beginArray();
objectBuilder << annotationObject;
objectBuilder.endArray();
objectBuilder.endDictionaryItem();
objectBuilder.endDictionary();
PDFObject pageAnnots = objectBuilder.takeObject();
appendTo(page, pageAnnots);
updateAnnotationAppearanceStreams(annotationObject);
return annotationObject;
}
PDFObjectReference PDFDocumentBuilder::createCatalog()
{
PDFObjectFactory objectBuilder;

View File

@ -437,6 +437,40 @@ public:
QColor color);
/// Ink anotation represents freehand scribble composed from one or more disjoint paths.
/// \param page Page to which is annotation added
/// \param inkPoints Ink points
/// \param borderWidth Border line width
/// \param strokeColor Stroke color
/// \param title Title
/// \param subject Subject
/// \param contents Contents
PDFObjectReference createAnnotationInk(PDFObjectReference page,
QPolygonF inkPoints,
PDFReal borderWidth,
QColor strokeColor,
QString title,
QString subject,
QString contents);
/// Ink anotation represents freehand scribble composed from one or more disjoint paths.
/// \param page Page to which is annotation added
/// \param inkPoints Ink points (vector of polygons)
/// \param borderWidth Border line width
/// \param strokeColor Stroke color
/// \param title Title
/// \param subject Subject
/// \param contents Contents
PDFObjectReference createAnnotationInk(PDFObjectReference page,
Polygons inkPoints,
PDFReal borderWidth,
QColor strokeColor,
QString title,
QString subject,
QString contents);
/// Line annotation represents straight line, or some more advanced graphics, such as dimension with
/// text. Line annotations are markup annotations, so they can have popup window. Line endings can
/// be specified.
@ -692,6 +726,16 @@ public:
bool open);
/// Text markup annotation is used to underline text. It is a markup annotation, so it can contain
/// window to be opened (and commented).
/// \param page Page to which is annotation added
/// \param rectangle Area in which is markup displayed
/// \param color Color
PDFObjectReference createAnnotationUnderline(PDFObjectReference page,
QRectF rectangle,
QColor color);
/// Text markup annotation is used to underline text. It is a markup annotation, so it can contain
/// window to be opened (and commented).
/// \param page Page to which is annotation added
@ -708,16 +752,6 @@ public:
QString contents);
/// Text markup annotation is used to underline text. It is a markup annotation, so it can contain
/// window to be opened (and commented).
/// \param page Page to which is annotation added
/// \param rectangle Area in which is markup displayed
/// \param color Color
PDFObjectReference createAnnotationUnderline(PDFObjectReference page,
QRectF rectangle,
QColor color);
/// Creates empty catalog. This function is used, when a new document is being created. Do not call
/// this function manually.
PDFObjectReference createCatalog();
@ -878,6 +912,7 @@ private:
PDFObjectReference getDocumentInfo() const;
PDFObjectReference getCatalogReference() const;
void updateDocumentInfo(PDFObject info);
QRectF getPolygonsBoundingRect(const Polygons& Polygons) const;
/// Copies objects from another storage. Objects have adjusted references to match
/// this storage references and objects are added after the last objects of active storage.