21#include <poppler/Catalog.h>
22#include <poppler/ErrorCodes.h>
23#include <poppler/FontInfo.h>
24#include <poppler/GfxFont.h>
25#include <poppler/GlobalParams.h>
26#include <poppler/OptionalContent.h>
27#include <poppler/PDFDoc.h>
28#include <poppler/Page.h>
29#include <poppler/goo/GooString.h>
31#ifdef HAVE_POPPLER_CAIRO
32#include <poppler/glib/poppler.h>
33#include <poppler/glib/poppler-document.h>
34#include <poppler/glib/poppler-page.h>
37#include <gdkmm/general.h>
38#include <glibmm/convert.h>
39#include <glibmm/i18n.h>
40#include <glibmm/miscutils.h>
41#include <gtkmm/builder.h>
42#include <gtkmm/button.h>
43#include <gtkmm/cellrenderercombo.h>
44#include <gtkmm/checkbutton.h>
45#include <gtkmm/comboboxtext.h>
46#include <gtkmm/drawingarea.h>
47#include <gtkmm/entry.h>
48#include <gtkmm/frame.h>
49#include <gtkmm/label.h>
50#include <gtkmm/liststore.h>
51#include <gtkmm/notebook.h>
52#include <gtkmm/scale.h>
76void sanitize_page_number(
int &page_num,
const int num_pages) {
77 if (page_num < 1 || page_num > num_pages) {
78 std::cerr <<
"Inkscape::Extension::Internal::PdfInput::open: Bad page number "
80 <<
". Import first page instead."
92class FontModelColumns :
public Gtk::TreeModel::ColumnRecord
107 ~FontModelColumns()
override =
default;
108 Gtk::TreeModelColumn<int> id;
109 Gtk::TreeModelColumn<Glib::ustring> family;
110 Gtk::TreeModelColumn<Glib::ustring> style;
111 Gtk::TreeModelColumn<Glib::ustring>
weight;
112 Gtk::TreeModelColumn<Glib::ustring> stretch;
113 Gtk::TreeModelColumn<Glib::ustring> proc_label;
114 Gtk::TreeModelColumn<int> proc_id;
115 Gtk::TreeModelColumn<Glib::ustring> icon;
116 Gtk::TreeModelColumn<bool> em;
121 if (value ==
"render-missing") {
123 }
else if (value ==
"substitute") {
125 }
else if (value ==
"keep-missing") {
127 }
else if (value ==
"delete-missing") {
129 }
else if (value ==
"render-all") {
131 }
else if (value ==
"delete-all") {
134 g_warning(
"Unknown fontRendering option '%s'", value.c_str());
144 : _pdf_doc(
std::move(doc))
147 , _page_numbers(UI::
get_widget<
Gtk::Entry>(_builder,
"page-numbers"))
148 , _preview_area(UI::
get_widget<
Gtk::DrawingArea>(_builder,
"preview-area"))
149 , _clip_to(UI::
get_widget<
Gtk::ComboBox>(_builder,
"clip-to"))
150 , _embed_images(UI::
get_widget<
Gtk::CheckButton>(_builder,
"embed-images"))
151 , _import_pages(UI::
get_widget<
Gtk::CheckButton>(_builder,
"import-pages"))
152 , _mesh_slider(UI::
get_widget<
Gtk::Scale>(_builder,
"mesh-slider"))
153 , _mesh_label(UI::
get_widget<
Gtk::Label>(_builder,
"mesh-label"))
154 , _next_page(UI::
get_widget<
Gtk::Button>(_builder,
"next-page"))
155 , _prev_page(UI::
get_widget<
Gtk::Button>(_builder,
"prev-page"))
156 , _current_page(UI::
get_widget<
Gtk::Label>(_builder,
"current-page"))
157 , _font_model(UI::
get_object<
Gtk::ListStore>(_builder,
"font-list"))
158 , _font_col(new FontModelColumns())
164 auto const okbutton = Gtk::make_managed<Gtk::Button>(_(
"_OK"),
true);
166 get_content_area()->set_homogeneous(
false);
167 get_content_area()->set_spacing(0);
170 this->set_title(_(
"PDF Import Settings"));
171 this->set_modal(
true);
173 this->set_resizable(
true);
174 this->property_destroy_with_parent().set_value(
false);
176 this->add_action_widget(*Gtk::make_managed<Gtk::Button>(_(
"_Cancel"),
true), -6);
177 this->add_action_widget(*okbutton, -5);
187#ifdef HAVE_POPPLER_CAIRO
195 std::string filename =
_pdf_doc->getFileName()->getCString();
196 if (!Glib::path_is_absolute(filename)) {
197 filename = Glib::build_filename(Glib::get_current_dir(),filename);
199 Glib::ustring full_uri = Glib::filename_to_uri(filename);
201 if (!full_uri.empty()) {
202 _poppler_doc = poppler_document_new_from_file(full_uri.c_str(), NULL, NULL);
216 okbutton->set_focusable();
217 set_default_widget(*okbutton);
218 set_focus(*okbutton);
220 auto &font_strat = UI::get_object_raw<Gtk::CellRendererCombo>(
_builder,
"cell-strat");
221 font_strat.signal_changed().connect([
this](
const Glib::ustring &path,
const Gtk::TreeModel::iterator &source) {
223 (*target)[_font_col->proc_id] = int((*source)[_font_col->id]);
224 (*target)[_font_col->proc_label] = Glib::ustring((*source)[_font_col->family]);
229 _mesh_slider.get_adjustment()->signal_value_changed().connect([
this]() {
232 static Glib::ustring labels[] = {
233 Glib::ustring(C_(
"PDF input precision",
"rough")), Glib::ustring(C_(
"PDF input precision",
"medium")),
234 Glib::ustring(C_(
"PDF input precision",
"fine")), Glib::ustring(C_(
"PDF input precision",
"very fine"))};
237 double min = adj->get_lower();
238 int comment_idx = (int)
floor((adj->get_value() - min) / (adj->get_upper() - min) * 4.0);
239 _mesh_label.set_label(labels[std::min(comment_idx, 3)]);
243 _clip_to.set_active_id(mod->get_param_optiongroup(
"clipTo"));
244 _clip_to.signal_changed().connect([
this]() {
248 _embed_images.set_active(mod->get_param_bool(
"embedImages",
true));
253 _import_pages.set_active(mod->get_param_bool(
"importPages",
true));
258 auto &font_render = UI::get_widget<Gtk::ComboBox>(
_builder,
"font-rendering");
259 auto render_pref = mod->get_param_optiongroup(
"fontRendering");
260 font_render.set_active_id(render_pref);
263 font_render.signal_changed().connect([
this]() {
264 auto &font_render = UI::get_widget<Gtk::ComboBox>(
_builder,
"font-rendering");
265 auto active_id = font_render.get_active_id();
273#ifdef HAVE_POPPLER_CAIRO
285 if ( b == Gtk::ResponseType::OK ) {
302 auto &import_type = UI::get_widget<Gtk::Notebook>(
_builder,
"import-type");
332 for (
auto pair : *fonts) {
333 auto font = pair.first;
334 auto &
data = pair.second;
345 if (font->isCIDFont()) {
346 row[
_font_col->icon] = Glib::ustring(
"text-convert-to-regular");
348 row[
_font_col->icon] = Glib::ustring(
data.found ?
"on" :
"off-outline");
382 child[
_font_col->proc_label] = _(
"Replace by closest-named installed font");
391#ifdef HAVE_POPPLER_CAIRO
402 int cairo_width, cairo_height, cairo_rowstride;
403 unsigned char *pixbuf_data, *dst, *cairo_data;
404 int pixbuf_rowstride, pixbuf_n_channels;
408 cairo_width = cairo_image_surface_get_width (
surface);
409 cairo_height = cairo_image_surface_get_height (
surface);
410 cairo_rowstride = cairo_image_surface_get_stride(
surface);
411 cairo_data = cairo_image_surface_get_data(
surface);
413 pixbuf_data = gdk_pixbuf_get_pixels (pixbuf);
414 pixbuf_rowstride = gdk_pixbuf_get_rowstride (pixbuf);
415 pixbuf_n_channels = gdk_pixbuf_get_n_channels (pixbuf);
417 if (cairo_width > gdk_pixbuf_get_width (pixbuf))
418 cairo_width = gdk_pixbuf_get_width (pixbuf);
419 if (cairo_height > gdk_pixbuf_get_height (pixbuf))
420 cairo_height = gdk_pixbuf_get_height (pixbuf);
421 for (y = 0; y < cairo_height; y++)
423 src =
reinterpret_cast<unsigned int *
>(cairo_data + y * cairo_rowstride);
424 dst = pixbuf_data + y * pixbuf_rowstride;
425 for (x = 0; x < cairo_width; x++)
427 dst[0] = (*src >> 16) & 0xff;
428 dst[1] = (*src >> 8) & 0xff;
429 dst[2] = (*src >> 0) & 0xff;
430 if (pixbuf_n_channels == 4)
431 dst[3] = (*src >> 24) & 0xff;
432 dst += pixbuf_n_channels;
444#ifdef HAVE_POPPLER_CAIRO
452 Glib::RefPtr<Gdk::Pixbuf> thumb;
455 thumb = Gdk::Pixbuf::create(Gdk::Colorspace::RGB,
true,
458 thumb = Gdk::Pixbuf::create_from_data(
_thumb_data, Gdk::Colorspace::RGB,
467 thumb->fill(0xffffffff);
468 Gdk::Cairo::set_source_pixbuf(cr, thumb, 0, 0);
472#ifdef HAVE_POPPLER_CAIRO
479 Gdk::Cairo::set_source_pixbuf(cr, thumb, 0,
_render_thumb ? 0 : 20);
494 std::ostringstream example;
520#ifdef HAVE_POPPLER_CAIRO
524 if ( rotate == 90 || rotate == 270 ) {
534 double scale_factor = ( scale_x > scale_y ) ? scale_y : scale_x;
547 auto cairo_surface = std::shared_ptr<cairo_surface_t>(
549 cairo_surface_destroy);
556 channel = std::move(src), dialog =
this,
page]()
mutable {
557 cairo_t *cr = cairo_create(cairo_surface.get());
560 cairo_scale(cr, scale_factor, scale_factor);
561 poppler_page_render(poppler_page.get(), cr);
563 channel.run([dialog,
page] {
564 dialog->_preview_rendering_in_progress =
false;
565 dialog->_preview_area.queue_draw();
566 if (dialog->_preview_page !=
page) {
568 dialog->_setPreviewPage(dialog->_preview_page);
583#ifdef HAVE_POPPLER_CAIRO
588 Glib::ustring* stream =
static_cast<Glib::ustring*
>(closure);
589 stream->append(
reinterpret_cast<const char*
>(
data), length);
591 return CAIRO_STATUS_SUCCESS;
602 globalParams = _POPPLER_NEW_GLOBAL_PARAMS();
607 std::shared_ptr<PDFDoc> pdf_doc;
611 pdf_doc = _POPPLER_MAKE_SHARED_PDFDOC(uri);
613 if (!pdf_doc->isOk()) {
614 int error = pdf_doc->getErrorCode();
615 if (error == errEncrypted) {
616 g_message(
"Document is encrypted.");
617 }
else if (error == errOpenFile) {
618 g_message(
"couldn't open the PDF file.");
619 }
else if (error == errBadCatalog) {
620 g_message(
"couldn't read the page catalog.");
621 }
else if (error == errDamaged) {
622 g_message(
"PDF file was damaged and couldn't be repaired.");
623 }
else if (error == errHighlightFile) {
624 g_message(
"nonexistent or invalid highlight file.");
625 }
else if (error == errBadPrinter) {
626 g_message(
"invalid printer.");
627 }
else if (error == errPrinting) {
628 g_message(
"Error during printing.");
629 }
else if (error == errPermission) {
630 g_message(
"PDF file does not allow that operation.");
631 }
else if (error == errBadPageNum) {
632 g_message(
"invalid page number.");
633 }
else if (error == errFileIO) {
634 g_message(
"file IO error.");
636 g_message(
"Failed to load document from data (error %d)", error);
642 std::optional<PdfImportDialog> dlg;
643 if (INKSCAPE.use_gui()) {
644 dlg.emplace(pdf_doc, uri, mod);
645 if (!dlg->showDialog()) {
651 std::string page_nums =
"1";
655 page_nums = dlg->getSelectedPages();
656 import_method = dlg->getImportMethod();
657 font_strats = dlg->getFontStrategies();
659 page_nums = INKSCAPE.get_pages();
660 auto strat = (
FontStrategy)INKSCAPE.get_pdf_font_strategy();
662#ifdef HAVE_POPPLER_CAIRO
667 auto pages =
parseIntRange(page_nums, 1, pdf_doc->getCatalog()->getNumPages());
670 g_warning(
"No pages selected, getting first page only.");
675 std::unique_ptr<SPDocument> doc;
684 gchar *docname = g_path_get_basename(uri);
685 gchar *
dot = g_strrstr(docname,
".");
690 builder->setFontStrategies(font_strats);
693 builder->setPageMode(mod->get_param_bool(
"importPages",
true));
694 builder->setEmbedImages(mod->get_param_bool(
"embedImages",
true));
695 std::string crop_to = mod->get_param_optiongroup(
"clipTo",
"none");
696 double color_delta = mod->get_param_float(
"approximationPrecision", 2.0);
698 for (
auto p : pages) {
705#ifdef HAVE_POPPLER_CAIRO
709 std::string full_path = uri;
710 if (!Glib::path_is_absolute(uri)) {
711 full_path = Glib::build_filename(Glib::get_current_dir(),uri);
713 Glib::ustring full_uri = Glib::filename_to_uri(full_path);
715 GError *error = NULL;
718 PopplerDocument* document = poppler_document_new_from_file(full_uri.c_str(), NULL, &error);
721 std::cerr <<
"PDFInput::open: error opening document: " << full_uri.raw() << std::endl;
722 g_error_free (error);
726 int page_num = *pages.begin();
727 if (PopplerPage*
page = poppler_document_get_page(document, page_num - 1)) {
731 Glib::ustring output;
736 cairo_svg_surface_set_document_unit(
surface, CAIRO_SVG_UNIT_PT);
742 cairo_svg_surface_restrict_to_version(
surface, CAIRO_SVG_VERSION_1_2 );
746 poppler_page_render_for_printing(
page, cr);
750 cairo_surface_destroy(
surface);
754 g_object_unref(G_OBJECT(
page));
755 }
else if (document) {
756 std::cerr <<
"PDFInput::open: error opening page " << page_num <<
" of document: " << full_uri.raw() << std::endl;
758 g_object_unref(G_OBJECT(document));
770 if (!doc->getRoot()->viewBox_set) {
771 doc->setViewBox(
Geom::Rect::from_xywh(0, 0, doc->getWidth().value(doc->getDisplayUnit()), doc->getHeight().value(doc->getDisplayUnit())));
787 Catalog *catalog = pdf_doc->getCatalog();
788 sanitize_page_number(page_num, catalog->getNumPages());
789 Page *
page = catalog->getPage(page_num);
791 std::cerr <<
"PDFInput::open: error opening page " << page_num << std::endl;
796 _POPPLER_CONST PDFRectangle *clipToBox =
nullptr;
798 if (crop_to ==
"media-box") {
799 clipToBox =
page->getMediaBox();
800 }
else if (crop_to ==
"crop-box") {
801 clipToBox =
page->getCropBox();
802 }
else if (crop_to ==
"trim-box") {
803 clipToBox =
page->getTrimBox();
804 }
else if (crop_to ==
"bleed-box") {
805 clipToBox =
page->getBleedBox();
806 }
else if (crop_to ==
"art-box") {
807 clipToBox =
page->getArtBox();
814 if ( color_delta <= 0.0 ) {
815 color_delta = 1.0 / 2.0;
817 color_delta = 1.0 / color_delta;
819 for (
int i = 1 ; i <= pdfNumShadingTypes ; i++ ) {
820 pdf_parser.setApproximationPrecision(i, color_delta, 6);
824 Object obj =
page->getContents();
826 pdf_parser.parse(&obj);
830 if (
auto annots =
page->getAnnotsObject(); annots.isArray()) {
831 auto const size = annots.arrayGetLength();
832 for (
int i = 0; i <
size; i++) {
833 pdf_parser.build_annots(annots.arrayGet(i), page_num);
838#include "../clear-n_.h"
844 "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI
"\">\n"
845 "<name>" N_(
"PDF Input")
"</name>\n"
846 "<id>org.inkscape.input.pdf</id>\n"
847 PDF_COMMON_INPUT_PARAMS
849 "<extension>.pdf</extension>\n"
850 "<mimetype>application/pdf</mimetype>\n"
851 "<filetypename>" N_(
"Portable Document Format (*.pdf)")
"</filetypename>\n"
852 "<filetypetooltip>" N_(
"Portable Document Format")
"</filetypetooltip>\n"
854 "</inkscape-extension>", std::make_unique<PdfInput>());
860 "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI
"\">\n"
861 "<name>" N_(
"AI Input")
"</name>\n"
862 "<id>org.inkscape.input.ai</id>\n"
863 PDF_COMMON_INPUT_PARAMS
865 "<extension>.ai</extension>\n"
866 "<mimetype>image/x-adobe-illustrator</mimetype>\n"
867 "<filetypename>" N_(
"Adobe Illustrator 9.0 and above (*.ai)")
"</filetypename>\n"
868 "<filetypetooltip>" N_(
"Open files saved in Adobe Illustrator 9.0 and newer versions")
"</filetypetooltip>\n"
870 "</inkscape-extension>", std::make_unique<PdfInput>());
struct _GdkPixbuf GdkPixbuf
Cairo::RefPtr< Cairo::ImageSurface > surface
static CRect from_xywh(Coord x, Coord y, Coord w, Coord h)
Create rectangle from origin and dimensions.
static bool getUndoSensitive(SPDocument const *document)
static void setUndoSensitive(SPDocument *doc, bool sensitive)
Set undo sensitivity.
double set_param_float(char const *name, double value)
Sets a parameter identified by name with the double in the parameter value.
double get_param_float(char const *name) const
Gets a float parameter identified by name with the double placed in value.
bool set_param_bool(char const *name, bool value)
Sets a parameter identified by name with the boolean in the parameter value.
char const * set_param_optiongroup(char const *name, char const *value)
Sets a parameter identified by name with the string in the parameter value.
PopplerDocument * _poppler_doc
void _setFonts(const FontList &fonts)
Set a full list of all fonts in use for the whole PDF document.
PdfImportType getImportMethod()
std::string _current_pages
std::shared_ptr< PDFDoc > _pdf_doc
void setFontStrategies(const FontStrategies &fs)
Update the font strats.
std::vector< Async::Channel::Dest > _channels
FontModelColumns * _font_col
bool _preview_rendering_in_progress
Gtk::CheckButton & _embed_images
Gtk::Entry & _page_numbers
Glib::RefPtr< Gtk::Builder > _builder
Gtk::Scale & _mesh_slider
Gtk::Label & _current_page
Gtk::CheckButton & _import_pages
FontStrategies getFontStrategies()
Saves each decided font strategy to the Svg Builder object.
~PdfImportDialog() override
Gtk::DrawingArea & _preview_area
PdfImportDialog(std::shared_ptr< PDFDoc > doc, const gchar *uri, Input *mod)
The PDF import dialog FIXME: Probably this should be placed into src/ui/dialog.
Glib::RefPtr< Gtk::ListStore > _font_model
unsigned char * _thumb_data
void _setPreviewPage(int page)
Renders the given page's thumbnail using Cairo.
void _drawFunc(const Cairo::RefPtr< Cairo::Context > &cr, int width, int height)
std::unordered_map< int, std::shared_ptr< cairo_surface_t > > _cairo_surfaces
void _onPageNumberChanged()
std::string getSelectedPages()
Builds the inner SVG representation using libpoppler from the calls of PdfParser.
static FontStrategies autoFontStrategies(FontStrategy s, FontList fonts)
Decide what to do for each font in the font list, with the given strategy.
A smart pointer that shares ownership of a GObject.
PDF parsing module using libpoppler's facilities.
Typed SVG document implementation.
static std::unique_ptr< SPDocument > createNewDocFromMem(std::span< char const > buffer, bool keepalive, std::string const &filename="")
static std::unique_ptr< SPDocument > createNewDoc(char const *filename, bool keepalive, bool make_new=false, SPDocument *parent=nullptr)
Fetches document from filename, or creates new, if NULL; public document appears in document list.
void sp_transientize(Gtk::Window &window)
Make the argument dialog transient to the currently active document window.
Event handler for dialog windows.
TODO: insert short description here.
struct _cairo_surface cairo_surface_t
auto floor(Geom::Rect const &rect)
std::pair< Source, Dest > create()
Create a linked Source - Destination pair forming a thread-safe communication channel.
void fire_and_forget(F &&f)
Launch an async which will delay program exit until its termination.
static FontStrategy pref_to_font_strategy(std::string const &value)
static cairo_status_t _write_ustring_cb(void *closure, const unsigned char *data, unsigned int length)
helper method
static void copy_cairo_surface_to_pixbuf(cairo_surface_t *surface, GdkPixbuf *pixbuf)
Copies image data from a Cairo surface to a pixbuf.
void build_from_mem(gchar const *buffer, std::unique_ptr< Implementation::Implementation > in_imp)
Create a module from a buffer holding an XML description.
Glib::RefPtr< Ob > get_object(Glib::RefPtr< Gtk::Builder > const &builder, char const *id)
W & get_widget(const Glib::RefPtr< Gtk::Builder > &builder, const char *id)
void pack_start(Gtk::Box &box, Gtk::Widget &child, bool const expand, bool const fill, unsigned const padding)
Adds child to box, packed with reference to the start of box.
int dialog_run(Gtk::Dialog &dialog)
This is a GTK4 porting aid meant to replace the removal of the Gtk::Dialog synchronous API.
Glib::RefPtr< Gtk::Builder > create_builder(const char *filename)
Helper class to stream background task notifications as a series of messages.
std::set< unsigned > parseIntRange(std::string const &input, unsigned start, unsigned end)
Parse integer ranges out of a string.
Helpers for using Gtk::Boxes, encapsulating large changes between GTK3 & GTK4.
PDF parsing using libpoppler.
FontList getPdfFonts(std::shared_ptr< PDFDoc > pdf_doc)
std::shared_ptr< std::map< FontPtr, FontData > > FontList
Singleton class to access the preferences file in a convenient way.
SPRoot: SVG <svg> implementation.
void dot(Cairo::RefPtr< Cairo::Context > &cr, double x, double y)
void cairo_set_source_rgba(cairo_t *cr, colour c)
Glib::RefPtr< Gtk::Builder > builder