summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorgiy Sgibnev <georgiy@lab50.net>2021-11-25 20:30:45 +0300
committerGeorgiy Sgibnev <georgiy@lab50.net>2021-11-26 13:27:05 +0300
commit0762e0144143e680e24ec8d4c34c46c3716b8713 (patch)
tree46bdb5a0a753599654ba41ab0d3dc4964c58ecdd
parent466b4e791dc02ede85898124a99024264f2290d5 (diff)
New fonts and images for unit-tests
-rw-r--r--unittestcases/fonts/FreeSans.otfbin0 -> 857468 bytes
-rw-r--r--unittestcases/fonts/RobotoCondensed-Regular.ttfbin0 -> 300664 bytes
-rw-r--r--unittestcases/images/malformed.jpg407
-rw-r--r--unittestcases/images/malformed.png407
4 files changed, 814 insertions, 0 deletions
diff --git a/unittestcases/fonts/FreeSans.otf b/unittestcases/fonts/FreeSans.otf
new file mode 100644
index 0000000..4cfda63
--- /dev/null
+++ b/unittestcases/fonts/FreeSans.otf
Binary files differ
diff --git a/unittestcases/fonts/RobotoCondensed-Regular.ttf b/unittestcases/fonts/RobotoCondensed-Regular.ttf
new file mode 100644
index 0000000..5ae7043
--- /dev/null
+++ b/unittestcases/fonts/RobotoCondensed-Regular.ttf
Binary files differ
diff --git a/unittestcases/images/malformed.jpg b/unittestcases/images/malformed.jpg
new file mode 100644
index 0000000..05b3247
--- /dev/null
+++ b/unittestcases/images/malformed.jpg
@@ -0,0 +1,407 @@
+˙Ř˙=======================================================================
+//
+// ImageEmbeddingUtils.cc
+//
+// Copyright (C) 2021 Georgiy Sgibnev <georgiy@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/unittestcases/images/malformed.png b/unittestcases/images/malformed.png
new file mode 100644
index 0000000..7fa3397
--- /dev/null
+++ b/unittestcases/images/malformed.png
@@ -0,0 +1,407 @@
+‰PNG======================================================================
+//
+// ImageEmbeddingUtils.cc
+//
+// Copyright (C) 2021 Georgiy Sgibnev <georgiy@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);
+}
+
+}