summaryrefslogtreecommitdiffstats
path: root/src/extension/internal/pdfinput
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:50:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:50:49 +0000
commitc853ffb5b2f75f5a889ed2e3ef89b818a736e87a (patch)
tree7d13a0883bb7936b84d6ecdd7bc332b41ed04bee /src/extension/internal/pdfinput
parentInitial commit. (diff)
downloadinkscape-c853ffb5b2f75f5a889ed2e3ef89b818a736e87a.tar.xz
inkscape-c853ffb5b2f75f5a889ed2e3ef89b818a736e87a.zip
Adding upstream version 1.3+ds.upstream/1.3+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/extension/internal/pdfinput')
-rw-r--r--src/extension/internal/pdfinput/enums.h34
-rw-r--r--src/extension/internal/pdfinput/pdf-input.cpp861
-rw-r--r--src/extension/internal/pdfinput/pdf-input.h158
-rw-r--r--src/extension/internal/pdfinput/pdf-parser.cpp3196
-rw-r--r--src/extension/internal/pdfinput/pdf-parser.h339
-rw-r--r--src/extension/internal/pdfinput/pdf-utils.cpp115
-rw-r--r--src/extension/internal/pdfinput/pdf-utils.h56
-rw-r--r--src/extension/internal/pdfinput/poppler-cairo-font-engine.cpp779
-rw-r--r--src/extension/internal/pdfinput/poppler-cairo-font-engine.h166
-rw-r--r--src/extension/internal/pdfinput/poppler-transition-api.h95
-rw-r--r--src/extension/internal/pdfinput/poppler-utils.cpp599
-rw-r--r--src/extension/internal/pdfinput/poppler-utils.h95
-rw-r--r--src/extension/internal/pdfinput/svg-builder.cpp2311
-rw-r--r--src/extension/internal/pdfinput/svg-builder.h296
14 files changed, 9100 insertions, 0 deletions
diff --git a/src/extension/internal/pdfinput/enums.h b/src/extension/internal/pdfinput/enums.h
new file mode 100644
index 0000000..b68f038
--- /dev/null
+++ b/src/extension/internal/pdfinput/enums.h
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * PDF Parsing utility functions and classes.
+ *//*
+ *
+ * Copyright (C) 2022 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef PDF_ENUMS_H
+#define PDF_ENUMS_H
+
+#include <map>
+
+enum class FontStrategy : unsigned char
+{
+ RENDER_MISSING,
+ RENDER_ALL,
+ SUBSTITUTE_MISSING,
+ KEEP_MISSING,
+ DELETE_MISSING,
+ DELETE_ALL
+};
+enum class FontFallback : unsigned char
+{
+ DELETE_TEXT = 0,
+ AS_SHAPES,
+ AS_TEXT,
+ AS_SUB,
+};
+typedef std::map<int, FontFallback> FontStrategies;
+
+#endif /* PDF_ENUMS_H */
diff --git a/src/extension/internal/pdfinput/pdf-input.cpp b/src/extension/internal/pdfinput/pdf-input.cpp
new file mode 100644
index 0000000..302af06
--- /dev/null
+++ b/src/extension/internal/pdfinput/pdf-input.cpp
@@ -0,0 +1,861 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Native PDF import using libpoppler.
+ *
+ * Authors:
+ * miklos erdelyi
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#include "pdf-input.h"
+
+#ifdef HAVE_POPPLER
+#include <poppler/Catalog.h>
+#include <poppler/ErrorCodes.h>
+#include <poppler/FontInfo.h>
+#include <poppler/GfxFont.h>
+#include <poppler/GlobalParams.h>
+#include <poppler/OptionalContent.h>
+#include <poppler/PDFDoc.h>
+#include <poppler/Page.h>
+#include <poppler/goo/GooString.h>
+
+#ifdef HAVE_POPPLER_CAIRO
+#include <poppler/glib/poppler.h>
+#include <poppler/glib/poppler-document.h>
+#include <poppler/glib/poppler-page.h>
+#endif
+
+#include <gdkmm/general.h>
+#include <glibmm/convert.h>
+#include <glibmm/i18n.h>
+#include <glibmm/miscutils.h>
+#include <gtk/gtk.h>
+#include <gtkmm/checkbutton.h>
+#include <gtkmm/comboboxtext.h>
+#include <gtkmm/drawingarea.h>
+#include <gtkmm/frame.h>
+#include <gtkmm/radiobutton.h>
+#include <gtkmm/scale.h>
+#include <utility>
+
+#include "document-undo.h"
+#include "extension/input.h"
+#include "extension/system.h"
+#include "inkscape.h"
+#include "object/sp-root.h"
+#include "pdf-parser.h"
+#include "ui/builder-utils.h"
+#include "ui/dialog-events.h"
+#include "ui/widget/frame.h"
+#include "ui/widget/spinbutton.h"
+#include "util/parse-int-range.h"
+#include "util/units.h"
+
+using namespace Inkscape::UI;
+
+namespace {
+
+void sanitize_page_number(int &page_num, const int num_pages) {
+ if (page_num < 1 || page_num > num_pages) {
+ std::cerr << "Inkscape::Extension::Internal::PdfInput::open: Bad page number "
+ << page_num
+ << ". Import first page instead."
+ << std::endl;
+ page_num = 1;
+ }
+}
+
+}
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+class FontModelColumns : public Gtk::TreeModel::ColumnRecord
+{
+public:
+ FontModelColumns()
+ {
+ add(id);
+ add(family);
+ add(style);
+ add(weight);
+ add(stretch);
+ add(proc_label);
+ add(proc_id);
+ add(icon);
+ add(em);
+ }
+ ~FontModelColumns() override = default;
+ Gtk::TreeModelColumn<int> id;
+ Gtk::TreeModelColumn<Glib::ustring> family;
+ Gtk::TreeModelColumn<Glib::ustring> style;
+ Gtk::TreeModelColumn<Glib::ustring> weight;
+ Gtk::TreeModelColumn<Glib::ustring> stretch;
+ Gtk::TreeModelColumn<Glib::ustring> proc_label;
+ Gtk::TreeModelColumn<int> proc_id;
+ Gtk::TreeModelColumn<Glib::ustring> icon;
+ Gtk::TreeModelColumn<bool> em;
+};
+
+/**
+ * \brief The PDF import dialog
+ * FIXME: Probably this should be placed into src/ui/dialog
+ */
+
+PdfImportDialog::PdfImportDialog(std::shared_ptr<PDFDoc> doc, const gchar * /*uri*/)
+ : _pdf_doc(std::move(doc))
+ , _builder(UI::create_builder("extension-pdfinput.glade"))
+ , _page_numbers(UI::get_widget<Gtk::Entry>(_builder, "page-numbers"))
+ , _preview_area(UI::get_widget<Gtk::DrawingArea>(_builder, "preview-area"))
+ , _embed_images(UI::get_widget<Gtk::CheckButton>(_builder, "embed-images"))
+ , _mesh_slider(UI::get_widget<Gtk::Scale>(_builder, "mesh-slider"))
+ , _mesh_label(UI::get_widget<Gtk::Label>(_builder, "mesh-label"))
+ , _next_page(UI::get_widget<Gtk::Button>(_builder, "next-page"))
+ , _prev_page(UI::get_widget<Gtk::Button>(_builder, "prev-page"))
+ , _current_page(UI::get_widget<Gtk::Label>(_builder, "current-page"))
+ , _font_model(UI::get_object<Gtk::ListStore>(_builder, "font-list"))
+ , _font_col(new FontModelColumns())
+{
+ assert(_pdf_doc);
+
+ _setFonts(getPdfFonts(_pdf_doc));
+
+ auto okbutton = Gtk::manage(new Gtk::Button(_("_OK"), true));
+
+ get_content_area()->set_homogeneous(false);
+ get_content_area()->set_spacing(0);
+
+ get_content_area()->pack_start(UI::get_widget<Gtk::Box>(_builder, "content"));
+
+ this->set_title(_("PDF Import Settings"));
+ this->set_modal(true);
+ sp_transientize(GTK_WIDGET(this->gobj())); //Make transient
+ this->property_window_position().set_value(Gtk::WIN_POS_NONE);
+ this->set_resizable(true);
+ this->property_destroy_with_parent().set_value(false);
+
+ this->add_action_widget(*Gtk::manage(new Gtk::Button(_("_Cancel"), true)), -6);
+ this->add_action_widget(*okbutton, -5);
+
+ this->show_all();
+
+ _render_thumb = false;
+
+ // Connect signals
+ _next_page.signal_clicked().connect([=] { _setPreviewPage(_preview_page + 1); });
+ _prev_page.signal_clicked().connect([=] { _setPreviewPage(_preview_page - 1); });
+ _preview_area.signal_draw().connect(sigc::mem_fun(*this, &PdfImportDialog::_onDraw));
+ _page_numbers.signal_changed().connect(sigc::mem_fun(*this, &PdfImportDialog::_onPageNumberChanged));
+ _mesh_slider.get_adjustment()->signal_value_changed().connect(
+ sigc::mem_fun(*this, &PdfImportDialog::_onPrecisionChanged));
+
+#ifdef HAVE_POPPLER_CAIRO
+ _render_thumb = true;
+
+ // Disable the page selector when there's only one page
+ _total_pages = _pdf_doc->getCatalog()->getNumPages();
+ _page_numbers.set_sensitive(_total_pages > 1);
+
+ // Create PopplerDocument
+ std::string filename = _pdf_doc->getFileName()->getCString();
+ if (!Glib::path_is_absolute(filename)) {
+ filename = Glib::build_filename(Glib::get_current_dir(),filename);
+ }
+ Glib::ustring full_uri = Glib::filename_to_uri(filename);
+
+ if (!full_uri.empty()) {
+ _poppler_doc = poppler_document_new_from_file(full_uri.c_str(), NULL, NULL);
+ }
+#endif
+
+ // Set default preview size
+ _preview_width = 200;
+ _preview_height = 300;
+
+ // Init preview
+ _thumb_data = nullptr;
+ _current_pages = "all";
+ _setPreviewPage(1);
+
+ okbutton->set_can_focus();
+ okbutton->set_can_default();
+ set_default(*okbutton);
+ set_focus(*okbutton);
+
+ auto &font_strat = UI::get_object_raw<Gtk::CellRendererCombo>(_builder, "cell-strat");
+ font_strat.signal_changed().connect([=](const Glib::ustring &path, const Gtk::TreeModel::iterator &source) {
+ if (auto target = _font_model->get_iter(path)) {
+ (*target)[_font_col->proc_id] = int((*source)[_font_col->id]);
+ (*target)[_font_col->proc_label] = Glib::ustring((*source)[_font_col->family]);
+ }
+ });
+
+ auto &font_render = UI::get_widget<Gtk::ComboBox>(_builder, "font-rendering");
+ font_render.signal_changed().connect(sigc::mem_fun(*this, &PdfImportDialog::_fontRenderChanged));
+ _fontRenderChanged();
+}
+
+PdfImportDialog::~PdfImportDialog() {
+#ifdef HAVE_POPPLER_CAIRO
+ if (_cairo_surface) {
+ cairo_surface_destroy(_cairo_surface);
+ }
+ if (_poppler_doc) {
+ g_object_unref(G_OBJECT(_poppler_doc));
+ }
+#endif
+ if (_thumb_data) {
+ gfree(_thumb_data);
+ }
+}
+
+bool PdfImportDialog::showDialog() {
+ show();
+ gint b = run();
+ hide();
+ if ( b == Gtk::RESPONSE_OK ) {
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+std::string PdfImportDialog::getSelectedPages() {
+ if (_page_numbers.get_sensitive()) {
+ return _current_pages;
+ }
+ return "all";
+}
+
+PdfImportType PdfImportDialog::getImportMethod()
+{
+ auto &import_type = UI::get_widget<Gtk::Notebook>(_builder, "import-type");
+ return (PdfImportType)import_type.get_current_page();
+}
+
+/**
+ * \brief Retrieves the current settings into a repr which SvgBuilder will use
+ * for determining the behaviour desired by the user
+ */
+void PdfImportDialog::getImportSettings(Inkscape::XML::Node *prefs) {
+ prefs->setAttribute("selectedPages", _current_pages);
+
+ auto &clip_to = UI::get_widget<Gtk::ComboBox>(_builder, "clip-to");
+
+ prefs->setAttribute("cropTo", clip_to.get_active_id());
+ prefs->setAttributeSvgDouble("approximationPrecision", _mesh_slider.get_value());
+ prefs->setAttributeBoolean("embedImages", _embed_images.get_active());
+}
+
+/**
+ * \brief Redisplay the comment on the current approximation precision setting
+ * Evenly divides the interval of possible values between the available labels.
+ */
+void PdfImportDialog::_onPrecisionChanged() {
+ static Glib::ustring labels[] = {
+ Glib::ustring(C_("PDF input precision", "rough")), Glib::ustring(C_("PDF input precision", "medium")),
+ Glib::ustring(C_("PDF input precision", "fine")), Glib::ustring(C_("PDF input precision", "very fine"))};
+
+ auto adj = _mesh_slider.get_adjustment();
+ double min = adj->get_lower();
+ double value = adj->get_value() - min;
+ double max = adj->get_upper() - min;
+ double interval_len = max / (double)(sizeof(labels) / sizeof(labels[0]));
+ int comment_idx = (int)floor(value / interval_len);
+ _mesh_label.set_label(labels[comment_idx]);
+}
+
+void PdfImportDialog::_onPageNumberChanged()
+{
+ _current_pages = _page_numbers.get_text();
+ auto nums = parseIntRange(_current_pages, 1, _total_pages);
+ if (!nums.empty()) {
+ _setPreviewPage(*nums.begin());
+ }
+}
+
+/**
+ * Set a full list of all fonts in use for the whole PDF document.
+ */
+void PdfImportDialog::_setFonts(const FontList &fonts)
+{
+ _font_model->clear();
+ _font_list = fonts;
+
+ // Find all fonts on this one page
+ /*std::set<int> found;
+ FontInfoScanner page_scanner(_pdf_doc.get(), page-1);
+ for (const FontInfo *font : page_scanner.scan(page)) {
+ found.insert(font->getRef().num);
+ delete font;
+ }*/
+
+ // Now add all fonts and mark the ones from this page
+ for (auto pair : *fonts) {
+ auto font = pair.first;
+ auto &data = pair.second;
+ auto row = *_font_model->append();
+
+ row[_font_col->id] = font->getID()->num;
+ row[_font_col->em] = false;
+ row[_font_col->family] = data.family;
+ row[_font_col->style] = data.style;
+ row[_font_col->weight] = data.weight;
+ row[_font_col->stretch] = data.stretch;
+ // row[_font_col->pages] = data.pages;
+
+ if (font->isCIDFont()) {
+ row[_font_col->icon] = Glib::ustring("text-convert-to-regular");
+ } else {
+ row[_font_col->icon] = Glib::ustring(data.found ? "on" : "off-outline");
+ }
+ }
+}
+
+void PdfImportDialog::_fontRenderChanged()
+{
+ auto &font_render = UI::get_widget<Gtk::ComboBox>(_builder, "font-rendering");
+ FontStrategy choice = (FontStrategy)std::stoi(font_render.get_active_id().c_str());
+ setFontStrategies(SvgBuilder::autoFontStrategies(choice, _font_list));
+}
+
+/**
+ * Saves each decided font strategy to the Svg Builder object.
+ */
+FontStrategies PdfImportDialog::getFontStrategies()
+{
+ FontStrategies fs;
+ for (auto child : _font_model->children()) {
+ auto value = (FontFallback) int(child[_font_col->proc_id]);
+ fs[child[_font_col->id]] = value;
+ }
+ return fs;
+}
+
+/**
+ * Update the font strats.
+ */
+void PdfImportDialog::setFontStrategies(const FontStrategies &fs)
+{
+ for (auto child : _font_model->children()) {
+ auto value = fs.at(child[_font_col->id]);
+ child[_font_col->proc_id] = (int)value;
+ switch (value) {
+ case FontFallback::AS_SHAPES:
+ child[_font_col->proc_label] = _("Convert to paths");
+ break;
+ case FontFallback::AS_TEXT:
+ child[_font_col->proc_label] = _("Keep original font name");
+ break;
+ case FontFallback::AS_SUB:
+ child[_font_col->proc_label] = _("Replace by closest-named installed font");
+ break;
+ case FontFallback::DELETE_TEXT:
+ child[_font_col->proc_label] = _("Delete text");
+ break;
+ }
+ }
+}
+
+#ifdef HAVE_POPPLER_CAIRO
+/**
+ * \brief Copies image data from a Cairo surface to a pixbuf
+ *
+ * Borrowed from libpoppler, from the file poppler-page.cc
+ * Copyright (C) 2005, Red Hat, Inc.
+ *
+ */
+static void copy_cairo_surface_to_pixbuf (cairo_surface_t *surface,
+ unsigned char *data,
+ GdkPixbuf *pixbuf)
+{
+ int cairo_width, cairo_height, cairo_rowstride;
+ unsigned char *pixbuf_data, *dst, *cairo_data;
+ int pixbuf_rowstride, pixbuf_n_channels;
+ unsigned int *src;
+ int x, y;
+
+ cairo_width = cairo_image_surface_get_width (surface);
+ cairo_height = cairo_image_surface_get_height (surface);
+ cairo_rowstride = cairo_width * 4;
+ cairo_data = data;
+
+ pixbuf_data = gdk_pixbuf_get_pixels (pixbuf);
+ pixbuf_rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+ pixbuf_n_channels = gdk_pixbuf_get_n_channels (pixbuf);
+
+ if (cairo_width > gdk_pixbuf_get_width (pixbuf))
+ cairo_width = gdk_pixbuf_get_width (pixbuf);
+ if (cairo_height > gdk_pixbuf_get_height (pixbuf))
+ cairo_height = gdk_pixbuf_get_height (pixbuf);
+ for (y = 0; y < cairo_height; y++)
+ {
+ src = reinterpret_cast<unsigned int *>(cairo_data + y * cairo_rowstride);
+ dst = pixbuf_data + y * pixbuf_rowstride;
+ for (x = 0; x < cairo_width; x++)
+ {
+ dst[0] = (*src >> 16) & 0xff;
+ dst[1] = (*src >> 8) & 0xff;
+ dst[2] = (*src >> 0) & 0xff;
+ if (pixbuf_n_channels == 4)
+ dst[3] = (*src >> 24) & 0xff;
+ dst += pixbuf_n_channels;
+ src++;
+ }
+ }
+}
+
+#endif
+
+bool PdfImportDialog::_onDraw(const Cairo::RefPtr<Cairo::Context>& cr) {
+ // Check if we have a thumbnail at all
+ if (!_thumb_data) {
+ return true;
+ }
+
+ // Create the pixbuf for the thumbnail
+ Glib::RefPtr<Gdk::Pixbuf> thumb;
+
+ if (_render_thumb) {
+ thumb = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true,
+ 8, _thumb_width, _thumb_height);
+ } else {
+ thumb = Gdk::Pixbuf::create_from_data(_thumb_data, Gdk::COLORSPACE_RGB,
+ false, 8, _thumb_width, _thumb_height, _thumb_rowstride);
+ }
+ if (!thumb) {
+ return true;
+ }
+
+ // Set background to white
+ if (_render_thumb) {
+ thumb->fill(0xffffffff);
+ Gdk::Cairo::set_source_pixbuf(cr, thumb, 0, 0);
+ cr->paint();
+ }
+#ifdef HAVE_POPPLER_CAIRO
+ // Copy the thumbnail image from the Cairo surface
+ if (_render_thumb) {
+ copy_cairo_surface_to_pixbuf(_cairo_surface, _thumb_data, thumb->gobj());
+ }
+#endif
+
+ Gdk::Cairo::set_source_pixbuf(cr, thumb, 0, _render_thumb ? 0 : 20);
+ cr->paint();
+ return true;
+}
+
+/**
+ * \brief Renders the given page's thumbnail using Cairo
+ */
+void PdfImportDialog::_setPreviewPage(int page) {
+ _previewed_page = _pdf_doc->getCatalog()->getPage(page);
+ g_return_if_fail(_previewed_page);
+
+ // Update the UI to select a different page
+ _preview_page = page;
+ _next_page.set_sensitive(page < _total_pages);
+ _prev_page.set_sensitive(page > 1);
+ std::ostringstream example;
+ example << page << " / " << _total_pages;
+ _current_page.set_label(example.str());
+
+ // Update the font list with per-page highlighting
+ // XXX Update this psuedo code with real code
+ /*for (auto iter : _font_model->children()) {
+ std::unorderd_list<int> *pages = row[_font_col->pages];
+ row[_font_col->em] = bool(page in pages);
+ }*/
+
+ // Try to get a thumbnail from the PDF if possible
+ if (!_render_thumb) {
+ if (_thumb_data) {
+ gfree(_thumb_data);
+ _thumb_data = nullptr;
+ }
+ if (!_previewed_page->loadThumb(&_thumb_data,
+ &_thumb_width, &_thumb_height, &_thumb_rowstride)) {
+ return;
+ }
+ // Redraw preview area
+ _preview_area.set_size_request(_thumb_width, _thumb_height + 20);
+ _preview_area.queue_draw();
+ return;
+ }
+#ifdef HAVE_POPPLER_CAIRO
+ // Get page size by accounting for rotation
+ double width, height;
+ int rotate = _previewed_page->getRotate();
+ if ( rotate == 90 || rotate == 270 ) {
+ height = _previewed_page->getCropWidth();
+ width = _previewed_page->getCropHeight();
+ } else {
+ width = _previewed_page->getCropWidth();
+ height = _previewed_page->getCropHeight();
+ }
+ // Calculate the needed scaling for the page
+ double scale_x = (double)_preview_width / width;
+ double scale_y = (double)_preview_height / height;
+ double scale_factor = ( scale_x > scale_y ) ? scale_y : scale_x;
+ // Create new Cairo surface
+ _thumb_width = (int)ceil( width * scale_factor );
+ _thumb_height = (int)ceil( height * scale_factor );
+ _thumb_rowstride = _thumb_width * 4;
+ if (_thumb_data) {
+ gfree(_thumb_data);
+ }
+ _thumb_data = reinterpret_cast<unsigned char *>(gmalloc(_thumb_rowstride * _thumb_height));
+ if (_cairo_surface) {
+ cairo_surface_destroy(_cairo_surface);
+ }
+ _cairo_surface = cairo_image_surface_create_for_data(_thumb_data,
+ CAIRO_FORMAT_ARGB32, _thumb_width, _thumb_height, _thumb_rowstride);
+ cairo_t *cr = cairo_create(_cairo_surface);
+ cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0); // Set fill color to white
+ cairo_paint(cr); // Clear it
+ cairo_scale(cr, scale_factor, scale_factor); // Use Cairo for resizing the image
+ // Render page
+ if (_poppler_doc != NULL) {
+ PopplerPage *poppler_page = poppler_document_get_page(_poppler_doc, page-1);
+ poppler_page_render(poppler_page, cr);
+ g_object_unref(G_OBJECT(poppler_page));
+ }
+ // Clean up
+ cairo_destroy(cr);
+ // Redraw preview area
+ _preview_area.set_size_request(_preview_width, _preview_height);
+ _preview_area.queue_draw();
+#endif
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+#ifdef HAVE_POPPLER_CAIRO
+/// helper method
+static cairo_status_t
+ _write_ustring_cb(void *closure, const unsigned char *data, unsigned int length)
+{
+ Glib::ustring* stream = static_cast<Glib::ustring*>(closure);
+ stream->append(reinterpret_cast<const char*>(data), length);
+
+ return CAIRO_STATUS_SUCCESS;
+}
+#endif
+
+/**
+ * Parses the selected page of the given PDF document using PdfParser.
+ */
+SPDocument *
+PdfInput::open(::Inkscape::Extension::Input * /*mod*/, const gchar * uri) {
+
+ // Initialize the globalParams variable for poppler
+ if (!globalParams) {
+ globalParams = _POPPLER_NEW_GLOBAL_PARAMS();
+ }
+
+
+ // Open the file using poppler
+ // PDFDoc is from poppler. PDFDoc is used for preview and for native import.
+ std::shared_ptr<PDFDoc> pdf_doc;
+
+ // poppler does not use glib g_open. So on win32 we must use unicode call. code was copied from
+ // glib gstdio.c
+ pdf_doc = _POPPLER_MAKE_SHARED_PDFDOC(uri); // TODO: Could ask for password
+
+ if (!pdf_doc->isOk()) {
+ int error = pdf_doc->getErrorCode();
+ if (error == errEncrypted) {
+ g_message("Document is encrypted.");
+ } else if (error == errOpenFile) {
+ g_message("couldn't open the PDF file.");
+ } else if (error == errBadCatalog) {
+ g_message("couldn't read the page catalog.");
+ } else if (error == errDamaged) {
+ g_message("PDF file was damaged and couldn't be repaired.");
+ } else if (error == errHighlightFile) {
+ g_message("nonexistent or invalid highlight file.");
+ } else if (error == errBadPrinter) {
+ g_message("invalid printer.");
+ } else if (error == errPrinting) {
+ g_message("Error during printing.");
+ } else if (error == errPermission) {
+ g_message("PDF file does not allow that operation.");
+ } else if (error == errBadPageNum) {
+ g_message("invalid page number.");
+ } else if (error == errFileIO) {
+ g_message("file IO error.");
+ } else {
+ g_message("Failed to load document from data (error %d)", error);
+ }
+
+ return nullptr;
+ }
+
+
+ std::unique_ptr<PdfImportDialog> dlg;
+ if (INKSCAPE.use_gui()) {
+ dlg = std::make_unique<PdfImportDialog>(pdf_doc, uri);
+ if (!dlg->showDialog()) {
+ throw Input::open_cancelled();
+ }
+ }
+
+ // Get options
+ std::string page_nums = "1";
+ PdfImportType import_method = PdfImportType::PDF_IMPORT_INTERNAL;
+ FontStrategies font_strats;
+ if (dlg) {
+ page_nums = dlg->getSelectedPages();
+ import_method = dlg->getImportMethod();
+ font_strats = dlg->getFontStrategies();
+ } else {
+ page_nums = INKSCAPE.get_pages();
+ auto strat = (FontStrategy)INKSCAPE.get_pdf_font_strategy();
+ font_strats = SvgBuilder::autoFontStrategies(strat, getPdfFonts(pdf_doc));
+#ifdef HAVE_POPPLER_CAIRO
+ import_method = (PdfImportType)INKSCAPE.get_pdf_poppler();
+#endif
+ }
+ // Both poppler and poppler+cairo can get page num info from poppler.
+ auto pages = parseIntRange(page_nums, 1, pdf_doc->getCatalog()->getNumPages());
+ if (pages.empty()) {
+ g_warning("No pages selected, getting first page only.");
+ pages.insert(1);
+ }
+
+ // Create Inkscape document from file
+ SPDocument *doc = nullptr;
+ bool saved = false;
+ if (import_method == PdfImportType::PDF_IMPORT_INTERNAL) {
+ // Create document
+ doc = SPDocument::createNewDoc(nullptr, true, true);
+ saved = DocumentUndo::getUndoSensitive(doc);
+ DocumentUndo::setUndoSensitive(doc, false); // No need to undo in this temporary document
+
+ // Create builder
+ gchar *docname = g_path_get_basename(uri);
+ gchar *dot = g_strrstr(docname, ".");
+ if (dot) {
+ *dot = 0;
+ }
+ SvgBuilder *builder = new SvgBuilder(doc, docname, pdf_doc->getXRef());
+ builder->setFontStrategies(font_strats);
+
+ // Get preferences
+ Inkscape::XML::Node *prefs = builder->getPreferences();
+ if (dlg)
+ dlg->getImportSettings(prefs);
+
+ for (auto p : pages) {
+ // And then add each of the pages
+ add_builder_page(pdf_doc, builder, doc, p);
+ }
+
+ delete builder;
+ g_free(docname);
+#ifdef HAVE_POPPLER_CAIRO
+ } else if (import_method == PdfImportType::PDF_IMPORT_CAIRO) {
+ // the poppler import
+
+ std::string full_path = uri;
+ if (!Glib::path_is_absolute(uri)) {
+ full_path = Glib::build_filename(Glib::get_current_dir(),uri);
+ }
+ Glib::ustring full_uri = Glib::filename_to_uri(full_path);
+
+ GError *error = NULL;
+ /// @todo handle password
+ /// @todo check if win32 unicode needs special attention
+ PopplerDocument* document = poppler_document_new_from_file(full_uri.c_str(), NULL, &error);
+
+ if(error != NULL) {
+ std::cerr << "PDFInput::open: error opening document: " << full_uri.raw() << std::endl;
+ g_error_free (error);
+ return nullptr;
+ }
+
+ int page_num = *pages.begin();
+ if (PopplerPage* page = poppler_document_get_page(document, page_num - 1)) {
+ double width, height;
+ poppler_page_get_size(page, &width, &height);
+
+ Glib::ustring output;
+ cairo_surface_t* surface = cairo_svg_surface_create_for_stream(Inkscape::Extension::Internal::_write_ustring_cb,
+ &output, width, height);
+
+ // Reset back to PT for cairo 1.17.6 and above which sets to UNIT_USER
+ cairo_svg_surface_set_document_unit(surface, CAIRO_SVG_UNIT_PT);
+
+ // This magical function results in more fine-grain fallbacks. In particular, a mesh
+ // gradient won't necessarily result in the whole PDF being rasterized. Of course, SVG
+ // 1.2 never made it as a standard, but hey, we'll take what we can get. This trick was
+ // found by examining the 'pdftocairo' code.
+ cairo_svg_surface_restrict_to_version( surface, CAIRO_SVG_VERSION_1_2 );
+
+ cairo_t* cr = cairo_create(surface);
+
+ poppler_page_render_for_printing(page, cr);
+ cairo_show_page(cr);
+
+ cairo_destroy(cr);
+ cairo_surface_destroy(surface);
+
+ doc = SPDocument::createNewDocFromMem(output.c_str(), output.length(), TRUE);
+
+ g_object_unref(G_OBJECT(page));
+ } else if (document) {
+ std::cerr << "PDFInput::open: error opening page " << page_num << " of document: " << full_uri.raw() << std::endl;
+ }
+ g_object_unref(G_OBJECT(document));
+
+ if (!doc) {
+ return nullptr;
+ }
+
+ saved = DocumentUndo::getUndoSensitive(doc);
+ DocumentUndo::setUndoSensitive(doc, false); // No need to undo in this temporary document
+#endif
+ }
+
+ // Set viewBox if it doesn't exist
+ if (!doc->getRoot()->viewBox_set) {
+ doc->setViewBox(Geom::Rect::from_xywh(0, 0, doc->getWidth().value(doc->getDisplayUnit()), doc->getHeight().value(doc->getDisplayUnit())));
+ }
+
+ // Restore undo
+ DocumentUndo::setUndoSensitive(doc, saved);
+
+ return doc;
+}
+
+/**
+ * Parses the selected page object of the given PDF document using PdfParser.
+ */
+void
+PdfInput::add_builder_page(std::shared_ptr<PDFDoc>pdf_doc, SvgBuilder *builder, SPDocument *doc, int page_num)
+{
+ Inkscape::XML::Node *prefs = builder->getPreferences();
+
+ // Check page exists
+ Catalog *catalog = pdf_doc->getCatalog();
+ sanitize_page_number(page_num, catalog->getNumPages());
+ Page *page = catalog->getPage(page_num);
+ if (!page) {
+ std::cerr << "PDFInput::open: error opening page " << page_num << std::endl;
+ return;
+ }
+
+ // Apply crop settings
+ _POPPLER_CONST PDFRectangle *clipToBox = nullptr;
+
+ switch (prefs->getAttributeInt("cropTo", -1)) {
+ case 0: // Media box
+ clipToBox = page->getMediaBox();
+ break;
+ case 1: // Crop box
+ clipToBox = page->getCropBox();
+ break;
+ case 2: // Trim box
+ clipToBox = page->getTrimBox();
+ break;
+ case 3: // Bleed box
+ clipToBox = page->getBleedBox();
+ break;
+ case 4: // Art box
+ clipToBox = page->getArtBox();
+ break;
+ default:
+ break;
+ }
+
+ // Create parser (extension/internal/pdfinput/pdf-parser.h)
+ PdfParser *pdf_parser = new PdfParser(pdf_doc, builder, page, clipToBox);
+
+ // Set up approximation precision for parser. Used for converting Mesh Gradients into tiles.
+ double color_delta = prefs->getAttributeDouble("approximationPrecision", 2.0);
+ if ( color_delta <= 0.0 ) {
+ color_delta = 1.0 / 2.0;
+ } else {
+ color_delta = 1.0 / color_delta;
+ }
+ for ( int i = 1 ; i <= pdfNumShadingTypes ; i++ ) {
+ pdf_parser->setApproximationPrecision(i, color_delta, 6);
+ }
+
+ // Parse the document structure
+#if defined(POPPLER_NEW_OBJECT_API)
+ Object obj = page->getContents();
+#else
+ Object obj;
+ page->getContents(&obj);
+#endif
+ if (!obj.isNull()) {
+ pdf_parser->parse(&obj);
+ }
+
+ // Cleanup
+#if !defined(POPPLER_NEW_OBJECT_API)
+ obj.free();
+#endif
+ delete pdf_parser;
+}
+
+#include "../clear-n_.h"
+
+void PdfInput::init() {
+ /* PDF in */
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("PDF Input") "</name>\n"
+ "<id>org.inkscape.input.pdf</id>\n"
+ "<input>\n"
+ "<extension>.pdf</extension>\n"
+ "<mimetype>application/pdf</mimetype>\n"
+ "<filetypename>" N_("Portable Document Format (*.pdf)") "</filetypename>\n"
+ "<filetypetooltip>" N_("Portable Document Format") "</filetypetooltip>\n"
+ "</input>\n"
+ "</inkscape-extension>", new PdfInput());
+ // clang-format on
+
+ /* AI in */
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("AI Input") "</name>\n"
+ "<id>org.inkscape.input.ai</id>\n"
+ "<input>\n"
+ "<extension>.ai</extension>\n"
+ "<mimetype>image/x-adobe-illustrator</mimetype>\n"
+ "<filetypename>" N_("Adobe Illustrator 9.0 and above (*.ai)") "</filetypename>\n"
+ "<filetypetooltip>" N_("Open files saved in Adobe Illustrator 9.0 and newer versions") "</filetypetooltip>\n"
+ "</input>\n"
+ "</inkscape-extension>", new PdfInput());
+ // clang-format on
+} // init
+
+} } } /* namespace Inkscape, Extension, Implementation */
+
+#endif /* HAVE_POPPLER */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/pdfinput/pdf-input.h b/src/extension/internal/pdfinput/pdf-input.h
new file mode 100644
index 0000000..e6d6c55
--- /dev/null
+++ b/src/extension/internal/pdfinput/pdf-input.h
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_EXTENSION_INTERNAL_PDFINPUT_H
+#define SEEN_EXTENSION_INTERNAL_PDFINPUT_H
+
+/*
+ * Authors:
+ * miklos erdelyi
+ *
+ * Copyright (C) 2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#ifdef HAVE_POPPLER
+#include <gtkmm.h>
+#include <gtkmm/dialog.h>
+
+#include "../../implementation/implementation.h"
+#include "poppler-transition-api.h"
+#include "poppler-utils.h"
+#include "svg-builder.h"
+
+#ifdef HAVE_POPPLER_CAIRO
+struct _PopplerDocument;
+typedef struct _PopplerDocument PopplerDocument;
+#endif
+
+struct _GdkEventExpose;
+typedef _GdkEventExpose GdkEventExpose;
+
+class Page;
+class PDFDoc;
+
+namespace Gtk {
+ class Button;
+ class CheckButton;
+ class ComboBoxText;
+ class DrawingArea;
+ class Frame;
+ class Scale;
+ class RadioButton;
+ class Box;
+ class Label;
+ class Entry;
+}
+
+namespace Inkscape {
+
+namespace UI {
+namespace Widget {
+ class SpinButton;
+ class Frame;
+}
+}
+
+enum class PdfImportType : unsigned char
+{
+ PDF_IMPORT_INTERNAL,
+ PDF_IMPORT_CAIRO,
+};
+
+namespace Extension {
+namespace Internal {
+
+class FontModelColumns;
+
+/**
+ * PDF import using libpoppler.
+ */
+class PdfImportDialog : public Gtk::Dialog
+{
+public:
+ PdfImportDialog(std::shared_ptr<PDFDoc> doc, const gchar *uri);
+ ~PdfImportDialog() override;
+
+ bool showDialog();
+ std::string getSelectedPages();
+ PdfImportType getImportMethod();
+ void getImportSettings(Inkscape::XML::Node *prefs);
+ FontStrategies getFontStrategies();
+ void setFontStrategies(const FontStrategies &fs);
+
+private:
+ void _fontRenderChanged();
+ void _setPreviewPage(int page);
+ void _setFonts(const FontList &fonts);
+
+ // Signal handlers
+ bool _onDraw(const Cairo::RefPtr<Cairo::Context>& cr);
+ void _onPageNumberChanged();
+ void _onPrecisionChanged();
+
+ Glib::RefPtr<Gtk::Builder> _builder;
+
+ Gtk::Entry &_page_numbers;
+ Gtk::DrawingArea &_preview_area;
+ Gtk::CheckButton &_embed_images;
+ Gtk::Scale &_mesh_slider;
+ Gtk::Label &_mesh_label;
+ Gtk::Button &_next_page;
+ Gtk::Button &_prev_page;
+ Gtk::Label &_current_page;
+ Glib::RefPtr<Gtk::ListStore> _font_model;
+ FontModelColumns *_font_col;
+
+ std::shared_ptr<PDFDoc> _pdf_doc; // Document to be imported
+ std::string _current_pages; // Current selected pages
+ FontList _font_list; // List of fonts and the pages they appear on
+ int _total_pages = 0;
+ int _preview_page = 1;
+ Page *_previewed_page; // Currently previewed page
+ unsigned char *_thumb_data; // Thumbnail image data
+ int _thumb_width, _thumb_height; // Thumbnail size
+ int _thumb_rowstride;
+ int _preview_width, _preview_height; // Size of the preview area
+ bool _render_thumb; // Whether we can/shall render thumbnails
+#ifdef HAVE_POPPLER_CAIRO
+ cairo_surface_t *_cairo_surface = nullptr;
+ PopplerDocument *_poppler_doc = nullptr;
+#endif
+};
+
+
+class PdfInput: public Inkscape::Extension::Implementation::Implementation {
+ PdfInput () = default;;
+public:
+ SPDocument *open( Inkscape::Extension::Input *mod,
+ const gchar *uri ) override;
+ static void init( );
+private:
+ void add_builder_page(
+ std::shared_ptr<PDFDoc> pdf_doc,
+ SvgBuilder *builder, SPDocument *doc,
+ int page_num);
+};
+
+} // namespace Implementation
+} // namespace Extension
+} // namespace Inkscape
+
+#endif // HAVE_POPPLER
+
+#endif // SEEN_EXTENSION_INTERNAL_PDFINPUT_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/pdfinput/pdf-parser.cpp b/src/extension/internal/pdfinput/pdf-parser.cpp
new file mode 100644
index 0000000..1d1df91
--- /dev/null
+++ b/src/extension/internal/pdfinput/pdf-parser.cpp
@@ -0,0 +1,3196 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * PDF parsing using libpoppler.
+ *//*
+ * Authors:
+ * Derived from poppler's Gfx.cc, which was derived from Xpdf by 1996-2003 Glyph & Cog, LLC
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#ifdef HAVE_POPPLER
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <cmath>
+#include <cstddef>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+#include "2geom/transforms.h"
+#include "Annot.h"
+#include "Array.h"
+#include "CharTypes.h"
+#include "Dict.h"
+#include "Error.h"
+#include "Gfx.h"
+#include "GfxFont.h"
+#include "GfxState.h"
+#include "GlobalParams.h"
+#include "Lexer.h"
+#include "Object.h"
+#include "OutputDev.h"
+#include "PDFDoc.h"
+#include "Page.h"
+#include "Parser.h"
+#include "Stream.h"
+#include "glib/poppler-features.h"
+#include "goo/GooString.h"
+#include "goo/gmem.h"
+#include "pdf-parser.h"
+#include "pdf-utils.h"
+#include "poppler-cairo-font-engine.h"
+#include "poppler-transition-api.h"
+#include "poppler-utils.h"
+#include "svg-builder.h"
+#include "util/units.h"
+
+// the MSVC math.h doesn't define this
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+//------------------------------------------------------------------------
+// constants
+//------------------------------------------------------------------------
+
+// Default max delta allowed in any color component for a shading fill.
+#define defaultShadingColorDelta (dblToCol( 1 / 2.0 ))
+
+// Default max recursive depth for a shading fill.
+#define defaultShadingMaxDepth 6
+
+// Max number of operators kept in the history list.
+#define maxOperatorHistoryDepth 16
+
+//------------------------------------------------------------------------
+// Operator table
+//------------------------------------------------------------------------
+
+PdfOperator PdfParser::opTab[] = {
+ {"\"", 3, {tchkNum, tchkNum, tchkString},
+ &PdfParser::opMoveSetShowText},
+ {"'", 1, {tchkString},
+ &PdfParser::opMoveShowText},
+ {"B", 0, {tchkNone},
+ &PdfParser::opFillStroke},
+ {"B*", 0, {tchkNone},
+ &PdfParser::opEOFillStroke},
+ {"BDC", 2, {tchkName, tchkProps},
+ &PdfParser::opBeginMarkedContent},
+ {"BI", 0, {tchkNone},
+ &PdfParser::opBeginImage},
+ {"BMC", 1, {tchkName},
+ &PdfParser::opBeginMarkedContent},
+ {"BT", 0, {tchkNone},
+ &PdfParser::opBeginText},
+ {"BX", 0, {tchkNone},
+ &PdfParser::opBeginIgnoreUndef},
+ {"CS", 1, {tchkName},
+ &PdfParser::opSetStrokeColorSpace},
+ {"DP", 2, {tchkName, tchkProps},
+ &PdfParser::opMarkPoint},
+ {"Do", 1, {tchkName},
+ &PdfParser::opXObject},
+ {"EI", 0, {tchkNone},
+ &PdfParser::opEndImage},
+ {"EMC", 0, {tchkNone},
+ &PdfParser::opEndMarkedContent},
+ {"ET", 0, {tchkNone},
+ &PdfParser::opEndText},
+ {"EX", 0, {tchkNone},
+ &PdfParser::opEndIgnoreUndef},
+ {"F", 0, {tchkNone},
+ &PdfParser::opFill},
+ {"G", 1, {tchkNum},
+ &PdfParser::opSetStrokeGray},
+ {"ID", 0, {tchkNone},
+ &PdfParser::opImageData},
+ {"J", 1, {tchkInt},
+ &PdfParser::opSetLineCap},
+ {"K", 4, {tchkNum, tchkNum, tchkNum, tchkNum},
+ &PdfParser::opSetStrokeCMYKColor},
+ {"M", 1, {tchkNum},
+ &PdfParser::opSetMiterLimit},
+ {"MP", 1, {tchkName},
+ &PdfParser::opMarkPoint},
+ {"Q", 0, {tchkNone},
+ &PdfParser::opRestore},
+ {"RG", 3, {tchkNum, tchkNum, tchkNum},
+ &PdfParser::opSetStrokeRGBColor},
+ {"S", 0, {tchkNone},
+ &PdfParser::opStroke},
+ {"SC", -4, {tchkNum, tchkNum, tchkNum, tchkNum},
+ &PdfParser::opSetStrokeColor},
+ {"SCN", -33, {tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN,},
+ &PdfParser::opSetStrokeColorN},
+ {"T*", 0, {tchkNone},
+ &PdfParser::opTextNextLine},
+ {"TD", 2, {tchkNum, tchkNum},
+ &PdfParser::opTextMoveSet},
+ {"TJ", 1, {tchkArray},
+ &PdfParser::opShowSpaceText},
+ {"TL", 1, {tchkNum},
+ &PdfParser::opSetTextLeading},
+ {"Tc", 1, {tchkNum},
+ &PdfParser::opSetCharSpacing},
+ {"Td", 2, {tchkNum, tchkNum},
+ &PdfParser::opTextMove},
+ {"Tf", 2, {tchkName, tchkNum},
+ &PdfParser::opSetFont},
+ {"Tj", 1, {tchkString},
+ &PdfParser::opShowText},
+ {"Tm", 6, {tchkNum, tchkNum, tchkNum, tchkNum,
+ tchkNum, tchkNum},
+ &PdfParser::opSetTextMatrix},
+ {"Tr", 1, {tchkInt},
+ &PdfParser::opSetTextRender},
+ {"Ts", 1, {tchkNum},
+ &PdfParser::opSetTextRise},
+ {"Tw", 1, {tchkNum},
+ &PdfParser::opSetWordSpacing},
+ {"Tz", 1, {tchkNum},
+ &PdfParser::opSetHorizScaling},
+ {"W", 0, {tchkNone},
+ &PdfParser::opClip},
+ {"W*", 0, {tchkNone},
+ &PdfParser::opEOClip},
+ {"b", 0, {tchkNone},
+ &PdfParser::opCloseFillStroke},
+ {"b*", 0, {tchkNone},
+ &PdfParser::opCloseEOFillStroke},
+ {"c", 6, {tchkNum, tchkNum, tchkNum, tchkNum,
+ tchkNum, tchkNum},
+ &PdfParser::opCurveTo},
+ {"cm", 6, {tchkNum, tchkNum, tchkNum, tchkNum,
+ tchkNum, tchkNum},
+ &PdfParser::opConcat},
+ {"cs", 1, {tchkName},
+ &PdfParser::opSetFillColorSpace},
+ {"d", 2, {tchkArray, tchkNum},
+ &PdfParser::opSetDash},
+ {"d0", 2, {tchkNum, tchkNum},
+ &PdfParser::opSetCharWidth},
+ {"d1", 6, {tchkNum, tchkNum, tchkNum, tchkNum,
+ tchkNum, tchkNum},
+ &PdfParser::opSetCacheDevice},
+ {"f", 0, {tchkNone},
+ &PdfParser::opFill},
+ {"f*", 0, {tchkNone},
+ &PdfParser::opEOFill},
+ {"g", 1, {tchkNum},
+ &PdfParser::opSetFillGray},
+ {"gs", 1, {tchkName},
+ &PdfParser::opSetExtGState},
+ {"h", 0, {tchkNone},
+ &PdfParser::opClosePath},
+ {"i", 1, {tchkNum},
+ &PdfParser::opSetFlat},
+ {"j", 1, {tchkInt},
+ &PdfParser::opSetLineJoin},
+ {"k", 4, {tchkNum, tchkNum, tchkNum, tchkNum},
+ &PdfParser::opSetFillCMYKColor},
+ {"l", 2, {tchkNum, tchkNum},
+ &PdfParser::opLineTo},
+ {"m", 2, {tchkNum, tchkNum},
+ &PdfParser::opMoveTo},
+ {"n", 0, {tchkNone},
+ &PdfParser::opEndPath},
+ {"q", 0, {tchkNone},
+ &PdfParser::opSave},
+ {"re", 4, {tchkNum, tchkNum, tchkNum, tchkNum},
+ &PdfParser::opRectangle},
+ {"rg", 3, {tchkNum, tchkNum, tchkNum},
+ &PdfParser::opSetFillRGBColor},
+ {"ri", 1, {tchkName},
+ &PdfParser::opSetRenderingIntent},
+ {"s", 0, {tchkNone},
+ &PdfParser::opCloseStroke},
+ {"sc", -4, {tchkNum, tchkNum, tchkNum, tchkNum},
+ &PdfParser::opSetFillColor},
+ {"scn", -33, {tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN,},
+ &PdfParser::opSetFillColorN},
+ {"sh", 1, {tchkName},
+ &PdfParser::opShFill},
+ {"v", 4, {tchkNum, tchkNum, tchkNum, tchkNum},
+ &PdfParser::opCurveTo1},
+ {"w", 1, {tchkNum},
+ &PdfParser::opSetLineWidth},
+ {"y", 4, {tchkNum, tchkNum, tchkNum, tchkNum},
+ &PdfParser::opCurveTo2}
+};
+
+#define numOps (sizeof(opTab) / sizeof(PdfOperator))
+
+namespace {
+
+GfxPatch blankPatch()
+{
+ GfxPatch patch;
+ memset(&patch, 0, sizeof(patch)); // quick-n-dirty
+ return patch;
+}
+
+} // namespace
+
+//------------------------------------------------------------------------
+// PdfParser
+//------------------------------------------------------------------------
+
+PdfParser::PdfParser(std::shared_ptr<PDFDoc> pdf_doc, Inkscape::Extension::Internal::SvgBuilder *builderA, Page *page,
+ _POPPLER_CONST PDFRectangle *cropBox)
+ : _pdf_doc(pdf_doc)
+ , xref(pdf_doc->getXRef())
+ , builder(builderA)
+ , subPage(false)
+ , printCommands(false)
+ , res(new GfxResources(xref, page->getResourceDict(), nullptr))
+ , // start the resource stack
+ state(new GfxState(96.0, 96.0, page->getCropBox(), page->getRotate(), true))
+ , fontChanged(gFalse)
+ , clip(clipNone)
+ , ignoreUndef(0)
+ , formDepth(0)
+ , parser(nullptr)
+ , colorDeltas()
+ , maxDepths()
+ , operatorHistory(nullptr)
+{
+ setDefaultApproximationPrecision();
+ loadOptionalContentLayers(page->getResourceDict());
+ loadColorProfile();
+ baseMatrix = stateToAffine(state);
+
+ if (page) {
+ // Increment the page building here and set page label
+ Catalog *catalog = pdf_doc->getCatalog();
+ GooString *label = new GooString("");
+ catalog->indexToLabel(page->getNum() - 1, label);
+ builder->pushPage(label->getCString(), state);
+ }
+
+ // Must come after pushPage!
+ builder->setDocumentSize(state->getPageWidth(), state->getPageHeight());
+
+ // Set margins, bleeds and page-cropping
+ auto page_box = getRect(page->getCropBox());
+ auto scale = Geom::Scale(state->getPageWidth() / page_box.width(),
+ state->getPageHeight() / page_box.height());
+ builder->setMargins(getRect(page->getTrimBox()) * scale,
+ getRect(page->getArtBox()) * scale,
+ getRect(page->getBleedBox()) * scale);
+ if (cropBox && getRect(cropBox) != page_box) {
+ builder->cropPage(getRect(cropBox) * scale);
+ }
+
+ saveState();
+ formDepth = 0;
+
+ pushOperator("startPage");
+}
+
+PdfParser::PdfParser(XRef *xrefA, Inkscape::Extension::Internal::SvgBuilder *builderA, Dict *resDict,
+ _POPPLER_CONST PDFRectangle *box)
+ : xref(xrefA)
+ , builder(builderA)
+ , subPage(true)
+ , printCommands(false)
+ , res(new GfxResources(xref, resDict, nullptr))
+ , // start the resource stack
+ state(new GfxState(72, 72, box, 0, false))
+ , fontChanged(gFalse)
+ , clip(clipNone)
+ , ignoreUndef(0)
+ , formDepth(0)
+ , parser(nullptr)
+ , colorDeltas()
+ , maxDepths()
+ , operatorHistory(nullptr)
+{
+ setDefaultApproximationPrecision();
+ baseMatrix = stateToAffine(state);
+ formDepth = 0;
+}
+
+PdfParser::~PdfParser() {
+ while(operatorHistory) {
+ OpHistoryEntry *tmp = operatorHistory->next;
+ delete operatorHistory;
+ operatorHistory = tmp;
+ }
+
+ while (state && state->hasSaves()) {
+ restoreState();
+ }
+
+ if (!subPage) {
+ //out->endPage();
+ }
+
+ while (res) {
+ popResources();
+ }
+
+ if (state) {
+ delete state;
+ state = nullptr;
+ }
+}
+
+void PdfParser::parse(Object *obj, GBool topLevel) {
+ Object obj2;
+
+ if (obj->isArray()) {
+ for (int i = 0; i < obj->arrayGetLength(); ++i) {
+ _POPPLER_CALL_ARGS(obj2, obj->arrayGet, i);
+ if (!obj2.isStream()) {
+ error(errInternal, -1, "Weird page contents");
+ _POPPLER_FREE(obj2);
+ return;
+ }
+ _POPPLER_FREE(obj2);
+ }
+ } else if (!obj->isStream()) {
+ error(errInternal, -1, "Weird page contents");
+ return;
+ }
+ parser = new _POPPLER_NEW_PARSER(xref, obj);
+ go(topLevel);
+ delete parser;
+ parser = nullptr;
+}
+
+void PdfParser::go(GBool /*topLevel*/)
+{
+ Object obj;
+ Object args[maxArgs];
+
+ // scan a sequence of objects
+ int numArgs = 0;
+ _POPPLER_CALL(obj, parser->getObj);
+ while (!obj.isEOF()) {
+
+ // got a command - execute it
+ if (obj.isCmd()) {
+ if (printCommands) {
+ obj.print(stdout);
+ for (int i = 0; i < numArgs; ++i) {
+ printf(" ");
+ args[i].print(stdout);
+ }
+ printf("\n");
+ fflush(stdout);
+ }
+
+ // Run the operation
+ execOp(&obj, args, numArgs);
+
+#if !defined(POPPLER_NEW_OBJECT_API)
+ _POPPLER_FREE(obj);
+ for (int i = 0; i < numArgs; ++i)
+ _POPPLER_FREE(args[i]);
+#endif
+ numArgs = 0;
+
+ // got an argument - save it
+ } else if (numArgs < maxArgs) {
+ args[numArgs++] = std::move(obj);
+
+ // too many arguments - something is wrong
+ } else {
+ error(errSyntaxError, getPos(), "Too many args in content stream");
+ if (printCommands) {
+ printf("throwing away arg: ");
+ obj.print(stdout);
+ printf("\n");
+ fflush(stdout);
+ }
+ _POPPLER_FREE(obj);
+ }
+
+ // grab the next object
+ _POPPLER_CALL(obj, parser->getObj);
+ }
+ _POPPLER_FREE(obj);
+
+ // args at end with no command
+ if (numArgs > 0) {
+ error(errSyntaxError, getPos(), "Leftover args in content stream");
+ if (printCommands) {
+ printf("%d leftovers:", numArgs);
+ for (int i = 0; i < numArgs; ++i) {
+ printf(" ");
+ args[i].print(stdout);
+ }
+ printf("\n");
+ fflush(stdout);
+ }
+#if !defined(POPPLER_NEW_OBJECT_API)
+ for (int i = 0; i < numArgs; ++i)
+ _POPPLER_FREE(args[i]);
+#endif
+ }
+}
+
+void PdfParser::pushOperator(const char *name)
+{
+ OpHistoryEntry *newEntry = new OpHistoryEntry;
+ newEntry->name = name;
+ newEntry->state = nullptr;
+ newEntry->depth = (operatorHistory != nullptr ? (operatorHistory->depth+1) : 0);
+ newEntry->next = operatorHistory;
+ operatorHistory = newEntry;
+
+ // Truncate list if needed
+ if (operatorHistory->depth > maxOperatorHistoryDepth) {
+ OpHistoryEntry *curr = operatorHistory;
+ OpHistoryEntry *prev = nullptr;
+ while (curr && curr->next != nullptr) {
+ curr->depth--;
+ prev = curr;
+ curr = curr->next;
+ }
+ if (prev) {
+ if (curr->state != nullptr)
+ delete curr->state;
+ delete curr;
+ prev->next = nullptr;
+ }
+ }
+}
+
+const char *PdfParser::getPreviousOperator(unsigned int look_back) {
+ OpHistoryEntry *prev = nullptr;
+ if (operatorHistory != nullptr && look_back > 0) {
+ prev = operatorHistory->next;
+ while (--look_back > 0 && prev != nullptr) {
+ prev = prev->next;
+ }
+ }
+ if (prev != nullptr) {
+ return prev->name;
+ } else {
+ return "";
+ }
+}
+
+void PdfParser::execOp(Object *cmd, Object args[], int numArgs) {
+ PdfOperator *op;
+ const char *name;
+ Object *argPtr;
+ int i;
+
+ // find operator
+ name = cmd->getCmd();
+ if (!(op = findOp(name))) {
+ if (ignoreUndef == 0)
+ error(errSyntaxError, getPos(), "Unknown operator '{0:s}'", name);
+ return;
+ }
+
+ // type check args
+ argPtr = args;
+ if (op->numArgs >= 0) {
+ if (numArgs < op->numArgs) {
+ error(errSyntaxError, getPos(), "Too few ({0:d}) args to '{1:d}' operator", numArgs, name);
+ return;
+ }
+ if (numArgs > op->numArgs) {
+#if 0
+ error(errSyntaxError, getPos(), "Too many ({0:d}) args to '{1:s}' operator", numArgs, name);
+#endif
+ argPtr += numArgs - op->numArgs;
+ numArgs = op->numArgs;
+ }
+ } else {
+ if (numArgs > -op->numArgs) {
+ error(errSyntaxError, getPos(), "Too many ({0:d}) args to '{1:s}' operator",
+ numArgs, name);
+ return;
+ }
+ }
+ for (i = 0; i < numArgs; ++i) {
+ if (!checkArg(&argPtr[i], op->tchk[i])) {
+ error(errSyntaxError, getPos(), "Arg #{0:d} to '{1:s}' operator is wrong type ({2:s})",
+ i, name, argPtr[i].getTypeName());
+ return;
+ }
+ }
+
+ // add to history
+ pushOperator((char*)&op->name);
+
+ // do it
+ (this->*op->func)(argPtr, numArgs);
+}
+
+PdfOperator* PdfParser::findOp(const char *name) {
+ int a = -1;
+ int b = numOps;
+ int cmp = -1;
+ // invariant: opTab[a] < name < opTab[b]
+ while (b - a > 1) {
+ const int m = (a + b) / 2;
+ cmp = strcmp(opTab[m].name, name);
+ if (cmp < 0)
+ a = m;
+ else if (cmp > 0)
+ b = m;
+ else
+ a = b = m;
+ }
+ if (cmp != 0) {
+ return nullptr;
+ }
+ return &opTab[a];
+}
+
+GBool PdfParser::checkArg(Object *arg, TchkType type) {
+ switch (type) {
+ case tchkBool: return arg->isBool();
+ case tchkInt: return arg->isInt();
+ case tchkNum: return arg->isNum();
+ case tchkString: return arg->isString();
+ case tchkName: return arg->isName();
+ case tchkArray: return arg->isArray();
+ case tchkProps: return arg->isDict() || arg->isName();
+ case tchkSCN: return arg->isNum() || arg->isName();
+ case tchkNone: return gFalse;
+ }
+ return gFalse;
+}
+
+int PdfParser::getPos() {
+ return parser ? parser->getPos() : -1;
+}
+
+//------------------------------------------------------------------------
+// graphics state operators
+//------------------------------------------------------------------------
+
+void PdfParser::opSave(Object /*args*/[], int /*numArgs*/)
+{
+ saveState();
+}
+
+void PdfParser::opRestore(Object /*args*/[], int /*numArgs*/)
+{
+ restoreState();
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+/**
+ * Concatenate transformation matrix to the current state
+ */
+void PdfParser::opConcat(Object args[], int /*numArgs*/)
+{
+ state->concatCTM(args[0].getNum(), args[1].getNum(),
+ args[2].getNum(), args[3].getNum(),
+ args[4].getNum(), args[5].getNum());
+ fontChanged = gTrue;
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetDash(Object args[], int /*numArgs*/)
+{
+ double *dash = nullptr;
+
+ Array *a = args[0].getArray();
+ int length = a->getLength();
+ if (length != 0) {
+ dash = (double *)gmallocn(length, sizeof(double));
+ for (int i = 0; i < length; ++i) {
+ Object obj;
+ dash[i] = _POPPLER_CALL_ARGS_DEREF(obj, a->get, i).getNum();
+ _POPPLER_FREE(obj);
+ }
+ }
+#if POPPLER_CHECK_VERSION(22, 9, 0)
+ state->setLineDash(std::vector<double> (dash, dash + length), args[1].getNum());
+#else
+ state->setLineDash(dash, length, args[1].getNum());
+#endif
+ builder->updateStyle(state);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetFlat(Object args[], int /*numArgs*/)
+{
+ state->setFlatness((int)args[0].getNum());
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetLineJoin(Object args[], int /*numArgs*/)
+{
+ state->setLineJoin(args[0].getInt());
+ builder->updateStyle(state);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetLineCap(Object args[], int /*numArgs*/)
+{
+ state->setLineCap(args[0].getInt());
+ builder->updateStyle(state);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetMiterLimit(Object args[], int /*numArgs*/)
+{
+ state->setMiterLimit(args[0].getNum());
+ builder->updateStyle(state);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetLineWidth(Object args[], int /*numArgs*/)
+{
+ state->setLineWidth(args[0].getNum());
+ builder->updateStyle(state);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetExtGState(Object args[], int /*numArgs*/)
+{
+ Object obj1, obj2, obj3, obj4, obj5;
+ Function *funcs[4] = {nullptr, nullptr, nullptr, nullptr};
+ GfxColor backdropColor;
+ GBool haveBackdropColor = gFalse;
+ GBool alpha = gFalse;
+
+ _POPPLER_CALL_ARGS(obj1, res->lookupGState, args[0].getName());
+ if (obj1.isNull()) {
+ return;
+ }
+ if (!obj1.isDict()) {
+ error(errSyntaxError, getPos(), "ExtGState '{0:s}' is wrong type"), args[0].getName();
+ _POPPLER_FREE(obj1);
+ return;
+ }
+ if (printCommands) {
+ printf(" gfx state dict: ");
+ obj1.print();
+ printf("\n");
+ }
+
+ // transparency support: blend mode, fill/stroke opacity
+ if (!_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "BM").isNull()) {
+ GfxBlendMode mode = gfxBlendNormal;
+ if (state->parseBlendMode(&obj2, &mode)) {
+ state->setBlendMode(mode);
+ } else {
+ error(errSyntaxError, getPos(), "Invalid blend mode in ExtGState");
+ }
+ }
+ _POPPLER_FREE(obj2);
+ if (_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "ca").isNum()) {
+ state->setFillOpacity(obj2.getNum());
+ }
+ _POPPLER_FREE(obj2);
+ if (_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "CA").isNum()) {
+ state->setStrokeOpacity(obj2.getNum());
+ }
+ _POPPLER_FREE(obj2);
+
+ // fill/stroke overprint
+ GBool haveFillOP = gFalse;
+ if ((haveFillOP = _POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "op").isBool())) {
+ state->setFillOverprint(obj2.getBool());
+ }
+ _POPPLER_FREE(obj2);
+ if (_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "OP").isBool()) {
+ state->setStrokeOverprint(obj2.getBool());
+ if (!haveFillOP) {
+ state->setFillOverprint(obj2.getBool());
+ }
+ }
+ _POPPLER_FREE(obj2);
+
+ // stroke adjust
+ if (_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "SA").isBool()) {
+ state->setStrokeAdjust(obj2.getBool());
+ }
+ _POPPLER_FREE(obj2);
+
+ // transfer function
+ if (_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "TR2").isNull()) {
+ _POPPLER_FREE(obj2);
+ _POPPLER_CALL_ARGS(obj2, obj1.dictLookup, "TR");
+ }
+ if (obj2.isName(const_cast<char*>("Default")) ||
+ obj2.isName(const_cast<char*>("Identity"))) {
+ funcs[0] = funcs[1] = funcs[2] = funcs[3] = nullptr;
+ state->setTransfer(funcs);
+ } else if (obj2.isArray() && obj2.arrayGetLength() == 4) {
+ int pos = 4;
+ for (int i = 0; i < 4; ++i) {
+ _POPPLER_CALL_ARGS(obj3, obj2.arrayGet, i);
+ funcs[i] = Function::parse(&obj3);
+ _POPPLER_FREE(obj3);
+ if (!funcs[i]) {
+ pos = i;
+ break;
+ }
+ }
+ if (pos == 4) {
+ state->setTransfer(funcs);
+ }
+ } else if (obj2.isName() || obj2.isDict() || obj2.isStream()) {
+ if ((funcs[0] = Function::parse(&obj2))) {
+ funcs[1] = funcs[2] = funcs[3] = nullptr;
+ state->setTransfer(funcs);
+ }
+ } else if (!obj2.isNull()) {
+ error(errSyntaxError, getPos(), "Invalid transfer function in ExtGState");
+ }
+ _POPPLER_FREE(obj2);
+
+ // soft mask
+ if (!_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "SMask").isNull()) {
+ if (obj2.isName(const_cast<char*>("None"))) {
+ // Do nothing.
+ } else if (obj2.isDict()) {
+ if (_POPPLER_CALL_ARGS_DEREF(obj3, obj2.dictLookup, "S").isName("Alpha")) {
+ alpha = gTrue;
+ } else { // "Luminosity"
+ alpha = gFalse;
+ }
+ _POPPLER_FREE(obj3);
+ funcs[0] = nullptr;
+ if (!_POPPLER_CALL_ARGS_DEREF(obj3, obj2.dictLookup, "TR").isNull()) {
+ funcs[0] = Function::parse(&obj3);
+ if (funcs[0]->getInputSize() != 1 ||
+ funcs[0]->getOutputSize() != 1) {
+ error(errSyntaxError, getPos(), "Invalid transfer function in soft mask in ExtGState");
+ delete funcs[0];
+ funcs[0] = nullptr;
+ }
+ }
+ _POPPLER_FREE(obj3);
+ if ((haveBackdropColor = _POPPLER_CALL_ARGS_DEREF(obj3, obj2.dictLookup, "BC").isArray())) {
+ for (int & i : backdropColor.c) {
+ i = 0;
+ }
+ for (int i = 0; i < obj3.arrayGetLength() && i < gfxColorMaxComps; ++i) {
+ _POPPLER_CALL_ARGS(obj4, obj3.arrayGet, i);
+ if (obj4.isNum()) {
+ backdropColor.c[i] = dblToCol(obj4.getNum());
+ }
+ _POPPLER_FREE(obj4);
+ }
+ }
+ _POPPLER_FREE(obj3);
+ if (_POPPLER_CALL_ARGS_DEREF(obj3, obj2.dictLookup, "G").isStream()) {
+ if (_POPPLER_CALL_ARGS_DEREF(obj4, obj3.streamGetDict()->lookup, "Group").isDict()) {
+ GfxColorSpace *blendingColorSpace = nullptr;
+ GBool isolated = gFalse;
+ GBool knockout = gFalse;
+ if (!_POPPLER_CALL_ARGS_DEREF(obj5, obj4.dictLookup, "CS").isNull()) {
+ blendingColorSpace = GfxColorSpace::parse(nullptr, &obj5, nullptr, state);
+ }
+ _POPPLER_FREE(obj5);
+ if (_POPPLER_CALL_ARGS_DEREF(obj5, obj4.dictLookup, "I").isBool()) {
+ isolated = obj5.getBool();
+ }
+ _POPPLER_FREE(obj5);
+ if (_POPPLER_CALL_ARGS_DEREF(obj5, obj4.dictLookup, "K").isBool()) {
+ knockout = obj5.getBool();
+ }
+ _POPPLER_FREE(obj5);
+ if (!haveBackdropColor) {
+ if (blendingColorSpace) {
+ blendingColorSpace->getDefaultColor(&backdropColor);
+ } else {
+ //~ need to get the parent or default color space (?)
+ for (int & i : backdropColor.c) {
+ i = 0;
+ }
+ }
+ }
+ doSoftMask(&obj3, alpha, blendingColorSpace,
+ isolated, knockout, funcs[0], &backdropColor);
+ if (funcs[0]) {
+ delete funcs[0];
+ }
+ } else {
+ error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState - missing group");
+ }
+ _POPPLER_FREE(obj4);
+ } else {
+ error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState - missing group");
+ }
+ _POPPLER_FREE(obj3);
+ } else if (!obj2.isNull()) {
+ error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState");
+ }
+ }
+ _POPPLER_FREE(obj2);
+
+ _POPPLER_FREE(obj1);
+}
+
+void PdfParser::doSoftMask(Object *str, GBool alpha,
+ GfxColorSpace *blendingColorSpace,
+ GBool isolated, GBool knockout,
+ Function *transferFunc, GfxColor *backdropColor) {
+ Dict *dict, *resDict;
+ double m[6], bbox[4];
+ Object obj1, obj2;
+ int i;
+
+ // check for excessive recursion
+ if (formDepth > 20) {
+ return;
+ }
+
+ // get stream dict
+ dict = str->streamGetDict();
+
+ // check form type
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "FormType");
+ if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) {
+ error(errSyntaxError, getPos(), "Unknown form type");
+ }
+ _POPPLER_FREE(obj1);
+
+ // get bounding box
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "BBox");
+ if (!obj1.isArray()) {
+ _POPPLER_FREE(obj1);
+ error(errSyntaxError, getPos(), "Bad form bounding box");
+ return;
+ }
+ for (i = 0; i < 4; ++i) {
+ _POPPLER_CALL_ARGS(obj2, obj1.arrayGet, i);
+ bbox[i] = obj2.getNum();
+ _POPPLER_FREE(obj2);
+ }
+ _POPPLER_FREE(obj1);
+
+ // get matrix
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "Matrix");
+ if (obj1.isArray()) {
+ for (i = 0; i < 6; ++i) {
+ _POPPLER_CALL_ARGS(obj2, obj1.arrayGet, i);
+ m[i] = obj2.getNum();
+ _POPPLER_FREE(obj2);
+ }
+ } else {
+ m[0] = 1; m[1] = 0;
+ m[2] = 0; m[3] = 1;
+ m[4] = 0; m[5] = 0;
+ }
+ _POPPLER_FREE(obj1);
+
+ // get resources
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "Resources");
+ resDict = obj1.isDict() ? obj1.getDict() : (Dict *)nullptr;
+
+ // draw it
+ ++formDepth;
+ doForm1(str, resDict, m, bbox, gTrue, gTrue,
+ blendingColorSpace, isolated, knockout,
+ alpha, transferFunc, backdropColor);
+ --formDepth;
+
+ if (blendingColorSpace) {
+ delete blendingColorSpace;
+ }
+ _POPPLER_FREE(obj1);
+}
+
+void PdfParser::opSetRenderingIntent(Object /*args*/[], int /*numArgs*/)
+{
+}
+
+//------------------------------------------------------------------------
+// color operators
+//------------------------------------------------------------------------
+
+/**
+ * Get a newly allocated color space instance by CS operation argument.
+ *
+ * Maintains a cache for named color spaces to avoid expensive re-parsing.
+ */
+GfxColorSpace *PdfParser::lookupColorSpaceCopy(Object &arg)
+{
+ assert(!arg.isNull());
+ GfxColorSpace *colorSpace = nullptr;
+
+ if (char const *name = arg.isName() ? arg.getName() : nullptr) {
+ auto cache_name = std::to_string(formDepth) + "-" + std::string(name);
+ if (colorSpace = colorSpacesCache[cache_name].get()) {
+ return colorSpace->copy();
+ }
+
+ Object obj = res->lookupColorSpace(name);
+ if (obj.isNull()) {
+ colorSpace = GfxColorSpace::parse(res, &arg, nullptr, state);
+ } else {
+ colorSpace = GfxColorSpace::parse(res, &obj, nullptr, state);
+ }
+
+ if (colorSpace && colorSpace->getMode() != csPattern) {
+ colorSpacesCache[cache_name].reset(colorSpace->copy());
+ }
+ } else {
+ // We were passed in an object directly.
+ colorSpace = GfxColorSpace::parse(res, &arg, nullptr, state);
+ }
+ return colorSpace;
+}
+
+/**
+ * Look up pattern/gradients from the GfxResource dictionary
+ */
+GfxPattern *PdfParser::lookupPattern(Object *obj, GfxState *state)
+{
+ if (!obj->isName())
+ return nullptr;
+ return res->lookupPattern(obj->getName(), nullptr, state);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetFillGray(Object args[], int /*numArgs*/)
+{
+ GfxColor color;
+
+ state->setFillPattern(nullptr);
+ state->setFillColorSpace(new GfxDeviceGrayColorSpace());
+ color.c[0] = dblToCol(args[0].getNum());
+ state->setFillColor(&color);
+ builder->updateStyle(state);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetStrokeGray(Object args[], int /*numArgs*/)
+{
+ GfxColor color;
+
+ state->setStrokePattern(nullptr);
+ state->setStrokeColorSpace(new GfxDeviceGrayColorSpace());
+ color.c[0] = dblToCol(args[0].getNum());
+ state->setStrokeColor(&color);
+ builder->updateStyle(state);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetFillCMYKColor(Object args[], int /*numArgs*/)
+{
+ GfxColor color;
+ int i;
+
+ state->setFillPattern(nullptr);
+ state->setFillColorSpace(new GfxDeviceCMYKColorSpace());
+ for (i = 0; i < 4; ++i) {
+ color.c[i] = dblToCol(args[i].getNum());
+ }
+ state->setFillColor(&color);
+ builder->updateStyle(state);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetStrokeCMYKColor(Object args[], int /*numArgs*/)
+{
+ GfxColor color;
+
+ state->setStrokePattern(nullptr);
+ state->setStrokeColorSpace(new GfxDeviceCMYKColorSpace());
+ for (int i = 0; i < 4; ++i) {
+ color.c[i] = dblToCol(args[i].getNum());
+ }
+ state->setStrokeColor(&color);
+ builder->updateStyle(state);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetFillRGBColor(Object args[], int /*numArgs*/)
+{
+ GfxColor color;
+
+ state->setFillPattern(nullptr);
+ state->setFillColorSpace(new GfxDeviceRGBColorSpace());
+ for (int i = 0; i < 3; ++i) {
+ color.c[i] = dblToCol(args[i].getNum());
+ }
+ state->setFillColor(&color);
+ builder->updateStyle(state);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetStrokeRGBColor(Object args[], int /*numArgs*/) {
+ GfxColor color;
+
+ state->setStrokePattern(nullptr);
+ state->setStrokeColorSpace(new GfxDeviceRGBColorSpace());
+ for (int i = 0; i < 3; ++i) {
+ color.c[i] = dblToCol(args[i].getNum());
+ }
+ state->setStrokeColor(&color);
+ builder->updateStyle(state);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetFillColorSpace(Object args[], int numArgs)
+{
+ assert(numArgs >= 1);
+ GfxColorSpace *colorSpace = lookupColorSpaceCopy(args[0]);
+
+ state->setFillPattern(nullptr);
+
+ if (colorSpace) {
+ GfxColor color;
+ state->setFillColorSpace(colorSpace);
+ colorSpace->getDefaultColor(&color);
+ state->setFillColor(&color);
+ builder->updateStyle(state);
+ } else {
+ error(errSyntaxError, getPos(), "Bad color space (fill)");
+ }
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetStrokeColorSpace(Object args[], int numArgs)
+{
+ assert(numArgs >= 1);
+ GfxColorSpace *colorSpace = lookupColorSpaceCopy(args[0]);
+
+ state->setStrokePattern(nullptr);
+
+ if (colorSpace) {
+ GfxColor color;
+ state->setStrokeColorSpace(colorSpace);
+ colorSpace->getDefaultColor(&color);
+ state->setStrokeColor(&color);
+ builder->updateStyle(state);
+ } else {
+ error(errSyntaxError, getPos(), "Bad color space (stroke)");
+ }
+}
+
+void PdfParser::opSetFillColor(Object args[], int numArgs) {
+ GfxColor color;
+ int i;
+
+ if (numArgs != state->getFillColorSpace()->getNComps()) {
+ error(errSyntaxError, getPos(), "Incorrect number of arguments in 'sc' command");
+ return;
+ }
+ state->setFillPattern(nullptr);
+ for (i = 0; i < numArgs; ++i) {
+ color.c[i] = dblToCol(args[i].getNum());
+ }
+ state->setFillColor(&color);
+ builder->updateStyle(state);
+}
+
+void PdfParser::opSetStrokeColor(Object args[], int numArgs) {
+ GfxColor color;
+ int i;
+
+ if (numArgs != state->getStrokeColorSpace()->getNComps()) {
+ error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SC' command");
+ return;
+ }
+ state->setStrokePattern(nullptr);
+ for (i = 0; i < numArgs; ++i) {
+ color.c[i] = dblToCol(args[i].getNum());
+ }
+ state->setStrokeColor(&color);
+ builder->updateStyle(state);
+}
+
+void PdfParser::opSetFillColorN(Object args[], int numArgs) {
+ GfxColor color;
+ int i;
+
+ if (state->getFillColorSpace()->getMode() == csPattern) {
+ if (numArgs > 1) {
+ if (!((GfxPatternColorSpace *)state->getFillColorSpace())->getUnder() ||
+ numArgs - 1 != ((GfxPatternColorSpace *)state->getFillColorSpace())
+ ->getUnder()->getNComps()) {
+ error(errSyntaxError, getPos(), "Incorrect number of arguments in 'scn' command");
+ return;
+ }
+ for (i = 0; i < numArgs - 1 && i < gfxColorMaxComps; ++i) {
+ if (args[i].isNum()) {
+ color.c[i] = dblToCol(args[i].getNum());
+ }
+ }
+ state->setFillColor(&color);
+ builder->updateStyle(state);
+ }
+ if (auto pattern = lookupPattern(&(args[numArgs - 1]), state)) {
+ state->setFillPattern(pattern);
+ builder->updateStyle(state);
+ }
+
+ } else {
+ if (numArgs != state->getFillColorSpace()->getNComps()) {
+ error(errSyntaxError, getPos(), "Incorrect number of arguments in 'scn' command");
+ return;
+ }
+ state->setFillPattern(nullptr);
+ for (i = 0; i < numArgs && i < gfxColorMaxComps; ++i) {
+ if (args[i].isNum()) {
+ color.c[i] = dblToCol(args[i].getNum());
+ }
+ }
+ state->setFillColor(&color);
+ builder->updateStyle(state);
+ }
+}
+
+void PdfParser::opSetStrokeColorN(Object args[], int numArgs) {
+ GfxColor color;
+ int i;
+
+ if (state->getStrokeColorSpace()->getMode() == csPattern) {
+ if (numArgs > 1) {
+ if (!((GfxPatternColorSpace *)state->getStrokeColorSpace())
+ ->getUnder() ||
+ numArgs - 1 != ((GfxPatternColorSpace *)state->getStrokeColorSpace())
+ ->getUnder()->getNComps()) {
+ error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SCN' command");
+ return;
+ }
+ for (i = 0; i < numArgs - 1 && i < gfxColorMaxComps; ++i) {
+ if (args[i].isNum()) {
+ color.c[i] = dblToCol(args[i].getNum());
+ }
+ }
+ state->setStrokeColor(&color);
+ builder->updateStyle(state);
+ }
+ if (auto pattern = lookupPattern(&(args[numArgs - 1]), state)) {
+ state->setStrokePattern(pattern);
+ builder->updateStyle(state);
+ }
+
+ } else {
+ if (numArgs != state->getStrokeColorSpace()->getNComps()) {
+ error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SCN' command");
+ return;
+ }
+ state->setStrokePattern(nullptr);
+ for (i = 0; i < numArgs && i < gfxColorMaxComps; ++i) {
+ if (args[i].isNum()) {
+ color.c[i] = dblToCol(args[i].getNum());
+ }
+ }
+ state->setStrokeColor(&color);
+ builder->updateStyle(state);
+ }
+}
+
+//------------------------------------------------------------------------
+// path segment operators
+//------------------------------------------------------------------------
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opMoveTo(Object args[], int /*numArgs*/)
+{
+ state->moveTo(args[0].getNum(), args[1].getNum());
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opLineTo(Object args[], int /*numArgs*/)
+{
+ if (!state->isCurPt()) {
+ error(errSyntaxError, getPos(), "No current point in lineto");
+ return;
+ }
+ state->lineTo(args[0].getNum(), args[1].getNum());
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opCurveTo(Object args[], int /*numArgs*/)
+{
+ if (!state->isCurPt()) {
+ error(errSyntaxError, getPos(), "No current point in curveto");
+ return;
+ }
+ double x1 = args[0].getNum();
+ double y1 = args[1].getNum();
+ double x2 = args[2].getNum();
+ double y2 = args[3].getNum();
+ double x3 = args[4].getNum();
+ double y3 = args[5].getNum();
+ state->curveTo(x1, y1, x2, y2, x3, y3);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opCurveTo1(Object args[], int /*numArgs*/)
+{
+ if (!state->isCurPt()) {
+ error(errSyntaxError, getPos(), "No current point in curveto1");
+ return;
+ }
+ double x1 = state->getCurX();
+ double y1 = state->getCurY();
+ double x2 = args[0].getNum();
+ double y2 = args[1].getNum();
+ double x3 = args[2].getNum();
+ double y3 = args[3].getNum();
+ state->curveTo(x1, y1, x2, y2, x3, y3);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opCurveTo2(Object args[], int /*numArgs*/)
+{
+ if (!state->isCurPt()) {
+ error(errSyntaxError, getPos(), "No current point in curveto2");
+ return;
+ }
+ double x1 = args[0].getNum();
+ double y1 = args[1].getNum();
+ double x2 = args[2].getNum();
+ double y2 = args[3].getNum();
+ double x3 = x2;
+ double y3 = y2;
+ state->curveTo(x1, y1, x2, y2, x3, y3);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opRectangle(Object args[], int /*numArgs*/)
+{
+ double x = args[0].getNum();
+ double y = args[1].getNum();
+ double w = args[2].getNum();
+ double h = args[3].getNum();
+ state->moveTo(x, y);
+ state->lineTo(x + w, y);
+ state->lineTo(x + w, y + h);
+ state->lineTo(x, y + h);
+ state->closePath();
+}
+
+void PdfParser::opClosePath(Object /*args*/[], int /*numArgs*/)
+{
+ if (!state->isCurPt()) {
+ error(errSyntaxError, getPos(), "No current point in closepath");
+ return;
+ }
+ state->closePath();
+}
+
+//------------------------------------------------------------------------
+// path painting operators
+//------------------------------------------------------------------------
+
+void PdfParser::opEndPath(Object /*args*/[], int /*numArgs*/)
+{
+ doEndPath();
+}
+
+void PdfParser::opStroke(Object /*args*/[], int /*numArgs*/)
+{
+ if (!state->isCurPt()) {
+ //error(getPos(), const_cast<char*>("No path in stroke"));
+ return;
+ }
+ if (state->isPath()) {
+ if (state->getStrokeColorSpace()->getMode() == csPattern &&
+ !builder->isPatternTypeSupported(state->getStrokePattern())) {
+ doPatternStrokeFallback();
+ } else {
+ builder->addPath(state, false, true);
+ }
+ }
+ doEndPath();
+}
+
+void PdfParser::opCloseStroke(Object * /*args[]*/, int /*numArgs*/) {
+ if (!state->isCurPt()) {
+ //error(getPos(), const_cast<char*>("No path in closepath/stroke"));
+ return;
+ }
+ state->closePath();
+ if (state->isPath()) {
+ if (state->getStrokeColorSpace()->getMode() == csPattern &&
+ !builder->isPatternTypeSupported(state->getStrokePattern())) {
+ doPatternStrokeFallback();
+ } else {
+ builder->addPath(state, false, true);
+ }
+ }
+ doEndPath();
+}
+
+void PdfParser::opFill(Object /*args*/[], int /*numArgs*/)
+{
+ if (!state->isCurPt()) {
+ //error(getPos(), const_cast<char*>("No path in fill"));
+ return;
+ }
+ if (state->isPath()) {
+ if (state->getFillColorSpace()->getMode() == csPattern &&
+ !builder->isPatternTypeSupported(state->getFillPattern())) {
+ doPatternFillFallback(gFalse);
+ } else {
+ builder->addPath(state, true, false);
+ }
+ }
+ doEndPath();
+}
+
+void PdfParser::opEOFill(Object /*args*/[], int /*numArgs*/)
+{
+ if (!state->isCurPt()) {
+ //error(getPos(), const_cast<char*>("No path in eofill"));
+ return;
+ }
+ if (state->isPath()) {
+ if (state->getFillColorSpace()->getMode() == csPattern &&
+ !builder->isPatternTypeSupported(state->getFillPattern())) {
+ doPatternFillFallback(gTrue);
+ } else {
+ builder->addPath(state, true, false, true);
+ }
+ }
+ doEndPath();
+}
+
+void PdfParser::opFillStroke(Object /*args*/[], int /*numArgs*/)
+{
+ if (!state->isCurPt()) {
+ //error(getPos(), const_cast<char*>("No path in fill/stroke"));
+ return;
+ }
+ if (state->isPath()) {
+ doFillAndStroke(gFalse);
+ } else {
+ builder->addPath(state, true, true);
+ }
+ doEndPath();
+}
+
+void PdfParser::opCloseFillStroke(Object /*args*/[], int /*numArgs*/)
+{
+ if (!state->isCurPt()) {
+ //error(getPos(), const_cast<char*>("No path in closepath/fill/stroke"));
+ return;
+ }
+ if (state->isPath()) {
+ state->closePath();
+ doFillAndStroke(gFalse);
+ }
+ doEndPath();
+}
+
+void PdfParser::opEOFillStroke(Object /*args*/[], int /*numArgs*/)
+{
+ if (!state->isCurPt()) {
+ //error(getPos(), const_cast<char*>("No path in eofill/stroke"));
+ return;
+ }
+ if (state->isPath()) {
+ doFillAndStroke(gTrue);
+ }
+ doEndPath();
+}
+
+void PdfParser::opCloseEOFillStroke(Object /*args*/[], int /*numArgs*/)
+{
+ if (!state->isCurPt()) {
+ //error(getPos(), const_cast<char*>("No path in closepath/eofill/stroke"));
+ return;
+ }
+ if (state->isPath()) {
+ state->closePath();
+ doFillAndStroke(gTrue);
+ }
+ doEndPath();
+}
+
+void PdfParser::doFillAndStroke(GBool eoFill) {
+ GBool fillOk = gTrue, strokeOk = gTrue;
+ if (state->getFillColorSpace()->getMode() == csPattern &&
+ !builder->isPatternTypeSupported(state->getFillPattern())) {
+ fillOk = gFalse;
+ }
+ if (state->getStrokeColorSpace()->getMode() == csPattern &&
+ !builder->isPatternTypeSupported(state->getStrokePattern())) {
+ strokeOk = gFalse;
+ }
+ if (fillOk && strokeOk) {
+ builder->addPath(state, true, true, eoFill);
+ } else {
+ doPatternFillFallback(eoFill);
+ doPatternStrokeFallback();
+ }
+}
+
+void PdfParser::doPatternFillFallback(GBool eoFill) {
+ GfxPattern *pattern;
+
+ if (!(pattern = state->getFillPattern())) {
+ return;
+ }
+ switch (pattern->getType()) {
+ case 1:
+ break;
+ case 2:
+ doShadingPatternFillFallback(static_cast<GfxShadingPattern *>(pattern), gFalse, eoFill);
+ break;
+ default:
+ error(errUnimplemented, getPos(), "Unimplemented pattern type (%d) in fill",
+ pattern->getType());
+ break;
+ }
+}
+
+void PdfParser::doPatternStrokeFallback() {
+ GfxPattern *pattern;
+
+ if (!(pattern = state->getStrokePattern())) {
+ return;
+ }
+ switch (pattern->getType()) {
+ case 1:
+ break;
+ case 2:
+ doShadingPatternFillFallback(static_cast<GfxShadingPattern *>(pattern), gTrue, gFalse);
+ break;
+ default:
+ error(errUnimplemented, getPos(), "Unimplemented pattern type ({0:d}) in stroke",
+ pattern->getType());
+ break;
+ }
+}
+
+void PdfParser::doShadingPatternFillFallback(GfxShadingPattern *sPat,
+ GBool stroke, GBool eoFill) {
+ GfxShading *shading;
+ GfxPath *savedPath;
+
+ shading = sPat->getShading();
+
+ // save current graphics state
+ savedPath = state->getPath()->copy();
+ saveState();
+
+ // clip to bbox
+ /*if (false ){//shading->getHasBBox()) {
+ double xMin, yMin, xMax, yMax;
+ shading->getBBox(&xMin, &yMin, &xMax, &yMax);
+ state->moveTo(xMin, yMin);
+ state->lineTo(xMax, yMin);
+ state->lineTo(xMax, yMax);
+ state->lineTo(xMin, yMax);
+ state->closePath();
+ state->clip();
+ state->setPath(savedPath->copy());
+ }*/
+
+ // clip to current path
+ if (stroke) {
+ state->clipToStrokePath();
+ } else {
+ state->clip();
+ // XXX WARNING WE HAVE REMOVED THE SET CLIP
+ /*if (eoFill) {
+ builder->setClipPath(state, true);
+ } else {
+ builder->setClipPath(state);
+ }*/
+ }
+
+ // set the color space
+ state->setFillColorSpace(shading->getColorSpace()->copy());
+
+ // background color fill
+ if (shading->getHasBackground()) {
+ state->setFillColor(shading->getBackground());
+ builder->addPath(state, true, false);
+ }
+ state->clearPath();
+
+ // construct a (pattern space) -> (current space) transform matrix
+ auto ptr = ctmToAffine(sPat->getMatrix());
+ auto m = (ptr * baseMatrix) * stateToAffine(state).inverse();
+
+ // Set the new matrix
+ state->concatCTM(m[0], m[1], m[2], m[3], m[4], m[5]);
+
+ // do shading type-specific operations
+ switch (shading->getType()) {
+ case 1:
+ doFunctionShFill(static_cast<GfxFunctionShading *>(shading));
+ break;
+ case 2:
+ case 3:
+ // no need to implement these
+ break;
+ case 4:
+ case 5:
+ doGouraudTriangleShFill(static_cast<GfxGouraudTriangleShading *>(shading));
+ break;
+ case 6:
+ case 7:
+ doPatchMeshShFill(static_cast<GfxPatchMeshShading *>(shading));
+ break;
+ }
+
+ // restore graphics state
+ restoreState();
+ state->setPath(savedPath);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opShFill(Object args[], int /*numArgs*/)
+{
+ GfxShading *shading = nullptr;
+ GfxPath *savedPath = nullptr;
+ bool savedState = false;
+
+ if (!(shading = res->lookupShading(args[0].getName(), nullptr, state))) {
+ return;
+ }
+
+ // save current graphics state
+ if (shading->getType() != 2 && shading->getType() != 3) {
+ savedPath = state->getPath()->copy();
+ saveState();
+ savedState = true;
+ }
+
+ // clip to bbox
+ /*if (shading->getHasBBox()) {
+ double xMin, yMin, xMax, yMax;
+ shading->getBBox(&xMin, &yMin, &xMax, &yMax);
+ state->moveTo(xMin, yMin);
+ state->lineTo(xMax, yMin);
+ state->lineTo(xMax, yMax);
+ state->lineTo(xMin, yMax);
+ state->closePath();
+ state->clip();
+ builder->setClip(state);
+ state->clearPath();
+ }*/
+
+ // set the color space
+ if (savedState)
+ state->setFillColorSpace(shading->getColorSpace()->copy());
+
+ // do shading type-specific operations
+ switch (shading->getType()) {
+ case 1: // Function-based shading
+ doFunctionShFill(static_cast<GfxFunctionShading *>(shading));
+ break;
+ case 2: // Axial shading
+ case 3: // Radial shading
+ builder->addClippedFill(shading, stateToAffine(state));
+ break;
+ case 4: // Free-form Gouraud-shaded triangle mesh
+ case 5: // Lattice-form Gouraud-shaded triangle mesh
+ doGouraudTriangleShFill(static_cast<GfxGouraudTriangleShading *>(shading));
+ break;
+ case 6: // Coons patch mesh
+ case 7: // Tensor-product patch mesh
+ doPatchMeshShFill(static_cast<GfxPatchMeshShading *>(shading));
+ break;
+ }
+
+ // restore graphics state
+ if (savedState) {
+ restoreState();
+ state->setPath(savedPath);
+ }
+
+ delete shading;
+}
+
+void PdfParser::doFunctionShFill(GfxFunctionShading *shading) {
+ double x0, y0, x1, y1;
+ GfxColor colors[4];
+
+ shading->getDomain(&x0, &y0, &x1, &y1);
+ shading->getColor(x0, y0, &colors[0]);
+ shading->getColor(x0, y1, &colors[1]);
+ shading->getColor(x1, y0, &colors[2]);
+ shading->getColor(x1, y1, &colors[3]);
+ doFunctionShFill1(shading, x0, y0, x1, y1, colors, 0);
+}
+
+void PdfParser::doFunctionShFill1(GfxFunctionShading *shading,
+ double x0, double y0,
+ double x1, double y1,
+ GfxColor *colors, int depth) {
+ GfxColor fillColor;
+ GfxColor color0M, color1M, colorM0, colorM1, colorMM;
+ GfxColor colors2[4];
+ double functionColorDelta = colorDeltas[pdfFunctionShading-1];
+ const double *matrix;
+ double xM, yM;
+ int nComps, i, j;
+
+ nComps = shading->getColorSpace()->getNComps();
+ matrix = shading->getMatrix();
+
+ // compare the four corner colors
+ for (i = 0; i < 4; ++i) {
+ for (j = 0; j < nComps; ++j) {
+ if (abs(colors[i].c[j] - colors[(i+1)&3].c[j]) > functionColorDelta) {
+ break;
+ }
+ }
+ if (j < nComps) {
+ break;
+ }
+ }
+
+ // center of the rectangle
+ xM = 0.5 * (x0 + x1);
+ yM = 0.5 * (y0 + y1);
+
+ // the four corner colors are close (or we hit the recursive limit)
+ // -- fill the rectangle; but require at least one subdivision
+ // (depth==0) to avoid problems when the four outer corners of the
+ // shaded region are the same color
+ if ((i == 4 && depth > 0) || depth == maxDepths[pdfFunctionShading-1]) {
+
+ // use the center color
+ shading->getColor(xM, yM, &fillColor);
+ state->setFillColor(&fillColor);
+
+ // fill the rectangle
+ state->moveTo(x0 * matrix[0] + y0 * matrix[2] + matrix[4],
+ x0 * matrix[1] + y0 * matrix[3] + matrix[5]);
+ state->lineTo(x1 * matrix[0] + y0 * matrix[2] + matrix[4],
+ x1 * matrix[1] + y0 * matrix[3] + matrix[5]);
+ state->lineTo(x1 * matrix[0] + y1 * matrix[2] + matrix[4],
+ x1 * matrix[1] + y1 * matrix[3] + matrix[5]);
+ state->lineTo(x0 * matrix[0] + y1 * matrix[2] + matrix[4],
+ x0 * matrix[1] + y1 * matrix[3] + matrix[5]);
+ state->closePath();
+ builder->addPath(state, true, false);
+ state->clearPath();
+
+ // the four corner colors are not close enough -- subdivide the
+ // rectangle
+ } else {
+
+ // colors[0] colorM0 colors[2]
+ // (x0,y0) (xM,y0) (x1,y0)
+ // +----------+----------+
+ // | | |
+ // | UL | UR |
+ // color0M | colorMM | color1M
+ // (x0,yM) +----------+----------+ (x1,yM)
+ // | (xM,yM) |
+ // | LL | LR |
+ // | | |
+ // +----------+----------+
+ // colors[1] colorM1 colors[3]
+ // (x0,y1) (xM,y1) (x1,y1)
+
+ shading->getColor(x0, yM, &color0M);
+ shading->getColor(x1, yM, &color1M);
+ shading->getColor(xM, y0, &colorM0);
+ shading->getColor(xM, y1, &colorM1);
+ shading->getColor(xM, yM, &colorMM);
+
+ // upper-left sub-rectangle
+ colors2[0] = colors[0];
+ colors2[1] = color0M;
+ colors2[2] = colorM0;
+ colors2[3] = colorMM;
+ doFunctionShFill1(shading, x0, y0, xM, yM, colors2, depth + 1);
+
+ // lower-left sub-rectangle
+ colors2[0] = color0M;
+ colors2[1] = colors[1];
+ colors2[2] = colorMM;
+ colors2[3] = colorM1;
+ doFunctionShFill1(shading, x0, yM, xM, y1, colors2, depth + 1);
+
+ // upper-right sub-rectangle
+ colors2[0] = colorM0;
+ colors2[1] = colorMM;
+ colors2[2] = colors[2];
+ colors2[3] = color1M;
+ doFunctionShFill1(shading, xM, y0, x1, yM, colors2, depth + 1);
+
+ // lower-right sub-rectangle
+ colors2[0] = colorMM;
+ colors2[1] = colorM1;
+ colors2[2] = color1M;
+ colors2[3] = colors[3];
+ doFunctionShFill1(shading, xM, yM, x1, y1, colors2, depth + 1);
+ }
+}
+
+void PdfParser::doGouraudTriangleShFill(GfxGouraudTriangleShading *shading) {
+ double x0, y0, x1, y1, x2, y2;
+ GfxColor color0, color1, color2;
+ int i;
+
+ for (i = 0; i < shading->getNTriangles(); ++i) {
+ shading->getTriangle(i, &x0, &y0, &color0,
+ &x1, &y1, &color1,
+ &x2, &y2, &color2);
+ gouraudFillTriangle(x0, y0, &color0, x1, y1, &color1, x2, y2, &color2,
+ shading->getColorSpace()->getNComps(), 0);
+ }
+}
+
+void PdfParser::gouraudFillTriangle(double x0, double y0, GfxColor *color0,
+ double x1, double y1, GfxColor *color1,
+ double x2, double y2, GfxColor *color2,
+ int nComps, int depth) {
+ double x01, y01, x12, y12, x20, y20;
+ double gouraudColorDelta = colorDeltas[pdfGouraudTriangleShading-1];
+ GfxColor color01, color12, color20;
+ int i;
+
+ for (i = 0; i < nComps; ++i) {
+ if (abs(color0->c[i] - color1->c[i]) > gouraudColorDelta ||
+ abs(color1->c[i] - color2->c[i]) > gouraudColorDelta) {
+ break;
+ }
+ }
+ if (i == nComps || depth == maxDepths[pdfGouraudTriangleShading-1]) {
+ state->setFillColor(color0);
+ state->moveTo(x0, y0);
+ state->lineTo(x1, y1);
+ state->lineTo(x2, y2);
+ state->closePath();
+ builder->addPath(state, true, false);
+ state->clearPath();
+ } else {
+ x01 = 0.5 * (x0 + x1);
+ y01 = 0.5 * (y0 + y1);
+ x12 = 0.5 * (x1 + x2);
+ y12 = 0.5 * (y1 + y2);
+ x20 = 0.5 * (x2 + x0);
+ y20 = 0.5 * (y2 + y0);
+ //~ if the shading has a Function, this should interpolate on the
+ //~ function parameter, not on the color components
+ for (i = 0; i < nComps; ++i) {
+ color01.c[i] = (color0->c[i] + color1->c[i]) / 2;
+ color12.c[i] = (color1->c[i] + color2->c[i]) / 2;
+ color20.c[i] = (color2->c[i] + color0->c[i]) / 2;
+ }
+ gouraudFillTriangle(x0, y0, color0, x01, y01, &color01,
+ x20, y20, &color20, nComps, depth + 1);
+ gouraudFillTriangle(x01, y01, &color01, x1, y1, color1,
+ x12, y12, &color12, nComps, depth + 1);
+ gouraudFillTriangle(x01, y01, &color01, x12, y12, &color12,
+ x20, y20, &color20, nComps, depth + 1);
+ gouraudFillTriangle(x20, y20, &color20, x12, y12, &color12,
+ x2, y2, color2, nComps, depth + 1);
+ }
+}
+
+void PdfParser::doPatchMeshShFill(GfxPatchMeshShading *shading) {
+ int start, i;
+
+ if (shading->getNPatches() > 128) {
+ start = 3;
+ } else if (shading->getNPatches() > 64) {
+ start = 2;
+ } else if (shading->getNPatches() > 16) {
+ start = 1;
+ } else {
+ start = 0;
+ }
+ for (i = 0; i < shading->getNPatches(); ++i) {
+ fillPatch(shading->getPatch(i), shading->getColorSpace()->getNComps(),
+ start);
+ }
+}
+
+void PdfParser::fillPatch(_POPPLER_CONST GfxPatch *patch, int nComps, int depth) {
+ GfxPatch patch00 = blankPatch();
+ GfxPatch patch01 = blankPatch();
+ GfxPatch patch10 = blankPatch();
+ GfxPatch patch11 = blankPatch();
+ GfxColor color = {{0}};
+ double xx[4][8];
+ double yy[4][8];
+ double xxm;
+ double yym;
+ double patchColorDelta = colorDeltas[pdfPatchMeshShading - 1];
+
+ int i;
+
+ for (i = 0; i < nComps; ++i) {
+ if (std::abs(patch->color[0][0].c[i] - patch->color[0][1].c[i])
+ > patchColorDelta ||
+ std::abs(patch->color[0][1].c[i] - patch->color[1][1].c[i])
+ > patchColorDelta ||
+ std::abs(patch->color[1][1].c[i] - patch->color[1][0].c[i])
+ > patchColorDelta ||
+ std::abs(patch->color[1][0].c[i] - patch->color[0][0].c[i])
+ > patchColorDelta) {
+ break;
+ }
+ color.c[i] = GfxColorComp(patch->color[0][0].c[i]);
+ }
+ if (i == nComps || depth == maxDepths[pdfPatchMeshShading-1]) {
+ state->setFillColor(&color);
+ state->moveTo(patch->x[0][0], patch->y[0][0]);
+ state->curveTo(patch->x[0][1], patch->y[0][1],
+ patch->x[0][2], patch->y[0][2],
+ patch->x[0][3], patch->y[0][3]);
+ state->curveTo(patch->x[1][3], patch->y[1][3],
+ patch->x[2][3], patch->y[2][3],
+ patch->x[3][3], patch->y[3][3]);
+ state->curveTo(patch->x[3][2], patch->y[3][2],
+ patch->x[3][1], patch->y[3][1],
+ patch->x[3][0], patch->y[3][0]);
+ state->curveTo(patch->x[2][0], patch->y[2][0],
+ patch->x[1][0], patch->y[1][0],
+ patch->x[0][0], patch->y[0][0]);
+ state->closePath();
+ builder->addPath(state, true, false);
+ state->clearPath();
+ } else {
+ for (i = 0; i < 4; ++i) {
+ xx[i][0] = patch->x[i][0];
+ yy[i][0] = patch->y[i][0];
+ xx[i][1] = 0.5 * (patch->x[i][0] + patch->x[i][1]);
+ yy[i][1] = 0.5 * (patch->y[i][0] + patch->y[i][1]);
+ xxm = 0.5 * (patch->x[i][1] + patch->x[i][2]);
+ yym = 0.5 * (patch->y[i][1] + patch->y[i][2]);
+ xx[i][6] = 0.5 * (patch->x[i][2] + patch->x[i][3]);
+ yy[i][6] = 0.5 * (patch->y[i][2] + patch->y[i][3]);
+ xx[i][2] = 0.5 * (xx[i][1] + xxm);
+ yy[i][2] = 0.5 * (yy[i][1] + yym);
+ xx[i][5] = 0.5 * (xxm + xx[i][6]);
+ yy[i][5] = 0.5 * (yym + yy[i][6]);
+ xx[i][3] = xx[i][4] = 0.5 * (xx[i][2] + xx[i][5]);
+ yy[i][3] = yy[i][4] = 0.5 * (yy[i][2] + yy[i][5]);
+ xx[i][7] = patch->x[i][3];
+ yy[i][7] = patch->y[i][3];
+ }
+ for (i = 0; i < 4; ++i) {
+ patch00.x[0][i] = xx[0][i];
+ patch00.y[0][i] = yy[0][i];
+ patch00.x[1][i] = 0.5 * (xx[0][i] + xx[1][i]);
+ patch00.y[1][i] = 0.5 * (yy[0][i] + yy[1][i]);
+ xxm = 0.5 * (xx[1][i] + xx[2][i]);
+ yym = 0.5 * (yy[1][i] + yy[2][i]);
+ patch10.x[2][i] = 0.5 * (xx[2][i] + xx[3][i]);
+ patch10.y[2][i] = 0.5 * (yy[2][i] + yy[3][i]);
+ patch00.x[2][i] = 0.5 * (patch00.x[1][i] + xxm);
+ patch00.y[2][i] = 0.5 * (patch00.y[1][i] + yym);
+ patch10.x[1][i] = 0.5 * (xxm + patch10.x[2][i]);
+ patch10.y[1][i] = 0.5 * (yym + patch10.y[2][i]);
+ patch00.x[3][i] = 0.5 * (patch00.x[2][i] + patch10.x[1][i]);
+ patch00.y[3][i] = 0.5 * (patch00.y[2][i] + patch10.y[1][i]);
+ patch10.x[0][i] = patch00.x[3][i];
+ patch10.y[0][i] = patch00.y[3][i];
+ patch10.x[3][i] = xx[3][i];
+ patch10.y[3][i] = yy[3][i];
+ }
+ for (i = 4; i < 8; ++i) {
+ patch01.x[0][i-4] = xx[0][i];
+ patch01.y[0][i-4] = yy[0][i];
+ patch01.x[1][i-4] = 0.5 * (xx[0][i] + xx[1][i]);
+ patch01.y[1][i-4] = 0.5 * (yy[0][i] + yy[1][i]);
+ xxm = 0.5 * (xx[1][i] + xx[2][i]);
+ yym = 0.5 * (yy[1][i] + yy[2][i]);
+ patch11.x[2][i-4] = 0.5 * (xx[2][i] + xx[3][i]);
+ patch11.y[2][i-4] = 0.5 * (yy[2][i] + yy[3][i]);
+ patch01.x[2][i-4] = 0.5 * (patch01.x[1][i-4] + xxm);
+ patch01.y[2][i-4] = 0.5 * (patch01.y[1][i-4] + yym);
+ patch11.x[1][i-4] = 0.5 * (xxm + patch11.x[2][i-4]);
+ patch11.y[1][i-4] = 0.5 * (yym + patch11.y[2][i-4]);
+ patch01.x[3][i-4] = 0.5 * (patch01.x[2][i-4] + patch11.x[1][i-4]);
+ patch01.y[3][i-4] = 0.5 * (patch01.y[2][i-4] + patch11.y[1][i-4]);
+ patch11.x[0][i-4] = patch01.x[3][i-4];
+ patch11.y[0][i-4] = patch01.y[3][i-4];
+ patch11.x[3][i-4] = xx[3][i];
+ patch11.y[3][i-4] = yy[3][i];
+ }
+ //~ if the shading has a Function, this should interpolate on the
+ //~ function parameter, not on the color components
+ for (i = 0; i < nComps; ++i) {
+ patch00.color[0][0].c[i] = patch->color[0][0].c[i];
+ patch00.color[0][1].c[i] = (patch->color[0][0].c[i] +
+ patch->color[0][1].c[i]) / 2;
+ patch01.color[0][0].c[i] = patch00.color[0][1].c[i];
+ patch01.color[0][1].c[i] = patch->color[0][1].c[i];
+ patch01.color[1][1].c[i] = (patch->color[0][1].c[i] +
+ patch->color[1][1].c[i]) / 2;
+ patch11.color[0][1].c[i] = patch01.color[1][1].c[i];
+ patch11.color[1][1].c[i] = patch->color[1][1].c[i];
+ patch11.color[1][0].c[i] = (patch->color[1][1].c[i] +
+ patch->color[1][0].c[i]) / 2;
+ patch10.color[1][1].c[i] = patch11.color[1][0].c[i];
+ patch10.color[1][0].c[i] = patch->color[1][0].c[i];
+ patch10.color[0][0].c[i] = (patch->color[1][0].c[i] +
+ patch->color[0][0].c[i]) / 2;
+ patch00.color[1][0].c[i] = patch10.color[0][0].c[i];
+ patch00.color[1][1].c[i] = (patch00.color[1][0].c[i] +
+ patch01.color[1][1].c[i]) / 2;
+ patch01.color[1][0].c[i] = patch00.color[1][1].c[i];
+ patch11.color[0][0].c[i] = patch00.color[1][1].c[i];
+ patch10.color[0][1].c[i] = patch00.color[1][1].c[i];
+ }
+ fillPatch(&patch00, nComps, depth + 1);
+ fillPatch(&patch10, nComps, depth + 1);
+ fillPatch(&patch01, nComps, depth + 1);
+ fillPatch(&patch11, nComps, depth + 1);
+ }
+}
+
+void PdfParser::doEndPath() {
+ if (state->isCurPt() && clip != clipNone) {
+ state->clip();
+ builder->setClip(state, clip);
+ clip = clipNone;
+ }
+ state->clearPath();
+}
+
+//------------------------------------------------------------------------
+// path clipping operators
+//------------------------------------------------------------------------
+
+void PdfParser::opClip(Object /*args*/[], int /*numArgs*/)
+{
+ clip = clipNormal;
+}
+
+void PdfParser::opEOClip(Object /*args*/[], int /*numArgs*/)
+{
+ clip = clipEO;
+}
+
+//------------------------------------------------------------------------
+// text object operators
+//------------------------------------------------------------------------
+
+void PdfParser::opBeginText(Object /*args*/[], int /*numArgs*/)
+{
+ state->setTextMat(1, 0, 0, 1, 0, 0);
+ state->textMoveTo(0, 0);
+ builder->updateTextPosition(0.0, 0.0);
+ fontChanged = gTrue;
+ builder->beginTextObject(state);
+}
+
+void PdfParser::opEndText(Object /*args*/[], int /*numArgs*/)
+{
+ builder->endTextObject(state);
+}
+
+//------------------------------------------------------------------------
+// text state operators
+//------------------------------------------------------------------------
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetCharSpacing(Object args[], int /*numArgs*/)
+{
+ state->setCharSpace(args[0].getNum());
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetFont(Object args[], int /*numArgs*/)
+{
+ auto font = res->lookupFont(args[0].getName());
+
+ if (!font) {
+ // unsetting the font (drawing no text) is better than using the
+ // previous one and drawing random glyphs from it
+ state->setFont(nullptr, args[1].getNum());
+ fontChanged = gTrue;
+ return;
+ }
+ if (printCommands) {
+ printf(" font: tag=%s name='%s' %g\n",
+#if POPPLER_CHECK_VERSION(21,11,0)
+ font->getTag().c_str(),
+#else
+ font->getTag()->getCString(),
+#endif
+ font->getName() ? font->getName()->getCString() : "???",
+ args[1].getNum());
+ fflush(stdout);
+ }
+
+#if !POPPLER_CHECK_VERSION(22, 4, 0)
+ font->incRefCnt();
+#endif
+ state->setFont(font, args[1].getNum());
+ fontChanged = gTrue;
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetTextLeading(Object args[], int /*numArgs*/)
+{
+ state->setLeading(args[0].getNum());
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetTextRender(Object args[], int /*numArgs*/)
+{
+ state->setRender(args[0].getInt());
+ builder->updateStyle(state);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetTextRise(Object args[], int /*numArgs*/)
+{
+ state->setRise(args[0].getNum());
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetWordSpacing(Object args[], int /*numArgs*/)
+{
+ state->setWordSpace(args[0].getNum());
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetHorizScaling(Object args[], int /*numArgs*/)
+{
+ state->setHorizScaling(args[0].getNum());
+ builder->updateTextMatrix(state, !subPage);
+ fontChanged = gTrue;
+}
+
+//------------------------------------------------------------------------
+// text positioning operators
+//------------------------------------------------------------------------
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opTextMove(Object args[], int /*numArgs*/)
+{
+ double tx, ty;
+
+ tx = state->getLineX() + args[0].getNum();
+ ty = state->getLineY() + args[1].getNum();
+ state->textMoveTo(tx, ty);
+ builder->updateTextPosition(tx, ty);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opTextMoveSet(Object args[], int /*numArgs*/)
+{
+ double tx, ty;
+
+ tx = state->getLineX() + args[0].getNum();
+ ty = args[1].getNum();
+ state->setLeading(-ty);
+ ty += state->getLineY();
+ state->textMoveTo(tx, ty);
+ builder->updateTextPosition(tx, ty);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetTextMatrix(Object args[], int /*numArgs*/)
+{
+ state->setTextMat(args[0].getNum(), args[1].getNum(),
+ args[2].getNum(), args[3].getNum(),
+ args[4].getNum(), args[5].getNum());
+ state->textMoveTo(0, 0);
+ builder->updateTextMatrix(state, !subPage);
+ builder->updateTextPosition(0.0, 0.0);
+ fontChanged = gTrue;
+}
+
+void PdfParser::opTextNextLine(Object /*args*/[], int /*numArgs*/)
+{
+ double tx, ty;
+
+ tx = state->getLineX();
+ ty = state->getLineY() - state->getLeading();
+ state->textMoveTo(tx, ty);
+ builder->updateTextPosition(tx, ty);
+}
+
+//------------------------------------------------------------------------
+// text string operators
+//------------------------------------------------------------------------
+
+void PdfParser::doUpdateFont()
+{
+ if (fontChanged) {
+ auto font = getFontEngine()->getFont(state->getFont(), _pdf_doc.get(), true, xref);
+ builder->updateFont(state, font, !subPage);
+ fontChanged = false;
+ }
+}
+
+std::shared_ptr<CairoFontEngine> PdfParser::getFontEngine()
+{
+ // poppler/CairoOutputDev.cc claims the FT Library needs to be kept around
+ // for a while. It's unclear if this is sure for our case.
+ static FT_Library ft_lib;
+ static std::once_flag ft_lib_once_flag;
+ std::call_once(ft_lib_once_flag, FT_Init_FreeType, &ft_lib);
+ if (!_font_engine) {
+ // This will make a new font engine per form1, in the future we could
+ // share this between PdfParser instances for the same PDF file.
+ _font_engine = std::make_shared<CairoFontEngine>(ft_lib);
+ }
+ return _font_engine;
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opShowText(Object args[], int /*numArgs*/)
+{
+ if (!state->getFont()) {
+ error(errSyntaxError, getPos(), "No font in show");
+ return;
+ }
+ doUpdateFont();
+ doShowText(args[0].getString());
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opMoveShowText(Object args[], int /*numArgs*/)
+{
+ double tx = 0;
+ double ty = 0;
+
+ if (!state->getFont()) {
+ error(errSyntaxError, getPos(), "No font in move/show");
+ return;
+ }
+ doUpdateFont();
+ tx = state->getLineX();
+ ty = state->getLineY() - state->getLeading();
+ state->textMoveTo(tx, ty);
+ builder->updateTextPosition(tx, ty);
+ doShowText(args[0].getString());
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opMoveSetShowText(Object args[], int /*numArgs*/)
+{
+ double tx = 0;
+ double ty = 0;
+
+ if (!state->getFont()) {
+ error(errSyntaxError, getPos(), "No font in move/set/show");
+ return;
+ }
+ doUpdateFont();
+ state->setWordSpace(args[0].getNum());
+ state->setCharSpace(args[1].getNum());
+ tx = state->getLineX();
+ ty = state->getLineY() - state->getLeading();
+ state->textMoveTo(tx, ty);
+ builder->updateTextPosition(tx, ty);
+ doShowText(args[2].getString());
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opShowSpaceText(Object args[], int /*numArgs*/)
+{
+ Array *a = nullptr;
+ Object obj;
+ int wMode = 0;
+
+ if (!state->getFont()) {
+ error(errSyntaxError, getPos(), "No font in show/space");
+ return;
+ }
+ doUpdateFont();
+ wMode = state->getFont()->getWMode();
+ a = args[0].getArray();
+ for (int i = 0; i < a->getLength(); ++i) {
+ _POPPLER_CALL_ARGS(obj, a->get, i);
+ if (obj.isNum()) {
+ // this uses the absolute value of the font size to match
+ // Acrobat's behavior
+ if (wMode) {
+ state->textShift(0, -obj.getNum() * 0.001 *
+ fabs(state->getFontSize()));
+ } else {
+ state->textShift(-obj.getNum() * 0.001 *
+ fabs(state->getFontSize()), 0);
+ }
+ builder->updateTextShift(state, obj.getNum());
+ } else if (obj.isString()) {
+ doShowText(obj.getString());
+ } else {
+ error(errSyntaxError, getPos(), "Element of show/space array must be number or string");
+ }
+ _POPPLER_FREE(obj);
+ }
+}
+
+#if POPPLER_CHECK_VERSION(0,64,0)
+void PdfParser::doShowText(const GooString *s) {
+#else
+void PdfParser::doShowText(GooString *s) {
+#endif
+ int wMode;
+ double riseX, riseY;
+ CharCode code;
+ Unicode _POPPLER_CONST_82 *u = nullptr;
+ double dx, dy, tdx, tdy;
+ double originX, originY, tOriginX, tOriginY;
+ Object charProc;
+#if POPPLER_CHECK_VERSION(0,64,0)
+ const char *p;
+#else
+ char *p;
+#endif
+ int len, n, uLen;
+
+ auto font = state->getFont();
+ wMode = font->getWMode();
+
+ builder->beginString(state, s->getLength());
+
+ // handle a Type 3 char
+ if (font->getType() == fontType3) {
+ g_warning("PDF fontType3 information ignored.");
+ }
+
+ state->textTransformDelta(0, state->getRise(), &riseX, &riseY);
+ p = s->getCString();
+ len = s->getLength();
+ while (len > 0) {
+ /* TODO: This looks like a memory leak for u. */
+ n = font->getNextChar(p, len, &code, &u, &uLen, &dx, &dy, &originX, &originY);
+
+ if (wMode) {
+ dx *= state->getFontSize();
+ dy = dy * state->getFontSize() + state->getCharSpace();
+ if (n == 1 && *p == ' ') {
+ dy += state->getWordSpace();
+ }
+ } else {
+ dx = dx * state->getFontSize() + state->getCharSpace();
+ if (n == 1 && *p == ' ') {
+ dx += state->getWordSpace();
+ }
+ dx *= state->getHorizScaling();
+ dy *= state->getFontSize();
+ }
+ state->textTransformDelta(dx, dy, &tdx, &tdy);
+ originX *= state->getFontSize();
+ originY *= state->getFontSize();
+ state->textTransformDelta(originX, originY, &tOriginX, &tOriginY);
+ // In Gfx.cc this is drawChar(...)
+ builder->addChar(state, state->getCurX() + riseX, state->getCurY() + riseY, dx, dy, tOriginX, tOriginY, code, n,
+ u, uLen);
+ state->shift(tdx, tdy);
+ p += n;
+ len -= n;
+ }
+ builder->endString(state);
+}
+
+
+//------------------------------------------------------------------------
+// XObject operators
+//------------------------------------------------------------------------
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opXObject(Object args[], int /*numArgs*/)
+{
+ Object obj1, obj2, obj3, refObj;
+
+#if POPPLER_CHECK_VERSION(0,64,0)
+ const char *name = args[0].getName();
+#else
+ char *name = args[0].getName();
+#endif
+ _POPPLER_CALL_ARGS(obj1, res->lookupXObject, name);
+ if (obj1.isNull()) {
+ return;
+ }
+ if (!obj1.isStream()) {
+ error(errSyntaxError, getPos(), "XObject '{0:s}' is wrong type", name);
+ _POPPLER_FREE(obj1);
+ return;
+ }
+ _POPPLER_CALL_ARGS(obj2, obj1.streamGetDict()->lookup, "Subtype");
+ if (obj2.isName(const_cast<char*>("Image"))) {
+ _POPPLER_CALL_ARGS(refObj, res->lookupXObjectNF, name);
+ doImage(&refObj, obj1.getStream(), gFalse);
+ _POPPLER_FREE(refObj);
+ } else if (obj2.isName(const_cast<char*>("Form"))) {
+ doForm(&obj1);
+ } else if (obj2.isName(const_cast<char*>("PS"))) {
+ _POPPLER_CALL_ARGS(obj3, obj1.streamGetDict()->lookup, "Level1");
+/* out->psXObject(obj1.getStream(),
+ obj3.isStream() ? obj3.getStream() : (Stream *)NULL);*/
+ } else if (obj2.isName()) {
+ error(errSyntaxError, getPos(), "Unknown XObject subtype '{0:s}'", obj2.getName());
+ } else {
+ error(errSyntaxError, getPos(), "XObject subtype is missing or wrong type");
+ }
+ _POPPLER_FREE(obj2);
+ _POPPLER_FREE(obj1);
+}
+
+void PdfParser::doImage(Object * /*ref*/, Stream *str, GBool inlineImg)
+{
+ Dict *dict;
+ int width, height;
+ int bits;
+ GBool interpolate;
+ StreamColorSpaceMode csMode;
+ GBool mask;
+ GBool invert;
+ Object maskObj, smaskObj;
+ GBool haveColorKeyMask, haveExplicitMask, haveSoftMask;
+ GBool maskInvert;
+ GBool maskInterpolate;
+ Object obj1, obj2;
+
+ // get info from the stream
+ bits = 0;
+ csMode = streamCSNone;
+ str->getImageParams(&bits, &csMode);
+
+ // get stream dict
+ dict = str->getDict();
+
+ // get size
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "Width");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "W");
+ }
+ if (obj1.isInt()){
+ width = obj1.getInt();
+ }
+ else if (obj1.isReal()) {
+ width = (int)obj1.getReal();
+ }
+ else {
+ goto err2;
+ }
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "Height");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "H");
+ }
+ if (obj1.isInt()) {
+ height = obj1.getInt();
+ }
+ else if (obj1.isReal()){
+ height = static_cast<int>(obj1.getReal());
+ }
+ else {
+ goto err2;
+ }
+ _POPPLER_FREE(obj1);
+
+ // image interpolation
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "Interpolate");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "I");
+ }
+ if (obj1.isBool())
+ interpolate = obj1.getBool();
+ else
+ interpolate = gFalse;
+ _POPPLER_FREE(obj1);
+ maskInterpolate = gFalse;
+
+ // image or mask?
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "ImageMask");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "IM");
+ }
+ mask = gFalse;
+ if (obj1.isBool()) {
+ mask = obj1.getBool();
+ }
+ else if (!obj1.isNull()) {
+ goto err2;
+ }
+ _POPPLER_FREE(obj1);
+
+ // bit depth
+ if (bits == 0) {
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "BitsPerComponent");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "BPC");
+ }
+ if (obj1.isInt()) {
+ bits = obj1.getInt();
+ } else if (mask) {
+ bits = 1;
+ } else {
+ goto err2;
+ }
+ _POPPLER_FREE(obj1);
+ }
+
+ // display a mask
+ if (mask) {
+ // check for inverted mask
+ if (bits != 1) {
+ goto err1;
+ }
+ invert = gFalse;
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "Decode");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "D");
+ }
+ if (obj1.isArray()) {
+ _POPPLER_CALL_ARGS(obj2, obj1.arrayGet, 0);
+ if (obj2.isInt() && obj2.getInt() == 1) {
+ invert = gTrue;
+ }
+ _POPPLER_FREE(obj2);
+ } else if (!obj1.isNull()) {
+ goto err2;
+ }
+ _POPPLER_FREE(obj1);
+
+ // draw it
+ builder->addImageMask(state, str, width, height, invert, interpolate);
+
+ } else {
+ // get color space and color map
+ GfxColorSpace *colorSpace;
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "ColorSpace");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "CS");
+ }
+ if (!obj1.isNull()) {
+ colorSpace = lookupColorSpaceCopy(obj1);
+ } else if (csMode == streamCSDeviceGray) {
+ colorSpace = new GfxDeviceGrayColorSpace();
+ } else if (csMode == streamCSDeviceRGB) {
+ colorSpace = new GfxDeviceRGBColorSpace();
+ } else if (csMode == streamCSDeviceCMYK) {
+ colorSpace = new GfxDeviceCMYKColorSpace();
+ } else {
+ colorSpace = nullptr;
+ }
+ _POPPLER_FREE(obj1);
+ if (!colorSpace) {
+ goto err1;
+ }
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "Decode");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "D");
+ }
+ GfxImageColorMap *colorMap = new GfxImageColorMap(bits, &obj1, colorSpace);
+ _POPPLER_FREE(obj1);
+ if (!colorMap->isOk()) {
+ delete colorMap;
+ goto err1;
+ }
+
+ // get the mask
+ int maskColors[2*gfxColorMaxComps];
+ haveColorKeyMask = haveExplicitMask = haveSoftMask = gFalse;
+ Stream *maskStr = nullptr;
+ int maskWidth = 0;
+ int maskHeight = 0;
+ maskInvert = gFalse;
+ GfxImageColorMap *maskColorMap = nullptr;
+ _POPPLER_CALL_ARGS(maskObj, dict->lookup, "Mask");
+ _POPPLER_CALL_ARGS(smaskObj, dict->lookup, "SMask");
+ Dict* maskDict;
+ if (smaskObj.isStream()) {
+ // soft mask
+ if (inlineImg) {
+ goto err1;
+ }
+ maskStr = smaskObj.getStream();
+ maskDict = smaskObj.streamGetDict();
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Width");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "W");
+ }
+ if (!obj1.isInt()) {
+ goto err2;
+ }
+ maskWidth = obj1.getInt();
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Height");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "H");
+ }
+ if (!obj1.isInt()) {
+ goto err2;
+ }
+ maskHeight = obj1.getInt();
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "BitsPerComponent");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "BPC");
+ }
+ if (!obj1.isInt()) {
+ goto err2;
+ }
+ int maskBits = obj1.getInt();
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Interpolate");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "I");
+ }
+ if (obj1.isBool())
+ maskInterpolate = obj1.getBool();
+ else
+ maskInterpolate = gFalse;
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "ColorSpace");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "CS");
+ }
+ GfxColorSpace *maskColorSpace = lookupColorSpaceCopy(obj1);
+ _POPPLER_FREE(obj1);
+ if (!maskColorSpace || maskColorSpace->getMode() != csDeviceGray) {
+ goto err1;
+ }
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Decode");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "D");
+ }
+ maskColorMap = new GfxImageColorMap(maskBits, &obj1, maskColorSpace);
+ _POPPLER_FREE(obj1);
+ if (!maskColorMap->isOk()) {
+ delete maskColorMap;
+ goto err1;
+ }
+ //~ handle the Matte entry
+ haveSoftMask = gTrue;
+ } else if (maskObj.isArray()) {
+ // color key mask
+ int i;
+ for (i = 0; i < maskObj.arrayGetLength() && i < 2*gfxColorMaxComps; ++i) {
+ _POPPLER_CALL_ARGS(obj1, maskObj.arrayGet, i);
+ maskColors[i] = obj1.getInt();
+ _POPPLER_FREE(obj1);
+ }
+ haveColorKeyMask = gTrue;
+ } else if (maskObj.isStream()) {
+ // explicit mask
+ if (inlineImg) {
+ goto err1;
+ }
+ maskStr = maskObj.getStream();
+ maskDict = maskObj.streamGetDict();
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Width");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "W");
+ }
+ if (!obj1.isInt()) {
+ goto err2;
+ }
+ maskWidth = obj1.getInt();
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Height");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "H");
+ }
+ if (!obj1.isInt()) {
+ goto err2;
+ }
+ maskHeight = obj1.getInt();
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "ImageMask");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "IM");
+ }
+ if (!obj1.isBool() || !obj1.getBool()) {
+ goto err2;
+ }
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Interpolate");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "I");
+ }
+ if (obj1.isBool())
+ maskInterpolate = obj1.getBool();
+ else
+ maskInterpolate = gFalse;
+ _POPPLER_FREE(obj1);
+ maskInvert = gFalse;
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Decode");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "D");
+ }
+ if (obj1.isArray()) {
+ _POPPLER_CALL_ARGS(obj2, obj1.arrayGet, 0);
+ if (obj2.isInt() && obj2.getInt() == 1) {
+ maskInvert = gTrue;
+ }
+ _POPPLER_FREE(obj2);
+ } else if (!obj1.isNull()) {
+ goto err2;
+ }
+ _POPPLER_FREE(obj1);
+ haveExplicitMask = gTrue;
+ }
+
+ // draw it
+ if (haveSoftMask) {
+ builder->addSoftMaskedImage(state, str, width, height, colorMap, interpolate,
+ maskStr, maskWidth, maskHeight, maskColorMap, maskInterpolate);
+ delete maskColorMap;
+ } else if (haveExplicitMask) {
+ builder->addMaskedImage(state, str, width, height, colorMap, interpolate,
+ maskStr, maskWidth, maskHeight, maskInvert, maskInterpolate);
+ } else {
+ builder->addImage(state, str, width, height, colorMap, interpolate,
+ haveColorKeyMask ? maskColors : static_cast<int *>(nullptr));
+ }
+ delete colorMap;
+
+ _POPPLER_FREE(maskObj);
+ _POPPLER_FREE(smaskObj);
+ }
+
+ return;
+
+ err2:
+ _POPPLER_FREE(obj1);
+ err1:
+ error(errSyntaxError, getPos(), "Bad image parameters");
+}
+
+void PdfParser::doForm(Object *str) {
+ Dict *dict;
+ GBool transpGroup, isolated, knockout;
+ GfxColorSpace *blendingColorSpace;
+ Object matrixObj, bboxObj;
+ double m[6], bbox[4];
+ Object resObj;
+ Dict *resDict;
+ Object obj1, obj2, obj3;
+ int i;
+
+ // check for excessive recursion
+ if (formDepth > 20) {
+ return;
+ }
+
+ // get stream dict
+ dict = str->streamGetDict();
+
+ // check form type
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "FormType");
+ if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) {
+ error(errSyntaxError, getPos(), "Unknown form type");
+ }
+ _POPPLER_FREE(obj1);
+
+ // get bounding box
+ _POPPLER_CALL_ARGS(bboxObj, dict->lookup, "BBox");
+ if (!bboxObj.isArray()) {
+ _POPPLER_FREE(bboxObj);
+ error(errSyntaxError, getPos(), "Bad form bounding box");
+ return;
+ }
+ for (i = 0; i < 4; ++i) {
+ _POPPLER_CALL_ARGS(obj1, bboxObj.arrayGet, i);
+ bbox[i] = obj1.getNum();
+ _POPPLER_FREE(obj1);
+ }
+ _POPPLER_FREE(bboxObj);
+
+ // get matrix
+ _POPPLER_CALL_ARGS(matrixObj, dict->lookup, "Matrix");
+ if (matrixObj.isArray()) {
+ for (i = 0; i < 6; ++i) {
+ _POPPLER_CALL_ARGS(obj1, matrixObj.arrayGet, i);
+ m[i] = obj1.getNum();
+ _POPPLER_FREE(obj1);
+ }
+ } else {
+ m[0] = 1; m[1] = 0;
+ m[2] = 0; m[3] = 1;
+ m[4] = 0; m[5] = 0;
+ }
+ _POPPLER_FREE(matrixObj);
+
+ // get resources
+ _POPPLER_CALL_ARGS(resObj, dict->lookup, "Resources");
+ resDict = resObj.isDict() ? resObj.getDict() : (Dict *)nullptr;
+
+ // check for a transparency group
+ transpGroup = isolated = knockout = gFalse;
+ blendingColorSpace = nullptr;
+ if (_POPPLER_CALL_ARGS_DEREF(obj1, dict->lookup, "Group").isDict()) {
+ if (_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "S").isName("Transparency")) {
+ transpGroup = gTrue;
+ if (!_POPPLER_CALL_ARGS_DEREF(obj3, obj1.dictLookup, "CS").isNull()) {
+ blendingColorSpace = GfxColorSpace::parse(nullptr, &obj3, nullptr, state);
+ }
+ _POPPLER_FREE(obj3);
+ if (_POPPLER_CALL_ARGS_DEREF(obj3, obj1.dictLookup, "I").isBool()) {
+ isolated = obj3.getBool();
+ }
+ _POPPLER_FREE(obj3);
+ if (_POPPLER_CALL_ARGS_DEREF(obj3, obj1.dictLookup, "K").isBool()) {
+ knockout = obj3.getBool();
+ }
+ _POPPLER_FREE(obj3);
+ }
+ _POPPLER_FREE(obj2);
+ }
+ _POPPLER_FREE(obj1);
+
+ // draw it
+ ++formDepth;
+ doForm1(str, resDict, m, bbox,
+ transpGroup, gFalse, blendingColorSpace, isolated, knockout);
+ --formDepth;
+
+ if (blendingColorSpace) {
+ delete blendingColorSpace;
+ }
+ _POPPLER_FREE(resObj);
+}
+
+void PdfParser::doForm1(Object *str, Dict *resDict, double *matrix, double *bbox, GBool transpGroup, GBool softMask,
+ GfxColorSpace *blendingColorSpace, GBool isolated, GBool knockout, GBool alpha,
+ Function *transferFunc, GfxColor *backdropColor)
+{
+ Parser *oldParser;
+
+ // push new resources on stack
+ pushResources(resDict);
+
+ // Add a new container group before saving the state
+ builder->startGroup(state, bbox, blendingColorSpace, isolated, knockout, softMask);
+
+ // save current graphics state
+ saveState();
+
+ // kill any pre-existing path
+ state->clearPath();
+
+ // save current parser
+ oldParser = parser;
+
+ // set form transformation matrix
+ state->concatCTM(matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
+
+ // set form bounding box
+ state->moveTo(bbox[0], bbox[1]);
+ state->lineTo(bbox[2], bbox[1]);
+ state->lineTo(bbox[2], bbox[3]);
+ state->lineTo(bbox[0], bbox[3]);
+ state->closePath();
+ state->clip();
+ builder->setClip(state, clipNormal, true);
+ state->clearPath();
+
+ if (softMask || transpGroup) {
+ if (state->getBlendMode() != gfxBlendNormal) {
+ state->setBlendMode(gfxBlendNormal);
+ }
+ if (state->getFillOpacity() != 1) {
+ builder->setGroupOpacity(state->getFillOpacity());
+ state->setFillOpacity(1);
+ }
+ if (state->getStrokeOpacity() != 1) {
+ state->setStrokeOpacity(1);
+ }
+ }
+
+ // set new base matrix
+ auto oldBaseMatrix = baseMatrix;
+ baseMatrix = stateToAffine(state);
+
+ // draw the form
+ parse(str, gFalse);
+
+ // restore base matrix
+ baseMatrix = oldBaseMatrix;
+
+ // restore parser
+ parser = oldParser;
+
+ // restore graphics state
+ restoreState();
+
+ // pop resource stack
+ popResources();
+
+ // complete any masking
+ builder->finishGroup(state, softMask);
+}
+
+//------------------------------------------------------------------------
+// in-line image operators
+//------------------------------------------------------------------------
+
+void PdfParser::opBeginImage(Object /*args*/[], int /*numArgs*/)
+{
+ // build dict/stream
+ Stream *str = buildImageStream();
+
+ // display the image
+ if (str) {
+ doImage(nullptr, str, gTrue);
+
+ // skip 'EI' tag
+ int c1 = str->getUndecodedStream()->getChar();
+ int c2 = str->getUndecodedStream()->getChar();
+ while (!(c1 == 'E' && c2 == 'I') && c2 != EOF) {
+ c1 = c2;
+ c2 = str->getUndecodedStream()->getChar();
+ }
+ delete str;
+ }
+}
+
+Stream *PdfParser::buildImageStream() {
+ Object dict;
+ Object obj;
+ Stream *str;
+
+ // build dictionary
+#if defined(POPPLER_NEW_OBJECT_API)
+ dict = Object(new Dict(xref));
+#else
+ dict.initDict(xref);
+#endif
+ _POPPLER_CALL(obj, parser->getObj);
+ while (!obj.isCmd(const_cast<char*>("ID")) && !obj.isEOF()) {
+ if (!obj.isName()) {
+ error(errSyntaxError, getPos(), "Inline image dictionary key must be a name object");
+ _POPPLER_FREE(obj);
+ } else {
+ Object obj2;
+ _POPPLER_CALL(obj2, parser->getObj);
+ if (obj2.isEOF() || obj2.isError()) {
+ _POPPLER_FREE(obj);
+ break;
+ }
+ _POPPLER_DICTADD(dict, obj.getName(), obj2);
+ _POPPLER_FREE(obj);
+ _POPPLER_FREE(obj2);
+ }
+ _POPPLER_CALL(obj, parser->getObj);
+ }
+ if (obj.isEOF()) {
+ error(errSyntaxError, getPos(), "End of file in inline image");
+ _POPPLER_FREE(obj);
+ _POPPLER_FREE(dict);
+ return nullptr;
+ }
+ _POPPLER_FREE(obj);
+
+ // make stream
+#if defined(POPPLER_NEW_OBJECT_API)
+ str = new EmbedStream(parser->getStream(), dict.copy(), gFalse, 0);
+ str = str->addFilters(dict.getDict());
+#else
+ str = new EmbedStream(parser->getStream(), &dict, gFalse, 0);
+ str = str->addFilters(&dict);
+#endif
+
+ return str;
+}
+
+void PdfParser::opImageData(Object /*args*/[], int /*numArgs*/)
+{
+ error(errInternal, getPos(), "Internal: got 'ID' operator");
+}
+
+void PdfParser::opEndImage(Object /*args*/[], int /*numArgs*/)
+{
+ error(errInternal, getPos(), "Internal: got 'EI' operator");
+}
+
+//------------------------------------------------------------------------
+// type 3 font operators
+//------------------------------------------------------------------------
+
+void PdfParser::opSetCharWidth(Object /*args*/[], int /*numArgs*/)
+{
+}
+
+void PdfParser::opSetCacheDevice(Object /*args*/[], int /*numArgs*/)
+{
+}
+
+//------------------------------------------------------------------------
+// compatibility operators
+//------------------------------------------------------------------------
+
+void PdfParser::opBeginIgnoreUndef(Object /*args*/[], int /*numArgs*/)
+{
+ ++ignoreUndef;
+}
+
+void PdfParser::opEndIgnoreUndef(Object /*args*/[], int /*numArgs*/)
+{
+ if (ignoreUndef > 0)
+ --ignoreUndef;
+}
+
+//------------------------------------------------------------------------
+// marked content operators
+//------------------------------------------------------------------------
+
+void PdfParser::opBeginMarkedContent(Object args[], int numArgs) {
+ if (formDepth != 0)
+ return;
+ if (printCommands) {
+ printf(" marked content: %s ", args[0].getName());
+ if (numArgs == 2)
+ args[2].print(stdout);
+ printf("\n");
+ fflush(stdout);
+ }
+ if (numArgs == 2 && args[1].isName()) {
+ // Optional content (OC) to add objects to layer.
+ builder->beginMarkedContent(args[0].getName(), args[1].getName());
+ } else {
+ builder->beginMarkedContent();
+ }
+}
+
+void PdfParser::opEndMarkedContent(Object /*args*/[], int /*numArgs*/)
+{
+ if (formDepth == 0)
+ builder->endMarkedContent();
+}
+
+void PdfParser::opMarkPoint(Object args[], int numArgs) {
+ if (printCommands) {
+ printf(" mark point: %s ", args[0].getName());
+ if (numArgs == 2)
+ args[2].print(stdout);
+ printf("\n");
+ fflush(stdout);
+ }
+
+ if(numArgs == 2) {
+ //out->markPoint(args[0].getName(),args[1].getDict());
+ } else {
+ //out->markPoint(args[0].getName());
+ }
+
+}
+
+//------------------------------------------------------------------------
+// misc
+//------------------------------------------------------------------------
+
+void PdfParser::saveState() {
+ bool is_radial = false;
+ GfxPattern *pattern = state->getFillPattern();
+
+ if (pattern && pattern->getType() == 2) {
+ GfxShadingPattern *shading_pattern = static_cast<GfxShadingPattern *>(pattern);
+ GfxShading *shading = shading_pattern->getShading();
+ if (shading->getType() == 3)
+ is_radial = true;
+ }
+
+ if (is_radial)
+ state->save(); // nasty hack to prevent GfxRadialShading from getting corrupted during copy operation
+ else
+ state = state->save(); // see LP Bug 919176 comment 8
+ builder->saveState(state);
+}
+
+void PdfParser::restoreState() {
+ builder->restoreState(state);
+ state = state->restore();
+}
+
+void PdfParser::pushResources(Dict *resDict) {
+ res = new GfxResources(xref, resDict, res);
+}
+
+void PdfParser::popResources() {
+ GfxResources *resPtr;
+
+ resPtr = res->getNext();
+ delete res;
+ res = resPtr;
+}
+
+void PdfParser::setDefaultApproximationPrecision() {
+ for (int i = 1; i <= pdfNumShadingTypes; ++i) {
+ setApproximationPrecision(i, defaultShadingColorDelta, defaultShadingMaxDepth);
+ }
+}
+
+void PdfParser::setApproximationPrecision(int shadingType, double colorDelta,
+ int maxDepth) {
+
+ if (shadingType > pdfNumShadingTypes || shadingType < 1) {
+ return;
+ }
+ colorDeltas[shadingType-1] = dblToCol(colorDelta);
+ maxDepths[shadingType-1] = maxDepth;
+}
+
+/**
+ * Optional content groups are often used in ai files, but
+ * not always and can be useful ways of collecting objects.
+ */
+void PdfParser::loadOptionalContentLayers(Dict *resources)
+{
+ auto props = resources->lookup("Properties");
+ if (!props.isDict())
+ return;
+
+ auto cat = _pdf_doc->getCatalog();
+ auto ocgs = cat->getOptContentConfig();
+ auto dict = props.getDict();
+
+ for (auto j = 0; j < dict->getLength(); j++) {
+ auto val = dict->getVal(j);
+ if (!val.isDict())
+ continue;
+ auto dict2 = val.getDict();
+ if (dict2->lookup("Type").isName("OCG") && ocgs) {
+ std::string label = getDictString(dict2, "Name");
+ auto visible = true;
+ // Normally we'd use poppler optContentIsVisible, but these dict
+ // objects don't retain their references so can't be used directly.
+ for (auto &[ref, ocg] : ocgs->getOCGs()) {
+ if (ocg->getName()->cmp(label) == 0)
+ visible = ocg->getState() == OptionalContentGroup::On;
+ }
+ builder->addOptionalGroup(dict->getKey(j), label, visible);
+ }
+ }
+}
+
+/**
+ * Load the internal ICC profile from the PDF file.
+ */
+void PdfParser::loadColorProfile()
+{
+ Object catDict = xref->getCatalog();
+ if (!catDict.isDict())
+ return;
+
+ Object outputIntents = catDict.dictLookup("OutputIntents");
+ if (!outputIntents.isArray() || outputIntents.arrayGetLength() != 1)
+ return;
+
+ Object firstElement = outputIntents.arrayGet(0);
+ if (!firstElement.isDict())
+ return;
+
+ Object profile = firstElement.dictLookup("DestOutputProfile");
+ if (!profile.isStream())
+ return;
+
+ Stream *iccStream = profile.getStream();
+#if POPPLER_CHECK_VERSION(22, 4, 0)
+ std::vector<unsigned char> profBuf = iccStream->toUnsignedChars(65536, 65536);
+ builder->addColorProfile(profBuf.data(), profBuf.size());
+#else
+ int length = 0;
+ unsigned char *profBuf = iccStream->toUnsignedChars(&length, 65536, 65536);
+ builder->addColorProfile(profBuf, length);
+#endif
+}
+
+void PdfParser::debug_array(const Array *array, int depth, XRef *xref)
+{
+ if (depth > 20) {
+ std::cout << "[ ... ]";
+ return;
+ }
+ std::cout << "[\n";
+ for (int i = 0; i < array->getLength(); ++i) {
+ for (int x = depth; x > -1; x--)
+ std::cout << " ";
+ std::cout << i << ": ";
+ Object obj = array->get(i);
+ PdfParser::debug_object(&obj, depth + 1, xref);
+ std::cout << ",\n";
+ }
+ for (int x = depth; x > 0; x--)
+ std::cout << " ";
+ std::cout << "]";
+}
+
+void PdfParser::debug_dict(const Dict *dict, int depth, XRef *xref)
+{
+ if (depth > 20) {
+ std::cout << "{ ... }";
+ return;
+ }
+ std::cout << "{\n";
+ for (auto j = 0; j < dict->getLength(); j++) {
+ auto key = dict->getKey(j);
+ auto val = dict->getVal(j);
+ for (int x = depth; x > -1; x--)
+ std::cout << " ";
+ std::cout << key << ": ";
+ PdfParser::debug_object(&val, depth + 1, xref);
+ std::cout << ",\n";
+ }
+ for (int x = depth; x > 0; x--)
+ std::cout << " ";
+ std::cout << "}";
+}
+
+void PdfParser::debug_object(const Object *obj, int depth, XRef *xref)
+{
+ if (obj->isRef()) {
+ std::cout << " > REF(" << obj->getRef().num << "):";
+ if (xref) {
+ auto ref = obj->fetch(xref);
+ PdfParser::debug_object(&ref, depth + 1, xref);
+ }
+ } else if (obj->isDict()) {
+ PdfParser::debug_dict(obj->getDict(), depth, xref);
+ } else if (obj->isArray()) {
+ PdfParser::debug_array(obj->getArray(), depth, xref);
+ } else if (obj->isString()) {
+ std::cout << " STR '" << obj->getString()->getCString() << "'";
+ } else if (obj->isName()) {
+ std::cout << " NAME '" << obj->getName() << "'";
+ } else if (obj->isBool()) {
+ std::cout << " BOOL " << (obj->getBool() ? "true" : "false");
+ } else if (obj->isNum()) {
+ std::cout << " NUM " << obj->getNum();
+ } else {
+ std::cout << " > ? " << obj->getType() << "";
+ }
+}
+
+#endif /* HAVE_POPPLER */
diff --git a/src/extension/internal/pdfinput/pdf-parser.h b/src/extension/internal/pdfinput/pdf-parser.h
new file mode 100644
index 0000000..77c73f2
--- /dev/null
+++ b/src/extension/internal/pdfinput/pdf-parser.h
@@ -0,0 +1,339 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * PDF parsing using libpoppler.
+ *//*
+ * Authors:
+ * see git history
+ *
+ * Derived from Gfx.h from poppler (?) which derives from Xpdf, Copyright 1996-2003 Glyph & Cog, LLC, which is under GPL2+.
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef PDF_PARSER_H
+#define PDF_PARSER_H
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#ifdef HAVE_POPPLER
+#include "poppler-transition-api.h"
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+namespace Inkscape {
+ namespace Extension {
+ namespace Internal {
+ class SvgBuilder;
+ }
+ }
+}
+
+// TODO clean up and remove using:
+using Inkscape::Extension::Internal::SvgBuilder;
+
+#include "glib/poppler-features.h"
+#include "Object.h"
+
+#include <map>
+#include <memory>
+#include <string>
+
+#define Operator Operator_Gfx
+#include <Gfx.h>
+#undef Operator
+
+class PDFDoc;
+class Page;
+class GooString;
+class XRef;
+class Array;
+class Stream;
+class Parser;
+class Dict;
+class Function;
+class OutputDev;
+class GfxFont;
+class GfxPattern;
+class GfxTilingPattern;
+class GfxShadingPattern;
+class GfxShading;
+class GfxFunctionShading;
+class GfxAxialShading;
+class GfxRadialShading;
+class GfxGouraudTriangleShading;
+class GfxPatchMeshShading;
+struct GfxPatch;
+class GfxState;
+struct GfxColor;
+class GfxColorSpace;
+class Gfx;
+class GfxResources;
+class PDFRectangle;
+class AnnotBorderStyle;
+class CairoFontEngine;
+
+class PdfParser;
+
+//------------------------------------------------------------------------
+
+#define maxOperatorArgs 33
+
+struct PdfOperator {
+ char name[4];
+ int numArgs;
+ TchkType tchk[maxOperatorArgs];
+ void (PdfParser::*func)(Object args[], int numArgs);
+};
+
+#undef maxOperatorArgs
+
+struct OpHistoryEntry {
+ const char *name; // operator's name
+ GfxState *state; // saved state, NULL if none
+ GBool executed; // whether the operator has been executed
+
+ OpHistoryEntry *next; // next entry on stack
+ unsigned depth; // total number of entries descending from this
+};
+
+//------------------------------------------------------------------------
+// PdfParser
+//------------------------------------------------------------------------
+
+//------------------------------------------------------------------------
+// constants
+//------------------------------------------------------------------------
+
+#define pdfFunctionShading 1
+#define pdfAxialShading 2
+#define pdfRadialShading 3
+#define pdfGouraudTriangleShading 4
+#define pdfPatchMeshShading 5
+#define pdfNumShadingTypes 5
+
+/**
+ * PDF parsing module using libpoppler's facilities.
+ */
+class PdfParser {
+public:
+
+ // Constructor for regular output.
+ PdfParser(std::shared_ptr<PDFDoc> pdf_doc, SvgBuilder *builderA, Page *page, _POPPLER_CONST PDFRectangle *cropBox);
+ // Constructor for a sub-page object.
+ PdfParser(XRef *xrefA, SvgBuilder *builderA, Dict *resDict, _POPPLER_CONST PDFRectangle *box);
+
+ virtual ~PdfParser();
+
+ // Interpret a stream or array of streams.
+ void parse(Object *obj, GBool topLevel = gTrue);
+
+ // Save graphics state.
+ void saveState();
+
+ // Restore graphics state.
+ void restoreState();
+
+ // Get the current graphics state object.
+ GfxState *getState() { return state; }
+
+ // Set the precision of approximation for specific shading fills.
+ void setApproximationPrecision(int shadingType, double colorDelta, int maxDepth);
+ void loadOptionalContentLayers(Dict *resources);
+ void loadPatternColorProfiles(Dict *resources);
+ void loadColorProfile();
+ void loadColorSpaceProfile(GfxColorSpace *space, Object *obj);
+ GfxPattern *lookupPattern(Object *obj, GfxState *state);
+
+ static void debug_array(const Array *array, int depth = 0, XRef *xref = nullptr);
+ static void debug_dict(const Dict *dict, int depth = 0, XRef *xref = nullptr);
+ static void debug_object(const Object *obj, int depth = 0, XRef *xref = nullptr);
+
+ std::shared_ptr<CairoFontEngine> getFontEngine();
+private:
+ std::shared_ptr<PDFDoc> _pdf_doc;
+ std::shared_ptr<CairoFontEngine> _font_engine;
+
+ XRef *xref; // the xref table for this PDF file
+ SvgBuilder *builder; // SVG generator
+ GBool subPage; // is this a sub-page object?
+ GBool printCommands; // print the drawing commands (for debugging)
+ GfxResources *res; // resource stack
+
+ GfxState *state; // current graphics state
+ GBool fontChanged; // set if font or text matrix has changed
+ GfxClipType clip; // do a clip?
+ int ignoreUndef; // current BX/EX nesting level
+ Geom::Affine baseMatrix; // default matrix for most recent
+ // page/form/pattern
+ int formDepth;
+
+ Parser *parser; // parser for page content stream(s)
+
+ static PdfOperator opTab[]; // table of operators
+
+ int colorDeltas[pdfNumShadingTypes];
+ // max deltas allowed in any color component
+ // for the approximation of shading fills
+ int maxDepths[pdfNumShadingTypes]; // max recursive depths
+
+ OpHistoryEntry *operatorHistory; // list containing the last N operators
+
+ //! Caches color spaces by name
+ std::map<std::string, std::unique_ptr<GfxColorSpace>> colorSpacesCache;
+
+ GfxColorSpace *lookupColorSpaceCopy(Object &);
+
+ void setDefaultApproximationPrecision(); // init color deltas
+ void pushOperator(const char *name);
+ OpHistoryEntry *popOperator();
+ const char *getPreviousOperator(unsigned int look_back = 1); // returns the nth previous operator's name
+
+ void go(GBool topLevel);
+ void execOp(Object *cmd, Object args[], int numArgs);
+ PdfOperator *findOp(const char *name);
+ GBool checkArg(Object *arg, TchkType type);
+ int getPos();
+
+ void opOptionalContentGroup(Object args[], int numArgs);
+
+ // graphics state operators
+ void opSave(Object args[], int numArgs);
+ void opRestore(Object args[], int numArgs);
+ void opConcat(Object args[], int numArgs);
+ void opSetDash(Object args[], int numArgs);
+ void opSetFlat(Object args[], int numArgs);
+ void opSetLineJoin(Object args[], int numArgs);
+ void opSetLineCap(Object args[], int numArgs);
+ void opSetMiterLimit(Object args[], int numArgs);
+ void opSetLineWidth(Object args[], int numArgs);
+ void opSetExtGState(Object args[], int numArgs);
+ void doSoftMask(Object *str, GBool alpha, GfxColorSpace *blendingColorSpace, GBool isolated, GBool knockout,
+ Function *transferFunc, GfxColor *backdropColor);
+ void opSetRenderingIntent(Object args[], int numArgs);
+
+ // color operators
+ void opSetFillGray(Object args[], int numArgs);
+ void opSetStrokeGray(Object args[], int numArgs);
+ void opSetFillCMYKColor(Object args[], int numArgs);
+ void opSetStrokeCMYKColor(Object args[], int numArgs);
+ void opSetFillRGBColor(Object args[], int numArgs);
+ void opSetStrokeRGBColor(Object args[], int numArgs);
+ void opSetFillColorSpace(Object args[], int numArgs);
+ void opSetStrokeColorSpace(Object args[], int numArgs);
+ void opSetFillColor(Object args[], int numArgs);
+ void opSetStrokeColor(Object args[], int numArgs);
+ void opSetFillColorN(Object args[], int numArgs);
+ void opSetStrokeColorN(Object args[], int numArgs);
+
+ // path segment operators
+ void opMoveTo(Object args[], int numArgs);
+ void opLineTo(Object args[], int numArgs);
+ void opCurveTo(Object args[], int numArgs);
+ void opCurveTo1(Object args[], int numArgs);
+ void opCurveTo2(Object args[], int numArgs);
+ void opRectangle(Object args[], int numArgs);
+ void opClosePath(Object args[], int numArgs);
+
+ // path painting operators
+ void opEndPath(Object args[], int numArgs);
+ void opStroke(Object args[], int numArgs);
+ void opCloseStroke(Object args[], int numArgs);
+ void opFill(Object args[], int numArgs);
+ void opEOFill(Object args[], int numArgs);
+ void opFillStroke(Object args[], int numArgs);
+ void opCloseFillStroke(Object args[], int numArgs);
+ void opEOFillStroke(Object args[], int numArgs);
+ void opCloseEOFillStroke(Object args[], int numArgs);
+ void doFillAndStroke(GBool eoFill);
+ void doPatternFillFallback(GBool eoFill);
+ void doPatternStrokeFallback();
+ void doShadingPatternFillFallback(GfxShadingPattern *sPat, GBool stroke, GBool eoFill);
+ void opShFill(Object args[], int numArgs);
+ void doFunctionShFill(GfxFunctionShading *shading);
+ void doFunctionShFill1(GfxFunctionShading *shading, double x0, double y0, double x1, double y1, GfxColor *colors,
+ int depth);
+ void doGouraudTriangleShFill(GfxGouraudTriangleShading *shading);
+ void gouraudFillTriangle(double x0, double y0, GfxColor *color0, double x1, double y1, GfxColor *color1, double x2,
+ double y2, GfxColor *color2, int nComps, int depth);
+ void doPatchMeshShFill(GfxPatchMeshShading *shading);
+ void fillPatch(_POPPLER_CONST GfxPatch *patch, int nComps, int depth);
+ void doEndPath();
+
+ // path clipping operators
+ void opClip(Object args[], int numArgs);
+ void opEOClip(Object args[], int numArgs);
+
+ // text object operators
+ void opBeginText(Object args[], int numArgs);
+ void opEndText(Object args[], int numArgs);
+
+ // text state operators
+ void opSetCharSpacing(Object args[], int numArgs);
+ void opSetFont(Object args[], int numArgs);
+ void opSetTextLeading(Object args[], int numArgs);
+ void opSetTextRender(Object args[], int numArgs);
+ void opSetTextRise(Object args[], int numArgs);
+ void opSetWordSpacing(Object args[], int numArgs);
+ void opSetHorizScaling(Object args[], int numArgs);
+
+ // text positioning operators
+ void opTextMove(Object args[], int numArgs);
+ void opTextMoveSet(Object args[], int numArgs);
+ void opSetTextMatrix(Object args[], int numArgs);
+ void opTextNextLine(Object args[], int numArgs);
+
+ // text string operators
+ void doUpdateFont();
+ void opShowText(Object args[], int numArgs);
+ void opMoveShowText(Object args[], int numArgs);
+ void opMoveSetShowText(Object args[], int numArgs);
+ void opShowSpaceText(Object args[], int numArgs);
+#if POPPLER_CHECK_VERSION(0,64,0)
+ void doShowText(const GooString *s);
+#else
+ void doShowText(GooString *s);
+#endif
+
+
+ // XObject operators
+ void opXObject(Object args[], int numArgs);
+ void doImage(Object *ref, Stream *str, GBool inlineImg);
+ void doForm(Object *str);
+ void doForm1(Object *str, Dict *resDict, double *matrix, double *bbox,
+ GBool transpGroup = gFalse, GBool softMask = gFalse,
+ GfxColorSpace *blendingColorSpace = nullptr,
+ GBool isolated = gFalse, GBool knockout = gFalse,
+ GBool alpha = gFalse, Function *transferFunc = nullptr,
+ GfxColor *backdropColor = nullptr);
+
+ // in-line image operators
+ void opBeginImage(Object args[], int numArgs);
+ Stream *buildImageStream();
+ void opImageData(Object args[], int numArgs);
+ void opEndImage(Object args[], int numArgs);
+
+ // type 3 font operators
+ void opSetCharWidth(Object args[], int numArgs);
+ void opSetCacheDevice(Object args[], int numArgs);
+
+ // compatibility operators
+ void opBeginIgnoreUndef(Object args[], int numArgs);
+ void opEndIgnoreUndef(Object args[], int numArgs);
+
+ // marked content operators
+ void opBeginMarkedContent(Object args[], int numArgs);
+ void opEndMarkedContent(Object args[], int numArgs);
+ void opMarkPoint(Object args[], int numArgs);
+
+ void pushResources(Dict *resDict);
+ void popResources();
+};
+
+#endif /* HAVE_POPPLER */
+
+#endif /* PDF_PARSER_H */
diff --git a/src/extension/internal/pdfinput/pdf-utils.cpp b/src/extension/internal/pdfinput/pdf-utils.cpp
new file mode 100644
index 0000000..05c4ac5
--- /dev/null
+++ b/src/extension/internal/pdfinput/pdf-utils.cpp
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Utility structures and functions for pdf parsing.
+ *//*
+ *
+ * Copyright (C) 2023 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glib.h>
+#include "pdf-utils.h"
+
+#include "poppler-utils.h"
+
+//------------------------------------------------------------------------
+// ClipHistoryEntry
+//------------------------------------------------------------------------
+
+ClipHistoryEntry::ClipHistoryEntry(GfxPath *clipPathA, GfxClipType clipTypeA)
+ : saved(nullptr)
+ , clipPath((clipPathA) ? clipPathA->copy() : nullptr)
+ , clipType(clipTypeA)
+{}
+
+ClipHistoryEntry::~ClipHistoryEntry()
+{
+ if (clipPath) {
+ delete clipPath;
+ clipPath = nullptr;
+ }
+}
+
+void ClipHistoryEntry::setClip(GfxState *state, GfxClipType clipTypeA, bool bbox)
+{
+ const GfxPath *clipPathA = state->getPath();
+
+ if (clipPath) {
+ if (copied) {
+ // Free previously copied clip path.
+ delete clipPath;
+ } else {
+ // This indicates a bad use of the ClipHistory API
+ g_error("Clip path is already set!");
+ return;
+ }
+ }
+
+ cleared = false;
+ copied = false;
+ if (clipPathA) {
+ affine = stateToAffine(state);
+ clipPath = clipPathA->copy();
+ clipType = clipTypeA;
+ is_bbox = bbox;
+ } else {
+ affine = Geom::identity();
+ clipPath = nullptr;
+ clipType = clipNormal;
+ is_bbox = false;
+ }
+}
+
+/**
+ * Create a new clip-history, appending it to the stack.
+ *
+ * If cleared is set to true, it will not remember the current clipping path.
+ */
+ClipHistoryEntry *ClipHistoryEntry::save(bool cleared)
+{
+ ClipHistoryEntry *newEntry = new ClipHistoryEntry(this, cleared);
+ newEntry->saved = this;
+ return newEntry;
+}
+
+ClipHistoryEntry *ClipHistoryEntry::restore()
+{
+ ClipHistoryEntry *oldEntry;
+
+ if (saved) {
+ oldEntry = saved;
+ saved = nullptr;
+ delete this; // TODO really should avoid deleting from inside.
+ } else {
+ oldEntry = this;
+ }
+
+ return oldEntry;
+}
+
+ClipHistoryEntry::ClipHistoryEntry(ClipHistoryEntry *other, bool cleared)
+{
+ if (other && other->clipPath) {
+ this->affine = other->affine;
+ this->clipPath = other->clipPath->copy();
+ this->clipType = other->clipType;
+ this->cleared = cleared;
+ this->copied = true;
+ this->is_bbox = other->is_bbox;
+ } else {
+ this->affine = Geom::identity();
+ this->clipPath = nullptr;
+ this->clipType = clipNormal;
+ this->cleared = false;
+ this->copied = false;
+ this->is_bbox = false;
+ }
+ saved = nullptr;
+}
+
+Geom::Rect getRect(_POPPLER_CONST PDFRectangle *box)
+{
+ return Geom::Rect(box->x1, box->y1, box->x2, box->y2);
+}
+
diff --git a/src/extension/internal/pdfinput/pdf-utils.h b/src/extension/internal/pdfinput/pdf-utils.h
new file mode 100644
index 0000000..e1a449a
--- /dev/null
+++ b/src/extension/internal/pdfinput/pdf-utils.h
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * PDF Parsing utility functions and classes.
+ *//*
+ *
+ * Copyright (C) 2022 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef PDF_UTILS_H
+#define PDF_UTILS_H
+
+#include <2geom/rect.h>
+#include "poppler-transition-api.h"
+#include "2geom/affine.h"
+#include "Gfx.h"
+#include "GfxState.h"
+#include "Page.h"
+
+class ClipHistoryEntry
+{
+public:
+ ClipHistoryEntry(GfxPath *clipPath = nullptr, GfxClipType clipType = clipNormal);
+ virtual ~ClipHistoryEntry();
+
+ // Manipulate clip path stack
+ ClipHistoryEntry *save(bool cleared = false);
+ ClipHistoryEntry *restore();
+ bool hasSaves() { return saved != nullptr; }
+ bool hasClipPath() { return clipPath != nullptr && !cleared; }
+ bool isCopied() { return copied; }
+ bool isBoundingBox() { return is_bbox; }
+ void setClip(GfxState *state, GfxClipType newClipType = clipNormal, bool bbox = false);
+ GfxPath *getClipPath() { return clipPath; }
+ GfxClipType getClipType() { return clipType; }
+ const Geom::Affine &getAffine() { return affine; }
+ bool evenOdd() { return clipType != clipNormal; }
+ void clear() { cleared = true; }
+
+private:
+ ClipHistoryEntry *saved; // next clip path on stack
+
+ Geom::Affine affine = Geom::identity(); // Saved affine state of the clipPath
+ GfxPath *clipPath; // used as the path to be filled for an 'sh' operator
+ GfxClipType clipType;
+ bool is_bbox = false;
+ bool cleared = false;
+ bool copied = false;
+
+ ClipHistoryEntry(ClipHistoryEntry *other, bool cleared = false);
+};
+
+Geom::Rect getRect(_POPPLER_CONST PDFRectangle *box);
+
+#endif /* PDF_UTILS_H */
diff --git a/src/extension/internal/pdfinput/poppler-cairo-font-engine.cpp b/src/extension/internal/pdfinput/poppler-cairo-font-engine.cpp
new file mode 100644
index 0000000..7f9183b
--- /dev/null
+++ b/src/extension/internal/pdfinput/poppler-cairo-font-engine.cpp
@@ -0,0 +1,779 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+//========================================================================
+//
+// CairoFontEngine.cc
+//
+// Copied into Inkscape from poppler-22.09.0 2022
+// - poppler/CairoFontEngine.*
+// - goo/ft_utils.*
+//
+// Copyright 2003 Glyph & Cog, LLC
+// Copyright 2004 Red Hat, Inc
+//
+//========================================================================
+
+//========================================================================
+//
+// Modified under the Poppler project - http://poppler.freedesktop.org
+//
+// All changes made under the Poppler project to this file are licensed
+// under GPL version 2 or later
+//
+// Copyright (C) 2005-2007 Jeff Muizelaar <jeff@infidigm.net>
+// Copyright (C) 2005, 2006 Kristian Høgsberg <krh@redhat.com>
+// Copyright (C) 2005 Martin Kretzschmar <martink@gnome.org>
+// Copyright (C) 2005, 2009, 2012, 2013, 2015, 2017-2019, 2021, 2022 Albert Astals Cid <aacid@kde.org>
+// Copyright (C) 2006, 2007, 2010, 2011 Carlos Garcia Campos <carlosgc@gnome.org>
+// Copyright (C) 2007 Koji Otani <sho@bbr.jp>
+// Copyright (C) 2008, 2009 Chris Wilson <chris@chris-wilson.co.uk>
+// Copyright (C) 2008, 2012, 2014, 2016, 2017, 2022 Adrian Johnson <ajohnson@redneon.com>
+// Copyright (C) 2009 Darren Kenny <darren.kenny@sun.com>
+// Copyright (C) 2010 Suzuki Toshiya <mpsuzuki@hiroshima-u.ac.jp>
+// Copyright (C) 2010 Jan Kümmel <jan+freedesktop@snorc.org>
+// Copyright (C) 2012 Hib Eris <hib@hiberis.nl>
+// Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
+// Copyright (C) 2015, 2016 Jason Crain <jason@aquaticape.us>
+// Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de>
+// Copyright (C) 2019 Christian Persch <chpe@src.gnome.org>
+// Copyright (C) 2020 Michal <sudolskym@gmail.com>
+// Copyright (C) 2021, 2022 Oliver Sander <oliver.sander@tu-dresden.de>
+// Copyright (C) 2022 Marcel Fabian Krüger <tex@2krueger.de>
+//
+//========================================================================
+
+#include "poppler-cairo-font-engine.h"
+
+#include <config.h>
+#include <cstring>
+#include <fofi/FoFiTrueType.h>
+#include <fofi/FoFiType1C.h>
+#include <fstream>
+
+#include "Error.h"
+#include "Gfx.h"
+#include "GlobalParams.h"
+#include "Page.h"
+#include "XRef.h"
+#include "goo/gfile.h"
+
+//========================================================================
+//
+// ft_util.cc
+//
+// FreeType helper functions.
+//
+// This file is licensed under the GPLv2 or later
+//
+// Copyright (C) 2022 Adrian Johnson <ajohnson@redneon.com>
+//
+//========================================================================
+
+#include <cstdio>
+
+FT_Error ft_new_face_from_file(FT_Library library, const char *filename_utf8, FT_Long face_index, FT_Face *aface);
+
+#ifdef _WIN32
+static unsigned long ft_stream_read(FT_Stream stream, unsigned long offset, unsigned char *buffer, unsigned long count)
+{
+ FILE *file = (FILE *)stream->descriptor.pointer;
+ fseek(file, offset, SEEK_SET);
+ return fread(buffer, 1, count, file);
+}
+
+static void ft_stream_close(FT_Stream stream)
+{
+ FILE *file = (FILE *)stream->descriptor.pointer;
+ fclose(file);
+ delete stream;
+}
+#endif
+
+// Same as FT_New_Face() but handles UTF-8 filenames on Windows
+FT_Error ft_new_face_from_file(FT_Library library, const char *filename_utf8, FT_Long face_index, FT_Face *aface)
+{
+#ifdef _WIN32
+ FILE *file;
+ long size;
+
+ if (!filename_utf8)
+ return FT_Err_Invalid_Argument;
+
+ file = openFile(filename_utf8, "rb");
+ if (!file)
+ return FT_Err_Cannot_Open_Resource;
+
+ fseek(file, 0, SEEK_END);
+ size = ftell(file);
+ rewind(file);
+
+ if (size <= 0)
+ return FT_Err_Cannot_Open_Stream;
+
+ FT_StreamRec *stream = new FT_StreamRec;
+ *stream = {};
+ stream->size = size;
+ stream->read = ft_stream_read;
+ stream->close = ft_stream_close;
+ stream->descriptor.pointer = file;
+
+ FT_Open_Args args = {};
+ args.flags = FT_OPEN_STREAM;
+ args.stream = stream;
+
+ return FT_Open_Face(library, &args, face_index, aface);
+#else
+ // On POSIX, FT_New_Face mmaps font files. If not Windows, prefer FT_New_Face over our stdio.h based FT_Open_Face.
+ return FT_New_Face(library, filename_utf8, face_index, aface);
+#endif
+}
+
+//------------------------------------------------------------------------
+// CairoFont
+//------------------------------------------------------------------------
+
+CairoFont::CairoFont(Ref refA, cairo_font_face_t *cairo_font_faceA, std::vector<int> &&codeToGIDA, bool substituteA,
+ bool printingA)
+ : ref(refA)
+ , cairo_font_face(cairo_font_faceA)
+ , substitute(substituteA)
+ , printing(printingA)
+{
+ codeToGID = std::move(codeToGIDA);
+}
+
+CairoFont::~CairoFont()
+{
+ cairo_font_face_destroy(cairo_font_face);
+}
+
+bool CairoFont::matches(Ref &other, bool printingA)
+{
+ return (other == ref);
+}
+
+cairo_font_face_t *CairoFont::getFontFace()
+{
+ return cairo_font_face;
+}
+
+unsigned long CairoFont::getGlyph(CharCode code, const Unicode *u, int uLen)
+{
+ FT_UInt gid;
+
+ if (code < codeToGID.size()) {
+ gid = (FT_UInt)codeToGID[code];
+ } else {
+ gid = (FT_UInt)code;
+ }
+ return gid;
+}
+
+#if POPPLER_CHECK_VERSION(22, 4, 0)
+double CairoFont::getSubstitutionCorrection(const std::shared_ptr<GfxFont> &gfxFont)
+#else
+double CairoFont::getSubstitutionCorrection(GfxFont *gfxFont)
+#endif
+{
+ double w1, w2, w3;
+ CharCode code;
+ const char *name;
+
+#if POPPLER_CHECK_VERSION(22, 4, 0)
+ auto gfx8bit = std::static_pointer_cast<Gfx8BitFont>(gfxFont);
+#else
+ auto gfx8bit = dynamic_cast<Gfx8BitFont *>(gfxFont);
+#endif
+
+ // for substituted fonts: adjust the font matrix -- compare the
+ // width of 'm' in the original font and the substituted font
+ if (isSubstitute() && !gfxFont->isCIDFont()) {
+ for (code = 0; code < 256; ++code) {
+ if ((name = gfx8bit->getCharName(code)) && name[0] == 'm' && name[1] == '\0') {
+ break;
+ }
+ }
+ if (code < 256) {
+ w1 = gfx8bit->getWidth(code);
+ {
+ cairo_matrix_t m;
+ cairo_matrix_init_identity(&m);
+ cairo_font_options_t *options = cairo_font_options_create();
+ cairo_font_options_set_hint_style(options, CAIRO_HINT_STYLE_NONE);
+ cairo_font_options_set_hint_metrics(options, CAIRO_HINT_METRICS_OFF);
+ cairo_scaled_font_t *scaled_font = cairo_scaled_font_create(cairo_font_face, &m, &m, options);
+
+ cairo_text_extents_t extents;
+ cairo_scaled_font_text_extents(scaled_font, "m", &extents);
+
+ cairo_scaled_font_destroy(scaled_font);
+ cairo_font_options_destroy(options);
+ w2 = extents.x_advance;
+ }
+ w3 = gfx8bit->getWidth(0);
+ if (!gfxFont->isSymbolic() && w2 > 0 && w1 > w3) {
+ // if real font is substantially narrower than substituted
+ // font, reduce the font size accordingly
+ if (w1 > 0.01 && w1 < 0.9 * w2) {
+ w1 /= w2;
+ return w1;
+ }
+ }
+ }
+ }
+ return 1.0;
+}
+
+//------------------------------------------------------------------------
+// CairoFreeTypeFont
+//------------------------------------------------------------------------
+
+static cairo_user_data_key_t ft_cairo_key;
+
+// Font resources to be freed when cairo_font_face_t is destroyed
+struct FreeTypeFontResource
+{
+ FT_Face face;
+ std::vector<unsigned char> font_data;
+};
+
+// cairo callback for when cairo_font_face_t is destroyed
+static void _ft_done_face(void *closure)
+{
+ FreeTypeFontResource *resource = (FreeTypeFontResource *)closure;
+
+ FT_Done_Face(resource->face);
+ delete resource;
+}
+
+CairoFreeTypeFont::CairoFreeTypeFont(Ref refA, cairo_font_face_t *cairo_font_faceA, std::vector<int> &&codeToGIDA,
+ bool substituteA)
+ : CairoFont(refA, cairo_font_faceA, std::move(codeToGIDA), substituteA, true)
+{}
+
+CairoFreeTypeFont::~CairoFreeTypeFont() {}
+
+// Create a cairo_font_face_t for the given font filename OR font data.
+static std::optional<FreeTypeFontFace> createFreeTypeFontFace(FT_Library lib, const std::string &filename,
+ std::vector<unsigned char> &&font_data)
+{
+ FreeTypeFontResource *resource = new FreeTypeFontResource;
+ FreeTypeFontFace font_face;
+
+ if (font_data.empty()) {
+ FT_Error err = ft_new_face_from_file(lib, filename.c_str(), 0, &resource->face);
+ if (err) {
+ delete resource;
+ return {};
+ }
+ } else {
+ resource->font_data = std::move(font_data);
+ FT_Error err = FT_New_Memory_Face(lib, (FT_Byte *)resource->font_data.data(), resource->font_data.size(), 0,
+ &resource->face);
+ if (err) {
+ delete resource;
+ return {};
+ }
+ }
+
+ font_face.cairo_font_face =
+ cairo_ft_font_face_create_for_ft_face(resource->face, FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP);
+ if (cairo_font_face_set_user_data(font_face.cairo_font_face, &ft_cairo_key, resource, _ft_done_face)) {
+ cairo_font_face_destroy(font_face.cairo_font_face);
+ _ft_done_face(resource);
+ return {};
+ }
+
+ font_face.face = resource->face;
+ return font_face;
+}
+
+// Create a cairo_font_face_t for the given font filename OR font data. First checks if external font
+// is in the cache.
+std::optional<FreeTypeFontFace> CairoFreeTypeFont::getFreeTypeFontFace(CairoFontEngine *fontEngine, FT_Library lib,
+ const std::string &filename,
+ std::vector<unsigned char> &&font_data)
+{
+ if (font_data.empty()) {
+ return fontEngine->getExternalFontFace(lib, filename);
+ }
+
+ return createFreeTypeFontFace(lib, filename, std::move(font_data));
+}
+
+#if POPPLER_CHECK_VERSION(22, 4, 0)
+CairoFreeTypeFont *CairoFreeTypeFont::create(const std::shared_ptr<GfxFont> &gfxFont, XRef *xref, FT_Library lib,
+ CairoFontEngine *fontEngine, bool useCIDs)
+#else
+CairoFreeTypeFont *CairoFreeTypeFont::create(GfxFont *gfxFont, XRef *xref, FT_Library lib, CairoFontEngine *fontEngine,
+ bool useCIDs)
+#endif
+{
+ std::string fileName;
+ std::vector<unsigned char> font_data;
+ int i, n;
+#if POPPLER_CHECK_VERSION(22, 2, 0)
+ std::optional<GfxFontLoc> fontLoc;
+#else
+ GfxFontLoc *fontLoc;
+#endif
+ char **enc;
+ const char *name;
+ FoFiType1C *ff1c;
+ std::optional<FreeTypeFontFace> font_face;
+ std::vector<int> codeToGID;
+ bool substitute = false;
+
+#if POPPLER_CHECK_VERSION(22, 4, 0)
+ auto gfxcid = std::static_pointer_cast<GfxCIDFont>(gfxFont);
+ auto gfx8bit = std::static_pointer_cast<Gfx8BitFont>(gfxFont);
+ typedef unsigned char *fontchar;
+#else
+ auto gfxcid = dynamic_cast<GfxCIDFont *>(gfxFont);
+ auto gfx8bit = dynamic_cast<Gfx8BitFont *>(gfxFont);
+ typedef char *fontchar;
+#endif
+
+ Ref ref = *gfxFont->getID();
+ Ref embFontID = Ref::INVALID();
+ gfxFont->getEmbeddedFontID(&embFontID);
+ GfxFontType fontType = gfxFont->getType();
+
+ if (!(fontLoc = gfxFont->locateFont(xref, nullptr))) {
+ error(errSyntaxError, -1, "Couldn't find a font for '{0:s}'",
+ gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)");
+ goto err2;
+ }
+
+ // embedded font
+ if (fontLoc->locType == gfxFontLocEmbedded) {
+#if POPPLER_CHECK_VERSION(22, 4, 0)
+ auto fd = gfxFont->readEmbFontFile(xref);
+ if (!fd || fd->empty()) {
+ goto err2;
+ }
+ font_data = std::move(fd.value());
+#else
+ int nSize = 0;
+ char *chars = gfxFont->readEmbFontFile(xref, &nSize);
+ if (!nSize || !chars) {
+ goto err2;
+ }
+ font_data = {chars, chars + nSize};
+ gfree(chars);
+#endif
+
+ // external font
+ } else { // gfxFontLocExternal
+
+#if POPPLER_CHECK_VERSION(22, 1, 0)
+ fileName = fontLoc->path;
+#else
+ fileName = fontLoc->path->toNonConstStr();
+#endif
+ fontType = fontLoc->fontType;
+ substitute = true;
+ }
+
+ switch (fontType) {
+ case fontType1:
+ case fontType1C:
+ case fontType1COT:
+ font_face = getFreeTypeFontFace(fontEngine, lib, fileName, std::move(font_data));
+ if (!font_face) {
+ error(errSyntaxError, -1, "could not create type1 face");
+ goto err2;
+ }
+
+ enc = gfx8bit->getEncoding();
+
+ codeToGID.resize(256);
+ for (i = 0; i < 256; ++i) {
+ codeToGID[i] = 0;
+ if ((name = enc[i])) {
+ codeToGID[i] = FT_Get_Name_Index(font_face->face, (char *)name);
+ if (codeToGID[i] == 0) {
+ Unicode u;
+ u = globalParams->mapNameToUnicodeText(name);
+ codeToGID[i] = FT_Get_Char_Index(font_face->face, u);
+ }
+ if (codeToGID[i] == 0) {
+ name = GfxFont::getAlternateName(name);
+ if (name) {
+ codeToGID[i] = FT_Get_Name_Index(font_face->face, (char *)name);
+ }
+ }
+ }
+ }
+ break;
+ case fontCIDType2:
+ case fontCIDType2OT:
+ if (gfxcid->getCIDToGID()) {
+ n = gfxcid->getCIDToGIDLen();
+ if (n) {
+ const int *src = gfxcid->getCIDToGID();
+ codeToGID.reserve(n);
+ codeToGID.insert(codeToGID.begin(), src, src + n);
+ }
+ } else {
+#if POPPLER_CHECK_VERSION(22, 1, 0)
+ std::unique_ptr<FoFiTrueType> ff;
+#else
+ FoFiTrueType *ff;
+#endif
+ if (!font_data.empty()) {
+ ff = FoFiTrueType::make((fontchar)font_data.data(), font_data.size());
+ } else {
+ ff = FoFiTrueType::load(fileName.c_str());
+ }
+ if (!ff) {
+ goto err2;
+ }
+#if POPPLER_CHECK_VERSION(22, 1, 0)
+ int *src = gfxcid->getCodeToGIDMap(ff.get(), &n);
+#else
+ int *src = gfxcid->getCodeToGIDMap(ff, &n);
+#endif
+ codeToGID.reserve(n);
+ codeToGID.insert(codeToGID.begin(), src, src + n);
+ gfree(src);
+ }
+ /* Fall through */
+ case fontTrueType:
+ case fontTrueTypeOT: {
+#if POPPLER_CHECK_VERSION(22, 1, 0)
+ std::unique_ptr<FoFiTrueType> ff;
+#else
+ FoFiTrueType *ff;
+#endif
+ if (!font_data.empty()) {
+ ff = FoFiTrueType::make((fontchar)font_data.data(), font_data.size());
+ } else {
+ ff = FoFiTrueType::load(fileName.c_str());
+ }
+ if (!ff) {
+ error(errSyntaxError, -1, "failed to load truetype font\n");
+ goto err2;
+ }
+ /* This might be set already for the CIDType2 case */
+ if (fontType == fontTrueType || fontType == fontTrueTypeOT) {
+#if POPPLER_CHECK_VERSION(22, 1, 0)
+ int *src = gfx8bit->getCodeToGIDMap(ff.get());
+#else
+ int *src = gfx8bit->getCodeToGIDMap(ff);
+#endif
+ codeToGID.reserve(256);
+ codeToGID.insert(codeToGID.begin(), src, src + 256);
+ gfree(src);
+ }
+ font_face = getFreeTypeFontFace(fontEngine, lib, fileName, std::move(font_data));
+ if (!font_face) {
+ error(errSyntaxError, -1, "could not create truetype face\n");
+ goto err2;
+ }
+ break;
+ }
+ case fontCIDType0:
+ case fontCIDType0C:
+ if (!useCIDs) {
+ if (!font_data.empty()) {
+ ff1c = FoFiType1C::make((fontchar)font_data.data(), font_data.size());
+ } else {
+ ff1c = FoFiType1C::load(fileName.c_str());
+ }
+ if (ff1c) {
+ int *src = ff1c->getCIDToGIDMap(&n);
+ codeToGID.reserve(n);
+ codeToGID.insert(codeToGID.begin(), src, src + n);
+ gfree(src);
+ delete ff1c;
+ }
+ }
+
+ font_face = getFreeTypeFontFace(fontEngine, lib, fileName, std::move(font_data));
+ if (!font_face) {
+ error(errSyntaxError, -1, "could not create cid face\n");
+ goto err2;
+ }
+ break;
+
+ case fontCIDType0COT:
+ if (gfxcid->getCIDToGID()) {
+ n = gfxcid->getCIDToGIDLen();
+ if (n) {
+ const int *src = gfxcid->getCIDToGID();
+ codeToGID.reserve(n);
+ codeToGID.insert(codeToGID.begin(), src, src + n);
+ }
+ }
+
+ if (codeToGID.empty()) {
+ if (!useCIDs) {
+#if POPPLER_CHECK_VERSION(22, 1, 0)
+ std::unique_ptr<FoFiTrueType> ff;
+#else
+ FoFiTrueType *ff;
+#endif
+ if (!font_data.empty()) {
+ ff = FoFiTrueType::make((fontchar)font_data.data(), font_data.size());
+ } else {
+ ff = FoFiTrueType::load(fileName.c_str());
+ }
+ if (ff) {
+ if (ff->isOpenTypeCFF()) {
+ int *src = ff->getCIDToGIDMap(&n);
+ codeToGID.reserve(n);
+ codeToGID.insert(codeToGID.begin(), src, src + n);
+ gfree(src);
+ }
+ }
+ }
+ }
+ font_face = getFreeTypeFontFace(fontEngine, lib, fileName, std::move(font_data));
+ if (!font_face) {
+ error(errSyntaxError, -1, "could not create cid (OT) face\n");
+ goto err2;
+ }
+ break;
+
+ default:
+ fprintf(stderr, "font type %d not handled\n", (int)fontType);
+ goto err2;
+ break;
+ }
+
+ return new CairoFreeTypeFont(ref, font_face->cairo_font_face, std::move(codeToGID), substitute);
+
+err2:
+ fprintf(stderr, "some font thing failed\n");
+ return nullptr;
+}
+
+//------------------------------------------------------------------------
+// CairoType3Font
+//------------------------------------------------------------------------
+
+static const cairo_user_data_key_t type3_font_key = {0};
+
+typedef struct _type3_font_info
+{
+#if POPPLER_CHECK_VERSION(22, 4, 0)
+ _type3_font_info(const std::shared_ptr<GfxFont> &fontA, PDFDoc *docA, CairoFontEngine *fontEngineA, bool printingA,
+ XRef *xrefA)
+ : font(fontA)
+ , doc(docA)
+ , fontEngine(fontEngineA)
+ , printing(printingA)
+ , xref(xrefA)
+ {}
+
+ std::shared_ptr<GfxFont> font;
+#else
+ _type3_font_info(GfxFont *fontA, PDFDoc *docA, CairoFontEngine *fontEngineA, bool printingA, XRef *xrefA)
+ : font(fontA)
+ , doc(docA)
+ , fontEngine(fontEngineA)
+ , printing(printingA)
+ , xref(xrefA)
+ {}
+
+ GfxFont *font;
+#endif
+
+ PDFDoc *doc;
+ CairoFontEngine *fontEngine;
+ bool printing;
+ XRef *xref;
+} type3_font_info_t;
+
+static void _free_type3_font_info(void *closure)
+{
+ type3_font_info_t *info = (type3_font_info_t *)closure;
+ delete info;
+}
+
+static cairo_status_t _init_type3_glyph(cairo_scaled_font_t *scaled_font, cairo_t *cr, cairo_font_extents_t *extents)
+{
+ type3_font_info_t *info;
+
+ info = (type3_font_info_t *)cairo_font_face_get_user_data(cairo_scaled_font_get_font_face(scaled_font),
+ &type3_font_key);
+ const double *mat = info->font->getFontBBox();
+ extents->ascent = mat[3]; /* y2 */
+ extents->descent = -mat[3]; /* -y1 */
+ extents->height = extents->ascent + extents->descent;
+ extents->max_x_advance = mat[2] - mat[1]; /* x2 - x1 */
+ extents->max_y_advance = 0;
+
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t _render_type3_glyph(cairo_scaled_font_t *scaled_font, unsigned long glyph, cairo_t *cr,
+ cairo_text_extents_t *metrics, bool color)
+{
+ // We have stripped out the type3 glyph support here, because it calls back
+ // into CairoOutputDev which is private and would pull in the entire poppler codebase.
+ return CAIRO_STATUS_USER_FONT_ERROR;
+}
+
+#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 17, 6)
+static cairo_status_t _render_type3_color_glyph(cairo_scaled_font_t *scaled_font, unsigned long glyph, cairo_t *cr,
+ cairo_text_extents_t *metrics)
+{
+ return _render_type3_glyph(scaled_font, glyph, cr, metrics, true);
+}
+#endif
+
+static cairo_status_t _render_type3_noncolor_glyph(cairo_scaled_font_t *scaled_font, unsigned long glyph, cairo_t *cr,
+ cairo_text_extents_t *metrics)
+{
+ return _render_type3_glyph(scaled_font, glyph, cr, metrics, false);
+}
+
+#if POPPLER_CHECK_VERSION(22, 4, 0)
+CairoType3Font *CairoType3Font::create(const std::shared_ptr<GfxFont> &gfxFont, PDFDoc *doc,
+ CairoFontEngine *fontEngine, bool printing, XRef *xref)
+{
+ auto gfx8bit = std::static_pointer_cast<Gfx8BitFont>(gfxFont);
+#else
+CairoType3Font *CairoType3Font::create(GfxFont *gfxFont, PDFDoc *doc, CairoFontEngine *fontEngine, bool printing,
+ XRef *xref)
+{
+ auto gfx8bit = dynamic_cast<Gfx8BitFont *>(gfxFont);
+#endif
+
+ std::vector<int> codeToGID;
+ char *name;
+
+ Dict *charProcs = gfx8bit->getCharProcs();
+ Ref ref = *gfxFont->getID();
+ cairo_font_face_t *font_face = cairo_user_font_face_create();
+ cairo_user_font_face_set_init_func(font_face, _init_type3_glyph);
+#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 17, 6)
+ // When both callbacks are set, Cairo will call the color glyph
+ // callback first. If that returns NOT_IMPLEMENTED, Cairo will
+ // then call the non-color glyph callback.
+ cairo_user_font_face_set_render_color_glyph_func(font_face, _render_type3_color_glyph);
+#endif
+ cairo_user_font_face_set_render_glyph_func(font_face, _render_type3_noncolor_glyph);
+ type3_font_info_t *info = new type3_font_info_t(gfxFont, doc, fontEngine, printing, xref);
+
+ cairo_font_face_set_user_data(font_face, &type3_font_key, (void *)info, _free_type3_font_info);
+
+ char **enc = gfx8bit->getEncoding();
+ codeToGID.resize(256);
+ for (int i = 0; i < 256; ++i) {
+ codeToGID[i] = 0;
+ if (charProcs && (name = enc[i])) {
+ for (int j = 0; j < charProcs->getLength(); j++) {
+ if (strcmp(name, charProcs->getKey(j)) == 0) {
+ codeToGID[i] = j;
+ }
+ }
+ }
+ }
+
+ return new CairoType3Font(ref, font_face, std::move(codeToGID), printing, xref);
+}
+
+CairoType3Font::CairoType3Font(Ref refA, cairo_font_face_t *cairo_font_faceA, std::vector<int> &&codeToGIDA,
+ bool printingA, XRef *xref)
+ : CairoFont(refA, cairo_font_faceA, std::move(codeToGIDA), false, printingA)
+{}
+
+CairoType3Font::~CairoType3Font() {}
+
+bool CairoType3Font::matches(Ref &other, bool printingA)
+{
+ return (other == ref && printing == printingA);
+}
+
+//------------------------------------------------------------------------
+// CairoFontEngine
+//------------------------------------------------------------------------
+
+std::unordered_map<std::string, FreeTypeFontFace> CairoFontEngine::fontFileCache;
+std::recursive_mutex CairoFontEngine::fontFileCacheMutex;
+
+CairoFontEngine::CairoFontEngine(FT_Library libA)
+{
+ lib = libA;
+ fontCache.reserve(cairoFontCacheSize);
+
+ FT_Int major, minor, patch;
+ // as of FT 2.1.8, CID fonts are indexed by CID instead of GID
+ FT_Library_Version(lib, &major, &minor, &patch);
+ useCIDs = major > 2 || (major == 2 && (minor > 1 || (minor == 1 && patch > 7)));
+}
+
+CairoFontEngine::~CairoFontEngine() {}
+
+#if POPPLER_CHECK_VERSION(22, 4, 0)
+std::shared_ptr<CairoFont> CairoFontEngine::getFont(const std::shared_ptr<GfxFont> &gfxFont, PDFDoc *doc, bool printing,
+ XRef *xref)
+#else
+std::shared_ptr<CairoFont> CairoFontEngine::getFont(GfxFont *gfxFont, PDFDoc *doc, bool printing, XRef *xref)
+#endif
+{
+ std::scoped_lock lock(mutex);
+ Ref ref = *gfxFont->getID();
+ std::shared_ptr<CairoFont> font;
+
+ // Check if font is in the MRU cache, and move it to the end if it is.
+ for (auto it = fontCache.rbegin(); it != fontCache.rend(); ++it) {
+ if ((*it)->matches(ref, printing)) {
+ font = *it;
+ // move it to the end
+ if (it != fontCache.rbegin()) {
+ // https://stackoverflow.com/questions/1830158/how-to-call-erase-with-a-reverse-iterator
+ fontCache.erase(std::next(it).base());
+ fontCache.push_back(font);
+ }
+ return font;
+ }
+ }
+
+ GfxFontType fontType = gfxFont->getType();
+ if (fontType == fontType3) {
+ font = std::shared_ptr<CairoFont>(CairoType3Font::create(gfxFont, doc, this, printing, xref));
+ } else {
+ font = std::shared_ptr<CairoFont>(CairoFreeTypeFont::create(gfxFont, xref, lib, this, useCIDs));
+ }
+
+ if (font) {
+ if (fontCache.size() == cairoFontCacheSize) {
+ fontCache.erase(fontCache.begin());
+ }
+ fontCache.push_back(font);
+ }
+ return font;
+}
+
+std::optional<FreeTypeFontFace> CairoFontEngine::getExternalFontFace(FT_Library ftlib, const std::string &filename)
+{
+ std::scoped_lock lock(fontFileCacheMutex);
+
+ auto it = fontFileCache.find(filename);
+ if (it != fontFileCache.end()) {
+ FreeTypeFontFace font = it->second;
+ cairo_font_face_reference(font.cairo_font_face);
+ return font;
+ }
+
+ std::optional<FreeTypeFontFace> font_face = createFreeTypeFontFace(ftlib, filename, {});
+ if (font_face) {
+ cairo_font_face_reference(font_face->cairo_font_face);
+ fontFileCache[filename] = *font_face;
+ }
+
+ it = fontFileCache.begin();
+ while (it != fontFileCache.end()) {
+ if (cairo_font_face_get_reference_count(it->second.cairo_font_face) == 1) {
+ cairo_font_face_destroy(it->second.cairo_font_face);
+ it = fontFileCache.erase(it);
+ } else {
+ ++it;
+ }
+ }
+
+ return font_face;
+}
diff --git a/src/extension/internal/pdfinput/poppler-cairo-font-engine.h b/src/extension/internal/pdfinput/poppler-cairo-font-engine.h
new file mode 100644
index 0000000..d3e1a94
--- /dev/null
+++ b/src/extension/internal/pdfinput/poppler-cairo-font-engine.h
@@ -0,0 +1,166 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+//========================================================================
+//
+// CairoFontEngine.h
+//
+// Copyright 2003 Glyph & Cog, LLC
+// Copyright 2004 Red Hat, Inc
+//
+//========================================================================
+
+//========================================================================
+//
+// Modified under the Poppler project - http://poppler.freedesktop.org
+//
+// All changes made under the Poppler project to this file are licensed
+// under GPL version 2 or later
+//
+// Copyright (C) 2005, 2006 Kristian Høgsberg <krh@redhat.com>
+// Copyright (C) 2005, 2018, 2019, 2021 Albert Astals Cid <aacid@kde.org>
+// Copyright (C) 2006, 2007 Jeff Muizelaar <jeff@infidigm.net>
+// Copyright (C) 2006, 2010 Carlos Garcia Campos <carlosgc@gnome.org>
+// Copyright (C) 2008, 2017, 2022 Adrian Johnson <ajohnson@redneon.com>
+// Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
+// Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de>
+// Copyright (C) 2022 Oliver Sander <oliver.sander@tu-dresden.de>
+//
+// 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
+//
+//========================================================================
+
+#ifndef CAIROFONTENGINE_H
+#define CAIROFONTENGINE_H
+
+#include <cairo-ft.h>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <unordered_map>
+#include <vector>
+
+#include "GfxFont.h"
+#include "PDFDoc.h"
+#include "poppler-config.h"
+#include "poppler-transition-api.h"
+
+class CairoFontEngine;
+
+class CairoFont
+{
+public:
+ CairoFont(Ref refA, cairo_font_face_t *cairo_font_faceA, std::vector<int> &&codeToGIDA, bool substituteA,
+ bool printingA);
+ virtual ~CairoFont();
+ CairoFont(const CairoFont &) = delete;
+ CairoFont &operator=(const CairoFont &other) = delete;
+
+ virtual bool matches(Ref &other, bool printing);
+ cairo_font_face_t *getFontFace();
+ unsigned long getGlyph(CharCode code, const Unicode *u, int uLen);
+#if POPPLER_CHECK_VERSION(22, 4, 0)
+ double getSubstitutionCorrection(const std::shared_ptr<GfxFont> &gfxFont);
+#else
+ double getSubstitutionCorrection(GfxFont *gfxFont);
+#endif
+
+ bool isSubstitute() { return substitute; }
+
+protected:
+ Ref ref;
+ cairo_font_face_t *cairo_font_face;
+
+ std::vector<int> codeToGID;
+
+ bool substitute;
+ bool printing;
+};
+
+//------------------------------------------------------------------------
+
+struct FreeTypeFontFace
+{
+ FT_Face face;
+ cairo_font_face_t *cairo_font_face;
+};
+
+class CairoFreeTypeFont : public CairoFont
+{
+public:
+#if POPPLER_CHECK_VERSION(22, 4, 0)
+ static CairoFreeTypeFont *create(const std::shared_ptr<GfxFont> &gfxFont, XRef *xref, FT_Library lib,
+ CairoFontEngine *fontEngine, bool useCIDs);
+#else
+ static CairoFreeTypeFont *create(GfxFont *gfxFont, XRef *xref, FT_Library lib, CairoFontEngine *fontEngine,
+ bool useCIDs);
+#endif
+ ~CairoFreeTypeFont() override;
+
+private:
+ CairoFreeTypeFont(Ref ref, cairo_font_face_t *cairo_font_face, std::vector<int> &&codeToGID, bool substitute);
+
+ static std::optional<FreeTypeFontFace> getFreeTypeFontFace(CairoFontEngine *fontEngine, FT_Library lib,
+ const std::string &filename,
+ std::vector<unsigned char> &&data);
+};
+
+//------------------------------------------------------------------------
+
+class CairoType3Font : public CairoFont
+{
+public:
+#if POPPLER_CHECK_VERSION(22, 4, 0)
+ static CairoType3Font *create(const std::shared_ptr<GfxFont> &gfxFont, PDFDoc *doc, CairoFontEngine *fontEngine,
+ bool printing, XRef *xref);
+#else
+ static CairoType3Font *create(GfxFont *gfxFont, PDFDoc *doc, CairoFontEngine *fontEngine, bool printing,
+ XRef *xref);
+#endif
+ ~CairoType3Font() override;
+
+ bool matches(Ref &other, bool printing) override;
+
+private:
+ CairoType3Font(Ref ref, cairo_font_face_t *cairo_font_face, std::vector<int> &&codeToGIDA, bool printing,
+ XRef *xref);
+};
+
+//------------------------------------------------------------------------
+
+//------------------------------------------------------------------------
+// CairoFontEngine
+//------------------------------------------------------------------------
+
+class CairoFontEngine
+{
+public:
+ // Create a font engine.
+ explicit CairoFontEngine(FT_Library libA);
+ ~CairoFontEngine();
+ CairoFontEngine(const CairoFontEngine &) = delete;
+ CairoFontEngine &operator=(const CairoFontEngine &other) = delete;
+
+#if POPPLER_CHECK_VERSION(22, 4, 0)
+ std::shared_ptr<CairoFont> getFont(const std::shared_ptr<GfxFont> &gfxFont, PDFDoc *doc, bool printing, XRef *xref);
+#else
+ std::shared_ptr<CairoFont> getFont(GfxFont *gfxFont, PDFDoc *doc, bool printing, XRef *xref);
+#endif
+
+ static std::optional<FreeTypeFontFace> getExternalFontFace(FT_Library ftlib, const std::string &filename);
+
+private:
+ FT_Library lib;
+ bool useCIDs;
+ mutable std::mutex mutex;
+
+ // Cache of CairoFont for current document
+ // Most recently used is at the end of the vector.
+ static const size_t cairoFontCacheSize = 64;
+ std::vector<std::shared_ptr<CairoFont>> fontCache;
+
+ // Global cache of cairo_font_face_t for external font files.
+ static std::unordered_map<std::string, FreeTypeFontFace> fontFileCache;
+ static std::recursive_mutex fontFileCacheMutex;
+};
+
+#endif
diff --git a/src/extension/internal/pdfinput/poppler-transition-api.h b/src/extension/internal/pdfinput/poppler-transition-api.h
new file mode 100644
index 0000000..dc9e47e
--- /dev/null
+++ b/src/extension/internal/pdfinput/poppler-transition-api.h
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO short description
+ *//*
+ * Authors:
+ * see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_POPPLER_TRANSITION_API_H
+#define SEEN_POPPLER_TRANSITION_API_H
+
+#include <glib/poppler-features.h>
+
+#if POPPLER_CHECK_VERSION(22, 4, 0)
+#define _POPPLER_FONTPTR_TO_GFX8(font_ptr) ((Gfx8BitFont *)font_ptr.get())
+#else
+#define _POPPLER_FONTPTR_TO_GFX8(font_ptr) ((Gfx8BitFont *)font_ptr)
+#endif
+
+#if POPPLER_CHECK_VERSION(22, 3, 0)
+#define _POPPLER_MAKE_SHARED_PDFDOC(uri) std::make_shared<PDFDoc>(std::make_unique<GooString>(uri))
+#else
+#define _POPPLER_MAKE_SHARED_PDFDOC(uri) std::make_shared<PDFDoc>(new GooString(uri), nullptr, nullptr, nullptr)
+#endif
+
+#if POPPLER_CHECK_VERSION(0, 83, 0)
+#define _POPPLER_CONST_83 const
+#else
+#define _POPPLER_CONST_83
+#endif
+
+#if POPPLER_CHECK_VERSION(0, 82, 0)
+#define _POPPLER_CONST_82 const
+#else
+#define _POPPLER_CONST_82
+#endif
+
+#if POPPLER_CHECK_VERSION(0, 76, 0)
+#define _POPPLER_NEW_PARSER(xref, obj) Parser(xref, obj, gFalse)
+#else
+#define _POPPLER_NEW_PARSER(xref, obj) Parser(xref, new Lexer(xref, obj), gFalse)
+#endif
+
+#if POPPLER_CHECK_VERSION(0, 83, 0)
+#define _POPPLER_NEW_GLOBAL_PARAMS(args...) std::unique_ptr<GlobalParams>(new GlobalParams(args))
+#else
+#define _POPPLER_NEW_GLOBAL_PARAMS(args...) new GlobalParams(args)
+#endif
+
+
+#if POPPLER_CHECK_VERSION(0, 72, 0)
+#define getCString c_str
+#endif
+
+#if POPPLER_CHECK_VERSION(0,71,0)
+typedef bool GBool;
+#define gTrue true
+#define gFalse false
+#endif
+
+#if POPPLER_CHECK_VERSION(0,70,0)
+#define _POPPLER_CONST const
+#else
+#define _POPPLER_CONST
+#endif
+
+#if POPPLER_CHECK_VERSION(0,69,0)
+#define _POPPLER_DICTADD(dict, key, obj) (dict).dictAdd(key, std::move(obj))
+#elif POPPLER_CHECK_VERSION(0,58,0)
+#define _POPPLER_DICTADD(dict, key, obj) (dict).dictAdd(copyString(key), std::move(obj))
+#else
+#define _POPPLER_DICTADD(dict, key, obj) (dict).dictAdd(copyString(key), &obj)
+#endif
+
+#if POPPLER_CHECK_VERSION(0,58,0)
+#define POPPLER_NEW_OBJECT_API
+#define _POPPLER_FREE(obj)
+#define _POPPLER_CALL(ret, func) (ret = func())
+#define _POPPLER_CALL_ARGS(ret, func, ...) (ret = func(__VA_ARGS__))
+#define _POPPLER_CALL_ARGS_DEREF _POPPLER_CALL_ARGS
+#else
+#define _POPPLER_FREE(obj) (obj).free()
+#define _POPPLER_CALL(ret, func) (*func(&ret))
+#define _POPPLER_CALL_ARGS(ret, func, ...) (func(__VA_ARGS__, &ret))
+#define _POPPLER_CALL_ARGS_DEREF(...) (*_POPPLER_CALL_ARGS(__VA_ARGS__))
+#endif
+
+#if !POPPLER_CHECK_VERSION(0, 29, 0)
+#error "Requires poppler >= 0.29"
+#endif
+
+#endif
diff --git a/src/extension/internal/pdfinput/poppler-utils.cpp b/src/extension/internal/pdfinput/poppler-utils.cpp
new file mode 100644
index 0000000..26746dc
--- /dev/null
+++ b/src/extension/internal/pdfinput/poppler-utils.cpp
@@ -0,0 +1,599 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * PDF parsing utilities for libpoppler.
+ *//*
+ * Authors:
+ * Martin Owens
+ *
+ * Copyright (C) 2022 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "poppler-utils.h"
+
+#include "2geom/affine.h"
+#include "GfxFont.h"
+#include "GfxState.h"
+#include "PDFDoc.h"
+#include "libnrtype/font-factory.h"
+
+/**
+ * Get the default transformation state from the GfxState
+ */
+Geom::Affine stateToAffine(GfxState *state)
+{
+ return ctmToAffine(state->getCTM());
+}
+
+/**
+ * Convert a transformation matrix to a lib2geom affine object.
+ */
+Geom::Affine ctmToAffine(const double *ctm)
+{
+ if (!ctm)
+ return Geom::identity();
+ return Geom::Affine(ctm[0], ctm[1], ctm[2], ctm[3], ctm[4], ctm[5]);
+}
+
+void ctmout(const char *label, const double *ctm)
+{
+ std::cout << "C:" << label << ":" << ctm[0] << "," << ctm[1] << "," << ctm[2] << "," << ctm[3] << "," << ctm[4]
+ << "," << ctm[5] << "\n";
+}
+
+void affout(const char *label, Geom::Affine ctm)
+{
+ std::cout << "A:" << label << ":" << ctm[0] << "," << ctm[1] << "," << ctm[2] << "," << ctm[3] << "," << ctm[4]
+ << "," << ctm[5] << "\n";
+}
+
+//------------------------------------------------------------------------
+// GfxFontDict from GfxFont.cc in poppler 22.09
+//
+// Modified under the Poppler project - http://poppler.freedesktop.org
+//
+// All changes made under the Poppler project to this file are licensed
+// under GPL version 2 or later
+//
+// See poppler source code for full list of copyright holders.
+//------------------------------------------------------------------------
+
+InkFontDict::InkFontDict(XRef *xref, Ref *fontDictRef, Dict *fontDict)
+{
+ Ref r;
+
+ fonts.resize(fontDict->getLength());
+ for (std::size_t i = 0; i < fonts.size(); ++i) {
+ const Object &obj1 = fontDict->getValNF(i);
+ Object obj2 = obj1.fetch(xref);
+ if (obj2.isDict()) {
+ if (obj1.isRef()) {
+ r = obj1.getRef();
+ } else if (fontDictRef) {
+ // legal generation numbers are five digits, so we use a
+ // 6-digit number here
+ r.gen = 100000 + fontDictRef->num;
+ r.num = i;
+ } else {
+ // no indirect reference for this font, or for the containing
+ // font dict, so hash the font and use that
+ r.gen = 100000;
+ r.num = hashFontObject(&obj2);
+ }
+ // Newer poppler will require some reworking as it gives a shared ptr.
+ fonts[i] = GfxFont::makeFont(xref, fontDict->getKey(i), r, obj2.getDict());
+ if (fonts[i] && !fonts[i]->isOk()) {
+ fonts[i] = nullptr;
+ }
+ } else {
+ error(errSyntaxError, -1, "font resource is not a dictionary");
+ fonts[i] = nullptr;
+ }
+ }
+}
+
+FontPtr InkFontDict::lookup(const char *tag) const
+{
+ for (const auto &font : fonts) {
+ if (font && font->matches(tag)) {
+ return font;
+ }
+ }
+ return nullptr;
+}
+
+// FNV-1a hash
+class FNVHash
+{
+public:
+ FNVHash() { h = 2166136261U; }
+
+ void hash(char c)
+ {
+ h ^= c & 0xff;
+ h *= 16777619;
+ }
+
+ void hash(const char *p, int n)
+ {
+ int i;
+ for (i = 0; i < n; ++i) {
+ hash(p[i]);
+ }
+ }
+
+ int get31() { return (h ^ (h >> 31)) & 0x7fffffff; }
+
+private:
+ unsigned int h;
+};
+
+int InkFontDict::hashFontObject(Object *obj)
+{
+ FNVHash h;
+
+ hashFontObject1(obj, &h);
+ return h.get31();
+}
+
+void InkFontDict::hashFontObject1(const Object *obj, FNVHash *h)
+{
+ const GooString *s;
+ const char *p;
+ double r;
+ int n, i;
+
+ switch (obj->getType()) {
+ case objBool:
+ h->hash('b');
+ h->hash(obj->getBool() ? 1 : 0);
+ break;
+ case objInt:
+ h->hash('i');
+ n = obj->getInt();
+ h->hash((char *)&n, sizeof(int));
+ break;
+ case objReal:
+ h->hash('r');
+ r = obj->getReal();
+ h->hash((char *)&r, sizeof(double));
+ break;
+ case objString:
+ h->hash('s');
+ s = obj->getString();
+ h->hash(s->c_str(), s->getLength());
+ break;
+ case objName:
+ h->hash('n');
+ p = obj->getName();
+ h->hash(p, (int)strlen(p));
+ break;
+ case objNull:
+ h->hash('z');
+ break;
+ case objArray:
+ h->hash('a');
+ n = obj->arrayGetLength();
+ h->hash((char *)&n, sizeof(int));
+ for (i = 0; i < n; ++i) {
+ const Object &obj2 = obj->arrayGetNF(i);
+ hashFontObject1(&obj2, h);
+ }
+ break;
+ case objDict:
+ h->hash('d');
+ n = obj->dictGetLength();
+ h->hash((char *)&n, sizeof(int));
+ for (i = 0; i < n; ++i) {
+ p = obj->dictGetKey(i);
+ h->hash(p, (int)strlen(p));
+ const Object &obj2 = obj->dictGetValNF(i);
+ hashFontObject1(&obj2, h);
+ }
+ break;
+ case objStream:
+ // this should never happen - streams must be indirect refs
+ break;
+ case objRef:
+ h->hash('f');
+ n = obj->getRefNum();
+ h->hash((char *)&n, sizeof(int));
+ n = obj->getRefGen();
+ h->hash((char *)&n, sizeof(int));
+ break;
+ default:
+ h->hash('u');
+ break;
+ }
+}
+
+std::string getNameWithoutSubsetTag(FontPtr font)
+{
+ if (!font->getName())
+ return {};
+
+ std::string tagname = font->getName()->c_str();
+ unsigned int i;
+ for (i = 0; i < tagname.size(); ++i) {
+ if (tagname[i] < 'A' || tagname[i] > 'Z') {
+ break;
+ }
+ }
+ if (i != 6 || tagname.size() <= 7 || tagname[6] != '+')
+ return tagname;
+ return tagname.substr(7);
+}
+
+/**
+ * Extract all the useful information from the GfxFont object
+ */
+FontData::FontData(FontPtr font)
+{
+ // Level one parsing is taking the data from the PDF font, although this
+ // information is almost always missing. Perhaps sometimes it's not.
+ found = false;
+
+ // Style: italic, oblique, normal
+ style = font->isItalic() ? "italic" : "";
+
+ // Weight: normal, bold, etc
+ weight = "normal";
+ switch (font->getWeight()) {
+ case GfxFont::WeightNotDefined:
+ break;
+ case GfxFont::W400:
+ weight = "normal";
+ break;
+ case GfxFont::W700:
+ weight = "bold";
+ break;
+ default:
+ weight = std::to_string(font->getWeight() * 100);
+ break;
+ }
+
+ // Stretch: condensed or expanded
+ stretch = "";
+ switch (font->getStretch()) {
+ case GfxFont::UltraCondensed:
+ stretch = "ultra-condensed";
+ break;
+ case GfxFont::ExtraCondensed:
+ stretch = "extra-condensed";
+ break;
+ case GfxFont::Condensed:
+ stretch = "condensed";
+ break;
+ case GfxFont::SemiCondensed:
+ stretch = "semi-condensed";
+ break;
+ case GfxFont::Normal:
+ stretch = "normal";
+ break;
+ case GfxFont::SemiExpanded:
+ stretch = "semi-expanded";
+ break;
+ case GfxFont::Expanded:
+ stretch = "expanded";
+ break;
+ case GfxFont::ExtraExpanded:
+ stretch = "extra-expanded";
+ break;
+ case GfxFont::UltraExpanded:
+ stretch = "ultra-expanded";
+ break;
+ }
+
+ name = getNameWithoutSubsetTag(font);
+ // Use this when min-poppler version is newer:
+ // name = font->getNameWithoutSubsetTag();
+
+ // Embeded CID Fonts don't have family names
+ if (!font->getFamily())
+ return;
+
+ family = font->getFamily()->c_str();
+
+ // Level two parsing, we break off the font description part of the name
+ // which often contains font data and use it as a pango font description.
+ auto desc_str = family;
+ auto pos = name.find("-");
+ if (pos != std::string::npos) {
+ // Insert spaces where we see capital letters.
+ std::stringstream ret;
+ auto str = name.substr(pos + 1, name.size());
+ for (char l : str) {
+ if (l >= 'A' && l <= 'Z')
+ ret << " ";
+ ret << l;
+ }
+ desc_str = desc_str + ret.str();
+ }
+
+ // Now we pull data out of the description.
+ if (auto desc = pango_font_description_from_string(desc_str.c_str())) {
+ auto new_family = pango_font_description_get_family(desc);
+ if (FontFactory::get().hasFontFamily(new_family)) {
+ family = new_family;
+
+ // Style from pango description
+ switch (pango_font_description_get_style(desc)) {
+ case PANGO_STYLE_ITALIC:
+ style = "italic";
+ break;
+ case PANGO_STYLE_OBLIQUE:
+ style = "oblique";
+ break;
+ }
+
+ // Weight from pango description
+ auto pw = pango_font_description_get_weight(desc);
+ if (pw != PANGO_WEIGHT_NORMAL) {
+ weight = std::to_string(pw); // Number 100-1000
+ }
+
+ // Stretch from pango description
+ switch (pango_font_description_get_stretch(desc)) {
+ case PANGO_STRETCH_ULTRA_CONDENSED:
+ stretch = "ultra-condensed";
+ break;
+ case PANGO_STRETCH_EXTRA_CONDENSED:
+ stretch = "extra-condensed";
+ break;
+ case PANGO_STRETCH_CONDENSED:
+ stretch = "condensed";
+ break;
+ case PANGO_STRETCH_SEMI_CONDENSED:
+ stretch = "semi-condensed";
+ break;
+ case PANGO_STRETCH_SEMI_EXPANDED:
+ stretch = "semi-expanded";
+ break;
+ case PANGO_STRETCH_EXPANDED:
+ stretch = "expanded";
+ break;
+ case PANGO_STRETCH_EXTRA_EXPANDED:
+ stretch = "extra-expanded";
+ break;
+ case PANGO_STRETCH_ULTRA_EXPANDED:
+ stretch = "ultra-expanded";
+ break;
+ }
+
+ // variant = TODO Convert to variant pango_font_description_get_variant(desc)
+
+ found = true;
+ // All information has been processed, don't over-write with level three.
+ return;
+ }
+ // Sometimes it's possible to match the description string directly.
+ if (auto desc = pango_font_description_from_string(family.c_str())) {
+ auto new_family = pango_font_description_get_family(desc);
+ if (FontFactory::get().hasFontFamily(new_family)) {
+ family = new_family;
+ }
+ }
+ }
+
+ found = FontFactory::get().hasFontFamily(family);
+ // TODO: If !found we could suggest a substitute
+
+ // Level three parsing, we take our name and attempt to match known style names
+ // Copy id-name stored in PDF and make it lower case and strip whitespaces
+ std::string source = name;
+ transform(source.begin(), source.end(), source.begin(), ::tolower);
+ source.erase(std::remove_if(source.begin(), source.end(), ::isspace), source.end());
+ auto contains = [=](const std::string &other) { return source.find(other) != std::string::npos; };
+
+ if (contains("italic") || contains("slanted")) {
+ style = "italic";
+ } else if (contains("oblique")) {
+ style = "oblique";
+ }
+
+ // Ordered by string matching pass through.
+ static std::map<std::string, std::string> weights{
+ // clang-format off
+ {"bold", "bold"},
+ {"ultrabold", "800"},
+ {"extrabold", "800"},
+ {"demibold", "600"},
+ {"semibold", "600"},
+ {"thin", "100"},
+ {"ultralight", "200"},
+ {"extralight", "200"},
+ {"light", "300"},
+ {"black", "900"},
+ {"heavy", "900"},
+ {"medium", "500"},
+ {"book", "normal"},
+ {"regular", "normal"},
+ {"roman", "normal"},
+ {"normal", "normal"},
+ // clang-format on
+ };
+ // Apply the font weight translations
+ for (auto w : weights) {
+ if (contains(w.first))
+ weight = w.second;
+ }
+
+ static std::map<std::string, std::string> stretches{
+ // clang-format off
+ {"ultracondensed", "ultra-condensed"},
+ {"extracondensed", "extra-condensed"},
+ {"semicondensed", "semi-condensed"},
+ {"condensed", "condensed"},
+ {"ultraexpanded", "ultra-expanded"},
+ {"extraexpanded", "extra-expanded"},
+ {"semiexpanded", "semi-expanded"},
+ {"expanded", "expanded"},
+ // clang-format on
+ };
+ // Apply the font weight translations
+ for (auto s : stretches) {
+ if (contains(s.first))
+ stretch = s.second;
+ }
+}
+
+/*
+ MatchingChars
+ Count for how many characters s1 matches sp taking into account
+ that a space in sp may be removed or replaced by some other tokens
+ specified in the code. (Bug LP #179589)
+*/
+static size_t MatchingChars(std::string s1, std::string sp)
+{
+ size_t is = 0;
+ size_t ip = 0;
+
+ while (is < s1.length() && ip < sp.length()) {
+ if (s1[is] == sp[ip]) {
+ is++;
+ ip++;
+ } else if (sp[ip] == ' ') {
+ ip++;
+ if (s1[is] == '_') { // Valid matches to spaces in sp.
+ is++;
+ }
+ } else {
+ break;
+ }
+ }
+ return ip;
+}
+
+/*
+ * Scan the available fonts to find the font name that best match.
+ *
+ * If nothing can be matched, returns an empty string.
+ */
+std::string FontData::getSubstitute() const
+{
+ if (found)
+ return "";
+
+ double bestMatch = 0;
+ std::string bestFontname = "";
+
+ for (auto fontname : FontFactory::get().GetAllFontNames()) {
+ // At least the first word of the font name should match.
+ size_t minMatch = fontname.find(" ");
+ if (minMatch == std::string::npos) {
+ minMatch = fontname.length();
+ }
+
+ size_t Match = MatchingChars(family, fontname);
+ if (Match >= minMatch) {
+ double relMatch = (float)Match / (fontname.length() + family.length());
+ if (relMatch > bestMatch) {
+ bestMatch = relMatch;
+ bestFontname = fontname;
+ }
+ }
+ }
+ return bestFontname.empty() ? "Arial" : bestFontname;
+}
+
+std::string FontData::getSpecification() const
+{
+ return family + (style.empty() ? "" : "-" + style);
+}
+
+//------------------------------------------------------------------------
+// scanFonts from FontInfo.cc
+//------------------------------------------------------------------------
+
+void _getFontsRecursive(std::shared_ptr<PDFDoc> pdf_doc, Dict *resources, const FontList &fontsList,
+ std::set<int> &visitedObjects, int page)
+{
+ auto xref = pdf_doc->getXRef();
+
+ InkFontDict *fontDict = nullptr;
+ const Object &obj1 = resources->lookupNF("Font");
+ if (obj1.isRef()) {
+ Object obj2 = obj1.fetch(xref);
+ if (obj2.isDict()) {
+ auto r = obj1.getRef();
+ fontDict = new InkFontDict(xref, &r, obj2.getDict());
+ }
+ } else if (obj1.isDict()) {
+ fontDict = new InkFontDict(xref, nullptr, obj1.getDict());
+ }
+
+ if (fontDict) {
+ for (int i = 0; i < fontDict->getNumFonts(); ++i) {
+ auto font = fontDict->getFont(i);
+ if (fontsList->find(font) == fontsList->end()) {
+ // Create new font data
+ fontsList->emplace(font, FontData(font));
+ }
+ fontsList->at(font).pages.insert(page);
+ }
+ }
+
+ // recursively scan any resource dictionaries in objects in this resource dictionary
+ const char *resTypes[] = {"XObject", "Pattern"};
+ for (const char *resType : resTypes) {
+ Object objDict = resources->lookup(resType);
+ if (!objDict.isDict())
+ continue;
+
+ for (int i = 0; i < objDict.dictGetLength(); ++i) {
+ Ref obj2Ref;
+ const Object obj2 = objDict.getDict()->getVal(i, &obj2Ref);
+ if (obj2Ref != Ref::INVALID() && !visitedObjects.insert(obj2Ref.num).second)
+ continue;
+
+ if (!obj2.isStream())
+ continue;
+
+ Ref resourcesRef;
+ const Object resObj = obj2.streamGetDict()->lookup("Resources", &resourcesRef);
+ if (resourcesRef != Ref::INVALID() && !visitedObjects.insert(resourcesRef.num).second)
+ continue;
+
+ if (resObj.isDict() && resObj.getDict() != resources) {
+ _getFontsRecursive(pdf_doc, resObj.getDict(), fontsList, visitedObjects, page);
+ }
+ }
+ }
+}
+
+FontList getPdfFonts(std::shared_ptr<PDFDoc> pdf_doc)
+{
+ auto fontsList = std::make_shared<std::map<FontPtr, FontData>>();
+ auto count = pdf_doc->getCatalog()->getNumPages();
+ std::set<int> visitedObjects;
+
+ for (auto page_num = 1; page_num <= count; page_num++) {
+ auto page = pdf_doc->getCatalog()->getPage(page_num);
+ auto resources = page->getResourceDict();
+ _getFontsRecursive(pdf_doc, resources, fontsList, visitedObjects, page_num);
+ }
+ return fontsList;
+}
+
+
+std::string getDictString(Dict *dict, const char *key)
+{
+ Object obj = dict->lookup(key);
+
+ if (!obj.isString()) {
+ return "";
+ }
+
+ const GooString *value = obj.getString();
+ if (value->hasUnicodeMarker()) {
+ return g_convert(value->getCString () + 2, value->getLength () - 2,
+ "UTF-8", "UTF-16BE", NULL, NULL, NULL);
+ } else if (value->hasUnicodeMarkerLE()) {
+ return g_convert(value->getCString () + 2, value->getLength () - 2,
+ "UTF-8", "UTF-16LE", NULL, NULL, NULL);
+ }
+ return value->toStr();
+}
+
+
diff --git a/src/extension/internal/pdfinput/poppler-utils.h b/src/extension/internal/pdfinput/poppler-utils.h
new file mode 100644
index 0000000..3cdec07
--- /dev/null
+++ b/src/extension/internal/pdfinput/poppler-utils.h
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * PDF parsing utilities for libpoppler.
+ *//*
+ * Authors:
+ * Martin Owens
+ *
+ * Copyright (C) 2022 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef POPPLER_UTILS_H
+#define POPPLER_UTILS_H
+
+#include <map>
+#include <memory>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "poppler-transition-api.h"
+
+namespace Geom {
+class Affine;
+}
+class Dict;
+class FNVHash;
+class GfxFont;
+class GfxState;
+class Object;
+class PDFDoc;
+class Ref;
+class XRef;
+
+Geom::Affine stateToAffine(GfxState *state);
+Geom::Affine ctmToAffine(const double *ctm);
+
+void ctmout(const char *label, const double *ctm);
+void affout(const char *label, Geom::Affine affine);
+
+#if POPPLER_CHECK_VERSION(22, 4, 0)
+typedef std::shared_ptr<GfxFont> FontPtr;
+#else
+typedef GfxFont *FontPtr;
+#endif
+
+class FontData
+{
+public:
+ FontData(FontPtr font);
+ std::string getSubstitute() const;
+ std::string getSpecification() const;
+
+ bool found = false;
+
+ std::unordered_set<int> pages;
+ std::string name;
+ std::string family;
+
+ std::string style;
+ std::string weight;
+ std::string stretch;
+ std::string variation;
+
+private:
+ void _parseStyle();
+};
+
+typedef std::shared_ptr<std::map<FontPtr, FontData>> FontList;
+
+FontList getPdfFonts(std::shared_ptr<PDFDoc> pdf_doc);
+std::string getDictString(Dict *dict, const char *key);
+
+// Replacate poppler FontDict
+class InkFontDict
+{
+public:
+ // Build the font dictionary, given the PDF font dictionary.
+ InkFontDict(XRef *xref, Ref *fontDictRef, Dict *fontDict);
+
+ // Iterative access.
+ int getNumFonts() const { return fonts.size(); }
+
+ // Get the specified font.
+ FontPtr lookup(const char *tag) const;
+ FontPtr getFont(int i) const { return fonts[i]; }
+ std::vector<FontPtr> fonts;
+
+private:
+ int hashFontObject(Object *obj);
+ void hashFontObject1(const Object *obj, FNVHash *h);
+};
+
+#endif /* POPPLER_UTILS_H */
diff --git a/src/extension/internal/pdfinput/svg-builder.cpp b/src/extension/internal/pdfinput/svg-builder.cpp
new file mode 100644
index 0000000..fbab2ff
--- /dev/null
+++ b/src/extension/internal/pdfinput/svg-builder.cpp
@@ -0,0 +1,2311 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Native PDF import using libpoppler.
+ *
+ * Authors:
+ * miklos erdelyi
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#include <string>
+#include <locale>
+#include <codecvt>
+
+#ifdef HAVE_POPPLER
+#define USE_CMS
+
+#include "Function.h"
+#include "GfxFont.h"
+#include "GfxState.h"
+#include "GlobalParams.h"
+#include "Page.h"
+#include "Stream.h"
+#include "UnicodeMap.h"
+#include "color.h"
+#include "display/cairo-utils.h"
+#include "display/nr-filter-utils.h"
+#include "document.h"
+#include "extract-uri.h"
+#include "libnrtype/font-factory.h"
+#include "libnrtype/font-instance.h"
+#include "object/color-profile.h"
+#include "object/sp-defs.h"
+#include "object/sp-item-group.h"
+#include "object/sp-namedview.h"
+#include "pdf-parser.h"
+#include "pdf-utils.h"
+#include "png.h"
+#include "poppler-cairo-font-engine.h"
+#include "profile-manager.h"
+#include "svg-builder.h"
+#include "svg/css-ostringstream.h"
+#include "svg/path-string.h"
+#include "svg/svg-color.h"
+#include "svg/svg.h"
+#include "util/units.h"
+#include "xml/document.h"
+#include "xml/node.h"
+#include "xml/repr.h"
+#include "xml/sp-css-attr.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+//#define IFTRACE(_code) _code
+#define IFTRACE(_code)
+
+#define TRACE(_args) IFTRACE(g_print _args)
+
+
+/**
+ * \class SvgBuilder
+ *
+ */
+
+SvgBuilder::SvgBuilder(SPDocument *document, gchar *docname, XRef *xref)
+{
+ _is_top_level = true;
+ _doc = document;
+ _docname = docname;
+ _xref = xref;
+ _xml_doc = _doc->getReprDoc();
+ _container = _root = _doc->getReprRoot();
+ _init();
+
+ // Set default preference settings
+ _preferences = _xml_doc->createElement("svgbuilder:prefs");
+ _preferences->setAttribute("embedImages", "1");
+}
+
+SvgBuilder::SvgBuilder(SvgBuilder *parent, Inkscape::XML::Node *root) {
+ _is_top_level = false;
+ _doc = parent->_doc;
+ _docname = parent->_docname;
+ _xref = parent->_xref;
+ _xml_doc = parent->_xml_doc;
+ _preferences = parent->_preferences;
+ _container = this->_root = root;
+ _init();
+}
+
+SvgBuilder::~SvgBuilder()
+{
+ if (_clip_history) {
+ delete _clip_history;
+ _clip_history = nullptr;
+ }
+}
+
+void SvgBuilder::_init() {
+ _clip_history = new ClipHistoryEntry();
+ _css_font = nullptr;
+ _font_specification = nullptr;
+ _in_text_object = false;
+ _invalidated_style = true;
+ _width = 0;
+ _height = 0;
+
+ _node_stack.push_back(_container);
+}
+
+/**
+ * We're creating a multi-page document, push page number.
+ */
+void SvgBuilder::pushPage(const std::string &label, GfxState *state)
+{
+ // Move page over by the last page width
+ if (_page && this->_width) {
+ int gap = 20;
+ _page_left += this->_width + gap;
+ // TODO: A more interesting page layout could be implemented here.
+ }
+ _page_num += 1;
+ _page_offset = true;
+
+ if (_page) {
+ Inkscape::GC::release(_page);
+ }
+ _page = _xml_doc->createElement("inkscape:page");
+ _page->setAttributeSvgDouble("x", _page_left);
+ _page->setAttributeSvgDouble("y", _page_top);
+
+ // Page translation is somehow lost in the way we're using poppler and the state management
+ // Applying the state directly doesn't work as many of the flips/rotates are baked in already.
+ // The translation alone must be added back to the page position so items end up in the
+ // right places. If a better method is found, please replace this code.
+ auto st = stateToAffine(state);
+ auto tr = st.translation();
+ if (st[0] < 0 || st[2] < 0) { // Flip or rotate in X
+ tr[Geom::X] = -tr[Geom::X] + state->getPageWidth();
+ }
+ if (st[1] < 0 || st[3] < 0) { // Flip or rotate in Y
+ tr[Geom::Y] = -tr[Geom::Y] + state->getPageHeight();
+ }
+ // Note: This translation is very rare in pdf files, most of the time their initial state doesn't contain
+ // any real translations, just a flip and the because of our GfxState constructor, the pt/px scale.
+ // Please use an example pdf which produces a non-zero translation in order to change this code!
+ _page_affine = Geom::Translate(tr).inverse() * Geom::Translate(_page_left, _page_top);
+
+ if (!label.empty()) {
+ _page->setAttribute("inkscape:label", label);
+ }
+ auto _nv = _doc->getNamedView()->getRepr();
+ _nv->appendChild(_page);
+
+ // No OptionalContentGroups means no layers, so make a default layer for this page.
+ if (_ocgs.empty()) {
+ // Reset to root
+ while (_container != _root) {
+ _popGroup();
+ }
+ _pushGroup();
+ setAsLayer(label.c_str(), true);
+ }
+}
+
+void SvgBuilder::setDocumentSize(double width, double height) {
+ this->_width = width;
+ this->_height = height;
+
+ if (_page_num < 2) {
+ _root->setAttributeSvgDouble("width", width);
+ _root->setAttributeSvgDouble("height", height);
+ }
+ if (_page) {
+ _page->setAttributeSvgDouble("width", width);
+ _page->setAttributeSvgDouble("height", height);
+ }
+}
+
+/**
+ * Crop to this bounding box, do this before setMargins() but after setDocumentSize
+ */
+void SvgBuilder::cropPage(const Geom::Rect &bbox)
+{
+ if (_container == _root) {
+ // We're not going to crop when there's PDF Layers
+ return;
+ }
+ auto box = bbox * _page_affine;
+ Inkscape::CSSOStringStream val;
+ val << "M" << box.left() << " " << box.top()
+ << "H" << box.right() << "V" << box.bottom()
+ << "H" << box.left() << "Z";
+ auto clip_path = _createClip(val.str(), Geom::identity(), false);
+ gchar *urltext = g_strdup_printf("url(#%s)", clip_path->attribute("id"));
+ _container->setAttribute("clip-path", urltext);
+ g_free(urltext);
+}
+
+/**
+ * Calculate the page margin size based on the pdf settings.
+ */
+void SvgBuilder::setMargins(const Geom::Rect &page, const Geom::Rect &margins, const Geom::Rect &bleed)
+{
+ if (page.width() != _width || page.height() != _height) {
+ // We need to re-set the page size and change the page_affine.
+ _page_affine *= Geom::Translate(-page.left(), -page.top());
+ setDocumentSize(page.width(), page.height());
+ }
+ if (page != margins) {
+ if (!_page) {
+ g_warning("Can not store PDF margins in bare document.");
+ return;
+ }
+ // Calculate the margins from the pdf art box.
+ Inkscape::CSSOStringStream val;
+ val << margins.top() - page.top() << " "
+ << page.right() - margins.right() << " "
+ << page.bottom() - margins.bottom() << " "
+ << margins.left() - page.left();
+ _page->setAttribute("margin", val.str());
+ }
+ if (page != bleed) {
+ if (!_page) {
+ g_warning("Can not store PDF bleed in bare document.");
+ return;
+ }
+ Inkscape::CSSOStringStream val;
+ val << page.top() - bleed.top() << " "
+ << bleed.right() - page.right() << " "
+ << bleed.bottom() - page.bottom() << " "
+ << page.left() - bleed.left();
+ _page->setAttribute("bleed", val.str());
+ }
+}
+
+/**
+ * \brief Sets groupmode of the current container to 'layer' and sets its label if given
+ */
+void SvgBuilder::setAsLayer(const char *layer_name, bool visible)
+{
+ _container->setAttribute("inkscape:groupmode", "layer");
+ if (layer_name) {
+ _container->setAttribute("inkscape:label", layer_name);
+ }
+ if (!visible) {
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ sp_repr_css_set_property(css, "display", "none");
+ sp_repr_css_change(_container, css, "style");
+ }
+}
+
+/**
+ * \brief Sets the current container's opacity
+ */
+void SvgBuilder::setGroupOpacity(double opacity) {
+ _container->setAttributeSvgDouble("opacity", CLAMP(opacity, 0.0, 1.0));
+}
+
+void SvgBuilder::saveState(GfxState *state)
+{
+ _clip_history = _clip_history->save();
+}
+
+void SvgBuilder::restoreState(GfxState *state) {
+ _clip_history = _clip_history->restore();
+
+ if (!_mask_groups.empty()) {
+ GfxState *mask_state = _mask_groups.back();
+ if (state == mask_state) {
+ popGroup(state);
+ _mask_groups.pop_back();
+ }
+ }
+ while (_clip_groups > 0) {
+ popGroup(nullptr);
+ _clip_groups--;
+ }
+}
+
+Inkscape::XML::Node *SvgBuilder::_pushContainer(const char *name)
+{
+ return _pushContainer(_xml_doc->createElement(name));
+}
+
+Inkscape::XML::Node *SvgBuilder::_pushContainer(Inkscape::XML::Node *node)
+{
+ _node_stack.push_back(node);
+ _container = node;
+ // Clear the clip history
+ _clip_history = _clip_history->save(true);
+ return node;
+}
+
+Inkscape::XML::Node *SvgBuilder::_popContainer()
+{
+ Inkscape::XML::Node *node = nullptr;
+ if ( _node_stack.size() > 1 ) {
+ node = _node_stack.back();
+ _node_stack.pop_back();
+ _container = _node_stack.back(); // Re-set container
+ _clip_history = _clip_history->restore();
+ } else {
+ TRACE(("_popContainer() called when stack is empty\n"));
+ node = _root;
+ }
+ return node;
+}
+
+/**
+ * Create an svg element and append it to the current container object.
+ */
+Inkscape::XML::Node *SvgBuilder::_addToContainer(const char *name)
+{
+ Inkscape::XML::Node *node = _xml_doc->createElement(name);
+ _addToContainer(node);
+ return node;
+}
+
+/**
+ * Append the given xml element to the current container object, clipping and masking as needed.
+ *
+ * if release is true (default), the XML node will be GC released too.
+ */
+void SvgBuilder::_addToContainer(Inkscape::XML::Node *node, bool release)
+{
+ if (!node->parent()) {
+ _container->appendChild(node);
+ }
+ if (release) {
+ Inkscape::GC::release(node);
+ }
+}
+
+void SvgBuilder::_setClipPath(Inkscape::XML::Node *node)
+{
+ if (_clip_history->hasClipPath() || _clip_text) {
+ auto tr = Geom::identity();
+ if (auto attr = node->attribute("transform")) {
+ sp_svg_transform_read(attr, &tr);
+ }
+ if (auto clip_path = _getClip(tr)) {
+ gchar *urltext = g_strdup_printf("url(#%s)", clip_path->attribute("id"));
+ node->setAttribute("clip-path", urltext);
+ g_free(urltext);
+ }
+ }
+}
+
+Inkscape::XML::Node *SvgBuilder::_pushGroup()
+{
+ Inkscape::XML::Node *saved_container = _container;
+ Inkscape::XML::Node *node = _pushContainer("svg:g");
+ saved_container->appendChild(node);
+ Inkscape::GC::release(node);
+ return _container;
+}
+
+Inkscape::XML::Node *SvgBuilder::_popGroup()
+{
+ if (_container != _root) { // Pop if the current container isn't root
+ _popContainer();
+ }
+ return _container;
+}
+
+static gchar *svgConvertRGBToText(double r, double g, double b) {
+ using Inkscape::Filters::clamp;
+ static gchar tmp[1023] = {0};
+ snprintf(tmp, 1023,
+ "#%02x%02x%02x",
+ clamp(SP_COLOR_F_TO_U(r)),
+ clamp(SP_COLOR_F_TO_U(g)),
+ clamp(SP_COLOR_F_TO_U(b)));
+ return (gchar *)&tmp;
+}
+
+static std::string svgConvertGfxRGB(GfxRGB *color)
+{
+ double r = (double)color->r / 65535.0;
+ double g = (double)color->g / 65535.0;
+ double b = (double)color->b / 65535.0;
+ return svgConvertRGBToText(r, g, b);
+}
+
+std::string SvgBuilder::convertGfxColor(const GfxColor *color, GfxColorSpace *space)
+{
+ std::string icc = "";
+ switch (space->getMode()) {
+ case csDeviceGray:
+ case csDeviceRGB:
+ case csDeviceCMYK:
+ icc = _icc_profile;
+ break;
+ case csICCBased:
+#if POPPLER_CHECK_VERSION(0, 90, 0)
+ auto icc_space = dynamic_cast<GfxICCBasedColorSpace *>(space);
+ icc = _getColorProfile(icc_space->getProfile().get());
+#else
+ g_warning("ICC profile ignored; libpoppler >= 0.90.0 required.");
+#endif
+ break;
+ }
+
+ GfxRGB rgb;
+ space->getRGB(color, &rgb);
+ auto rgb_color = svgConvertGfxRGB(&rgb);
+
+ if (!icc.empty()) {
+ Inkscape::CSSOStringStream icc_color;
+ icc_color << rgb_color << " icc-color(" << icc;
+ for (int i = 0; i < space->getNComps(); ++i) {
+ icc_color << ", " << colToDbl((*color).c[i]);
+ }
+ icc_color << ");";
+ return icc_color.str();
+ }
+
+ return rgb_color;
+}
+
+static void svgSetTransform(Inkscape::XML::Node *node, Geom::Affine matrix) {
+ if (node->attribute("clip-path")) {
+ g_error("Adding transform AFTER clipping path.");
+ }
+ node->setAttributeOrRemoveIfEmpty("transform", sp_svg_transform_write(matrix));
+}
+
+/**
+ * \brief Generates a SVG path string from poppler's data structure
+ */
+static gchar *svgInterpretPath(_POPPLER_CONST_83 GfxPath *path) {
+ Inkscape::SVG::PathString pathString;
+ for (int i = 0 ; i < path->getNumSubpaths() ; ++i ) {
+ _POPPLER_CONST_83 GfxSubpath *subpath = path->getSubpath(i);
+ if (subpath->getNumPoints() > 0) {
+ pathString.moveTo(subpath->getX(0), subpath->getY(0));
+ int j = 1;
+ while (j < subpath->getNumPoints()) {
+ if (subpath->getCurve(j)) {
+ pathString.curveTo(subpath->getX(j), subpath->getY(j),
+ subpath->getX(j+1), subpath->getY(j+1),
+ subpath->getX(j+2), subpath->getY(j+2));
+
+ j += 3;
+ } else {
+ pathString.lineTo(subpath->getX(j), subpath->getY(j));
+ ++j;
+ }
+ }
+ if (subpath->isClosed()) {
+ pathString.closePath();
+ }
+ }
+ }
+
+ return g_strdup(pathString.c_str());
+}
+
+/**
+ * \brief Sets stroke style from poppler's GfxState data structure
+ * Uses the given SPCSSAttr for storing the style properties
+ */
+void SvgBuilder::_setStrokeStyle(SPCSSAttr *css, GfxState *state) {
+ // Stroke color/pattern
+ auto space = state->getStrokeColorSpace();
+ if (space->getMode() == csPattern) {
+ gchar *urltext = _createPattern(state->getStrokePattern(), state, true);
+ sp_repr_css_set_property(css, "stroke", urltext);
+ if (urltext) {
+ g_free(urltext);
+ }
+ } else {
+ sp_repr_css_set_property(css, "stroke", convertGfxColor(state->getStrokeColor(), space).c_str());
+ }
+
+ // Opacity
+ Inkscape::CSSOStringStream os_opacity;
+ os_opacity << state->getStrokeOpacity();
+ sp_repr_css_set_property(css, "stroke-opacity", os_opacity.str().c_str());
+
+ // Line width
+ Inkscape::CSSOStringStream os_width;
+ double lw = state->getLineWidth();
+ // emit a stroke which is 1px in toplevel user units
+ os_width << (lw > 0.0 ? lw : 1.0);
+ sp_repr_css_set_property(css, "stroke-width", os_width.str().c_str());
+
+ // Line cap
+ switch (state->getLineCap()) {
+ case 0:
+ sp_repr_css_set_property(css, "stroke-linecap", "butt");
+ break;
+ case 1:
+ sp_repr_css_set_property(css, "stroke-linecap", "round");
+ break;
+ case 2:
+ sp_repr_css_set_property(css, "stroke-linecap", "square");
+ break;
+ }
+
+ // Line join
+ switch (state->getLineJoin()) {
+ case 0:
+ sp_repr_css_set_property(css, "stroke-linejoin", "miter");
+ break;
+ case 1:
+ sp_repr_css_set_property(css, "stroke-linejoin", "round");
+ break;
+ case 2:
+ sp_repr_css_set_property(css, "stroke-linejoin", "bevel");
+ break;
+ }
+
+ // Miterlimit
+ Inkscape::CSSOStringStream os_ml;
+ os_ml << state->getMiterLimit();
+ sp_repr_css_set_property(css, "stroke-miterlimit", os_ml.str().c_str());
+
+ // Line dash
+ int dash_length;
+ double dash_start;
+#if POPPLER_CHECK_VERSION(22, 9, 0)
+ const double *dash_pattern;
+ const std::vector<double> &dash = state->getLineDash(&dash_start);
+ dash_pattern = dash.data();
+ dash_length = dash.size();
+#else
+ double *dash_pattern;
+ state->getLineDash(&dash_pattern, &dash_length, &dash_start);
+#endif
+ if ( dash_length > 0 ) {
+ Inkscape::CSSOStringStream os_array;
+ for ( int i = 0 ; i < dash_length ; i++ ) {
+ os_array << dash_pattern[i];
+ if (i < (dash_length - 1)) {
+ os_array << ",";
+ }
+ }
+ sp_repr_css_set_property(css, "stroke-dasharray", os_array.str().c_str());
+
+ Inkscape::CSSOStringStream os_offset;
+ os_offset << dash_start;
+ sp_repr_css_set_property(css, "stroke-dashoffset", os_offset.str().c_str());
+ } else {
+ sp_repr_css_set_property(css, "stroke-dasharray", "none");
+ sp_repr_css_set_property(css, "stroke-dashoffset", nullptr);
+ }
+}
+
+/**
+ * \brief Sets fill style from poppler's GfxState data structure
+ * Uses the given SPCSSAttr for storing the style properties.
+ */
+void SvgBuilder::_setFillStyle(SPCSSAttr *css, GfxState *state, bool even_odd) {
+
+ // Fill color/pattern
+ auto space = state->getFillColorSpace();
+ if (space->getMode() == csPattern) {
+ gchar *urltext = _createPattern(state->getFillPattern(), state);
+ sp_repr_css_set_property(css, "fill", urltext);
+ if (urltext) {
+ g_free(urltext);
+ }
+ } else {
+ sp_repr_css_set_property(css, "fill", convertGfxColor(state->getFillColor(), space).c_str());
+ }
+
+ // Opacity
+ Inkscape::CSSOStringStream os_opacity;
+ os_opacity << state->getFillOpacity();
+ sp_repr_css_set_property(css, "fill-opacity", os_opacity.str().c_str());
+
+ // Fill rule
+ sp_repr_css_set_property(css, "fill-rule", even_odd ? "evenodd" : "nonzero");
+}
+
+/**
+ * \brief Sets blend style properties from poppler's GfxState data structure
+ * \update a SPCSSAttr with all mix-blend-mode set
+ */
+void SvgBuilder::_setBlendMode(Inkscape::XML::Node *node, GfxState *state)
+{
+ SPCSSAttr *css = sp_repr_css_attr(node, "style");
+ GfxBlendMode blendmode = state->getBlendMode();
+ if (blendmode) {
+ sp_repr_css_set_property(css, "mix-blend-mode", enum_blend_mode[blendmode].key);
+ }
+ Glib::ustring value;
+ sp_repr_css_write_string(css, value);
+ node->setAttributeOrRemoveIfEmpty("style", value);
+ sp_repr_css_attr_unref(css);
+}
+
+void SvgBuilder::_setTransform(Inkscape::XML::Node *node, GfxState *state, Geom::Affine extra)
+{
+ svgSetTransform(node, extra * stateToAffine(state) * _page_affine);
+}
+
+/**
+ * \brief Sets style properties from poppler's GfxState data structure
+ * \return SPCSSAttr with all the relevant properties set
+ */
+SPCSSAttr *SvgBuilder::_setStyle(GfxState *state, bool fill, bool stroke, bool even_odd) {
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ if (fill) {
+ _setFillStyle(css, state, even_odd);
+ } else {
+ sp_repr_css_set_property(css, "fill", "none");
+ }
+
+ if (stroke) {
+ _setStrokeStyle(css, state);
+ } else {
+ sp_repr_css_set_property(css, "stroke", "none");
+ }
+
+ return css;
+}
+
+/**
+ * Returns the CSSAttr of the previously added path if it's exactly
+ * the same path AND is missing the fill or stroke that is now being painted.
+ */
+bool SvgBuilder::shouldMergePath(bool is_fill, const std::string &path)
+{
+ auto prev = _container->lastChild();
+ if (!prev || prev->attribute("mask"))
+ return false;
+
+ auto prev_d = prev->attribute("d");
+ if (!prev_d)
+ return false;
+
+ if (path != prev_d && path != std::string(prev_d) + " Z")
+ return false;
+
+ auto prev_css = sp_repr_css_attr(prev, "style");
+ std::string prev_val = sp_repr_css_property(prev_css, is_fill ? "fill" : "stroke", "");
+ // Very specific check excludes paths created elsewhere who's fill/stroke was unset.
+ return prev_val == "none";
+}
+
+/**
+ * Set the fill XOR stroke of the previously added path, if that path
+ * is missing the given attribute AND the path is exactly the same.
+ *
+ * This effectively merges the two objects and is an 'interpretation' step.
+ */
+bool SvgBuilder::mergePath(GfxState *state, bool is_fill, const std::string &path, bool even_odd)
+{
+ if (shouldMergePath(is_fill, path)) {
+ auto prev = _container->lastChild();
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ if (is_fill) {
+ _setFillStyle(css, state, even_odd);
+ // Fill after stroke indicates a different paint order.
+ sp_repr_css_set_property(css, "paint-order", "stroke fill markers");
+ } else {
+ _setStrokeStyle(css, state);
+ }
+ sp_repr_css_change(prev, css, "style");
+ sp_repr_css_attr_unref(css);
+ return true;
+ }
+ return false;
+}
+
+/**
+ * \brief Emits the current path in poppler's GfxState data structure
+ * Can be used to do filling and stroking at once.
+ *
+ * \param fill whether the path should be filled
+ * \param stroke whether the path should be stroked
+ * \param even_odd whether the even-odd rule should be used when filling the path
+ */
+void SvgBuilder::addPath(GfxState *state, bool fill, bool stroke, bool even_odd) {
+ gchar *pathtext = svgInterpretPath(state->getPath());
+
+ if (!pathtext)
+ return;
+
+ if (!strlen(pathtext) || (fill != stroke && mergePath(state, fill, pathtext, even_odd))) {
+ g_free(pathtext);
+ return;
+ }
+
+ Inkscape::XML::Node *path = _addToContainer("svg:path");
+ path->setAttribute("d", pathtext);
+ g_free(pathtext);
+
+ // Set style
+ SPCSSAttr *css = _setStyle(state, fill, stroke, even_odd);
+ sp_repr_css_change(path, css, "style");
+ sp_repr_css_attr_unref(css);
+ _setBlendMode(path, state);
+ _setTransform(path, state);
+ _setClipPath(path);
+}
+
+void SvgBuilder::addClippedFill(GfxShading *shading, const Geom::Affine shading_tr)
+{
+ if (_clip_history->getClipPath()) {
+ addShadedFill(shading, shading_tr, _clip_history->getClipPath(), _clip_history->getAffine(),
+ _clip_history->getClipType() == clipEO);
+ }
+}
+
+/**
+ * \brief Emits the current path in poppler's GfxState data structure
+ * The path is set to be filled with the given shading.
+ */
+void SvgBuilder::addShadedFill(GfxShading *shading, const Geom::Affine shading_tr, GfxPath *path, const Geom::Affine tr,
+ bool even_odd)
+{
+ auto prev = _container->lastChild();
+ gchar *pathtext = svgInterpretPath(path);
+
+ // Create a new gradient object before comitting to creating a path for it
+ // And package it into a css bundle which can be applied
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ // We remove the shape's affine to adjust the gradient back into place
+ gchar *id = _createGradient(shading, shading_tr * tr.inverse(), true);
+ if (id) {
+ gchar *urltext = g_strdup_printf ("url(#%s)", id);
+ sp_repr_css_set_property(css, "fill", urltext);
+ g_free(urltext);
+ g_free(id);
+ } else {
+ sp_repr_css_attr_unref(css);
+ return;
+ }
+ if (even_odd) {
+ sp_repr_css_set_property(css, "fill-rule", "evenodd");
+ }
+ // Merge the style with the previous shape
+ if (shouldMergePath(true, pathtext)) {
+ // POSSIBLE: The gradientTransform might now incorrect if the
+ // state of the transformation was different between the two paths.
+ sp_repr_css_change(prev, css, "style");
+ g_free(pathtext);
+ return;
+ }
+
+ Inkscape::XML::Node *path_node = _addToContainer("svg:path");
+ path_node->setAttribute("d", pathtext);
+ g_free(pathtext);
+
+ // Don't add transforms to mask children.
+ if (std::string("svg:mask") != _container->name()) {
+ svgSetTransform(path_node, tr * _page_affine);
+ }
+
+ // Set the gradient into this new path.
+ sp_repr_css_set_property(css, "stroke", "none");
+ sp_repr_css_change(path_node, css, "style");
+ sp_repr_css_attr_unref(css);
+}
+
+/**
+ * \brief Clips to the current path set in GfxState
+ * \param state poppler's data structure
+ * \param even_odd whether the even-odd rule should be applied
+ */
+void SvgBuilder::setClip(GfxState *state, GfxClipType clip, bool is_bbox)
+{
+ // When there's already a clip path, we add clipping groups to handle them.
+ if (!is_bbox && _clip_history->hasClipPath() && !_clip_history->isCopied()) {
+ _pushContainer("svg:g");
+ _clip_groups++;
+ }
+ if (clip == clipNormal) {
+ _clip_history->setClip(state, clipNormal, is_bbox);
+ } else {
+ _clip_history->setClip(state, clipEO);
+ }
+}
+
+/**
+ * Return the active clip as a new xml node.
+ */
+Inkscape::XML::Node *SvgBuilder::_getClip(const Geom::Affine &node_tr)
+{
+ // In SVG the path-clip transforms are compounded, so we have to do extra work to
+ // pull transforms back out of the clipping object and set them. Otherwise this
+ // would all be a lot simpler.
+ if (_clip_text) {
+ auto node = _clip_text;
+
+ auto text_tr = Geom::identity();
+ if (auto attr = node->attribute("transform")) {
+ sp_svg_transform_read(attr, &text_tr);
+ node->removeAttribute("transform");
+ }
+
+ for (auto child = node->firstChild(); child; child = child->next()) {
+ Geom::Affine child_tr = text_tr * _page_affine * node_tr.inverse();
+ svgSetTransform(child, child_tr);
+ }
+
+ _clip_text = nullptr;
+ return node;
+ }
+ if (_clip_history->hasClipPath()) {
+ std::string clip_d = svgInterpretPath(_clip_history->getClipPath());
+ Geom::Affine tr = _clip_history->getAffine() * _page_affine * node_tr.inverse();
+ return _createClip(clip_d, tr, _clip_history->evenOdd());
+ }
+ return nullptr;
+}
+
+Inkscape::XML::Node *SvgBuilder::_createClip(const std::string &d, const Geom::Affine tr, bool even_odd)
+{
+ Inkscape::XML::Node *clip_path = _xml_doc->createElement("svg:clipPath");
+ clip_path->setAttribute("clipPathUnits", "userSpaceOnUse");
+
+ // Create the path
+ Inkscape::XML::Node *path = _xml_doc->createElement("svg:path");
+ path->setAttribute("d", d);
+ svgSetTransform(path, tr);
+
+ if (even_odd) {
+ path->setAttribute("clip-rule", "evenodd");
+ }
+ clip_path->appendChild(path);
+ Inkscape::GC::release(path);
+
+ // Append clipPath to defs and get id
+ _doc->getDefs()->getRepr()->appendChild(clip_path);
+ Inkscape::GC::release(clip_path);
+ return clip_path;
+}
+
+void SvgBuilder::beginMarkedContent(const char *name, const char *group)
+{
+ if (name && group && std::string(name) == "OC") {
+ auto layer_id = std::string("layer-") + group;
+ if (auto existing = _doc->getObjectById(layer_id)) {
+ if (existing->getRepr()->parent() == _container) {
+ _container = existing->getRepr();
+ _node_stack.push_back(_container);
+ } else {
+ g_warning("Unexpected marked content group in PDF!");
+ _pushGroup();
+ }
+ } else {
+ auto node = _pushGroup();
+ node->setAttribute("id", layer_id);
+ if (_ocgs.find(group) != _ocgs.end()) {
+ auto pair = _ocgs[group];
+ setAsLayer(pair.first.c_str(), pair.second);
+ }
+ }
+ } else {
+ auto node = _pushGroup();
+ if (group) {
+ node->setAttribute("id", std::string("group-") + group);
+ }
+ }
+}
+
+void SvgBuilder::addOptionalGroup(const std::string &oc, const std::string &label, bool visible)
+{
+ _ocgs[oc] = {label, visible};
+}
+
+void SvgBuilder::endMarkedContent()
+{
+ _popGroup();
+}
+
+void SvgBuilder::addColorProfile(unsigned char *profBuf, int length)
+{
+ cmsHPROFILE hp = cmsOpenProfileFromMem(profBuf, length);
+ if (!hp) {
+ g_warning("Failed to read ICCBased color space profile from PDF file.");
+ return;
+ }
+ _icc_profile = _getColorProfile(hp);
+}
+
+/**
+ * Return the color profile name if it's already been added
+ */
+std::string SvgBuilder::_getColorProfile(cmsHPROFILE hp)
+{
+ if (!hp)
+ return "";
+
+ // Cached name of this profile by reference
+ if (_icc_profiles.find(hp) != _icc_profiles.end())
+ return _icc_profiles[hp];
+
+ std::string name = Inkscape::ColorProfile::getNameFromProfile(hp);
+ Inkscape::ColorProfile::sanitizeName(name);
+
+ // Find the named profile in the document (if already added)
+ if (_doc->getProfileManager().find(name.c_str()))
+ return name;
+
+ // Add the profile, we've never seen it before.
+ cmsUInt32Number len = 0;
+ cmsSaveProfileToMem(hp, nullptr, &len);
+ auto buf = (unsigned char *)malloc(len * sizeof(unsigned char));
+ cmsSaveProfileToMem(hp, buf, &len);
+
+ Inkscape::XML::Node *icc_node = _xml_doc->createElement("svg:color-profile");
+ std::string label = Inkscape::ColorProfile::getNameFromProfile(hp);
+ icc_node->setAttribute("inkscape:label", label);
+ icc_node->setAttribute("name", name);
+
+ auto *base64String = g_base64_encode(buf, len);
+ auto icc_data = std::string("data:application/vnd.iccprofile;base64,") + base64String;
+ g_free(base64String);
+ icc_node->setAttributeOrRemoveIfEmpty("xlink:href", icc_data);
+ _doc->getDefs()->getRepr()->appendChild(icc_node);
+ Inkscape::GC::release(icc_node);
+
+ free(buf);
+ _icc_profiles[hp] = name;
+ return name;
+}
+
+/**
+ * \brief Checks whether the given pattern type can be represented in SVG
+ * Used by PdfParser to decide when to do fallback operations.
+ */
+bool SvgBuilder::isPatternTypeSupported(GfxPattern *pattern) {
+ if ( pattern != nullptr ) {
+ if ( pattern->getType() == 2 ) { // shading pattern
+ GfxShading *shading = (static_cast<GfxShadingPattern *>(pattern))->getShading();
+ int shadingType = shading->getType();
+ if ( shadingType == 2 || // axial shading
+ shadingType == 3 ) { // radial shading
+ return true;
+ }
+ return false;
+ } else if ( pattern->getType() == 1 ) { // tiling pattern
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * \brief Creates a pattern from poppler's data structure
+ * Handles linear and radial gradients. Creates a new PdfParser and uses it to
+ * build a tiling pattern.
+ * \return a url pointing to the created pattern
+ */
+gchar *SvgBuilder::_createPattern(GfxPattern *pattern, GfxState *state, bool is_stroke) {
+ gchar *id = nullptr;
+ if ( pattern != nullptr ) {
+ if ( pattern->getType() == 2 ) { // Shading pattern
+ GfxShadingPattern *shading_pattern = static_cast<GfxShadingPattern *>(pattern);
+ // construct a (pattern space) -> (current space) transform matrix
+ auto flip = Geom::Affine(1.0, 0.0, 0.0, -1.0, 0.0, _height);
+ auto pt = Geom::Scale(Inkscape::Util::Quantity::convert(1.0, "pt", "px"));
+ auto grad_affine = ctmToAffine(shading_pattern->getMatrix());
+ auto obj_affine = stateToAffine(state);
+ // SVG applies the object's affine on top of the gradient's affine,
+ // So we must remove the object affine to move it back into place.
+ auto affine = (grad_affine * pt * flip) * obj_affine.inverse();
+ id = _createGradient(shading_pattern->getShading(), affine, !is_stroke);
+ } else if ( pattern->getType() == 1 ) { // Tiling pattern
+ id = _createTilingPattern(static_cast<GfxTilingPattern*>(pattern), state, is_stroke);
+ }
+ } else {
+ return nullptr;
+ }
+ gchar *urltext = g_strdup_printf ("url(#%s)", id);
+ g_free(id);
+ return urltext;
+}
+
+/**
+ * \brief Creates a tiling pattern from poppler's data structure
+ * Creates a sub-page PdfParser and uses it to parse the pattern's content stream.
+ * \return id of the created pattern
+ */
+gchar *SvgBuilder::_createTilingPattern(GfxTilingPattern *tiling_pattern,
+ GfxState *state, bool is_stroke) {
+
+ Inkscape::XML::Node *pattern_node = _xml_doc->createElement("svg:pattern");
+ // Set pattern transform matrix
+ auto pat_matrix = ctmToAffine(tiling_pattern->getMatrix());
+ pattern_node->setAttributeOrRemoveIfEmpty("patternTransform", sp_svg_transform_write(pat_matrix));
+ pattern_node->setAttribute("patternUnits", "userSpaceOnUse");
+ // Set pattern tiling
+ // FIXME: don't ignore XStep and YStep
+ const double *bbox = tiling_pattern->getBBox();
+ pattern_node->setAttributeSvgDouble("x", 0.0);
+ pattern_node->setAttributeSvgDouble("y", 0.0);
+ pattern_node->setAttributeSvgDouble("width", bbox[2] - bbox[0]);
+ pattern_node->setAttributeSvgDouble("height", bbox[3] - bbox[1]);
+
+ // Convert BBox for PdfParser
+ PDFRectangle box;
+ box.x1 = bbox[0];
+ box.y1 = bbox[1];
+ box.x2 = bbox[2];
+ box.y2 = bbox[3];
+ // Create new SvgBuilder and sub-page PdfParser
+ SvgBuilder *pattern_builder = new SvgBuilder(this, pattern_node);
+ PdfParser *pdf_parser = new PdfParser(_xref, pattern_builder, tiling_pattern->getResDict(),
+ &box);
+ // Get pattern color space
+ GfxPatternColorSpace *pat_cs = (GfxPatternColorSpace *)( is_stroke ? state->getStrokeColorSpace()
+ : state->getFillColorSpace() );
+ // Set fill/stroke colors if this is an uncolored tiling pattern
+ GfxColorSpace *cs = nullptr;
+ if ( tiling_pattern->getPaintType() == 2 && ( cs = pat_cs->getUnder() ) ) {
+ GfxState *pattern_state = pdf_parser->getState();
+ pattern_state->setFillColorSpace(cs->copy());
+ pattern_state->setFillColor(state->getFillColor());
+ pattern_state->setStrokeColorSpace(cs->copy());
+ pattern_state->setStrokeColor(state->getFillColor());
+ }
+
+ // Generate the SVG pattern
+ pdf_parser->parse(tiling_pattern->getContentStream());
+
+ // Cleanup
+ delete pdf_parser;
+ delete pattern_builder;
+
+ // Append the pattern to defs
+ _doc->getDefs()->getRepr()->appendChild(pattern_node);
+ gchar *id = g_strdup(pattern_node->attribute("id"));
+ Inkscape::GC::release(pattern_node);
+
+ return id;
+}
+
+/**
+ * \brief Creates a linear or radial gradient from poppler's data structure
+ * \param shading poppler's data structure for the shading
+ * \param matrix gradient transformation, can be null
+ * \param for_shading true if we're creating this for a shading operator; false otherwise
+ * \return id of the created object
+ */
+gchar *SvgBuilder::_createGradient(GfxShading *shading, const Geom::Affine pat_matrix, bool for_shading)
+{
+ Inkscape::XML::Node *gradient;
+ _POPPLER_CONST Function *func;
+ int num_funcs;
+ bool extend0, extend1;
+
+ if ( shading->getType() == 2 ) { // Axial shading
+ gradient = _xml_doc->createElement("svg:linearGradient");
+ GfxAxialShading *axial_shading = static_cast<GfxAxialShading*>(shading);
+ double x1, y1, x2, y2;
+ axial_shading->getCoords(&x1, &y1, &x2, &y2);
+ gradient->setAttributeSvgDouble("x1", x1);
+ gradient->setAttributeSvgDouble("y1", y1);
+ gradient->setAttributeSvgDouble("x2", x2);
+ gradient->setAttributeSvgDouble("y2", y2);
+ extend0 = axial_shading->getExtend0();
+ extend1 = axial_shading->getExtend1();
+ num_funcs = axial_shading->getNFuncs();
+ func = axial_shading->getFunc(0);
+ } else if (shading->getType() == 3) { // Radial shading
+ gradient = _xml_doc->createElement("svg:radialGradient");
+ GfxRadialShading *radial_shading = static_cast<GfxRadialShading*>(shading);
+ double x1, y1, r1, x2, y2, r2;
+ radial_shading->getCoords(&x1, &y1, &r1, &x2, &y2, &r2);
+ // FIXME: the inner circle's radius is ignored here
+ gradient->setAttributeSvgDouble("fx", x1);
+ gradient->setAttributeSvgDouble("fy", y1);
+ gradient->setAttributeSvgDouble("cx", x2);
+ gradient->setAttributeSvgDouble("cy", y2);
+ gradient->setAttributeSvgDouble("r", r2);
+ extend0 = radial_shading->getExtend0();
+ extend1 = radial_shading->getExtend1();
+ num_funcs = radial_shading->getNFuncs();
+ func = radial_shading->getFunc(0);
+ } else { // Unsupported shading type
+ return nullptr;
+ }
+ gradient->setAttribute("gradientUnits", "userSpaceOnUse");
+ // If needed, flip the gradient transform around the y axis
+ if (pat_matrix != Geom::identity()) {
+ gradient->setAttributeOrRemoveIfEmpty("gradientTransform", sp_svg_transform_write(pat_matrix));
+ }
+
+ if ( extend0 && extend1 ) {
+ gradient->setAttribute("spreadMethod", "pad");
+ }
+
+ if ( num_funcs > 1 || !_addGradientStops(gradient, shading, func) ) {
+ Inkscape::GC::release(gradient);
+ return nullptr;
+ }
+
+ _doc->getDefs()->getRepr()->appendChild(gradient);
+ gchar *id = g_strdup(gradient->attribute("id"));
+ Inkscape::GC::release(gradient);
+
+ return id;
+}
+
+#define EPSILON 0.0001
+/**
+ * \brief Adds a stop with the given properties to the gradient's representation
+ */
+void SvgBuilder::_addStopToGradient(Inkscape::XML::Node *gradient, double offset, GfxColor *color, GfxColorSpace *space,
+ double opacity)
+{
+ Inkscape::XML::Node *stop = _xml_doc->createElement("svg:stop");
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ Inkscape::CSSOStringStream os_opacity;
+ std::string color_text = "#ffffff";
+ if (space->getMode() == csDeviceGray) {
+ // This is a transparency mask.
+ GfxRGB rgb;
+ space->getRGB(color, &rgb);
+ double gray = (double)rgb.r / 65535.0;
+ gray = CLAMP(gray, 0.0, 1.0);
+ os_opacity << gray;
+ } else {
+ os_opacity << opacity;
+ color_text = convertGfxColor(color, space);
+ }
+ sp_repr_css_set_property(css, "stop-opacity", os_opacity.str().c_str());
+ sp_repr_css_set_property(css, "stop-color", color_text.c_str());
+
+ sp_repr_css_change(stop, css, "style");
+ sp_repr_css_attr_unref(css);
+ stop->setAttributeCssDouble("offset", offset);
+
+ gradient->appendChild(stop);
+ Inkscape::GC::release(stop);
+}
+
+static bool svgGetShadingColor(GfxShading *shading, double offset, GfxColor *result)
+{
+ if ( shading->getType() == 2 ) { // Axial shading
+ (static_cast<GfxAxialShading *>(shading))->getColor(offset, result);
+ } else if ( shading->getType() == 3 ) { // Radial shading
+ (static_cast<GfxRadialShading *>(shading))->getColor(offset, result);
+ } else {
+ return false;
+ }
+ return true;
+}
+
+#define INT_EPSILON 8
+bool SvgBuilder::_addGradientStops(Inkscape::XML::Node *gradient, GfxShading *shading,
+ _POPPLER_CONST Function *func) {
+ int type = func->getType();
+ auto space = shading->getColorSpace();
+ if ( type == 0 || type == 2 ) { // Sampled or exponential function
+ GfxColor stop1, stop2;
+ if (!svgGetShadingColor(shading, 0.0, &stop1) || !svgGetShadingColor(shading, 1.0, &stop2)) {
+ return false;
+ } else {
+ _addStopToGradient(gradient, 0.0, &stop1, space, 1.0);
+ _addStopToGradient(gradient, 1.0, &stop2, space, 1.0);
+ }
+ } else if ( type == 3 ) { // Stitching
+ auto stitchingFunc = static_cast<_POPPLER_CONST StitchingFunction*>(func);
+ const double *bounds = stitchingFunc->getBounds();
+ const double *encode = stitchingFunc->getEncode();
+ int num_funcs = stitchingFunc->getNumFuncs();
+ // Adjust gradient so it's always between 0.0 - 1.0
+ double max_bound = std::max({1.0, bounds[num_funcs]});
+
+ // Add stops from all the stitched functions
+ GfxColor prev_color, color;
+ svgGetShadingColor(shading, bounds[0], &prev_color);
+ _addStopToGradient(gradient, bounds[0], &prev_color, space, 1.0);
+ for ( int i = 0 ; i < num_funcs ; i++ ) {
+ svgGetShadingColor(shading, bounds[i + 1], &color);
+ // Add stops
+ if (stitchingFunc->getFunc(i)->getType() == 2) { // process exponential fxn
+ double expE = (static_cast<_POPPLER_CONST ExponentialFunction*>(stitchingFunc->getFunc(i)))->getE();
+ if (expE > 1.0) {
+ expE = (bounds[i + 1] - bounds[i])/expE; // approximate exponential as a single straight line at x=1
+ if (encode[2*i] == 0) { // normal sequence
+ auto offset = (bounds[i + 1] - expE) / max_bound;
+ _addStopToGradient(gradient, offset, &prev_color, space, 1.0);
+ } else { // reflected sequence
+ auto offset = (bounds[i] + expE) / max_bound;
+ _addStopToGradient(gradient, offset, &color, space, 1.0);
+ }
+ }
+ }
+ _addStopToGradient(gradient, bounds[i + 1] / max_bound, &color, space, 1.0);
+ prev_color = color;
+ }
+ } else { // Unsupported function type
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * \brief Sets _invalidated_style to true to indicate that styles have to be updated
+ * Used for text output when glyphs are buffered till a font change
+ */
+void SvgBuilder::updateStyle(GfxState *state) {
+ if (_in_text_object) {
+ _invalidated_style = true;
+ }
+}
+
+/**
+ * \brief Updates _css_font according to the font set in parameter state
+ */
+void SvgBuilder::updateFont(GfxState *state, std::shared_ptr<CairoFont> cairo_font, bool flip)
+{
+ TRACE(("updateFont()\n"));
+ updateTextMatrix(state, flip); // Ensure that we have a text matrix built
+
+ auto font = state->getFont();
+ auto font_id = font->getID()->num;
+
+ auto new_font_size = state->getFontSize();
+ if (font->getType() == fontType3) {
+ const double *font_matrix = font->getFontMatrix();
+ if (font_matrix[0] != 0.0) {
+ new_font_size *= font_matrix[3] / font_matrix[0];
+ }
+ }
+ if (new_font_size != _css_font_size) {
+ _css_font_size = new_font_size;
+ _invalidated_style = true;
+ }
+ bool was_css_font = (bool)_css_font;
+ // Clean up any previous css font
+ if (_css_font) {
+ sp_repr_css_attr_unref(_css_font);
+ _css_font = nullptr;
+ }
+
+ auto font_strategy = FontFallback::AS_TEXT;
+ if (_font_strategies.find(font_id) != _font_strategies.end()) {
+ font_strategy = _font_strategies[font_id];
+ }
+
+ if (font_strategy == FontFallback::DELETE_TEXT) {
+ _invalidated_strategy = true;
+ _cairo_font = nullptr;
+ return;
+ }
+ if (font_strategy == FontFallback::AS_SHAPES) {
+ _invalidated_strategy = _invalidated_strategy || was_css_font;
+ _invalidated_style = (_cairo_font != cairo_font);
+ _cairo_font = cairo_font;
+ return;
+ }
+
+ auto font_data = FontData(font);
+ _font_specification = font_data.getSpecification().c_str();
+ _invalidated_strategy = (bool)_cairo_font;
+ _invalidated_style = true;
+
+ // Font family
+ _cairo_font = nullptr;
+ _css_font = sp_repr_css_attr_new();
+ if (font->getFamily()) { // if font family is explicitly given use it.
+ sp_repr_css_set_property(_css_font, "font-family", font->getFamily()->getCString());
+ } else if (font_strategy == FontFallback::AS_SUB && !font_data.found) {
+ sp_repr_css_set_property(_css_font, "font-family", font_data.getSubstitute().c_str());
+ } else {
+ sp_repr_css_set_property(_css_font, "font-family", font_data.family.c_str());
+ }
+
+ // Set the font data
+ sp_repr_css_set_property(_css_font, "font-style", font_data.style.c_str());
+ sp_repr_css_set_property(_css_font, "font-weight", font_data.weight.c_str());
+ sp_repr_css_set_property(_css_font, "font-stretch", font_data.stretch.c_str());
+ sp_repr_css_set_property(_css_font, "font-variant", "normal");
+
+ // Writing mode
+ if ( font->getWMode() == 0 ) {
+ sp_repr_css_set_property(_css_font, "writing-mode", "lr");
+ } else {
+ sp_repr_css_set_property(_css_font, "writing-mode", "tb");
+ }
+}
+
+/**
+ * \brief Shifts the current text position by the given amount (specified in text space)
+ */
+void SvgBuilder::updateTextShift(GfxState *state, double shift) {
+ double shift_value = -shift * 0.001 * fabs(state->getFontSize());
+ if (state->getFont()->getWMode()) {
+ _text_position[1] += shift_value;
+ } else {
+ _text_position[0] += shift_value;
+ }
+}
+
+/**
+ * \brief Updates current text position
+ */
+void SvgBuilder::updateTextPosition(double tx, double ty) {
+ _text_position = Geom::Point(tx, ty);
+}
+
+/**
+ * \brief Flushes the buffered characters
+ */
+void SvgBuilder::updateTextMatrix(GfxState *state, bool flip) {
+ // Update text matrix, it contains an extra flip which we must undo.
+ auto new_matrix = Geom::Scale(1, flip ? -1 : 1) * ctmToAffine(state->getTextMat());
+ // TODO: Detect if the text matrix is actually just a rotational kern
+ // this can help stich back together texts where letters are rotated
+ if (new_matrix != _text_matrix) {
+ _flushText(state);
+ _text_matrix = new_matrix;
+ }
+}
+
+/**
+ * \brief Writes the buffered characters to the SVG document
+ *
+ * This is a dual path function that can produce either a text element
+ * or a group of path elements depending on the font handling mode.
+ */
+void SvgBuilder::_flushText(GfxState *state)
+{
+ // Set up a clipPath group
+ if (state->getRender() & 4 && !_clip_text_group) {
+ auto defs = _doc->getDefs()->getRepr();
+ _clip_text_group = _pushContainer("svg:clipPath");
+ _clip_text_group->setAttribute("clipPathUnits", "userSpaceOnUse");
+ defs->appendChild(_clip_text_group);
+ Inkscape::GC::release(_clip_text_group);
+ }
+
+ // Ignore empty strings
+ if (_glyphs.empty()) {
+ _glyphs.clear();
+ return;
+ }
+ std::vector<SvgGlyph>::iterator i = _glyphs.begin();
+ const SvgGlyph& first_glyph = (*i);
+
+ // Ignore invisible characters
+ if (first_glyph.state->getRender() == 3) {
+ _glyphs.clear();
+ return;
+ }
+
+ // If cairo, then no text node is needed.
+ Inkscape::XML::Node *text_group = nullptr;
+ Inkscape::XML::Node *text_node = nullptr;
+ cairo_glyph_t *cairo_glyphs = nullptr;
+ unsigned int cairo_glyph_count = 0;
+
+ if (!first_glyph.cairo_font) {
+ // we preserve spaces in the text objects we create, this applies to any descendant
+ text_node = _addToContainer("svg:text");
+ text_node->setAttribute("xml:space", "preserve");
+ }
+
+ // Strip out text size from text_matrix and remove from text_transform
+ double text_scale = _text_matrix.expansionX();
+ Geom::Affine tr = stateToAffine(state);
+ Geom::Affine text_transform = _text_matrix * tr * Geom::Scale(text_scale).inverse();
+ // The glyph position must be moved by the document scale without flipping
+ // the text object itself. This is why the text affine is applied to the
+ // translation point and not simply used in the text element directly.
+ auto pos = first_glyph.position * tr;
+ text_transform.setTranslation(pos);
+ // Cache the text transform when clipping
+ if (_clip_text_group) {
+ svgSetTransform(_clip_text_group, text_transform);
+ }
+
+ bool new_tspan = true;
+ bool same_coords[2] = {true, true};
+ Geom::Point last_delta_pos;
+ unsigned int glyphs_in_a_row = 0;
+ Inkscape::XML::Node *tspan_node = nullptr;
+ Glib::ustring x_coords;
+ Glib::ustring y_coords;
+ Glib::ustring text_buffer;
+
+ // Output all buffered glyphs
+ while (true) {
+ const SvgGlyph& glyph = (*i);
+ auto prev_iterator = (i == _glyphs.begin()) ? _glyphs.end() : (i-1);
+ // Check if we need to make a new tspan
+ if (glyph.style_changed) {
+ new_tspan = true;
+ } else if ( i != _glyphs.begin() ) {
+ const SvgGlyph& prev_glyph = (*prev_iterator);
+ if (!((glyph.delta[Geom::Y] == 0.0 && prev_glyph.delta[Geom::Y] == 0.0 &&
+ glyph.text_position[1] == prev_glyph.text_position[1]) ||
+ (glyph.delta[Geom::X] == 0.0 && prev_glyph.delta[Geom::X] == 0.0 &&
+ glyph.text_position[0] == prev_glyph.text_position[0]))) {
+ new_tspan = true;
+ }
+ }
+
+ // Create tspan node if needed
+ if (!first_glyph.cairo_font && text_node && (new_tspan || i == _glyphs.end())) {
+ if (tspan_node) {
+ // Set the x and y coordinate arrays
+ if (same_coords[0]) {
+ tspan_node->setAttributeSvgDouble("x", last_delta_pos[0]);
+ } else {
+ tspan_node->setAttributeOrRemoveIfEmpty("x", x_coords);
+ }
+ if (same_coords[1]) {
+ tspan_node->setAttributeSvgDouble("y", last_delta_pos[1]);
+ } else {
+ tspan_node->setAttributeOrRemoveIfEmpty("y", y_coords);
+ }
+ TRACE(("tspan content: %s\n", text_buffer.c_str()));
+ if ( glyphs_in_a_row > 1 ) {
+ tspan_node->setAttribute("sodipodi:role", "line");
+ }
+ // Add text content node to tspan
+ Inkscape::XML::Node *text_content = _xml_doc->createTextNode(text_buffer.c_str());
+ tspan_node->appendChild(text_content);
+ Inkscape::GC::release(text_content);
+ text_node->appendChild(tspan_node);
+ // Clear temporary buffers
+ x_coords.clear();
+ y_coords.clear();
+ text_buffer.clear();
+ Inkscape::GC::release(tspan_node);
+ glyphs_in_a_row = 0;
+ }
+ if ( i == _glyphs.end() ) {
+ sp_repr_css_attr_unref((*prev_iterator).css_font);
+ break;
+ } else {
+ tspan_node = _xml_doc->createElement("svg:tspan");
+
+ // Set style and unref SPCSSAttr if it won't be needed anymore
+ // assume all <tspan> nodes in a <text> node share the same style
+ double text_size = text_scale * glyph.text_size;
+ sp_repr_css_set_property_double(glyph.css_font, "font-size", text_size);
+ _setTextStyle(tspan_node, glyph.state, glyph.css_font, text_transform);
+ if ( glyph.style_changed && i != _glyphs.begin() ) { // Free previous style
+ sp_repr_css_attr_unref((*prev_iterator).css_font);
+ }
+ }
+ new_tspan = false;
+ }
+ if ( glyphs_in_a_row > 0 && i != _glyphs.begin() ) {
+ x_coords.append(" ");
+ y_coords.append(" ");
+ // Check if we have the same coordinates
+ const SvgGlyph& prev_glyph = (*prev_iterator);
+ for ( int p = 0 ; p < 2 ; p++ ) {
+ if ( glyph.text_position[p] != prev_glyph.text_position[p] ) {
+ same_coords[p] = false;
+ }
+ }
+ }
+ // Append the coordinates to their respective strings
+ Geom::Point delta_pos(glyph.text_position - first_glyph.text_position);
+ delta_pos[1] += glyph.rise;
+ delta_pos[1] *= -1.0; // flip it
+ delta_pos *= Geom::Scale(text_scale);
+ Inkscape::CSSOStringStream os_x;
+ os_x << delta_pos[0];
+ x_coords.append(os_x.str());
+ Inkscape::CSSOStringStream os_y;
+ os_y << delta_pos[1];
+ y_coords.append(os_y.str());
+ last_delta_pos = delta_pos;
+
+ if (first_glyph.cairo_font) {
+ if (!cairo_glyphs) {
+ cairo_glyphs = (cairo_glyph_t *)gmallocn(_glyphs.size(), sizeof(cairo_glyph_t));
+ }
+ bool is_last_glyph = i + 1 == _glyphs.end();
+
+ // Push the data into the cairo glyph list for later rendering.
+ cairo_glyphs[cairo_glyph_count].index = glyph.cairo_index;
+ cairo_glyphs[cairo_glyph_count].x = delta_pos[Geom::X];
+ cairo_glyphs[cairo_glyph_count].y = delta_pos[Geom::Y];
+ cairo_glyph_count++;
+
+ bool style_will_change = is_last_glyph ? true : (i+1)->style_changed;
+ if (style_will_change) {
+ if (style_will_change && !is_last_glyph && !text_group) {
+ // We create a group, so each style can be contained within the resulting path.
+ text_group = _pushGroup();
+ }
+
+ // Render and set the style for this drawn text.
+ double text_size = text_scale * glyph.text_size;
+
+ // Set to 'text_node' because if the style does NOT change, we won't have a group
+ // but still need to set this text's position and blend modes.
+ text_node = _renderText(glyph.cairo_font, text_size, text_transform, cairo_glyphs, cairo_glyph_count);
+ if (text_node) {
+ _setTextStyle(text_node, glyph.state, nullptr, text_transform);
+ }
+
+ // Free up the used glyph stack.
+ gfree(cairo_glyphs);
+ cairo_glyphs = nullptr;
+ cairo_glyph_count = 0;
+
+ if (is_last_glyph) {
+ // Stop drawing text now, we have cleaned up.
+ break;
+ }
+ }
+ } else {
+ // Append the character to the text buffer
+ if (!glyph.code.empty()) {
+ text_buffer.append(1, glyph.code[0]);
+ }
+
+ /* Append any utf8 conversion doublets and request a new tspan.
+ *
+ * This is a fix for the unusual situation in some PDF files that use
+ * certain fonts where two ascii letters have been bolted together into
+ * one Unicode position and our conversion to UTF8 produces extra glyphs
+ * which if we don't add will be missing and if we add without ending the
+ * tspan will cause the rest of the glyph-positions to be off by one.
+ */
+ for (int j = 1; j < glyph.code.size(); j++) {
+ text_buffer.append(1, glyph.code[j]);
+ new_tspan = true;
+ }
+ }
+
+ glyphs_in_a_row++;
+ ++i;
+ }
+ if (text_group) {
+ // Pop the group so the clip and transform can be applied to it.
+ text_node = text_group;
+ _popGroup();
+ }
+
+ if (text_node) {
+ if (first_glyph.cairo_font) {
+ // Save aria-label for any rendered text blocks
+ text_node->setAttribute("aria-label", _aria_label);
+ }
+
+ // Set the text matrix which sits under the page's position
+ _setBlendMode(text_node, state);
+ svgSetTransform(text_node, text_transform * _page_affine);
+ _setClipPath(text_node);
+ }
+
+ _aria_label = "";
+ _glyphs.clear();
+}
+
+/**
+ * Sets the style for the text, rendered or un-rendered, preserving the text_transform for any
+ * gradients or other patterns. These values were promised to us when the font was updated.
+ */
+void SvgBuilder::_setTextStyle(Inkscape::XML::Node *node, GfxState *state, SPCSSAttr *font_style, Geom::Affine ta)
+{
+ int render_mode = state->getRender();
+ bool has_fill = !(render_mode & 1);
+ bool has_stroke = ( render_mode & 3 ) == 1 || ( render_mode & 3 ) == 2;
+
+ state = state->save();
+ state->setCTM(ta[0], ta[1], ta[2], ta[3], ta[4], ta[5]);
+ auto style = _setStyle(state, has_fill, has_stroke);
+ state = state->restore();
+ if (font_style) {
+ sp_repr_css_merge(style, font_style);
+ }
+ sp_repr_css_change(node, style, "style");
+ sp_repr_css_attr_unref(style);
+}
+
+/**
+ * Renders the text as a path object using cairo and returns the node object.
+ *
+ * cairo_font - The font that cairo can use to convert text to path.
+ * font_size - The size of the text when drawing the path.
+ * transform - The matrix which will place the text on the page, this is critical
+ * to allow cairo to render all the required parts of the text.
+ * cairo_glyphs - A pointer to a list of glyphs to render.
+ * count - A count of the number of glyphs to render.
+ */
+Inkscape::XML::Node *SvgBuilder::_renderText(std::shared_ptr<CairoFont> cairo_font, double font_size,
+ const Geom::Affine &transform,
+ cairo_glyph_t *cairo_glyphs, unsigned int count)
+{
+ if (!cairo_glyphs || !cairo_font || _aria_label.empty())
+ return nullptr;
+
+ // The surface isn't actually used, no rendering in cairo takes place.
+ cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, _width, _height);
+ cairo_t *cairo = cairo_create(surface);
+ cairo_set_font_face(cairo, cairo_font->getFontFace());
+ cairo_set_font_size(cairo, font_size);
+ ink_cairo_transform(cairo, transform);
+ cairo_glyph_path(cairo, cairo_glyphs, count);
+ auto pathv = extract_pathvector_from_cairo(cairo);
+ cairo_destroy(cairo);
+ cairo_surface_destroy(surface);
+
+ // Failing to render text.
+ if (!pathv) {
+ g_warning("Failed to render PDF text!");
+ return nullptr;
+ }
+
+ auto textpath = sp_svg_write_path(*pathv);
+ if (textpath.empty())
+ return nullptr;
+
+ Inkscape::XML::Node *path = _addToContainer("svg:path");
+ path->setAttribute("d", textpath);
+ return path;
+}
+
+/**
+ * Begin and end string is the inner most text processing step
+ * which tells us we're about to have a certain number of chars.
+ */
+void SvgBuilder::beginString(GfxState *state, int len)
+{
+ if (!_glyphs.empty()) {
+ // What to do about unflushed text in the buffer.
+ if (_invalidated_strategy) {
+ _flushText(state);
+ _invalidated_strategy = false;
+ } else {
+ // Add seperator for aria text.
+ _aria_space = true;
+ }
+ }
+ IFTRACE(double *m = state->getTextMat());
+ TRACE(("tm: %f %f %f %f %f %f\n",m[0], m[1],m[2], m[3], m[4], m[5]));
+ IFTRACE(m = state->getCTM());
+ TRACE(("ctm: %f %f %f %f %f %f\n",m[0], m[1],m[2], m[3], m[4], m[5]));
+}
+void SvgBuilder::endString(GfxState *state)
+{
+}
+
+/**
+ * \brief Adds the specified character to the text buffer
+ * Takes care of converting it to UTF-8 and generates a new style repr if style
+ * has changed since the last call.
+ */
+void SvgBuilder::addChar(GfxState *state, double x, double y, double dx, double dy, double originX, double originY,
+ CharCode code, int /*nBytes*/, Unicode const *u, int uLen)
+{
+ if (_aria_space) {
+ const SvgGlyph& prev_glyph = _glyphs.back();
+ // This helps reconstruct the aria text, though it could be made better
+ if (prev_glyph.position[Geom::Y] != (y - originY)) {
+ _aria_label += "\n";
+ }
+ _aria_space = false;
+ }
+ static std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> conv1;
+ if (u) {
+ _aria_label += conv1.to_bytes(*u);
+ }
+
+ // Skip control characters, found in LaTeX generated PDFs
+ // https://gitlab.com/inkscape/inkscape/-/issues/1369
+ if (uLen > 0 && u[0] < 0x80 && g_ascii_iscntrl(u[0]) && !g_ascii_isspace(u[0])) {
+ g_warning("Skipping ASCII control character %u", u[0]);
+ _text_position += Geom::Point(dx, dy);
+ return;
+ }
+
+ if (!_css_font && !_cairo_font) {
+ // Deleted text.
+ return;
+ }
+
+ bool is_space = ( uLen == 1 && u[0] == 32 );
+ // Skip beginning space
+ if ( is_space && _glyphs.empty()) {
+ Geom::Point delta(dx, dy);
+ _text_position += delta;
+ return;
+ }
+ // Allow only one space in a row
+ if ( is_space && (_glyphs[_glyphs.size() - 1].code.size() == 1) &&
+ (_glyphs[_glyphs.size() - 1].code[0] == 32) ) {
+ Geom::Point delta(dx, dy);
+ _text_position += delta;
+ return;
+ }
+
+ SvgGlyph new_glyph;
+ new_glyph.is_space = is_space;
+ new_glyph.delta = Geom::Point(dx, dy);
+ new_glyph.position = Geom::Point( x - originX, y - originY );
+ new_glyph.text_position = _text_position;
+ new_glyph.text_size = _css_font_size;
+ new_glyph.state = state;
+ if (_cairo_font) {
+ new_glyph.cairo_font = _cairo_font;
+ new_glyph.cairo_index = _cairo_font->getGlyph(code, u, uLen);
+ }
+ _text_position += new_glyph.delta;
+
+ // Convert the character to UTF-8 since that's our SVG document's encoding
+ {
+ gunichar2 uu[8] = {0};
+
+ for (int i = 0; i < uLen; i++) {
+ uu[i] = u[i];
+ }
+
+ gchar *tmp = g_utf16_to_utf8(uu, uLen, nullptr, nullptr, nullptr);
+ if ( tmp && *tmp ) {
+ new_glyph.code = tmp;
+ } else {
+ new_glyph.code.clear();
+ }
+ g_free(tmp);
+ }
+
+ // Copy current style if it has changed since the previous glyph
+ if (_invalidated_style || _glyphs.empty()) {
+ _invalidated_style = false;
+ new_glyph.style_changed = true;
+ if (_css_font) {
+ new_glyph.css_font = sp_repr_css_attr_new();
+ sp_repr_css_merge(new_glyph.css_font, _css_font);
+ }
+ } else {
+ new_glyph.style_changed = false;
+ // Point to previous glyph's style information
+ const SvgGlyph& prev_glyph = _glyphs.back();
+ new_glyph.css_font = prev_glyph.css_font;
+ }
+ new_glyph.font_specification = _font_specification;
+ new_glyph.rise = state->getRise();
+
+ _glyphs.push_back(new_glyph);
+}
+
+/**
+ * These text object functions are the outer most calls for begining and
+ * ending text. No text functions should be called outside of these two calls
+ */
+void SvgBuilder::beginTextObject(GfxState *state) {
+ _in_text_object = true;
+ _invalidated_style = true; // Force copying of current state
+}
+
+void SvgBuilder::endTextObject(GfxState *state)
+{
+ _in_text_object = false;
+ _flushText(state);
+
+ if (_clip_text_group) {
+ // Use the clip as a real clip path
+ _clip_text = _popContainer();
+ _clip_text_group = nullptr;
+ }
+}
+
+/**
+ * Helper functions for supporting direct PNG output into a base64 encoded stream
+ */
+void png_write_vector(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+ auto *v_ptr = reinterpret_cast<std::vector<guchar> *>(png_get_io_ptr(png_ptr)); // Get pointer to stream
+ for ( unsigned i = 0 ; i < length ; i++ ) {
+ v_ptr->push_back(data[i]);
+ }
+}
+
+/**
+ * \brief Creates an <image> element containing the given ImageStream as a PNG
+ *
+ */
+Inkscape::XML::Node *SvgBuilder::_createImage(Stream *str, int width, int height,
+ GfxImageColorMap *color_map, bool interpolate,
+ int *mask_colors, bool alpha_only,
+ bool invert_alpha) {
+
+ // Create PNG write struct
+ png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+ if ( png_ptr == nullptr ) {
+ return nullptr;
+ }
+ // Create PNG info struct
+ png_infop info_ptr = png_create_info_struct(png_ptr);
+ if ( info_ptr == nullptr ) {
+ png_destroy_write_struct(&png_ptr, nullptr);
+ return nullptr;
+ }
+ // Set error handler
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ return nullptr;
+ }
+ // Decide whether we should embed this image
+ bool embed_image = _preferences->getAttributeBoolean("embedImages", true);
+
+ // Set read/write functions
+ std::vector<guchar> png_buffer;
+ FILE *fp = nullptr;
+ gchar *file_name = nullptr;
+ if (embed_image) {
+ png_set_write_fn(png_ptr, &png_buffer, png_write_vector, nullptr);
+ } else {
+ static int counter = 0;
+ file_name = g_strdup_printf("%s_img%d.png", _docname, counter++);
+ fp = fopen(file_name, "wb");
+ if ( fp == nullptr ) {
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ g_free(file_name);
+ return nullptr;
+ }
+ png_init_io(png_ptr, fp);
+ }
+
+ // Set header data
+ if ( !invert_alpha && !alpha_only ) {
+ png_set_invert_alpha(png_ptr);
+ }
+ png_color_8 sig_bit;
+ if (alpha_only) {
+ png_set_IHDR(png_ptr, info_ptr,
+ width,
+ height,
+ 8, /* bit_depth */
+ PNG_COLOR_TYPE_GRAY,
+ PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_BASE,
+ PNG_FILTER_TYPE_BASE);
+ sig_bit.red = 0;
+ sig_bit.green = 0;
+ sig_bit.blue = 0;
+ sig_bit.gray = 8;
+ sig_bit.alpha = 0;
+ } else {
+ png_set_IHDR(png_ptr, info_ptr,
+ width,
+ height,
+ 8, /* bit_depth */
+ PNG_COLOR_TYPE_RGB_ALPHA,
+ PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_BASE,
+ PNG_FILTER_TYPE_BASE);
+ sig_bit.red = 8;
+ sig_bit.green = 8;
+ sig_bit.blue = 8;
+ sig_bit.alpha = 8;
+ }
+ png_set_sBIT(png_ptr, info_ptr, &sig_bit);
+ png_set_bgr(png_ptr);
+ // Write the file header
+ png_write_info(png_ptr, info_ptr);
+
+ // Convert pixels
+ ImageStream *image_stream;
+ if (alpha_only) {
+ if (color_map) {
+ image_stream = new ImageStream(str, width, color_map->getNumPixelComps(),
+ color_map->getBits());
+ } else {
+ image_stream = new ImageStream(str, width, 1, 1);
+ }
+ image_stream->reset();
+
+ // Convert grayscale values
+ unsigned char *buffer = new unsigned char[width];
+ int invert_bit = invert_alpha ? 1 : 0;
+ for ( int y = 0 ; y < height ; y++ ) {
+ unsigned char *row = image_stream->getLine();
+ if (color_map) {
+ color_map->getGrayLine(row, buffer, width);
+ } else {
+ unsigned char *buf_ptr = buffer;
+ for ( int x = 0 ; x < width ; x++ ) {
+ if ( row[x] ^ invert_bit ) {
+ *buf_ptr++ = 0;
+ } else {
+ *buf_ptr++ = 255;
+ }
+ }
+ }
+ png_write_row(png_ptr, (png_bytep)buffer);
+ }
+ delete [] buffer;
+ } else if (color_map) {
+ image_stream = new ImageStream(str, width,
+ color_map->getNumPixelComps(),
+ color_map->getBits());
+ image_stream->reset();
+
+ // Convert RGB values
+ unsigned int *buffer = new unsigned int[width];
+ if (mask_colors) {
+ for ( int y = 0 ; y < height ; y++ ) {
+ unsigned char *row = image_stream->getLine();
+ color_map->getRGBLine(row, buffer, width);
+
+ unsigned int *dest = buffer;
+ for ( int x = 0 ; x < width ; x++ ) {
+ // Check each color component against the mask
+ for ( int i = 0; i < color_map->getNumPixelComps() ; i++) {
+ if ( row[i] < mask_colors[2*i] * 255 ||
+ row[i] > mask_colors[2*i + 1] * 255 ) {
+ *dest = *dest | 0xff000000;
+ break;
+ }
+ }
+ // Advance to the next pixel
+ row += color_map->getNumPixelComps();
+ dest++;
+ }
+ // Write it to the PNG
+ png_write_row(png_ptr, (png_bytep)buffer);
+ }
+ } else {
+ for ( int i = 0 ; i < height ; i++ ) {
+ unsigned char *row = image_stream->getLine();
+ memset((void*)buffer, 0xff, sizeof(int) * width);
+ color_map->getRGBLine(row, buffer, width);
+ png_write_row(png_ptr, (png_bytep)buffer);
+ }
+ }
+ delete [] buffer;
+
+ } else { // A colormap must be provided, so quit
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ if (!embed_image) {
+ fclose(fp);
+ g_free(file_name);
+ }
+ return nullptr;
+ }
+ delete image_stream;
+ str->close();
+ // Close PNG
+ png_write_end(png_ptr, info_ptr);
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+
+ // Create repr
+ Inkscape::XML::Node *image_node = _xml_doc->createElement("svg:image");
+ image_node->setAttributeSvgDouble("width", 1);
+ image_node->setAttributeSvgDouble("height", 1);
+ if( !interpolate ) {
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ // This should be changed after CSS4 Images widely supported.
+ sp_repr_css_set_property(css, "image-rendering", "optimizeSpeed");
+ sp_repr_css_change(image_node, css, "style");
+ sp_repr_css_attr_unref(css);
+ }
+
+ // PS/PDF images are placed via a transformation matrix, no preserveAspectRatio used
+ image_node->setAttribute("preserveAspectRatio", "none");
+
+ // Create href
+ if (embed_image) {
+ // Append format specification to the URI
+ auto *base64String = g_base64_encode(png_buffer.data(), png_buffer.size());
+ auto png_data = std::string("data:image/png;base64,") + base64String;
+ g_free(base64String);
+ image_node->setAttributeOrRemoveIfEmpty("xlink:href", png_data);
+ } else {
+ fclose(fp);
+ image_node->setAttribute("xlink:href", file_name);
+ g_free(file_name);
+ }
+
+ return image_node;
+}
+
+/**
+ * \brief Creates a <mask> with the specified width and height and adds to <defs>
+ * If we're not the top-level SvgBuilder, creates a <defs> too and adds the mask to it.
+ * \return the created XML node
+ */
+Inkscape::XML::Node *SvgBuilder::_createMask(double width, double height) {
+ Inkscape::XML::Node *mask_node = _xml_doc->createElement("svg:mask");
+ mask_node->setAttribute("maskUnits", "userSpaceOnUse");
+ mask_node->setAttributeSvgDouble("x", 0.0);
+ mask_node->setAttributeSvgDouble("y", 0.0);
+ mask_node->setAttributeSvgDouble("width", width);
+ mask_node->setAttributeSvgDouble("height", height);
+ // Append mask to defs
+ if (_is_top_level) {
+ _doc->getDefs()->getRepr()->appendChild(mask_node);
+ Inkscape::GC::release(mask_node);
+ return _doc->getDefs()->getRepr()->lastChild();
+ } else { // Work around for renderer bug when mask isn't defined in pattern
+ static int mask_count = 0;
+ gchar *mask_id = g_strdup_printf("_mask%d", mask_count++);
+ mask_node->setAttribute("id", mask_id);
+ g_free(mask_id);
+ _doc->getDefs()->getRepr()->appendChild(mask_node);
+ Inkscape::GC::release(mask_node);
+ return mask_node;
+ }
+}
+
+void SvgBuilder::addImage(GfxState *state, Stream *str, int width, int height, GfxImageColorMap *color_map,
+ bool interpolate, int *mask_colors)
+{
+ Inkscape::XML::Node *image_node = _createImage(str, width, height, color_map, interpolate, mask_colors);
+ if (image_node) {
+ _setBlendMode(image_node, state);
+ _setTransform(image_node, state, Geom::Affine(1.0, 0.0, 0.0, -1.0, 0.0, 1.0));
+ _addToContainer(image_node);
+ _setClipPath(image_node);
+ }
+}
+
+void SvgBuilder::addImageMask(GfxState *state, Stream *str, int width, int height,
+ bool invert, bool interpolate) {
+
+ // Create a rectangle
+ Inkscape::XML::Node *rect = _addToContainer("svg:rect");
+ rect->setAttributeSvgDouble("x", 0.0);
+ rect->setAttributeSvgDouble("y", 0.0);
+ rect->setAttributeSvgDouble("width", 1.0);
+ rect->setAttributeSvgDouble("height", 1.0);
+
+ // Get current fill style and set it on the rectangle
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ _setFillStyle(css, state, false);
+ sp_repr_css_change(rect, css, "style");
+ sp_repr_css_attr_unref(css);
+ _setBlendMode(rect, state);
+ _setTransform(rect, state, Geom::Affine(1.0, 0.0, 0.0, -1.0, 0.0, 1.0));
+ _setClipPath(rect);
+
+ // Scaling 1x1 surfaces might not work so skip setting a mask with this size
+ if ( width > 1 || height > 1 ) {
+ Inkscape::XML::Node *mask_image_node =
+ _createImage(str, width, height, nullptr, interpolate, nullptr, true, invert);
+ if (mask_image_node) {
+ // Create the mask
+ Inkscape::XML::Node *mask_node = _createMask(1.0, 1.0);
+ // Remove unnecessary transformation from the mask image
+ mask_image_node->removeAttribute("transform");
+ mask_node->appendChild(mask_image_node);
+ Inkscape::GC::release(mask_image_node);
+ gchar *mask_url = g_strdup_printf("url(#%s)", mask_node->attribute("id"));
+ rect->setAttribute("mask", mask_url);
+ g_free(mask_url);
+ }
+ }
+}
+
+void SvgBuilder::addMaskedImage(GfxState *state, Stream *str, int width, int height, GfxImageColorMap *color_map,
+ bool interpolate, Stream *mask_str, int mask_width, int mask_height, bool invert_mask,
+ bool mask_interpolate)
+{
+ Inkscape::XML::Node *mask_image_node = _createImage(mask_str, mask_width, mask_height,
+ nullptr, mask_interpolate, nullptr, true, invert_mask);
+ Inkscape::XML::Node *image_node = _createImage(str, width, height, color_map, interpolate, nullptr);
+ if ( mask_image_node && image_node ) {
+ // Create mask for the image
+ Inkscape::XML::Node *mask_node = _createMask(1.0, 1.0);
+ // Remove unnecessary transformation from the mask image
+ mask_image_node->removeAttribute("transform");
+ mask_node->appendChild(mask_image_node);
+ // Scale the mask to the size of the image
+ Geom::Affine mask_transform((double)width, 0.0, 0.0, (double)height, 0.0, 0.0);
+ mask_node->setAttributeOrRemoveIfEmpty("maskTransform", sp_svg_transform_write(mask_transform));
+ // Set mask and add image
+ gchar *mask_url = g_strdup_printf("url(#%s)", mask_node->attribute("id"));
+ image_node->setAttribute("mask", mask_url);
+ g_free(mask_url);
+ _setBlendMode(image_node, state);
+ _setTransform(image_node, state, Geom::Affine(1.0, 0.0, 0.0, -1.0, 0.0, 1.0));
+ _addToContainer(image_node);
+ _setClipPath(image_node);
+ } else if (image_node) {
+ Inkscape::GC::release(image_node);
+ }
+ if (mask_image_node) {
+ Inkscape::GC::release(mask_image_node);
+ }
+}
+
+void SvgBuilder::addSoftMaskedImage(GfxState *state, Stream *str, int width, int height, GfxImageColorMap *color_map,
+ bool interpolate, Stream *mask_str, int mask_width, int mask_height,
+ GfxImageColorMap *mask_color_map, bool mask_interpolate)
+{
+ Inkscape::XML::Node *mask_image_node = _createImage(mask_str, mask_width, mask_height,
+ mask_color_map, mask_interpolate, nullptr, true);
+ Inkscape::XML::Node *image_node = _createImage(str, width, height, color_map, interpolate, nullptr);
+ if ( mask_image_node && image_node ) {
+ // Create mask for the image
+ Inkscape::XML::Node *mask_node = _createMask(1.0, 1.0);
+ // Remove unnecessary transformation from the mask image
+ mask_image_node->removeAttribute("transform");
+ mask_node->appendChild(mask_image_node);
+ // Set mask and add image
+ gchar *mask_url = g_strdup_printf("url(#%s)", mask_node->attribute("id"));
+ image_node->setAttribute("mask", mask_url);
+ g_free(mask_url);
+ _addToContainer(image_node);
+ _setBlendMode(image_node, state);
+ _setTransform(image_node, state, Geom::Affine(1.0, 0.0, 0.0, -1.0, 0.0, 1.0));
+ _setClipPath(image_node);
+ } else if (image_node) {
+ Inkscape::GC::release(image_node);
+ }
+ if (mask_image_node) {
+ Inkscape::GC::release(mask_image_node);
+ }
+}
+
+/**
+ * Find the fill or stroke gradient we previously set on this node.
+ */
+Inkscape::XML::Node *SvgBuilder::_getGradientNode(Inkscape::XML::Node *node, bool is_fill)
+{
+ auto css = sp_repr_css_attr(node, "style");
+ if (auto id = try_extract_uri_id(css->attribute(is_fill ? "fill" : "stroke"))) {
+ if (auto obj = _doc->getObjectById(*id)) {
+ return obj->getRepr();
+ }
+ }
+ return nullptr;
+}
+
+bool SvgBuilder::_attrEqual(Inkscape::XML::Node *a, Inkscape::XML::Node *b, char const *attr)
+{
+ return (!a->attribute(attr) && !b->attribute(attr)) || std::string(a->attribute(attr)) == b->attribute(attr);
+}
+
+/**
+ * Take a constructed mask and decide how to apply it to the target.
+ */
+void SvgBuilder::applyOptionalMask(Inkscape::XML::Node *mask, Inkscape::XML::Node *target)
+{
+ // Merge transparency gradient back into real gradient if possible
+ if (mask->childCount() == 1) {
+ auto source = mask->firstChild();
+ auto source_gr = _getGradientNode(source, true);
+ auto target_gr = _getGradientNode(target, true);
+ // Both objects have a gradient, try and merge them
+ if (source_gr && target_gr && source_gr->childCount() == target_gr->childCount()) {
+ bool same_pos = _attrEqual(source_gr, target_gr, "x1") && _attrEqual(source_gr, target_gr, "x2")
+ && _attrEqual(source_gr, target_gr, "y1") && _attrEqual(source_gr, target_gr, "y2");
+
+ bool white_mask = false;
+ for (auto source_st = source_gr->firstChild(); source_st != nullptr; source_st = source_st->next()) {
+ auto source_css = sp_repr_css_attr(source_st, "style");
+ white_mask = white_mask or source_css->getAttributeDouble("stop-opacity") != 1.0;
+ if (std::string(source_css->attribute("stop-color")) != "#ffffff") {
+ white_mask = false;
+ break;
+ }
+ }
+
+ if (same_pos && white_mask) {
+ // We move the stop-opacity from the source to the target
+ auto target_st = target_gr->firstChild();
+ for (auto source_st = source_gr->firstChild(); source_st != nullptr; source_st = source_st->next()) {
+ auto target_css = sp_repr_css_attr(target_st, "style");
+ auto source_css = sp_repr_css_attr(source_st, "style");
+ sp_repr_css_set_property(target_css, "stop-opacity", source_css->attribute("stop-opacity"));
+ sp_repr_css_change(target_st, target_css, "style");
+ target_st = target_st->next();
+ }
+ // Remove mask and gradient xml objects
+ mask->parent()->removeChild(mask);
+ source_gr->parent()->removeChild(source_gr);
+ return;
+ }
+ }
+ }
+ gchar *mask_url = g_strdup_printf("url(#%s)", mask->attribute("id"));
+ target->setAttribute("mask", mask_url);
+ g_free(mask_url);
+}
+
+
+/**
+ * \brief Starts building a new transparency group
+ */
+void SvgBuilder::startGroup(GfxState *state, double *bbox, GfxColorSpace * /*blending_color_space*/, bool isolated,
+ bool knockout, bool for_softmask)
+{
+ // Push group node, but don't attach to previous container yet
+ _pushContainer("svg:g");
+
+ if (for_softmask) {
+ _mask_groups.push_back(state);
+ // Create a container for the mask
+ _pushContainer(_createMask(1.0, 1.0));
+ }
+
+ // TODO: In the future we could use state to insert transforms
+ // and then remove the inverse from the items added into the children
+ // to reduce the transformational duplication.
+}
+
+void SvgBuilder::finishGroup(GfxState *state, bool for_softmask)
+{
+ if (for_softmask) {
+ // Create mask
+ auto mask_node = _popContainer();
+ applyOptionalMask(mask_node, _container);
+ } else {
+ popGroup(state);
+ }
+}
+
+void SvgBuilder::popGroup(GfxState *state)
+{
+ // Restore node stack
+ auto parent = _popContainer();
+ bool will_clip = _clip_history->hasClipPath() && !_clip_history->isBoundingBox();
+
+ if (parent->childCount() == 1 && !parent->attribute("transform")) {
+ // Merge this opacity and remove unnecessary group
+ auto child = parent->firstChild();
+
+ if (will_clip && child->attribute("d")) {
+ // Note to future: this means the group contains a single path, this path is likely
+ // a fake bounding box path and the real path is contained within the clipping region
+ // Moving the clipping region out into the path object and deleting the group would
+ // improve output here.
+ }
+
+ // Do not merge masked or clipped groups, to avoid clobering
+ if (!will_clip && !child->attribute("mask") && !child->attribute("clip-path")) {
+ auto orig = child->getAttributeDouble("opacity", 1.0);
+ auto grp = parent->getAttributeDouble("opacity", 1.0);
+ child->setAttributeSvgDouble("opacity", orig * grp);
+
+ if (auto mask_id = try_extract_uri_id(parent->attribute("mask"))) {
+ if (auto obj = _doc->getObjectById(*mask_id)) {
+ applyOptionalMask(obj->getRepr(), child);
+ }
+ }
+ if (auto clip = parent->attribute("clip-path")) {
+ child->setAttribute("clip-path", clip);
+ }
+
+ // This duplicate child will get applied in the place of the group
+ parent->removeChild(child);
+ Inkscape::GC::anchor(child);
+ parent = child;
+ }
+ }
+
+ // Add the parent to the last container
+ _addToContainer(parent);
+ _setClipPath(parent);
+}
+
+/**
+ * Decide what to do for each font in the font list, with the given strategy.
+ */
+FontStrategies SvgBuilder::autoFontStrategies(FontStrategy s, FontList fonts)
+{
+ FontStrategies ret;
+ for (auto font : *fonts.get()) {
+ int id = font.first->getID()->num;
+ bool found = font.second.found;
+ switch (s) {
+ case FontStrategy::RENDER_ALL:
+ ret[id] = FontFallback::AS_SHAPES;
+ break;
+ case FontStrategy::DELETE_ALL:
+ ret[id] = FontFallback::DELETE_TEXT;
+ break;
+ case FontStrategy::RENDER_MISSING:
+ ret[id] = found ? FontFallback::AS_TEXT : FontFallback::AS_SHAPES;
+ break;
+ case FontStrategy::SUBSTITUTE_MISSING:
+ ret[id] = found ? FontFallback::AS_TEXT : FontFallback::AS_SUB;
+ break;
+ case FontStrategy::KEEP_MISSING:
+ ret[id] = FontFallback::AS_TEXT;
+ break;
+ case FontStrategy::DELETE_MISSING:
+ ret[id] = found ? FontFallback::AS_TEXT : FontFallback::DELETE_TEXT;
+ break;
+ }
+ }
+ return ret;
+}
+} } } /* namespace Inkscape, Extension, Internal */
+
+#endif /* HAVE_POPPLER */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/pdfinput/svg-builder.h b/src/extension/internal/pdfinput/svg-builder.h
new file mode 100644
index 0000000..6d43095
--- /dev/null
+++ b/src/extension/internal/pdfinput/svg-builder.h
@@ -0,0 +1,296 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_EXTENSION_INTERNAL_PDFINPUT_SVGBUILDER_H
+#define SEEN_EXTENSION_INTERNAL_PDFINPUT_SVGBUILDER_H
+
+/*
+ * Authors:
+ * miklos erdelyi
+ *
+ * Copyright (C) 2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#ifdef HAVE_POPPLER
+#include "poppler-transition-api.h"
+
+class SPDocument;
+namespace Inkscape {
+ namespace XML {
+ struct Document;
+ class Node;
+ }
+}
+
+#define Operator Operator_Gfx
+#include <Gfx.h>
+#undef Operator
+
+#include <2geom/affine.h>
+#include <2geom/point.h>
+#include <cairo-ft.h>
+#include <glibmm/ustring.h>
+#include <lcms2.h>
+
+#include "CharTypes.h"
+#include "enums.h"
+#include "poppler-utils.h"
+class Function;
+class GfxState;
+struct GfxColor;
+class GfxColorSpace;
+enum GfxClipType;
+struct GfxRGB;
+class GfxPath;
+class GfxPattern;
+class GfxTilingPattern;
+class GfxShading;
+class GfxFont;
+class GfxImageColorMap;
+class Stream;
+class XRef;
+
+class CairoFont;
+class SPCSSAttr;
+class ClipHistoryEntry;
+
+#include <glib.h>
+#include <map>
+#include <memory>
+#include <vector>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+/**
+ * Holds information about glyphs added by PdfParser which haven't been added
+ * to the document yet.
+ */
+struct SvgGlyph {
+ Geom::Point position; // Absolute glyph coords
+ Geom::Point text_position; // Absolute glyph coords in text space
+ Geom::Point delta; // X, Y advance values
+ double rise; // Text rise parameter
+ Glib::ustring code; // UTF-8 coded character
+ bool is_space;
+
+ bool style_changed; // Set to true if style has to be reset
+ GfxState *state; // A promise of the future text style
+ double text_size; // Text size
+
+ const char *font_specification; // Pointer to current font specification
+ SPCSSAttr *css_font; // The font style as a css style
+ unsigned int cairo_index; // The index into the selected cairo font
+ std::shared_ptr<CairoFont> cairo_font; // A pointer to the selected cairo font
+};
+
+/**
+ * Builds the inner SVG representation using libpoppler from the calls of PdfParser.
+ */
+class SvgBuilder {
+public:
+ SvgBuilder(SPDocument *document, gchar *docname, XRef *xref);
+ SvgBuilder(SvgBuilder *parent, Inkscape::XML::Node *root);
+ virtual ~SvgBuilder();
+
+ // Property setting
+ void setDocumentSize(double width, double height); // Document size in px
+ void setMargins(const Geom::Rect &page, const Geom::Rect &margins, const Geom::Rect &bleed);
+ void cropPage(const Geom::Rect &bbox);
+ void setAsLayer(const char *layer_name = nullptr, bool visible = true);
+ void setGroupOpacity(double opacity);
+ Inkscape::XML::Node *getPreferences() {
+ return _preferences;
+ }
+ void pushPage(const std::string &label, GfxState *state);
+
+ // Path adding
+ bool shouldMergePath(bool is_fill, const std::string &path);
+ bool mergePath(GfxState *state, bool is_fill, const std::string &path, bool even_odd = false);
+ void addPath(GfxState *state, bool fill, bool stroke, bool even_odd=false);
+ void addClippedFill(GfxShading *shading, const Geom::Affine shading_tr);
+ void addShadedFill(GfxShading *shading, const Geom::Affine shading_tr, GfxPath *path, const Geom::Affine tr,
+ bool even_odd = false);
+
+ // Image handling
+ void addImage(GfxState *state, Stream *str, int width, int height,
+ GfxImageColorMap *color_map, bool interpolate, int *mask_colors);
+ void addImageMask(GfxState *state, Stream *str, int width, int height,
+ bool invert, bool interpolate);
+ void addMaskedImage(GfxState *state, Stream *str, int width, int height,
+ GfxImageColorMap *color_map, bool interpolate,
+ Stream *mask_str, int mask_width, int mask_height,
+ bool invert_mask, bool mask_interpolate);
+ void addSoftMaskedImage(GfxState *state, Stream *str, int width, int height,
+ GfxImageColorMap *color_map, bool interpolate,
+ Stream *mask_str, int mask_width, int mask_height,
+ GfxImageColorMap *mask_color_map, bool mask_interpolate);
+ void applyOptionalMask(Inkscape::XML::Node *mask, Inkscape::XML::Node *target);
+
+ // Groups, Transparency group and soft mask handling
+ void startGroup(GfxState *state, double *bbox, GfxColorSpace *blending_color_space, bool isolated, bool knockout,
+ bool for_softmask);
+ void finishGroup(GfxState *state, bool for_softmask);
+ void popGroup(GfxState *state);
+
+ // Text handling
+ void beginString(GfxState *state, int len);
+ void endString(GfxState *state);
+ void addChar(GfxState *state, double x, double y,
+ double dx, double dy,
+ double originX, double originY,
+ CharCode code, int nBytes, Unicode const *u, int uLen);
+ void beginTextObject(GfxState *state);
+ void endTextObject(GfxState *state);
+
+ bool isPatternTypeSupported(GfxPattern *pattern);
+ void setFontStrategies(FontStrategies fs) { _font_strategies = fs; }
+ static FontStrategies autoFontStrategies(FontStrategy s, FontList fonts);
+
+ // State manipulation
+ void saveState(GfxState *state);
+ void restoreState(GfxState *state);
+ void updateStyle(GfxState *state);
+ void updateFont(GfxState *state, std::shared_ptr<CairoFont> cairo_font, bool flip);
+ void updateTextPosition(double tx, double ty);
+ void updateTextShift(GfxState *state, double shift);
+ void updateTextMatrix(GfxState *state, bool flip);
+
+ // Clipping
+ void setClip(GfxState *state, GfxClipType clip, bool is_bbox = false);
+
+ // Layers i.e Optional Groups
+ void addOptionalGroup(const std::string &oc, const std::string &label, bool visible = true);
+ void beginMarkedContent(const char *name = nullptr, const char *group = nullptr);
+ void endMarkedContent();
+
+ void addColorProfile(unsigned char *profBuf, int length);
+
+private:
+ void _init();
+
+ // Pattern creation
+ gchar *_createPattern(GfxPattern *pattern, GfxState *state, bool is_stroke=false);
+ gchar *_createGradient(GfxShading *shading, const Geom::Affine pat_matrix, bool for_shading = false);
+ void _addStopToGradient(Inkscape::XML::Node *gradient, double offset, GfxColor *color, GfxColorSpace *space,
+ double opacity);
+ bool _addGradientStops(Inkscape::XML::Node *gradient, GfxShading *shading,
+ _POPPLER_CONST Function *func);
+ gchar *_createTilingPattern(GfxTilingPattern *tiling_pattern, GfxState *state,
+ bool is_stroke=false);
+ // Image/mask creation
+ Inkscape::XML::Node *_createImage(Stream *str, int width, int height,
+ GfxImageColorMap *color_map, bool interpolate,
+ int *mask_colors, bool alpha_only=false,
+ bool invert_alpha=false);
+ Inkscape::XML::Node *_createMask(double width, double height);
+ Inkscape::XML::Node *_createClip(const std::string &d, const Geom::Affine tr, bool even_odd);
+
+ // Style setting
+ SPCSSAttr *_setStyle(GfxState *state, bool fill, bool stroke, bool even_odd=false);
+ void _setStrokeStyle(SPCSSAttr *css, GfxState *state);
+ void _setFillStyle(SPCSSAttr *css, GfxState *state, bool even_odd);
+ void _setTextStyle(Inkscape::XML::Node *node, GfxState *state, SPCSSAttr *font_style, Geom::Affine text_affine);
+ void _setBlendMode(Inkscape::XML::Node *node, GfxState *state);
+ void _setTransform(Inkscape::XML::Node *node, GfxState *state, Geom::Affine extra = Geom::identity());
+ // Write buffered text into doc
+ void _flushText(GfxState *state);
+ std::string _aria_label;
+ bool _aria_space = false;
+
+ // Handling of node stack
+ Inkscape::XML::Node *_pushGroup();
+ Inkscape::XML::Node *_popGroup();
+ Inkscape::XML::Node *_pushContainer(const char *name);
+ Inkscape::XML::Node *_pushContainer(Inkscape::XML::Node *node);
+ Inkscape::XML::Node *_popContainer();
+ std::vector<Inkscape::XML::Node *> _node_stack;
+ std::vector<GfxState *> _mask_groups;
+ int _clip_groups = 0;
+
+ Inkscape::XML::Node *_getClip(const Geom::Affine &node_tr);
+ Inkscape::XML::Node *_addToContainer(const char *name);
+ Inkscape::XML::Node *_renderText(std::shared_ptr<CairoFont> cairo_font, double font_size,
+ const Geom::Affine &transform,
+ cairo_glyph_t *cairo_glyphs, unsigned int count);
+
+ void _setClipPath(Inkscape::XML::Node *node);
+ void _addToContainer(Inkscape::XML::Node *node, bool release = true);
+
+ Inkscape::XML::Node *_getGradientNode(Inkscape::XML::Node *node, bool is_fill);
+ static bool _attrEqual(Inkscape::XML::Node *a, Inkscape::XML::Node *b, char const *attr);
+
+ // Colors
+ std::string convertGfxColor(const GfxColor *color, GfxColorSpace *space);
+ std::string _getColorProfile(cmsHPROFILE hp);
+
+ // The calculated font style, if not set, the text must be rendered with cairo instead.
+ FontStrategies _font_strategies;
+ double _css_font_size = 1.0;
+ SPCSSAttr *_css_font;
+ const char *_font_specification;
+ double _text_size;
+ Geom::Affine _text_matrix;
+ Geom::Point _text_position;
+ std::vector<SvgGlyph> _glyphs; // Added characters
+
+ // The font when drawing the text into vector glyphs instead of text elements.
+ std::shared_ptr<CairoFont> _cairo_font;
+
+ bool _in_text_object; // Whether we are inside a text object
+ bool _invalidated_style;
+ bool _invalidated_strategy = false;
+ bool _for_softmask = false;
+
+ bool _is_top_level; // Whether this SvgBuilder is the top-level one
+ SPDocument *_doc;
+ gchar *_docname; // Basename of the URI from which this document is created
+ XRef *_xref; // Cross-reference table from the PDF doc we're converting from
+ Inkscape::XML::Document *_xml_doc;
+ Inkscape::XML::Node *_root; // Root node from the point of view of this SvgBuilder
+ Inkscape::XML::Node *_container; // Current container (group/pattern/mask)
+ Inkscape::XML::Node *_preferences; // Preferences container node
+ double _width; // Document size in px
+ double _height; // Document size in px
+
+ Inkscape::XML::Node *_page = nullptr; // XML Page definition
+ int _page_num = 0; // Are we on a page
+ double _page_left = 0 ; // Move to the left for more pages
+ double _page_top = 0 ; // Move to the top (maybe)
+ bool _page_offset = false;
+ Geom::Affine _page_affine = Geom::identity();
+
+ std::map<std::string, std::pair<std::string, bool>> _ocgs;
+
+ std::string _icc_profile;
+ std::map<cmsHPROFILE, std::string> _icc_profiles;
+
+ ClipHistoryEntry *_clip_history; // clip path stack
+ Inkscape::XML::Node *_clip_text = nullptr;
+ Inkscape::XML::Node *_clip_text_group = nullptr;
+};
+
+
+} // namespace Internal
+} // namespace Extension
+} // namespace Inkscape
+
+#endif // HAVE_POPPLER
+
+#endif // SEEN_EXTENSION_INTERNAL_PDFINPUT_SVGBUILDER_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :