[poppler] CMakeLists.txt poppler/Annot.cc poppler/Annot.h poppler/AnnotStampImageHelper.cc poppler/AnnotStampImageHelper.h qt5/src qt6/src

GitLab Mirror gitlab-mirror at kemper.freedesktop.org
Sun Sep 12 20:14:26 UTC 2021


 CMakeLists.txt                       |    2 
 poppler/Annot.cc                     |  127 +++++++++++++++++++--
 poppler/Annot.h                      |   13 ++
 poppler/AnnotStampImageHelper.cc     |   77 +++++++++++++
 poppler/AnnotStampImageHelper.h      |   69 +++++++++++
 qt5/src/poppler-annotation-private.h |   12 ++
 qt5/src/poppler-annotation.cc        |  204 +++++++++++++++++++++++++++++++++++
 qt5/src/poppler-annotation.h         |   41 +++++++
 qt6/src/poppler-annotation-private.h |   12 ++
 qt6/src/poppler-annotation.cc        |  204 +++++++++++++++++++++++++++++++++++
 qt6/src/poppler-annotation.h         |   41 +++++++
 11 files changed, 789 insertions(+), 13 deletions(-)

New commits:
commit 3ad10c30433f19da65f638326336865504fb972a
Author: Mahmoud Khalil <mahmoudkhalil11 at gmail.com>
Date:   Sun Sep 12 20:14:24 2021 +0000

    Improve support for custom stamp annotations
    
    This commit improves Poppler support for custom stamp
    annotations, by adding a new class called AnnotStampImageHelper in
    Poppler core.
    
    The new class takes image data and create an Image
    XObject in the document, the AnnotStamp class has been modified to
    support the new helper class and to reference the created XObject.
    
    This new implementation has been exposed in the qt5 wrapper as well as the
    qt6 one, in which the extraction of the QImage data has been handled.
    
    A new API for preserving the annotation AP stream has been exposed using
    the qt wrapper as well, so that users are able to temporarily store it.

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 07f754f8..c32c05e6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -396,6 +396,7 @@ set(poppler_SRCS
   fofi/FoFiType1C.cc
   fofi/FoFiIdentifier.cc
   poppler/Annot.cc
+  poppler/AnnotStampImageHelper.cc
   poppler/Array.cc
   poppler/CachedFile.cc
   poppler/Catalog.cc
@@ -602,6 +603,7 @@ install(TARGETS poppler RUNTIME DESTINATION bin LIBRARY DESTINATION ${CMAKE_INST
 if(ENABLE_UNSTABLE_API_ABI_HEADERS)
   install(FILES
     poppler/Annot.h
+    poppler/AnnotStampImageHelper.h
     poppler/Array.h
     poppler/CachedFile.h
     poppler/Catalog.h
diff --git a/poppler/Annot.cc b/poppler/Annot.cc
index e1a05c84..fb764254 100644
--- a/poppler/Annot.cc
+++ b/poppler/Annot.cc
@@ -1960,6 +1960,38 @@ void Annot::draw(Gfx *gfx, bool printing)
     gfx->drawAnnot(&obj, nullptr, color.get(), rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
 }
 
+void Annot::setNewAppearance(Object &&newAppearance)
+{
+    if (newAppearance.isNull())
+        return;
+
+    annotLocker();
+    if (newAppearance.getType() == ObjType::objStream) {
+        invalidateAppearance();
+        appearance = std::move(newAppearance);
+
+        Ref updatedAppearanceStream = doc->getXRef()->addIndirectObject(&appearance);
+
+        Object obj1 = Object(new Dict(doc->getXRef()));
+        obj1.dictAdd("N", Object(updatedAppearanceStream));
+        update("AP", std::move(obj1));
+
+        Object updatedAP = annotObj.dictLookup("AP");
+        appearStreams = std::make_unique<AnnotAppearance>(doc, &updatedAP);
+    } else {
+        appearStreams = std::make_unique<AnnotAppearance>(doc, &newAppearance);
+        update("AP", std::move(newAppearance));
+
+        if (appearStreams)
+            appearance = appearStreams->getAppearanceStream(AnnotAppearance::appearNormal, appearState->c_str());
+    }
+}
+
+Object Annot::getAppearance() const
+{
+    return appearance.fetch(doc->getXRef());
+}
+
 //------------------------------------------------------------------------
 // AnnotPopup
 //------------------------------------------------------------------------
@@ -3881,17 +3913,6 @@ bool AnnotWidget::setFormAdditionalAction(FormAdditionalActionsType formAddition
     return true;
 }
 
-void AnnotWidget::setNewAppearance(Object &&newAppearance)
-{
-    if (!newAppearance.isNull()) {
-        appearStreams = std::make_unique<AnnotAppearance>(doc, &newAppearance);
-        update("AP", std::move(newAppearance));
-    }
-
-    if (appearStreams)
-        appearance = appearStreams->getAppearanceStream(AnnotAppearance::appearNormal, appearState->c_str());
-}
-
 // Grand unified handler for preparing text strings to be drawn into form
 // fields.  Takes as input a text string (in PDFDocEncoding or UTF-16).
 // Converts some or all of this string to the appropriate encoding for the
@@ -5361,7 +5382,10 @@ AnnotStamp::AnnotStamp(PDFDoc *docA, Object &&dictObject, const Object *obj) : A
     initialize(docA, annotObj.getDict());
 }
 
-AnnotStamp::~AnnotStamp() = default;
+AnnotStamp::~AnnotStamp()
+{
+    delete stampImageHelper;
+}
 
 void AnnotStamp::initialize(PDFDoc *docA, Dict *dict)
 {
@@ -5371,6 +5395,53 @@ void AnnotStamp::initialize(PDFDoc *docA, Dict *dict)
     } else {
         icon = std::make_unique<GooString>("Draft");
     }
+
+    stampImageHelper = nullptr;
+    updatedAppearanceStream = Ref::INVALID();
+}
+
+void AnnotStamp::generateStampAppearance()
+{
+    Ref imgRef = stampImageHelper->getRef();
+    std::string imgStrName = "X" + std::to_string(imgRef.num);
+    GooString imgRefName(imgStrName);
+
+    AnnotAppearanceBuilder appearBuilder;
+    appearBuilder.append("q\n");
+    appearBuilder.append("/GS0 gs\n");
+    appearBuilder.appendf("{0:.3f} 0 0 {1:.3f} 0 0 cm\n", rect->x2 - rect->x1, rect->y2 - rect->y1);
+    appearBuilder.append("/");
+    appearBuilder.append(imgRefName.c_str());
+    appearBuilder.append(" Do\n");
+    appearBuilder.append("Q\n");
+
+    Dict *resDict = createResourcesDict(imgRefName.c_str(), Object(imgRef), "GS0", opacity, nullptr);
+
+    double bboxArray[4] = { 0, 0, rect->x2 - rect->x1, rect->y2 - rect->y1 };
+    const GooString *appearBuf = appearBuilder.buffer();
+    appearance = createForm(appearBuf, bboxArray, false, resDict);
+}
+
+void AnnotStamp::draw(Gfx *gfx, bool printing)
+{
+    if (!isVisible(printing))
+        return;
+
+    annotLocker();
+    if (appearance.isNull()) {
+        if (stampImageHelper == nullptr)
+            return;
+
+        generateStampAppearance();
+    }
+
+    // draw the appearance stream
+    Object obj = appearance.fetch(gfx->getXRef());
+    if (appearBBox) {
+        gfx->drawAnnot(&obj, nullptr, color.get(), appearBBox->getPageXMin(), appearBBox->getPageYMin(), appearBBox->getPageXMax(), appearBBox->getPageYMax(), getRotation());
+    } else {
+        gfx->drawAnnot(&obj, nullptr, color.get(), rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
+    }
 }
 
 void AnnotStamp::setIcon(GooString *new_icon)
@@ -5385,6 +5456,38 @@ void AnnotStamp::setIcon(GooString *new_icon)
     invalidateAppearance();
 }
 
+void AnnotStamp::setCustomImage(AnnotStampImageHelper *stampImageHelperA)
+{
+    if (!stampImageHelperA)
+        return;
+
+    annotLocker();
+    clearCustomImage();
+
+    stampImageHelper = stampImageHelperA;
+    generateStampAppearance();
+
+    if (updatedAppearanceStream == Ref::INVALID()) {
+        updatedAppearanceStream = doc->getXRef()->addIndirectObject(&appearance);
+    } else {
+        Object obj1 = appearance.fetch(doc->getXRef());
+        doc->getXRef()->setModifiedObject(&obj1, updatedAppearanceStream);
+    }
+
+    Object obj1 = Object(new Dict(doc->getXRef()));
+    obj1.dictAdd("N", Object(updatedAppearanceStream));
+    update("AP", std::move(obj1));
+}
+
+void AnnotStamp::clearCustomImage()
+{
+    if (stampImageHelper != nullptr) {
+        stampImageHelper->removeAnnotStampImageObject();
+        delete stampImageHelper;
+    }
+    invalidateAppearance();
+}
+
 //------------------------------------------------------------------------
 // AnnotGeometry
 //------------------------------------------------------------------------
diff --git a/poppler/Annot.h b/poppler/Annot.h
index daaf7b10..d5c132c9 100644
--- a/poppler/Annot.h
+++ b/poppler/Annot.h
@@ -53,6 +53,7 @@
 #include <mutex>
 #include <vector>
 
+#include "AnnotStampImageHelper.h"
 #include "Object.h"
 #include "poppler_private_export.h"
 
@@ -731,6 +732,8 @@ public:
     const GooString *getName() const { return name.get(); }
     const GooString *getModified() const { return modified.get(); }
     unsigned int getFlags() const { return flags; }
+    Object getAppearance() const;
+    void setNewAppearance(Object &&newAppearance);
     AnnotAppearance *getAppearStreams() const { return appearStreams.get(); }
     const GooString *getAppearState() const { return appearState.get(); }
     AnnotBorder *getBorder() const { return border.get(); }
@@ -1195,15 +1198,24 @@ public:
     AnnotStamp(PDFDoc *docA, Object &&dictObject, const Object *obj);
     ~AnnotStamp() override;
 
+    void draw(Gfx *gfx, bool printing) override;
+
     void setIcon(GooString *new_icon);
 
+    void setCustomImage(AnnotStampImageHelper *stampImageHelperA);
+
+    void clearCustomImage();
+
     // getters
     const GooString *getIcon() const { return icon.get(); }
 
 private:
     void initialize(PDFDoc *docA, Dict *dict);
+    void generateStampAppearance();
 
     std::unique_ptr<GooString> icon; // Name       (Default Draft)
+    AnnotStampImageHelper *stampImageHelper;
+    Ref updatedAppearanceStream;
 };
 
 //------------------------------------------------------------------------
@@ -1436,7 +1448,6 @@ public:
     std::unique_ptr<LinkAction> getAdditionalAction(AdditionalActionsType type);
     std::unique_ptr<LinkAction> getFormAdditionalAction(FormAdditionalActionsType type);
     Dict *getParent() { return parent; }
-    void setNewAppearance(Object &&newAppearance);
 
     bool setFormAdditionalAction(FormAdditionalActionsType type, const GooString &js);
 
diff --git a/poppler/AnnotStampImageHelper.cc b/poppler/AnnotStampImageHelper.cc
new file mode 100644
index 00000000..d761f9d8
--- /dev/null
+++ b/poppler/AnnotStampImageHelper.cc
@@ -0,0 +1,77 @@
+//========================================================================
+//
+// AnnotStampImageHelper.cc
+//
+// Copyright (C) 2021 Mahmoud Ahmed Khalil <mahmoudkhalil11 at gmail.com>
+//
+// Licensed under GPLv2 or later
+//
+//========================================================================
+
+#include "AnnotStampImageHelper.h"
+
+#include "goo/gmem.h"
+#include "goo/gstrtod.h"
+#include "PDFDoc.h"
+#include "Stream.h"
+#include "Dict.h"
+
+#include <iostream>
+
+AnnotStampImageHelper::AnnotStampImageHelper(PDFDoc *docA, int widthA, int heightA, ColorSpace colorSpace, int bitsPerComponent, char *data, int dataLength)
+{
+    initialize(docA, widthA, heightA, colorSpace, bitsPerComponent, data, dataLength);
+}
+
+AnnotStampImageHelper::AnnotStampImageHelper(PDFDoc *docA, int widthA, int heightA, ColorSpace colorSpace, int bitsPerComponent, char *data, int dataLength, Ref softMaskRef)
+{
+    initialize(docA, widthA, heightA, colorSpace, bitsPerComponent, data, dataLength);
+
+    sMaskRef = softMaskRef;
+    Dict *dict = imgObj.streamGetDict();
+    dict->add("SMask", Object(sMaskRef));
+}
+
+void AnnotStampImageHelper::initialize(PDFDoc *docA, int widthA, int heightA, ColorSpace colorSpace, int bitsPerComponent, char *data, int dataLength)
+{
+    doc = docA;
+    width = widthA;
+    height = heightA;
+    sMaskRef = Ref::INVALID();
+
+    Dict *dict = new Dict(docA->getXRef());
+    dict->add("Type", Object(objName, "XObject"));
+    dict->add("Subtype", Object(objName, "Image"));
+    dict->add("Width", Object(width));
+    dict->add("Height", Object(height));
+    dict->add("ImageMask", Object(false));
+    dict->add("BitsPerComponent", Object(bitsPerComponent));
+    dict->add("Length", Object(dataLength));
+
+    switch (colorSpace) {
+    case ColorSpace::DeviceGray:
+        dict->add("ColorSpace", Object(objName, "DeviceGray"));
+        break;
+    case ColorSpace::DeviceRGB:
+        dict->add("ColorSpace", Object(objName, "DeviceRGB"));
+        break;
+    case ColorSpace::DeviceCMYK:
+        dict->add("ColorSpace", Object(objName, "DeviceCMYK"));
+        break;
+    }
+
+    char *dataCopied = (char *)gmalloc(sizeof(char) * (dataLength));
+    std::memcpy(dataCopied, data, dataLength);
+
+    Stream *dataStream = new AutoFreeMemStream(dataCopied, 0, dataLength, Object(dict));
+    imgObj = Object(dataStream);
+    ref = doc->getXRef()->addIndirectObject(&imgObj);
+}
+
+void AnnotStampImageHelper::removeAnnotStampImageObject()
+{
+    if (sMaskRef != Ref::INVALID())
+        doc->getXRef()->removeIndirectObject(sMaskRef);
+
+    doc->getXRef()->removeIndirectObject(ref);
+}
diff --git a/poppler/AnnotStampImageHelper.h b/poppler/AnnotStampImageHelper.h
new file mode 100644
index 00000000..75313a20
--- /dev/null
+++ b/poppler/AnnotStampImageHelper.h
@@ -0,0 +1,69 @@
+//========================================================================
+//
+// AnnotStampImageHelper.h
+//
+// Copyright (C) 2021 Mahmoud Ahmed Khalil <mahmoudkhalil11 at gmail.com>
+//
+// Licensed under GPLv2 or later
+//
+//========================================================================
+
+#ifndef ANNOTSTAMPIMAGEHELPER_H
+#define ANNOTSTAMPIMAGEHELPER_H
+
+#include "Object.h"
+
+class PDFDoc;
+
+enum ColorSpace
+{
+    DeviceGray,
+    DeviceRGB,
+    DeviceCMYK
+};
+
+/**
+ * This class is used only to load Image XObjects into stamp annotations. It takes in
+ * the image parameters in its constructors and creates a new Image XObject that gets
+ * added to the XRef table, so that the annotations that would like to use it be able
+ * to get its ref number.
+ *
+ * To have transparency in the image, you should first try to create the soft
+ * mask of the image, by creating a AnnotStampImageHelper object giving it the soft
+ * image data normally. You would then need to pass in the created soft mask Image XObject
+ * ref to the actual image you'd like to be created by this helper class.
+ */
+class POPPLER_PRIVATE_EXPORT AnnotStampImageHelper
+{
+public:
+    AnnotStampImageHelper(PDFDoc *docA, int widthA, int heightA, ColorSpace colorSpace, int bitsPerComponent, char *data, int dataLength);
+    AnnotStampImageHelper(PDFDoc *docA, int widthA, int heightA, ColorSpace colorSpace, int bitsPerComponent, char *data, int dataLength, Ref softMaskRef);
+    ~AnnotStampImageHelper() { }
+
+    // Returns the ref to the created Image XObject
+    Ref getRef() { return ref; }
+
+    // Returns the width of the image
+    int getWidth() const { return width; }
+    // Returns the height of the image
+    int getHeight() const { return height; }
+
+    Object &getAnnotStampImageHelperObject() { return imgObj; }
+
+    // Removes the created Image XObject as well as its soft mask from the XRef Table
+    void removeAnnotStampImageObject();
+
+private:
+    void initialize(PDFDoc *docA, int widthA, int heightA, ColorSpace colorSpace, int bitsPerComponent, char *data, int dataLength);
+
+    PDFDoc *doc;
+
+    Object imgObj;
+    Ref ref;
+    Ref sMaskRef;
+
+    int width;
+    int height;
+};
+
+#endif
diff --git a/qt5/src/poppler-annotation-private.h b/qt5/src/poppler-annotation-private.h
index 6458a4d3..c25e36a3 100644
--- a/qt5/src/poppler-annotation-private.h
+++ b/qt5/src/poppler-annotation-private.h
@@ -30,6 +30,7 @@
 #include "poppler-annotation.h"
 
 #include <Object.h>
+#include <AnnotStampImageHelper.h>
 
 class Annot;
 class AnnotPath;
@@ -40,6 +41,7 @@ namespace Poppler {
 class DocumentData;
 
 PDFRectangle boundaryToPdfRectangle(::Page *pdfPage, const QRectF &r, int flags);
+void getRawDataFromQImage(const QImage &qimg, int bitsPerPixel, QByteArray *data, QByteArray *sMaskData);
 
 class AnnotationPrivate : public QSharedData
 {
@@ -108,6 +110,16 @@ public:
     Ref pdfObjectReference() const;
 
     Link *additionalAction(Annotation::AdditionalActionType type) const;
+
+    Object annotationAppearance;
+};
+
+class AnnotationAppearancePrivate
+{
+public:
+    explicit AnnotationAppearancePrivate(Annot *annot);
+
+    Object appearance;
 };
 
 }
diff --git a/qt5/src/poppler-annotation.cc b/qt5/src/poppler-annotation.cc
index 336a395b..c99795a8 100644
--- a/qt5/src/poppler-annotation.cc
+++ b/qt5/src/poppler-annotation.cc
@@ -38,6 +38,7 @@
 #include <QtXml/QDomElement>
 #include <QtGui/QColor>
 #include <QtGui/QTransform>
+#include <QImage>
 
 // local includes
 #include "poppler-annotation.h"
@@ -127,9 +128,71 @@ QDomElement AnnotationUtils::findChildElement(const QDomNode &parentNode, const
 }
 // END AnnotationUtils implementation
 
+// BEGIN AnnotationAppearancePrivate implementation
+AnnotationAppearancePrivate::AnnotationAppearancePrivate(Annot *annot)
+{
+    if (annot) {
+        appearance = annot->getAppearance();
+    } else {
+        appearance.setToNull();
+    }
+}
+// END AnnotationAppearancePrivate implementation
+
+// BEGIN AnnotationAppearance implementation
+AnnotationAppearance::AnnotationAppearance(AnnotationAppearancePrivate *annotationAppearancePrivate) : d(annotationAppearancePrivate) { }
+
+AnnotationAppearance::~AnnotationAppearance()
+{
+    delete d;
+}
+// END AnnotationAppearance implementation
+
 // BEGIN Annotation implementation
 AnnotationPrivate::AnnotationPrivate() : flags(0), revisionScope(Annotation::Root), revisionType(Annotation::None), pdfAnnot(nullptr), pdfPage(nullptr), parentDoc(nullptr) { }
 
+void getRawDataFromQImage(const QImage &qimg, int bitsPerPixel, QByteArray *data, QByteArray *sMaskData)
+{
+    int height = qimg.height();
+    int width = qimg.width();
+
+    switch (bitsPerPixel) {
+    case 1:
+        for (int line = 0; line < height; line++) {
+            const char *lineData = reinterpret_cast<const char *>(qimg.scanLine(line));
+            for (int offset = 0; offset < (width + 7) / 8; offset++) {
+                data->append(lineData[offset]);
+            }
+        }
+        break;
+    case 8:
+    case 24:
+#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
+        data->append((const char *)qimg.bits(), static_cast<int>(qimg.sizeInBytes()));
+#else
+        data->append((const char *)qimg.bits(), qimg.byteCount());
+#endif
+        break;
+    case 32:
+        for (int line = 0; line < height; line++) {
+            const QRgb *lineData = reinterpret_cast<const QRgb *>(qimg.scanLine(line));
+            for (int offset = 0; offset < width; offset++) {
+                char a = (char)qAlpha(lineData[offset]);
+                char r = (char)qRed(lineData[offset]);
+                char g = (char)qGreen(lineData[offset]);
+                char b = (char)qBlue(lineData[offset]);
+
+                data->append(r);
+                data->append(g);
+                data->append(b);
+
+                sMaskData->append(a);
+            }
+        }
+        break;
+    }
+}
+
 void AnnotationPrivate::addRevision(Annotation *ann, Annotation::RevScope scope, Annotation::RevType type)
 {
     /* Since ownership stays with the caller, create an alias of ann */
@@ -786,6 +849,10 @@ void AnnotationPrivate::addAnnotationToPage(::Page *pdfPage, DocumentData *doc,
     // is private. Therefore, createNativeAnnot will never return 0
     Annot *nativeAnnot = ann->d_ptr->createNativeAnnot(pdfPage, doc);
     Q_ASSERT(nativeAnnot);
+
+    if (ann->d_ptr->annotationAppearance.isStream())
+        nativeAnnot->setNewAppearance(ann->d_ptr->annotationAppearance.copy());
+
     pdfPage->addAnnot(nativeAnnot);
 }
 
@@ -1730,6 +1797,28 @@ QList<Annotation *> Annotation::revisions() const
     return AnnotationPrivate::findAnnotations(d->pdfPage, d->parentDoc, QSet<Annotation::SubType>(), d->pdfAnnot->getId());
 }
 
+std::unique_ptr<AnnotationAppearance> Annotation::annotationAppearance() const
+{
+    Q_D(const Annotation);
+
+    return std::make_unique<AnnotationAppearance>(new AnnotationAppearancePrivate(d->pdfAnnot));
+}
+
+void Annotation::setAnnotationAppearance(const AnnotationAppearance &annotationAppearance)
+{
+    Q_D(Annotation);
+
+    if (!d->pdfAnnot) {
+        d->annotationAppearance = annotationAppearance.d->appearance.copy();
+        return;
+    }
+
+    // Moving the appearance object using std::move would result
+    // in the object being completed moved from the AnnotationAppearancePrivate
+    // class. So, we'll not be able to retrieve the stamp's original AP stream
+    d->pdfAnnot->setNewAppearance(annotationAppearance.d->appearance.copy());
+}
+
 // END Annotation implementation
 
 /** TextAnnotation [Annotation] */
@@ -3122,8 +3211,11 @@ public:
     Annotation *makeAlias() override;
     Annot *createNativeAnnot(::Page *destPage, DocumentData *doc) override;
 
+    AnnotStampImageHelper *convertQImageToAnnotStampImageHelper(const QImage &qimg);
+
     // data fields
     QString stampIconName;
+    QImage stampCustomImage;
 };
 
 StampAnnotationPrivate::StampAnnotationPrivate() : AnnotationPrivate(), stampIconName(QStringLiteral("Draft")) { }
@@ -3148,6 +3240,7 @@ Annot *StampAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData
     // Set properties
     flushBaseAnnotationProperties();
     q->setStampIconName(stampIconName);
+    q->setStampCustomImage(stampCustomImage);
 
     delete q;
 
@@ -3156,6 +3249,99 @@ Annot *StampAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData
     return pdfAnnot;
 }
 
+AnnotStampImageHelper *StampAnnotationPrivate::convertQImageToAnnotStampImageHelper(const QImage &qimg)
+{
+    QImage convertedQImage = qimg;
+
+    QByteArray data;
+    QByteArray sMaskData;
+    int width = convertedQImage.width();
+    int height = convertedQImage.height();
+    int bitsPerComponent = 1;
+    ColorSpace colorSpace = ColorSpace::DeviceGray;
+
+    switch (convertedQImage.format()) {
+    case QImage::Format_MonoLSB:
+        if (!convertedQImage.allGray()) {
+            convertedQImage = convertedQImage.convertToFormat(QImage::Format_RGB888);
+
+            colorSpace = ColorSpace::DeviceRGB;
+            bitsPerComponent = 8;
+        } else {
+            convertedQImage = convertedQImage.convertToFormat(QImage::Format_Mono);
+        }
+        break;
+    case QImage::Format_Mono:
+        if (!convertedQImage.allGray()) {
+            convertedQImage = convertedQImage.convertToFormat(QImage::Format_RGB888);
+
+            colorSpace = ColorSpace::DeviceRGB;
+            bitsPerComponent = 8;
+        }
+        break;
+    case QImage::Format_RGB32:
+    case QImage::Format_ARGB32_Premultiplied:
+    case QImage::Format_ARGB8565_Premultiplied:
+    case QImage::Format_ARGB6666_Premultiplied:
+    case QImage::Format_ARGB8555_Premultiplied:
+    case QImage::Format_ARGB4444_Premultiplied:
+    case QImage::Format_Alpha8:
+        convertedQImage = convertedQImage.convertToFormat(QImage::Format_ARGB32);
+        colorSpace = ColorSpace::DeviceRGB;
+        bitsPerComponent = 8;
+        break;
+    case QImage::Format_RGBA8888:
+    case QImage::Format_RGBA8888_Premultiplied:
+    case QImage::Format_RGBX8888:
+    case QImage::Format_ARGB32:
+        colorSpace = ColorSpace::DeviceRGB;
+        bitsPerComponent = 8;
+        break;
+    case QImage::Format_Grayscale8:
+        bitsPerComponent = 8;
+        break;
+#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
+    case QImage::Format_Grayscale16:
+        convertedQImage = convertedQImage.convertToFormat(QImage::Format_Grayscale8);
+
+        colorSpace = ColorSpace::DeviceGray;
+        bitsPerComponent = 8;
+        break;
+#endif
+    case QImage::Format_RGB16:
+    case QImage::Format_RGB666:
+    case QImage::Format_RGB555:
+    case QImage::Format_RGB444:
+        convertedQImage = convertedQImage.convertToFormat(QImage::Format_RGB888);
+        colorSpace = ColorSpace::DeviceRGB;
+        bitsPerComponent = 8;
+        break;
+    case QImage::Format_RGB888:
+        colorSpace = ColorSpace::DeviceRGB;
+        bitsPerComponent = 8;
+        break;
+    default:
+        convertedQImage = convertedQImage.convertToFormat(QImage::Format_ARGB32);
+
+        colorSpace = ColorSpace::DeviceRGB;
+        bitsPerComponent = 8;
+        break;
+    }
+
+    getRawDataFromQImage(convertedQImage, convertedQImage.depth(), &data, &sMaskData);
+
+    AnnotStampImageHelper *annotImg;
+
+    if (sMaskData.count() > 0) {
+        AnnotStampImageHelper sMask(parentDoc->doc, width, height, ColorSpace::DeviceGray, 8, sMaskData.data(), sMaskData.count());
+        annotImg = new AnnotStampImageHelper(parentDoc->doc, width, height, colorSpace, bitsPerComponent, data.data(), data.count(), sMask.getRef());
+    } else {
+        annotImg = new AnnotStampImageHelper(parentDoc->doc, width, height, colorSpace, bitsPerComponent, data.data(), data.count());
+    }
+
+    return annotImg;
+}
+
 StampAnnotation::StampAnnotation() : Annotation(*new StampAnnotationPrivate()) { }
 
 StampAnnotation::StampAnnotation(StampAnnotationPrivate &dd) : Annotation(dd) { }
@@ -3226,6 +3412,24 @@ void StampAnnotation::setStampIconName(const QString &name)
     stampann->setIcon(&s);
 }
 
+void StampAnnotation::setStampCustomImage(const QImage &image)
+{
+    if (image.isNull()) {
+        return;
+    }
+
+    Q_D(StampAnnotation);
+
+    if (!d->pdfAnnot) {
+        d->stampCustomImage = QImage(image);
+        return;
+    }
+
+    AnnotStamp *stampann = static_cast<AnnotStamp *>(d->pdfAnnot);
+    AnnotStampImageHelper *annotCustomImage = d->convertQImageToAnnotStampImageHelper(image);
+    stampann->setCustomImage(annotCustomImage);
+}
+
 /** InkAnnotation [Annotation] */
 class InkAnnotationPrivate : public AnnotationPrivate
 {
diff --git a/qt5/src/poppler-annotation.h b/qt5/src/poppler-annotation.h
index ca9489ed..b4995be0 100644
--- a/qt5/src/poppler-annotation.h
+++ b/qt5/src/poppler-annotation.h
@@ -44,10 +44,13 @@
 #include <QtXml/QDomDocument>
 #include "poppler-export.h"
 
+#include <memory>
+
 namespace Poppler {
 
 class Annotation;
 class AnnotationPrivate;
+class AnnotationAppearancePrivate;
 class TextAnnotationPrivate;
 class LineAnnotationPrivate;
 class GeomAnnotationPrivate;
@@ -97,6 +100,29 @@ public:
     Q_DECL_DEPRECATED static QDomElement findChildElement(const QDomNode &parentNode, const QString &name);
 };
 
+/**
+ * \short AnnotationAppearance class wrapping Poppler's AP stream object
+ *
+ * The Annotation's Appearance Stream is a Form XObject containing
+ * information required to properly render the Annotation on the document.
+ *
+ * This class wraps Poppler's Object implementing the appearance stream
+ * for the calling annotation. It can be used to preserve the current
+ * Appearance Stream for the calling annotation.
+ */
+class POPPLER_QT5_EXPORT AnnotationAppearance
+{
+    friend class Annotation;
+
+public:
+    explicit AnnotationAppearance(AnnotationAppearancePrivate *annotationAppearancePrivate);
+    ~AnnotationAppearance();
+
+private:
+    AnnotationAppearancePrivate *d;
+    Q_DISABLE_COPY(AnnotationAppearance)
+};
+
 /**
  * \short Annotation class holding properties shared by all annotations.
  *
@@ -421,6 +447,16 @@ public:
      */
     virtual SubType subType() const = 0;
 
+    /**
+     * Returns the current appearance stream of this annotation.
+     */
+    std::unique_ptr<AnnotationAppearance> annotationAppearance() const;
+
+    /**
+     * Sets the annotation's appearance stream with the @p annotationAppearance.
+     */
+    void setAnnotationAppearance(const AnnotationAppearance &annotationAppearance);
+
     /**
      * Destructor.
      */
@@ -778,6 +814,11 @@ public:
     */
     void setStampIconName(const QString &name);
 
+    /**
+       Set a custom icon for this stamp annotation.
+    */
+    void setStampCustomImage(const QImage &image);
+
 private:
     explicit StampAnnotation(const QDomNode &node);
     explicit StampAnnotation(StampAnnotationPrivate &dd);
diff --git a/qt6/src/poppler-annotation-private.h b/qt6/src/poppler-annotation-private.h
index 692eddcc..a98fe44c 100644
--- a/qt6/src/poppler-annotation-private.h
+++ b/qt6/src/poppler-annotation-private.h
@@ -32,6 +32,7 @@
 #include "poppler-annotation.h"
 
 #include <Object.h>
+#include <AnnotStampImageHelper.h>
 
 class Annot;
 class AnnotPath;
@@ -42,6 +43,7 @@ namespace Poppler {
 class DocumentData;
 
 PDFRectangle boundaryToPdfRectangle(::Page *pdfPage, const QRectF &r, int flags);
+void getRawDataFromQImage(const QImage &qimg, int bitsPerPixel, QByteArray *data, QByteArray *sMaskData);
 
 class AnnotationPrivate : public QSharedData
 {
@@ -110,6 +112,16 @@ public:
     Ref pdfObjectReference() const;
 
     std::unique_ptr<Link> additionalAction(Annotation::AdditionalActionType type) const;
+
+    Object annotationAppearance;
+};
+
+class AnnotationAppearancePrivate
+{
+public:
+    explicit AnnotationAppearancePrivate(Annot *annot);
+
+    Object appearance;
 };
 
 }
diff --git a/qt6/src/poppler-annotation.cc b/qt6/src/poppler-annotation.cc
index 337ed898..e54177d2 100644
--- a/qt6/src/poppler-annotation.cc
+++ b/qt6/src/poppler-annotation.cc
@@ -36,6 +36,7 @@
 #include <QtCore/QtAlgorithms>
 #include <QtGui/QColor>
 #include <QtGui/QTransform>
+#include <QImage>
 
 // local includes
 #include "poppler-annotation.h"
@@ -63,9 +64,71 @@
 
 namespace Poppler {
 
+// BEGIN AnnotationAppearancePrivate implementation
+AnnotationAppearancePrivate::AnnotationAppearancePrivate(Annot *annot)
+{
+    if (annot) {
+        appearance = annot->getAppearance();
+    } else {
+        appearance.setToNull();
+    }
+}
+// END AnnotationAppearancePrivate implementation
+
+// BEGIN AnnotationAppearance implementation
+AnnotationAppearance::AnnotationAppearance(AnnotationAppearancePrivate *annotationAppearancePrivate) : d(annotationAppearancePrivate) { }
+
+AnnotationAppearance::~AnnotationAppearance()
+{
+    delete d;
+}
+// END AnnotationAppearance implementation
+
 // BEGIN Annotation implementation
 AnnotationPrivate::AnnotationPrivate() : revisionScope(Annotation::Root), revisionType(Annotation::None), pdfAnnot(nullptr), pdfPage(nullptr), parentDoc(nullptr) { }
 
+void getRawDataFromQImage(const QImage &qimg, int bitsPerPixel, QByteArray *data, QByteArray *sMaskData)
+{
+    int height = qimg.height();
+    int width = qimg.width();
+
+    switch (bitsPerPixel) {
+    case 1:
+        for (int line = 0; line < height; line++) {
+            const char *lineData = reinterpret_cast<const char *>(qimg.scanLine(line));
+            for (int offset = 0; offset < (width + 7) / 8; offset++) {
+                data->append(lineData[offset]);
+            }
+        }
+        break;
+    case 8:
+    case 24:
+#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
+        data->append((const char *)qimg.bits(), static_cast<int>(qimg.sizeInBytes()));
+#else
+        data->append((const char *)qimg.bits(), qimg.byteCount());
+#endif
+        break;
+    case 32:
+        for (int line = 0; line < height; line++) {
+            const QRgb *lineData = reinterpret_cast<const QRgb *>(qimg.scanLine(line));
+            for (int offset = 0; offset < width; offset++) {
+                char a = (char)qAlpha(lineData[offset]);
+                char r = (char)qRed(lineData[offset]);
+                char g = (char)qGreen(lineData[offset]);
+                char b = (char)qBlue(lineData[offset]);
+
+                data->append(r);
+                data->append(g);
+                data->append(b);
+
+                sMaskData->append(a);
+            }
+        }
+        break;
+    }
+}
+
 void AnnotationPrivate::addRevision(Annotation *ann, Annotation::RevScope scope, Annotation::RevType type)
 {
     /* Since ownership stays with the caller, create an alias of ann */
@@ -712,6 +775,10 @@ void AnnotationPrivate::addAnnotationToPage(::Page *pdfPage, DocumentData *doc,
     // is private. Therefore, createNativeAnnot will never return 0
     Annot *nativeAnnot = ann->d_ptr->createNativeAnnot(pdfPage, doc);
     Q_ASSERT(nativeAnnot);
+
+    if (ann->d_ptr->annotationAppearance.isStream())
+        nativeAnnot->setNewAppearance(ann->d_ptr->annotationAppearance.copy());
+
     pdfPage->addAnnot(nativeAnnot);
 }
 
@@ -1399,6 +1466,28 @@ std::vector<std::unique_ptr<Annotation>> Annotation::revisions() const
     return AnnotationPrivate::findAnnotations(d->pdfPage, d->parentDoc, QSet<Annotation::SubType>(), d->pdfAnnot->getId());
 }
 
+std::unique_ptr<AnnotationAppearance> Annotation::annotationAppearance() const
+{
+    Q_D(const Annotation);
+
+    return std::make_unique<AnnotationAppearance>(new AnnotationAppearancePrivate(d->pdfAnnot));
+}
+
+void Annotation::setAnnotationAppearance(const AnnotationAppearance &annotationAppearance)
+{
+    Q_D(Annotation);
+
+    if (!d->pdfAnnot) {
+        d->annotationAppearance = annotationAppearance.d->appearance.copy();
+        return;
+    }
+
+    // Moving the appearance object using std::move would result
+    // in the object being completed moved from the AnnotationAppearancePrivate
+    // class. So, we'll not be able to retrieve the stamp's original AP stream
+    d->pdfAnnot->setNewAppearance(annotationAppearance.d->appearance.copy());
+}
+
 // END Annotation implementation
 
 /** TextAnnotation [Annotation] */
@@ -2508,8 +2597,11 @@ public:
     Annotation *makeAlias() override;
     Annot *createNativeAnnot(::Page *destPage, DocumentData *doc) override;
 
+    AnnotStampImageHelper *convertQImageToAnnotStampImageHelper(const QImage &qimg);
+
     // data fields
     QString stampIconName;
+    QImage stampCustomImage;
 };
 
 StampAnnotationPrivate::StampAnnotationPrivate() : AnnotationPrivate(), stampIconName(QStringLiteral("Draft")) { }
@@ -2534,6 +2626,7 @@ Annot *StampAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData
     // Set properties
     flushBaseAnnotationProperties();
     q->setStampIconName(stampIconName);
+    q->setStampCustomImage(stampCustomImage);
 
     delete q;
 
@@ -2542,6 +2635,99 @@ Annot *StampAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData
     return pdfAnnot;
 }
 
+AnnotStampImageHelper *StampAnnotationPrivate::convertQImageToAnnotStampImageHelper(const QImage &qimg)
+{
+    QImage convertedQImage = qimg;
+
+    QByteArray data;
+    QByteArray sMaskData;
+    int width = convertedQImage.width();
+    int height = convertedQImage.height();
+    int bitsPerComponent = 1;
+    ColorSpace colorSpace = ColorSpace::DeviceGray;
+
+    switch (convertedQImage.format()) {
+    case QImage::Format_MonoLSB:
+        if (!convertedQImage.allGray()) {
+            convertedQImage = convertedQImage.convertToFormat(QImage::Format_RGB888);
+
+            colorSpace = ColorSpace::DeviceRGB;
+            bitsPerComponent = 8;
+        } else {
+            convertedQImage = convertedQImage.convertToFormat(QImage::Format_Mono);
+        }
+        break;
+    case QImage::Format_Mono:
+        if (!convertedQImage.allGray()) {
+            convertedQImage = convertedQImage.convertToFormat(QImage::Format_RGB888);
+
+            colorSpace = ColorSpace::DeviceRGB;
+            bitsPerComponent = 8;
+        }
+        break;
+    case QImage::Format_RGB32:
+    case QImage::Format_ARGB32_Premultiplied:
+    case QImage::Format_ARGB8565_Premultiplied:
+    case QImage::Format_ARGB6666_Premultiplied:
+    case QImage::Format_ARGB8555_Premultiplied:
+    case QImage::Format_ARGB4444_Premultiplied:
+    case QImage::Format_Alpha8:
+        convertedQImage = convertedQImage.convertToFormat(QImage::Format_ARGB32);
+        colorSpace = ColorSpace::DeviceRGB;
+        bitsPerComponent = 8;
+        break;
+    case QImage::Format_RGBA8888:
+    case QImage::Format_RGBA8888_Premultiplied:
+    case QImage::Format_RGBX8888:
+    case QImage::Format_ARGB32:
+        colorSpace = ColorSpace::DeviceRGB;
+        bitsPerComponent = 8;
+        break;
+    case QImage::Format_Grayscale8:
+        bitsPerComponent = 8;
+        break;
+#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
+    case QImage::Format_Grayscale16:
+        convertedQImage = convertedQImage.convertToFormat(QImage::Format_Grayscale8);
+
+        colorSpace = ColorSpace::DeviceGray;
+        bitsPerComponent = 8;
+        break;
+#endif
+    case QImage::Format_RGB16:
+    case QImage::Format_RGB666:
+    case QImage::Format_RGB555:
+    case QImage::Format_RGB444:
+        convertedQImage = convertedQImage.convertToFormat(QImage::Format_RGB888);
+        colorSpace = ColorSpace::DeviceRGB;
+        bitsPerComponent = 8;
+        break;
+    case QImage::Format_RGB888:
+        colorSpace = ColorSpace::DeviceRGB;
+        bitsPerComponent = 8;
+        break;
+    default:
+        convertedQImage = convertedQImage.convertToFormat(QImage::Format_ARGB32);
+
+        colorSpace = ColorSpace::DeviceRGB;
+        bitsPerComponent = 8;
+        break;
+    }
+
+    getRawDataFromQImage(convertedQImage, convertedQImage.depth(), &data, &sMaskData);
+
+    AnnotStampImageHelper *annotImg;
+
+    if (sMaskData.count() > 0) {
+        AnnotStampImageHelper sMask(parentDoc->doc, width, height, ColorSpace::DeviceGray, 8, sMaskData.data(), sMaskData.count());
+        annotImg = new AnnotStampImageHelper(parentDoc->doc, width, height, colorSpace, bitsPerComponent, data.data(), data.count(), sMask.getRef());
+    } else {
+        annotImg = new AnnotStampImageHelper(parentDoc->doc, width, height, colorSpace, bitsPerComponent, data.data(), data.count());
+    }
+
+    return annotImg;
+}
+
 StampAnnotation::StampAnnotation() : Annotation(*new StampAnnotationPrivate()) { }
 
 StampAnnotation::StampAnnotation(StampAnnotationPrivate &dd) : Annotation(dd) { }
@@ -2579,6 +2765,24 @@ void StampAnnotation::setStampIconName(const QString &name)
     stampann->setIcon(&s);
 }
 
+void StampAnnotation::setStampCustomImage(const QImage &image)
+{
+    if (image.isNull()) {
+        return;
+    }
+
+    Q_D(StampAnnotation);
+
+    if (!d->pdfAnnot) {
+        d->stampCustomImage = QImage(image);
+        return;
+    }
+
+    AnnotStamp *stampann = static_cast<AnnotStamp *>(d->pdfAnnot);
+    AnnotStampImageHelper *annotCustomImage = d->convertQImageToAnnotStampImageHelper(image);
+    stampann->setCustomImage(annotCustomImage);
+}
+
 /** InkAnnotation [Annotation] */
 class InkAnnotationPrivate : public AnnotationPrivate
 {
diff --git a/qt6/src/poppler-annotation.h b/qt6/src/poppler-annotation.h
index eceb234f..fd3d2504 100644
--- a/qt6/src/poppler-annotation.h
+++ b/qt6/src/poppler-annotation.h
@@ -43,10 +43,13 @@
 #include <QtGui/QFont>
 #include "poppler-export.h"
 
+#include <memory>
+
 namespace Poppler {
 
 class Annotation;
 class AnnotationPrivate;
+class AnnotationAppearancePrivate;
 class TextAnnotationPrivate;
 class LineAnnotationPrivate;
 class GeomAnnotationPrivate;
@@ -68,6 +71,29 @@ class MovieObject;
 class LinkRendition;
 class Page;
 
+/**
+ * \short AnnotationAppearance class wrapping Poppler's AP stream object
+ *
+ * The Annotation's Appearance Stream is a Form XObject containing
+ * information required to properly render the Annotation on the document.
+ *
+ * This class wraps Poppler's Object implementing the appearance stream
+ * for the calling annotation. It can be used to preserve the current
+ * Appearance Stream for the calling annotation.
+ */
+class POPPLER_QT6_EXPORT AnnotationAppearance
+{
+    friend class Annotation;
+
+public:
+    explicit AnnotationAppearance(AnnotationAppearancePrivate *annotationAppearancePrivate);
+    ~AnnotationAppearance();
+
+private:
+    AnnotationAppearancePrivate *d;
+    Q_DISABLE_COPY(AnnotationAppearance)
+};
+
 /**
  * \short Annotation class holding properties shared by all annotations.
  *
@@ -377,6 +403,16 @@ public:
      */
     virtual SubType subType() const = 0;
 
+    /**
+     * Returns the current appearance stream of this annotation.
+     */
+    std::unique_ptr<AnnotationAppearance> annotationAppearance() const;
+
+    /**
+     * Sets the annotation's appearance stream with the @p annotationAppearance.
+     */
+    void setAnnotationAppearance(const AnnotationAppearance &annotationAppearance);
+
     /**
      * Destructor.
      */
@@ -714,6 +750,11 @@ public:
     */
     void setStampIconName(const QString &name);
 
+    /**
+       Set a custom icon for this stamp annotation.
+    */
+    void setStampCustomImage(const QImage &image);
+
 private:
     explicit StampAnnotation(StampAnnotationPrivate &dd);
     Q_DECLARE_PRIVATE(StampAnnotation)


More information about the poppler mailing list