[poppler] CMakeLists.txt poppler/Annot.cc poppler/Annot.h poppler/Form.cc poppler/Form.h poppler/ImageEmbeddingUtils.cc poppler/ImageEmbeddingUtils.h poppler/PDFDoc.cc poppler/PDFDoc.h poppler/Stream.cc poppler/Stream.h poppler/XRef.cc poppler/XRef.h test/CMakeLists.txt test/image-embedding.cc
GitLab Mirror
gitlab-mirror at kemper.freedesktop.org
Mon Nov 22 13:47:13 UTC 2021
CMakeLists.txt | 1
poppler/Annot.cc | 29 ++
poppler/Annot.h | 3
poppler/Form.cc | 10 +
poppler/Form.h | 5
poppler/ImageEmbeddingUtils.cc | 407 +++++++++++++++++++++++++++++++++++++++++
poppler/ImageEmbeddingUtils.h | 36 +++
poppler/PDFDoc.cc | 20 +-
poppler/PDFDoc.h | 5
poppler/Stream.cc | 10 +
poppler/Stream.h | 8
poppler/XRef.cc | 15 +
poppler/XRef.h | 7
test/CMakeLists.txt | 67 ++++++
test/image-embedding.cc | 106 ++++++++++
15 files changed, 723 insertions(+), 6 deletions(-)
New commits:
commit d1070d73747d3c8771175c43e214f84537b65037
Author: Georgiy Sgibnev <georgiy at lab50.net>
Date: Fri Jul 23 13:45:44 2021 +0300
Image embedding API
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7e959305..88d6392c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -419,6 +419,7 @@ set(poppler_SRCS
poppler/GfxState.cc
poppler/GlobalParams.cc
poppler/Hints.cc
+ poppler/ImageEmbeddingUtils.cc
poppler/JArithmeticDecoder.cc
poppler/JBIG2Stream.cc
poppler/JSInfo.cc
diff --git a/poppler/Annot.cc b/poppler/Annot.cc
index 9f2289b5..6d1a2aea 100644
--- a/poppler/Annot.cc
+++ b/poppler/Annot.cc
@@ -4934,7 +4934,7 @@ bool AnnotAppearanceBuilder::drawSignatureFieldText(const FormFieldSignature *fi
const GooString &leftText = field->getCustomAppearanceLeftContent();
if (leftText.toStr().empty()) {
- drawSignatureFieldText(contents, DefaultAppearance(_da), border, rect, xref, resourcesDict, 0, false /* don't center vertically */, false /* don't center horizontally */);
+ drawSignatureFieldText(contents, DefaultAppearance(_da), border, rect, xref, resourcesDict, 0, false /* don't center vertically */, false /* don't center horizontally */, field->getImageResource());
} else {
DefaultAppearance daLeft(_da);
daLeft.setFontPtSize(field->getCustomAppearanceLeftFontSize());
@@ -4949,8 +4949,20 @@ bool AnnotAppearanceBuilder::drawSignatureFieldText(const FormFieldSignature *fi
return true;
}
+// Helper function for AnnotAppearanceBuilder::drawSignatureFieldText(). Registers a resource.
+// Argument resourceType should be "XObject" or "Font".
+static void registerResourceForWidget(const char *resourceType, Dict *resourcesDict, const char *resourceId, const Ref resourceRef, XRef *xref)
+{
+ Object childDictionaryObj = resourcesDict->lookup(resourceType);
+ if (!childDictionaryObj.isDict()) {
+ childDictionaryObj = Object(new Dict(xref));
+ resourcesDict->add(resourceType, childDictionaryObj.copy());
+ }
+ childDictionaryObj.dictSet(resourceId, Object(resourceRef));
+}
+
void AnnotAppearanceBuilder::drawSignatureFieldText(const GooString &text, const DefaultAppearance &da, const AnnotBorder *border, const PDFRectangle *rect, XRef *xref, Dict *resourcesDict, double leftMargin, bool centerVertically,
- bool centerHorizontally)
+ bool centerHorizontally, const Ref imageResourceRef)
{
double borderWidth = 0;
append("q\n");
@@ -4967,6 +4979,19 @@ void AnnotAppearanceBuilder::drawSignatureFieldText(const GooString &text, const
const double textmargin = borderWidth * 2;
const double textwidth = width - 2 * textmargin;
+ // Print a background image.
+ if (imageResourceRef != Ref::INVALID()) {
+ static const char *imageResourceId = "SigImg";
+ registerResourceForWidget("XObject", resourcesDict, imageResourceId, imageResourceRef, xref);
+
+ Matrix matrix = { 1.0, 0.0, 0.0, 1.0, 0.0, 0.0 };
+ matrix.scale(width, height);
+ static const char *IMG_TMPL = "\nq {0:.1g} {1:.1g} {2:.1g} {3:.1g} {4:.1g} {5:.1g} cm /{6:s} Do Q\n";
+ const GooString *imgBuffer = GooString::format(IMG_TMPL, matrix.m[0], matrix.m[1], matrix.m[2], matrix.m[3], matrix.m[4], matrix.m[5], imageResourceId);
+ append(imgBuffer->c_str());
+ delete imgBuffer;
+ }
+
// create a Helvetica fake font
GfxFont *font = createAnnotDrawFont(xref, resourcesDict, da.getFontName().getName());
diff --git a/poppler/Annot.h b/poppler/Annot.h
index 745618db..22621c1e 100644
--- a/poppler/Annot.h
+++ b/poppler/Annot.h
@@ -597,7 +597,8 @@ private:
XRef *xref, Dict *resourcesDict);
bool drawSignatureFieldText(const FormFieldSignature *field, const Form *form, const GfxResources *resources, const GooString *da, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs, const PDFRectangle *rect,
XRef *xref, Dict *resourcesDict);
- void drawSignatureFieldText(const GooString &text, const DefaultAppearance &da, const AnnotBorder *border, const PDFRectangle *rect, XRef *xref, Dict *resourcesDict, double leftMargin, bool centerVertically, bool centerHorizontally);
+ void drawSignatureFieldText(const GooString &text, const DefaultAppearance &da, const AnnotBorder *border, const PDFRectangle *rect, XRef *xref, Dict *resourcesDict, double leftMargin, bool centerVertically, bool centerHorizontally,
+ const Ref imageResourceRef = Ref::INVALID());
bool drawText(const GooString *text, const GooString *da, const GfxResources *resources, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs, const PDFRectangle *rect, bool multiline, int comb, int quadding,
bool txField, bool forceZapfDingbats, XRef *xref, bool password, Dict *resourcesDict, const char *defaultFallback = "Helvetica");
void drawArrowPath(double x, double y, const Matrix &m, int orientation = 1);
diff --git a/poppler/Form.cc b/poppler/Form.cc
index fe39d3cb..46ed2cc9 100644
--- a/poppler/Form.cc
+++ b/poppler/Form.cc
@@ -2032,6 +2032,16 @@ void FormFieldSignature::setCustomAppearanceLeftFontSize(double size)
customAppearanceLeftFontSize = size;
}
+Ref FormFieldSignature::getImageResource() const
+{
+ return imageResource;
+}
+
+void FormFieldSignature::setImageResource(const Ref imageResourceA)
+{
+ imageResource = imageResourceA;
+}
+
void FormFieldSignature::setCertificateInfo(std::unique_ptr<X509CertificateInfo> &certInfo)
{
certificate_info.swap(certInfo);
diff --git a/poppler/Form.h b/poppler/Form.h
index d88f0a0a..61174e83 100644
--- a/poppler/Form.h
+++ b/poppler/Form.h
@@ -624,6 +624,10 @@ public:
double getCustomAppearanceLeftFontSize() const;
void setCustomAppearanceLeftFontSize(double size);
+ // Background image (ref to an object of type XObject). Invalid ref if not required.
+ Ref getImageResource() const;
+ void setImageResource(const Ref imageResourceA);
+
void setCertificateInfo(std::unique_ptr<X509CertificateInfo> &);
FormWidget *getCreateWidget();
@@ -639,6 +643,7 @@ private:
GooString customAppearanceContent;
GooString customAppearanceLeftContent;
double customAppearanceLeftFontSize = 20;
+ Ref imageResource = Ref::INVALID();
std::unique_ptr<X509CertificateInfo> certificate_info;
void print(int indent) override;
diff --git a/poppler/ImageEmbeddingUtils.cc b/poppler/ImageEmbeddingUtils.cc
new file mode 100644
index 00000000..a3dd5868
--- /dev/null
+++ b/poppler/ImageEmbeddingUtils.cc
@@ -0,0 +1,407 @@
+//========================================================================
+//
+// ImageEmbeddingUtils.cc
+//
+// Copyright (C) 2021 Georgiy Sgibnev <georgiy at sgibnev.com>. Work sponsored by lab50.net.
+//
+// This file is licensed under the GPLv2 or later
+//
+//========================================================================
+
+#include <config.h>
+
+#include <memory>
+#ifdef ENABLE_LIBJPEG
+extern "C" {
+# include <jpeglib.h>
+}
+#endif
+#ifdef ENABLE_LIBPNG
+# include <png.h>
+#endif
+
+#include "ImageEmbeddingUtils.h"
+#include "goo/gmem.h"
+#include "Object.h"
+#include "Array.h"
+#include "Error.h"
+#include "PDFDoc.h"
+
+namespace ImageEmbeddingUtils {
+
+static const uint8_t PNG_MAGIC_NUM[] = { 0x89, 0x50, 0x4e, 0x47 };
+static const uint8_t JPEG_MAGIC_NUM[] = { 0xff, 0xd8, 0xff };
+static const uint8_t JPEG2000_MAGIC_NUM[] = { 0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20 };
+static const Goffset MAX_MAGIC_NUM_SIZE = sizeof(JPEG2000_MAGIC_NUM);
+
+static bool checkMagicNum(const uint8_t *fileContent, const uint8_t *magicNum, const uint8_t size)
+{
+ return (memcmp(fileContent, magicNum, size) == 0);
+}
+
+// Transforms an image to XObject.
+class ImageEmbedder
+{
+protected:
+ static constexpr const char *DEVICE_GRAY = "DeviceGray";
+ static constexpr const char *DEVICE_RGB = "DeviceRGB";
+
+ int m_width;
+ int m_height;
+
+ ImageEmbedder(const int width, const int height) : m_width(width), m_height(height) { }
+
+ // Creates an object of type XObject. You own the returned ptr.
+ static Dict *createImageDict(XRef *xref, const char *colorSpace, const int width, const int height, const int bitsPerComponent)
+ {
+ Dict *imageDict = new Dict(xref);
+ imageDict->add("Type", Object(objName, "XObject"));
+ imageDict->add("Subtype", Object(objName, "Image"));
+ imageDict->add("ColorSpace", Object(objName, colorSpace));
+ imageDict->add("Width", Object(width));
+ imageDict->add("Height", Object(height));
+ imageDict->add("BitsPerComponent", Object(bitsPerComponent));
+ return imageDict;
+ }
+
+public:
+ ImageEmbedder() = delete;
+ ImageEmbedder(const ImageEmbedder &) = delete;
+ ImageEmbedder &operator=(const ImageEmbedder &) = delete;
+ virtual ~ImageEmbedder();
+
+ // Call it only once.
+ // Returns ref to a new object or Ref::INVALID.
+ virtual Ref embedImage(XRef *xref) = 0;
+};
+
+ImageEmbedder::~ImageEmbedder() { }
+
+#ifdef ENABLE_LIBPNG
+// Transforms a PNG image to XObject.
+class PngEmbedder : public ImageEmbedder
+{
+ // LibpngInputStream is a simple replacement for GInputStream.
+ // Used with png_set_read_fn().
+ class LibpngInputStream
+ {
+ uint8_t *m_fileContent;
+ uint8_t *m_iterator;
+ png_size_t m_remainingSize;
+
+ void read(png_bytep out, const png_size_t size)
+ {
+ const png_size_t fixedSize = (m_remainingSize >= size) ? size : m_remainingSize;
+ memcpy(out, m_iterator, fixedSize);
+ m_iterator += fixedSize;
+ m_remainingSize -= fixedSize;
+ }
+
+ public:
+ // LibpngInputStream takes ownership over the buffer.
+ LibpngInputStream(uint8_t *fileContent, const Goffset size) : m_fileContent(fileContent), m_iterator(fileContent), m_remainingSize(size) { }
+ LibpngInputStream() = delete;
+ LibpngInputStream(const LibpngInputStream &) = delete;
+ LibpngInputStream &operator=(const LibpngInputStream &) = delete;
+ ~LibpngInputStream() { gfree(m_fileContent); }
+
+ // Pass this static function to png_set_read_fn().
+ static void readCallback(png_structp png, png_bytep out, png_size_t size)
+ {
+ LibpngInputStream *stream = (LibpngInputStream *)png_get_io_ptr(png);
+ if (stream) {
+ stream->read(out, size);
+ }
+ }
+ };
+
+ png_structp m_png;
+ png_infop m_info;
+ LibpngInputStream *m_stream;
+ const png_byte m_type;
+ const bool m_hasAlpha;
+ // Number of color channels.
+ const png_byte m_n;
+ // Number of color channels excluding alpha channel. Should be 1 or 3.
+ const png_byte m_nWithoutAlpha;
+ // Shold be 8 or 16.
+ const png_byte m_bitDepth;
+ // Should be 1 or 2.
+ const png_byte m_byteDepth;
+
+ PngEmbedder(png_structp png, png_infop info, LibpngInputStream *stream)
+ : ImageEmbedder(png_get_image_width(png, info), png_get_image_height(png, info)),
+ m_png(png),
+ m_info(info),
+ m_stream(stream),
+ m_type(png_get_color_type(m_png, m_info)),
+ m_hasAlpha(m_type & PNG_COLOR_MASK_ALPHA),
+ m_n(png_get_channels(m_png, m_info)),
+ m_nWithoutAlpha(m_hasAlpha ? m_n - 1 : m_n),
+ m_bitDepth(png_get_bit_depth(m_png, m_info)),
+ m_byteDepth(m_bitDepth / 8)
+ {
+ }
+
+ // Reads pixels into mainBuffer (RGB/gray channels) and maskBuffer (alpha channel).
+ void readPixels(png_bytep mainBuffer, png_bytep maskBuffer)
+ {
+ // Read pixels from m_png.
+ const int rowSize = png_get_rowbytes(m_png, m_info);
+ png_bytepp pixels = new png_bytep[m_height];
+ for (int y = 0; y < m_height; y++) {
+ pixels[y] = new png_byte[rowSize];
+ }
+ png_read_image(m_png, pixels);
+
+ // Copy pixels into mainBuffer and maskBuffer.
+ const png_byte pixelSizeWithoutAlpha = m_nWithoutAlpha * m_byteDepth;
+ for (int y = 0; y < m_height; y++) {
+ png_bytep row = pixels[y];
+ for (int x = 0; x < m_width; x++) {
+ memcpy(mainBuffer, row, pixelSizeWithoutAlpha);
+ mainBuffer += pixelSizeWithoutAlpha;
+ row += pixelSizeWithoutAlpha;
+ if (m_hasAlpha) {
+ memcpy(maskBuffer, row, m_byteDepth);
+ maskBuffer += m_byteDepth;
+ row += m_byteDepth;
+ }
+ }
+ }
+
+ // Cleanup.
+ for (int y = 0; y < m_height; y++) {
+ delete[] pixels[y];
+ }
+ delete[] pixels;
+ }
+
+ // Supportive function for create().
+ // We don't want to deal with palette images.
+ // We don't want to deal with 1/2/4-bit samples.
+ static void fixPng(png_structp png, png_infop info)
+ {
+ const png_byte colorType = png_get_color_type(png, info);
+ const png_byte bitDepth = png_get_bit_depth(png, info);
+
+ bool updateRequired = false;
+ if (colorType == PNG_COLOR_TYPE_PALETTE) {
+ png_set_palette_to_rgb(png);
+ updateRequired = true;
+ }
+ if ((colorType == PNG_COLOR_TYPE_GRAY) && (bitDepth < 8)) {
+ png_set_expand_gray_1_2_4_to_8(png);
+ updateRequired = true;
+ }
+ if (png_get_valid(png, info, PNG_INFO_tRNS)) {
+ png_set_tRNS_to_alpha(png);
+ updateRequired = true;
+ }
+ if (bitDepth < 8) {
+ png_set_packing(png);
+ updateRequired = true;
+ }
+ if (updateRequired) {
+ png_read_update_info(png, info);
+ }
+ }
+
+public:
+ PngEmbedder() = delete;
+ PngEmbedder(const PngEmbedder &) = delete;
+ PngEmbedder &operator=(const PngEmbedder &) = delete;
+ ~PngEmbedder() override
+ {
+ png_destroy_read_struct(&m_png, &m_info, nullptr);
+ delete m_stream;
+ }
+
+ Ref embedImage(XRef *xref) override;
+
+ // The function takes ownership over fileContent.
+ static std::unique_ptr<ImageEmbedder> create(uint8_t *fileContent, const Goffset fileSize)
+ {
+ png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+ if (png == nullptr) {
+ error(errInternal, -1, "Couldn't load PNG. png_create_read_struct() failed");
+ gfree(fileContent);
+ return nullptr;
+ }
+ png_infop info = png_create_info_struct(png);
+ if (info == nullptr) {
+ error(errInternal, -1, "Couldn't load PNG. png_create_info_struct() failed");
+ png_destroy_read_struct(&png, nullptr, nullptr);
+ gfree(fileContent);
+ return nullptr;
+ }
+ if (setjmp(png_jmpbuf(png))) {
+ error(errInternal, -1, "Couldn't load PNG. Failed to set up error handling for reading PNG");
+ png_destroy_read_struct(&png, &info, nullptr);
+ gfree(fileContent);
+ return nullptr;
+ }
+
+ LibpngInputStream *stream = new LibpngInputStream(fileContent, fileSize);
+ png_set_read_fn(png, stream, LibpngInputStream::readCallback);
+ png_read_info(png, info);
+ fixPng(png, info);
+ const png_byte bitDepth = png_get_bit_depth(png, info);
+ if ((bitDepth != 8) && (bitDepth != 16)) {
+ error(errInternal, -1, "Couldn't load PNG. Fixing bit depth failed");
+ png_destroy_read_struct(&png, &info, nullptr);
+ delete stream;
+ return nullptr;
+ }
+ return std::unique_ptr<ImageEmbedder>(new PngEmbedder(png, info, stream));
+ }
+};
+
+Ref PngEmbedder::embedImage(XRef *xref)
+{
+ // Read pixels.
+ const Goffset mainBufferSize = m_width * m_height * m_nWithoutAlpha * m_byteDepth;
+ png_bytep mainBuffer = (png_bytep)gmalloc(mainBufferSize);
+ const Goffset maskBufferSize = m_width * m_height * m_byteDepth;
+ png_bytep maskBuffer = (m_hasAlpha) ? (png_bytep)gmalloc(maskBufferSize) : nullptr;
+ readPixels(mainBuffer, maskBuffer);
+
+ // Create a mask XObject and a main XObject.
+ const char *colorSpace = ((m_type == PNG_COLOR_TYPE_GRAY) || (m_type == PNG_COLOR_TYPE_GRAY_ALPHA)) ? DEVICE_GRAY : DEVICE_RGB;
+ Dict *baseImageDict = createImageDict(xref, colorSpace, m_width, m_height, m_bitDepth);
+ if (m_hasAlpha) {
+ Dict *maskImageDict = createImageDict(xref, DEVICE_GRAY, m_width, m_height, m_bitDepth);
+ Ref maskImageRef = xref->addStreamObject(maskImageDict, maskBuffer, maskBufferSize);
+ baseImageDict->add("SMask", Object(maskImageRef));
+ }
+ return xref->addStreamObject(baseImageDict, mainBuffer, mainBufferSize);
+}
+#endif
+
+#ifdef ENABLE_LIBJPEG
+
+struct JpegErrorManager
+{
+ jpeg_error_mgr pub;
+ jmp_buf setjmpBuffer;
+};
+
+// Note: an address of pub is equal to an address of a JpegErrorManager instance.
+static void jpegExitErrorHandler(j_common_ptr info)
+{
+ JpegErrorManager *errorManager = (JpegErrorManager *)info->err;
+ (*errorManager->pub.output_message)(info);
+ // Jump to the setjmp point.
+ longjmp(errorManager->setjmpBuffer, 1);
+}
+
+// Transforms a JPEG image to XObject.
+class JpegEmbedder : public ImageEmbedder
+{
+ uint8_t *m_fileContent;
+ Goffset m_fileSize;
+
+ JpegEmbedder(const int width, const int height, uint8_t *fileContent, const Goffset fileSize) : ImageEmbedder(width, height), m_fileContent(fileContent), m_fileSize(fileSize) { }
+
+public:
+ JpegEmbedder() = delete;
+ JpegEmbedder(const JpegEmbedder &) = delete;
+ JpegEmbedder &operator=(const JpegEmbedder &) = delete;
+ ~JpegEmbedder() override
+ {
+ if (m_fileContent) {
+ gfree(m_fileContent);
+ }
+ }
+
+ Ref embedImage(XRef *xref) override;
+
+ // The function takes ownership over fileContent.
+ static std::unique_ptr<ImageEmbedder> create(uint8_t *fileContent, const Goffset fileSize)
+ {
+ jpeg_decompress_struct info;
+ JpegErrorManager errorManager;
+ info.err = jpeg_std_error(&errorManager.pub);
+ errorManager.pub.error_exit = jpegExitErrorHandler;
+ if (setjmp(errorManager.setjmpBuffer)) {
+ // The setjmp point.
+ jpeg_destroy_decompress(&info);
+ error(errInternal, -1, "libjpeg failed to process the file");
+ return nullptr;
+ }
+
+ jpeg_create_decompress(&info);
+ jpeg_mem_src(&info, fileContent, fileSize);
+ jpeg_read_header(&info, TRUE);
+ jpeg_start_decompress(&info);
+ auto result = std::unique_ptr<ImageEmbedder>(new JpegEmbedder(info.output_width, info.output_height, fileContent, fileSize));
+ jpeg_abort_decompress(&info);
+ jpeg_destroy_decompress(&info);
+ return result;
+ }
+};
+
+Ref JpegEmbedder::embedImage(XRef *xref)
+{
+ if (m_fileContent == nullptr) {
+ return Ref::INVALID();
+ }
+ Dict *baseImageDict = createImageDict(xref, DEVICE_RGB, m_width, m_height, 8);
+ baseImageDict->add("Filter", Object(objName, "DCTDecode"));
+ Ref baseImageRef = xref->addStreamObject(baseImageDict, m_fileContent, m_fileSize);
+ m_fileContent = nullptr;
+ return baseImageRef;
+}
+#endif
+
+Ref embed(XRef *xref, const GooFile &imageFile)
+{
+ // Load the image file.
+ const Goffset fileSize = imageFile.size();
+ uint8_t *fileContent = (uint8_t *)gmalloc(fileSize);
+ const Goffset bytesRead = imageFile.read((char *)fileContent, fileSize, 0);
+ if ((bytesRead != fileSize) || (fileSize < MAX_MAGIC_NUM_SIZE)) {
+ gfree(fileContent);
+ error(errIO, -1, "Couldn't load the image file");
+ return Ref::INVALID();
+ }
+
+ std::unique_ptr<ImageEmbedder> embedder;
+ if (checkMagicNum(fileContent, PNG_MAGIC_NUM, sizeof(PNG_MAGIC_NUM))) {
+#ifdef ENABLE_LIBPNG
+ embedder = PngEmbedder::create(fileContent, fileSize);
+#else
+ error(errUnimplemented, -1, "PNG format is not supported");
+#endif
+ } else if (checkMagicNum(fileContent, JPEG_MAGIC_NUM, sizeof(JPEG_MAGIC_NUM))) {
+#ifdef ENABLE_LIBJPEG
+ embedder = JpegEmbedder::create(fileContent, fileSize);
+#else
+ error(errUnimplemented, -1, "JPEG format is not supported");
+#endif
+ } else if (checkMagicNum(fileContent, JPEG2000_MAGIC_NUM, sizeof(JPEG2000_MAGIC_NUM))) {
+ // TODO: implement JPEG2000 support using libopenjpeg2.
+ error(errUnimplemented, -1, "JPEG2000 format is not supported");
+ return Ref::INVALID();
+ } else {
+ error(errUnimplemented, -1, "Image format is not supported");
+ return Ref::INVALID();
+ }
+
+ if (!embedder) {
+ return Ref::INVALID();
+ }
+ return embedder->embedImage(xref);
+}
+
+Ref embed(XRef *xref, const std::string &imagePath)
+{
+ std::unique_ptr<GooFile> imageFile(GooFile::open(imagePath));
+ if (!imageFile) {
+ error(errIO, -1, "Couldn't open {0:s}", imagePath.c_str());
+ return Ref::INVALID();
+ }
+ return embed(xref, *imageFile);
+}
+
+}
diff --git a/poppler/ImageEmbeddingUtils.h b/poppler/ImageEmbeddingUtils.h
new file mode 100644
index 00000000..7fafa7f5
--- /dev/null
+++ b/poppler/ImageEmbeddingUtils.h
@@ -0,0 +1,36 @@
+//========================================================================
+//
+// ImageEmbeddingUtils.h
+//
+// Copyright (C) 2021 Georgiy Sgibnev <georgiy at sgibnev.com>. Work sponsored by lab50.net.
+//
+// This file is licensed under the GPLv2 or later
+//
+//========================================================================
+
+#ifndef IMAGE_EMBEDDING_UTILS_H
+#define IMAGE_EMBEDDING_UTILS_H
+
+#include <string>
+
+#include "poppler_private_export.h"
+
+class GooFile;
+struct Ref;
+class XRef;
+
+namespace ImageEmbeddingUtils {
+
+// Creates a new base image (an object of type XObject referred to in a resource dictionary).
+// Supported formats: PNG, JPEG.
+// Args:
+// xref: Document's xref.
+// imageFile: An image file to embed.
+// Returns ref to a new object or Ref::INVALID.
+Ref POPPLER_PRIVATE_EXPORT embed(XRef *xref, const GooFile &imageFile);
+
+// Same as above, but imagePath is a path to an image file.
+Ref POPPLER_PRIVATE_EXPORT embed(XRef *xref, const std::string &imagePath);
+
+}
+#endif
diff --git a/poppler/PDFDoc.cc b/poppler/PDFDoc.cc
index 86c97486..e75d6405 100644
--- a/poppler/PDFDoc.cc
+++ b/poppler/PDFDoc.cc
@@ -94,6 +94,7 @@
#include "Hints.h"
#include "UTF.h"
#include "JSInfo.h"
+#include "ImageEmbeddingUtils.h"
//------------------------------------------------------------------------
@@ -1452,6 +1453,10 @@ void PDFDoc::writeObject(Object *obj, OutStream *outStr, XRef *xRef, unsigned in
stream->getDict()->set("Length", Object(tmp));
// Remove Stream encoding
+ AutoFreeMemStream *internalStream = dynamic_cast<AutoFreeMemStream *>(stream);
+ if (internalStream && internalStream->isFilterRemovalForbidden()) {
+ removeFilter = false;
+ }
if (removeFilter) {
stream->getDict()->remove("Filter");
}
@@ -2122,9 +2127,20 @@ bool PDFDoc::hasJavascript()
}
bool PDFDoc::sign(const char *saveFilename, const char *certNickname, const char *password, GooString *partialFieldName, int page, const PDFRectangle &rect, const GooString &signatureText, const GooString &signatureTextLeft,
- double fontSize, std::unique_ptr<AnnotColor> &&fontColor, double borderWidth, std::unique_ptr<AnnotColor> &&borderColor, std::unique_ptr<AnnotColor> &&backgroundColor, const GooString *reason, const GooString *location)
+ double fontSize, std::unique_ptr<AnnotColor> &&fontColor, double borderWidth, std::unique_ptr<AnnotColor> &&borderColor, std::unique_ptr<AnnotColor> &&backgroundColor, const GooString *reason, const GooString *location,
+ const std::string &imagePath)
{
::Page *destPage = getPage(page);
+ if (destPage == nullptr) {
+ return false;
+ }
+ Ref imageResourceRef = Ref::INVALID();
+ if (!imagePath.empty()) {
+ imageResourceRef = ImageEmbeddingUtils::embed(xref, imagePath);
+ if (imageResourceRef == Ref::INVALID()) {
+ return false;
+ }
+ }
const DefaultAppearance da { { objName, "SigFont" }, fontSize, std::move(fontColor) };
@@ -2147,9 +2163,9 @@ bool PDFDoc::sign(const char *saveFilename, const char *certNickname, const char
catalog->addFormToAcroForm(ref);
std::unique_ptr<::FormFieldSignature> field = std::make_unique<::FormFieldSignature>(this, Object(annotObj.getDict()), ref, nullptr, nullptr);
-
field->setCustomAppearanceContent(signatureText);
field->setCustomAppearanceLeftContent(signatureTextLeft);
+ field->setImageResource(imageResourceRef);
Object refObj(ref);
AnnotWidget *signatureAnnot = new AnnotWidget(this, &annotObj, &refObj, field.get());
diff --git a/poppler/PDFDoc.h b/poppler/PDFDoc.h
index b1801aa4..cea59282 100644
--- a/poppler/PDFDoc.h
+++ b/poppler/PDFDoc.h
@@ -332,8 +332,11 @@ public:
// Arguments signatureText and signatureTextLeft are UTF-16 big endian strings with BOM.
// Arguments reason and location are UTF-16 big endian strings with BOM. An empty string and nullptr are acceptable too.
+ // Argument imagePath is a background image (a path to a file).
+ // sign() takes ownership of partialFieldName.
bool sign(const char *saveFilename, const char *certNickname, const char *password, GooString *partialFieldName, int page, const PDFRectangle &rect, const GooString &signatureText, const GooString &signatureTextLeft, double fontSize,
- std::unique_ptr<AnnotColor> &&fontColor, double borderWidth, std::unique_ptr<AnnotColor> &&borderColor, std::unique_ptr<AnnotColor> &&backgroundColor, const GooString *reason = nullptr, const GooString *location = nullptr);
+ std::unique_ptr<AnnotColor> &&fontColor, double borderWidth, std::unique_ptr<AnnotColor> &&borderColor, std::unique_ptr<AnnotColor> &&backgroundColor, const GooString *reason = nullptr, const GooString *location = nullptr,
+ const std::string &imagePath = "");
private:
// insert referenced objects in XRef
diff --git a/poppler/Stream.cc b/poppler/Stream.cc
index 406870e7..a9342842 100644
--- a/poppler/Stream.cc
+++ b/poppler/Stream.cc
@@ -1103,6 +1103,16 @@ AutoFreeMemStream::~AutoFreeMemStream()
gfree(buf);
}
+bool AutoFreeMemStream::isFilterRemovalForbidden() const
+{
+ return filterRemovalForbidden;
+}
+
+void AutoFreeMemStream::setFilterRemovalForbidden(bool forbidden)
+{
+ filterRemovalForbidden = forbidden;
+}
+
//------------------------------------------------------------------------
// EmbedStream
//------------------------------------------------------------------------
diff --git a/poppler/Stream.h b/poppler/Stream.h
index 303435ed..b6e9b3bf 100644
--- a/poppler/Stream.h
+++ b/poppler/Stream.h
@@ -734,9 +734,17 @@ public:
class AutoFreeMemStream : public BaseMemStream<char>
{
+ bool filterRemovalForbidden;
+
public:
+ // AutoFreeMemStream takes ownership over the buffer.
+ // The buffer should be created using gmalloc().
AutoFreeMemStream(char *bufA, Goffset startA, Goffset lengthA, Object &&dictA) : BaseMemStream(bufA, startA, lengthA, std::move(dictA)) { }
~AutoFreeMemStream() override;
+
+ // A hack to deal with the strange behaviour of PDFDoc::writeObject().
+ bool isFilterRemovalForbidden() const;
+ void setFilterRemovalForbidden(bool forbidden);
};
//------------------------------------------------------------------------
diff --git a/poppler/XRef.cc b/poppler/XRef.cc
index 3e0e95fa..46433285 100644
--- a/poppler/XRef.cc
+++ b/poppler/XRef.cc
@@ -31,6 +31,7 @@
// Copyright (C) 2020 Klarälvdalens Datakonsult AB, a KDAB Group company, <info at kdab.com>. Work sponsored by Technische Universität Dresden
// Copyright (C) 2010 William Bader <william at newspapersystems.com>
// Copyright (C) 2021 Mahmoud Khalil <mahmoudkhalil11 at gmail.com>
+// Copyright (C) 2021 Georgiy Sgibnev <georgiy at sgibnev.com>. Work sponsored by lab50.net.
//
// To see a description of the changes please see the Changelog file that
// came with your tarball or type make ChangeLog if you are building from git
@@ -1455,6 +1456,20 @@ void XRef::removeIndirectObject(Ref r)
setModified();
}
+Ref XRef::addStreamObject(Dict *dict, char *buffer, const Goffset bufferSize)
+{
+ dict->add("Length", Object((int)bufferSize));
+ AutoFreeMemStream *stream = new AutoFreeMemStream(buffer, 0, bufferSize, Object(dict));
+ stream->setFilterRemovalForbidden(true);
+ Object streamObj((Stream *)stream);
+ return addIndirectObject(&streamObj);
+}
+
+Ref XRef::addStreamObject(Dict *dict, uint8_t *buffer, const Goffset bufferSize)
+{
+ return addStreamObject(dict, (char *)buffer, bufferSize);
+}
+
void XRef::writeXRef(XRef::XRefWriter *writer, bool writeAllEntries)
{
// create free entries linked-list
diff --git a/poppler/XRef.h b/poppler/XRef.h
index 76069a1b..a40c7312 100644
--- a/poppler/XRef.h
+++ b/poppler/XRef.h
@@ -26,6 +26,7 @@
// Copyright (C) 2018 Adam Reichold <adam.reichold at t-online.de>
// Copyright (C) 2018 Marek Kasik <mkasik at redhat.com>
// Copyright (C) 2021 Mahmoud Khalil <mahmoudkhalil11 at gmail.com>
+// Copyright (C) 2021 Georgiy Sgibnev <georgiy at sgibnev.com>. Work sponsored by lab50.net.
//
// To see a description of the changes please see the Changelog file that
// came with your tarball or type make ChangeLog if you are building from git
@@ -208,6 +209,12 @@ public:
void removeIndirectObject(Ref r);
void add(int num, int gen, Goffset offs, bool used);
void add(Ref ref, Goffset offs, bool used);
+ // Adds a stream object using AutoFreeMemStream.
+ // The function takes ownership over dict and buffer.
+ // The buffer should be created using gmalloc().
+ // Returns ref to a new object.
+ Ref addStreamObject(Dict *dict, char *buffer, const Goffset bufferSize);
+ Ref addStreamObject(Dict *dict, uint8_t *buffer, const Goffset bufferSize);
// Output XRef table to stream
void writeTableToFile(OutStream *outStr, bool writeAllEntries);
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index b9251e04..a365babb 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -55,4 +55,71 @@ set (pdf_fullrewrite_SRCS
add_executable(pdf-fullrewrite ${pdf_fullrewrite_SRCS})
target_link_libraries(pdf-fullrewrite poppler)
+# Test image embedding API.
+if(ENABLE_LIBJPEG AND ENABLE_LIBPNG)
+ set(image_embedding_SRCS
+ image-embedding.cc
+ ../utils/parseargs.cc
+ )
+ add_executable(image-embedding ${image_embedding_SRCS})
+ target_link_libraries(image-embedding poppler)
+ set(INPUT_PDF ${TESTDATADIR}/unittestcases/xr01.pdf)
+ set(IMG_DIR ${TESTDATADIR}/unittestcases/images)
+ set(IMAGE_EMBEDDING_PATH ${EXECUTABLE_OUTPUT_PATH}/image-embedding)
+ add_test(
+ NAME embed-png-g1
+ COMMAND ${IMAGE_EMBEDDING_PATH} ${INPUT_PDF} ${IMG_DIR}/png-g1.png -depth 8 -colorspace DeviceGray
+ )
+ add_test(
+ NAME embed-png-g2
+ COMMAND ${IMAGE_EMBEDDING_PATH} ${INPUT_PDF} ${IMG_DIR}/png-g2.png -depth 8 -colorspace DeviceGray
+ )
+ add_test(
+ NAME embed-png-g4
+ COMMAND ${IMAGE_EMBEDDING_PATH} ${INPUT_PDF} ${IMG_DIR}/png-g4.png -depth 8 -colorspace DeviceGray
+ )
+ add_test(
+ NAME embed-png-g8
+ COMMAND ${IMAGE_EMBEDDING_PATH} ${INPUT_PDF} ${IMG_DIR}/png-g8.png -depth 8 -colorspace DeviceGray
+ )
+ add_test(
+ NAME embed-png-g16
+ COMMAND ${IMAGE_EMBEDDING_PATH} ${INPUT_PDF} ${IMG_DIR}/png-g16.png -depth 16 -colorspace DeviceGray
+ )
+ add_test(
+ NAME embed-png-ga8
+ COMMAND ${IMAGE_EMBEDDING_PATH} ${INPUT_PDF} ${IMG_DIR}/png-ga8.png -depth 8 -colorspace DeviceGray -smask
+ )
+ add_test(
+ NAME embed-png-ga16
+ COMMAND ${IMAGE_EMBEDDING_PATH} ${INPUT_PDF} ${IMG_DIR}/png-ga16.png -depth 16 -colorspace DeviceGray -smask
+ )
+ add_test(
+ NAME embed-png-palette
+ COMMAND ${IMAGE_EMBEDDING_PATH} ${INPUT_PDF} ${IMG_DIR}/png-palette.png -depth 8 -colorspace DeviceRGB
+ )
+ add_test(
+ NAME embed-png-rgb8
+ COMMAND ${IMAGE_EMBEDDING_PATH} ${INPUT_PDF} ${IMG_DIR}/png-rgb8.png -depth 8 -colorspace DeviceRGB
+ )
+ add_test(
+ NAME embed-png-rgb16
+ COMMAND ${IMAGE_EMBEDDING_PATH} ${INPUT_PDF} ${IMG_DIR}/png-rgb16.png -depth 16 -colorspace DeviceRGB
+ )
+ add_test(
+ NAME embed-png-rgba8
+ COMMAND ${IMAGE_EMBEDDING_PATH} ${INPUT_PDF} ${IMG_DIR}/png-rgba8.png -depth 8 -colorspace DeviceRGB -smask
+ )
+ add_test(
+ NAME embed-png-rgba16
+ COMMAND ${IMAGE_EMBEDDING_PATH} ${INPUT_PDF} ${IMG_DIR}/png-rgba16.png -depth 16 -colorspace DeviceRGB -smask
+ )
+ add_test(
+ NAME embed-jpeg
+ COMMAND ${IMAGE_EMBEDDING_PATH} ${INPUT_PDF} ${IMG_DIR}/jpeg.jpg -depth 8 -colorspace DeviceRGB -filter DCTDecode
+ )
+ unset(IMAGE_EMBEDDING_PATH)
+ unset(IMG_DIR)
+ unset(INPUT_PDF)
+endif()
diff --git a/test/image-embedding.cc b/test/image-embedding.cc
new file mode 100644
index 00000000..6ffcdb2d
--- /dev/null
+++ b/test/image-embedding.cc
@@ -0,0 +1,106 @@
+//========================================================================
+//
+// image-embedding.cc
+// A test util to check ImageEmbeddingUtils::embed().
+//
+// This file is licensed under the GPLv2 or later
+//
+// Copyright (C) 2021 Georgiy Sgibnev <georgiy at sgibnev.com>. Work sponsored by lab50.net.
+//
+//========================================================================
+
+#include <config.h>
+#include <cstdio>
+#include <string>
+
+#include "utils/parseargs.h"
+#include "goo/GooString.h"
+#include "Object.h"
+#include "Dict.h"
+#include "PDFDoc.h"
+#include "PDFDocFactory.h"
+#include "ImageEmbeddingUtils.h"
+
+static int depth = 0;
+static GooString colorSpace;
+static GooString filter;
+static bool smask = false;
+static bool printHelp = false;
+
+static const ArgDesc argDesc[] = { { "-depth", argInt, &depth, 0, "XObject's property 'BitsPerComponent'" },
+ { "-colorspace", argGooString, &colorSpace, 0, "XObject's property 'ColorSpace'" },
+ { "-filter", argGooString, &filter, 0, "XObject's property 'Filter'" },
+ { "-smask", argFlag, &smask, 0, "SMask should exist" },
+ { "-h", argFlag, &printHelp, 0, "print usage information" },
+ { "-help", argFlag, &printHelp, 0, "print usage information" },
+ { "--help", argFlag, &printHelp, 0, "print usage information" },
+ { "-?", argFlag, &printHelp, 0, "print usage information" },
+ {} };
+
+int main(int argc, char *argv[])
+{
+ // Parse args.
+ const bool ok = parseArgs(argDesc, &argc, argv);
+ if (!ok || (argc != 3) || printHelp) {
+ printUsage(argv[0], "PDF-FILE IMAGE-FILE", argDesc);
+ return (printHelp) ? 0 : 1;
+ }
+ const GooString docPath(argv[1]);
+ const GooString imagePath(argv[2]);
+
+ auto doc = std::unique_ptr<PDFDoc>(PDFDocFactory().createPDFDoc(docPath, nullptr, nullptr));
+ if (!doc->isOk()) {
+ fprintf(stderr, "Error opening input PDF file.\n");
+ return 1;
+ }
+
+ // Embed an image.
+ Ref baseImageRef = ImageEmbeddingUtils::embed(doc->getXRef(), imagePath.toStr());
+ if (baseImageRef == Ref::INVALID()) {
+ fprintf(stderr, "embedImage() failed.\n");
+ return 1;
+ }
+
+ // Save updated PDF document.
+ // const GooString outputPathSuffix(".pdf");
+ // const GooString outputPath = GooString(&imagePath, &outputPathSuffix);
+ // doc->saveAs(&outputPath, writeForceRewrite);
+
+ // Check the base image.
+ Object baseImageObj = Object(baseImageRef).fetch(doc->getXRef());
+ Dict *baseImageDict = baseImageObj.streamGetDict();
+ if (std::string("XObject") != baseImageDict->lookup("Type").getName()) {
+ fprintf(stderr, "A problem with Type.\n");
+ return 1;
+ }
+ if (std::string("Image") != baseImageDict->lookup("Subtype").getName()) {
+ fprintf(stderr, "A problem with Subtype.\n");
+ return 1;
+ }
+ if (depth > 0) {
+ if (baseImageDict->lookup("BitsPerComponent").getInt() != depth) {
+ fprintf(stderr, "A problem with BitsPerComponent.\n");
+ return 1;
+ }
+ }
+ if (!colorSpace.toStr().empty()) {
+ if (colorSpace.cmp(baseImageDict->lookup("ColorSpace").getName()) != 0) {
+ fprintf(stderr, "A problem with ColorSpace.\n");
+ return 1;
+ }
+ }
+ if (!filter.toStr().empty()) {
+ if (filter.cmp(baseImageDict->lookup("Filter").getName()) != 0) {
+ fprintf(stderr, "A problem with Filter.\n");
+ return 1;
+ }
+ }
+ if (smask) {
+ Object maskObj = baseImageDict->lookup("SMask");
+ if (!maskObj.isStream()) {
+ fprintf(stderr, "A problem with SMask.\n");
+ return 1;
+ }
+ }
+ return 0;
+}
More information about the poppler
mailing list