Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
pdf-input.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Native PDF import using libpoppler.
4 *
5 * Authors:
6 * miklos erdelyi
7 * Abhishek Sharma
8 *
9 * Copyright (C) 2007 Authors
10 *
11 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
12 *
13 */
14
15#ifdef HAVE_CONFIG_H
16# include "config.h" // only include where actually required!
17#endif
18
19#include "pdf-input.h"
20
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>
30
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>
35#endif
36
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>
53#include <utility>
54
55#include "async/async.h"
56#include "document-undo.h"
57#include "document.h"
58#include "extension/input.h"
59#include "extension/system.h"
60#include "inkscape.h"
61#include "object/sp-root.h"
62#include "pdf-parser.h"
63#include "preferences.h"
64#include "ui/builder-utils.h"
65#include "ui/dialog-events.h"
66#include "ui/dialog-run.h"
67#include "ui/pack.h"
68#include "util/gobjectptr.h"
70#include "util/units.h"
71
72using namespace Inkscape::UI;
73
74namespace {
75
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 "
79 << page_num
80 << ". Import first page instead."
81 << std::endl;
82 page_num = 1;
83 }
84}
85
86}
87
88namespace Inkscape {
89namespace Extension {
90namespace Internal {
91
92class FontModelColumns : public Gtk::TreeModel::ColumnRecord
93{
94public:
95 FontModelColumns()
96 {
97 add(id);
98 add(family);
99 add(style);
100 add(weight);
101 add(stretch);
102 add(proc_label);
103 add(proc_id);
104 add(icon);
105 add(em);
106 }
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;
117};
118
119static FontStrategy pref_to_font_strategy(std::string const &value)
120{
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") {
133 }
134 g_warning("Unknown fontRendering option '%s'", value.c_str());
136}
137
143PdfImportDialog::PdfImportDialog(std::shared_ptr<PDFDoc> doc, const gchar * /*uri*/, Input *mod)
144 : _pdf_doc(std::move(doc))
145 , _mod(mod)
146 , _builder(UI::create_builder("extension-pdfinput.glade"))
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())
159{
160 assert(_pdf_doc);
161
163
164 auto const okbutton = Gtk::make_managed<Gtk::Button>(_("_OK"), true);
165
166 get_content_area()->set_homogeneous(false);
167 get_content_area()->set_spacing(0);
168 UI::pack_start(*get_content_area(), UI::get_widget<Gtk::Box>(_builder, "content"));
169
170 this->set_title(_("PDF Import Settings"));
171 this->set_modal(true);
172 sp_transientize(*this);
173 this->set_resizable(true);
174 this->property_destroy_with_parent().set_value(false);
175
176 this->add_action_widget(*Gtk::make_managed<Gtk::Button>(_("_Cancel"), true), -6);
177 this->add_action_widget(*okbutton, -5);
178
179 _render_thumb = false;
180
181 // Connect signals
182 _next_page.signal_clicked().connect([this] { _setPreviewPage(_preview_page + 1); });
183 _prev_page.signal_clicked().connect([this] { _setPreviewPage(_preview_page - 1); });
184 _preview_area.set_draw_func(sigc::mem_fun(*this, &PdfImportDialog::_drawFunc));
185 _page_numbers.signal_changed().connect(sigc::mem_fun(*this, &PdfImportDialog::_onPageNumberChanged));
186
187#ifdef HAVE_POPPLER_CAIRO
188 _render_thumb = true;
189
190 // Disable the page selector when there's only one page
191 _total_pages = _pdf_doc->getCatalog()->getNumPages();
192 _page_numbers.set_sensitive(_total_pages > 1);
193
194 // Create PopplerDocument
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);
198 }
199 Glib::ustring full_uri = Glib::filename_to_uri(filename);
200
201 if (!full_uri.empty()) {
202 _poppler_doc = poppler_document_new_from_file(full_uri.c_str(), NULL, NULL);
203 }
204#endif
205
206 // Set default preview size
207 _preview_width = 200;
208 _preview_height = 300;
210
211 // Init preview
212 _thumb_data = nullptr;
213 _current_pages = "all";
215
216 okbutton->set_focusable();
217 set_default_widget(*okbutton);
218 set_focus(*okbutton);
219
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) {
222 if (auto target = _font_model->get_iter(path)) {
223 (*target)[_font_col->proc_id] = int((*source)[_font_col->id]);
224 (*target)[_font_col->proc_label] = Glib::ustring((*source)[_font_col->family]);
225 }
226 });
227
228 _mesh_slider.set_value(_mod->get_param_float("approximationPrecision", 2.0));
229 _mesh_slider.get_adjustment()->signal_value_changed().connect([this]() {
230 // Redisplay the comment on the current approximation precision setting
231 // Evenly divides the interval of possible values between the available labels.
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"))};
235
236 auto adj = _mesh_slider.get_adjustment();
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)]);
240 _mod->set_param_float("approximationPrecision", _mesh_slider.get_value());
241 });
242
243 _clip_to.set_active_id(mod->get_param_optiongroup("clipTo"));
244 _clip_to.signal_changed().connect([this]() {
245 _mod->set_param_optiongroup("clipTo", _clip_to.get_active_id().c_str());
246 });
247
248 _embed_images.set_active(mod->get_param_bool("embedImages", true));
249 _embed_images.signal_toggled().connect([this]() {
250 _mod->set_param_bool("embedImages", _embed_images.get_active());
251 });
252
253 _import_pages.set_active(mod->get_param_bool("importPages", true));
254 _import_pages.signal_toggled().connect([this]() {
255 _mod->set_param_bool("importPages", _import_pages.get_active());
256 });
257
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);
261 // Update the font list with this as the default
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();
266 _mod->set_param_optiongroup("fontRendering", active_id.c_str());
267 // Reset any font specific items with the new global strategy
269 });
270}
271
273#ifdef HAVE_POPPLER_CAIRO
274 if (_poppler_doc) {
275 g_object_unref(G_OBJECT(_poppler_doc));
276 }
277#endif
278 if (_thumb_data) {
279 gfree(_thumb_data);
280 }
281}
282
284 gint b = UI::dialog_run(*this);
285 if ( b == Gtk::ResponseType::OK ) {
286 return TRUE;
287 } else {
288 return FALSE;
289 }
290}
291
293{
294 if (_page_numbers.get_sensitive()) {
295 return _current_pages;
296 }
297 return "all";
298}
299
301{
302 auto &import_type = UI::get_widget<Gtk::Notebook>(_builder, "import-type");
303 return (PdfImportType)import_type.get_current_page();
304}
305
307{
308 _current_pages = _page_numbers.get_text();
310 if (!nums.empty()) {
311 _setPreviewPage(*nums.begin());
312 }
313}
314
319{
320 _font_model->clear();
321 _font_list = fonts;
322
323 // Find all fonts on this one page
324 /*std::set<int> found;
325 FontInfoScanner page_scanner(_pdf_doc.get(), page-1);
326 for (const FontInfo *font : page_scanner.scan(page)) {
327 found.insert(font->getRef().num);
328 delete font;
329 }*/
330
331 // Now add all fonts and mark the ones from this page
332 for (auto pair : *fonts) {
333 auto font = pair.first;
334 auto &data = pair.second;
335 auto row = *_font_model->append();
336
337 row[_font_col->id] = font->getID()->num;
338 row[_font_col->em] = false;
339 row[_font_col->family] = !data.family.empty() ? data.family : data.name + " -> " + data.getSubstitute();
340 row[_font_col->style] = data.style;
341 row[_font_col->weight] = data.weight;
342 row[_font_col->stretch] = data.stretch;
343 // row[_font_col->pages] = data.pages;
344
345 if (font->isCIDFont()) {
346 row[_font_col->icon] = Glib::ustring("text-convert-to-regular");
347 } else {
348 row[_font_col->icon] = Glib::ustring(data.found ? "on" : "off-outline");
349 }
350 }
351}
352
357{
359 for (auto child : _font_model->children()) {
360 auto value = (FontFallback) int(child[_font_col->proc_id]);
361 fs[child[_font_col->id]] = value;
362 }
363 return fs;
364}
365
370{
371 for (auto child : _font_model->children()) {
372 auto value = fs.at(child[_font_col->id]);
373 child[_font_col->proc_id] = (int)value;
374 switch (value) {
376 child[_font_col->proc_label] = _("Convert to paths");
377 break;
379 child[_font_col->proc_label] = _("Keep original font name");
380 break;
382 child[_font_col->proc_label] = _("Replace by closest-named installed font");
383 break;
385 child[_font_col->proc_label] = _("Delete text");
386 break;
387 }
388 }
389}
390
391#ifdef HAVE_POPPLER_CAIRO
400 GdkPixbuf *pixbuf)
401{
402 int cairo_width, cairo_height, cairo_rowstride;
403 unsigned char *pixbuf_data, *dst, *cairo_data;
404 int pixbuf_rowstride, pixbuf_n_channels;
405 unsigned int *src;
406 int x, y;
407
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);
412
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);
416
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++)
422 {
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++)
426 {
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;
433 src++;
434 }
435 }
436}
437
438#endif
439
440void PdfImportDialog::_drawFunc(const Cairo::RefPtr<Cairo::Context>& cr, int const width, int const height)
441{
442 // Check if we have a thumbnail at all
443 if (!_thumb_data
444#ifdef HAVE_POPPLER_CAIRO
446#endif
447 ) {
448 return;
449 }
450
451 // Create the pixbuf for the thumbnail
452 Glib::RefPtr<Gdk::Pixbuf> thumb;
453
454 if (_render_thumb) {
455 thumb = Gdk::Pixbuf::create(Gdk::Colorspace::RGB, true,
457 } else if (_thumb_data) {
458 thumb = Gdk::Pixbuf::create_from_data(_thumb_data, Gdk::Colorspace::RGB,
460 }
461 if (!thumb) {
462 return;
463 }
464
465 // Set background to white
466 if (_render_thumb) {
467 thumb->fill(0xffffffff);
468 Gdk::Cairo::set_source_pixbuf(cr, thumb, 0, 0);
469 cr->paint();
470 }
471
472#ifdef HAVE_POPPLER_CAIRO
473 // Copy the thumbnail image from the Cairo surface
474 if (_render_thumb) {
476 }
477#endif
478
479 Gdk::Cairo::set_source_pixbuf(cr, thumb, 0, _render_thumb ? 0 : 20);
480 cr->paint();
481}
482
487 _previewed_page = _pdf_doc->getCatalog()->getPage(page);
488 g_return_if_fail(_previewed_page);
489
490 // Update the UI to select a different page
492 _next_page.set_sensitive(page < _total_pages);
493 _prev_page.set_sensitive(page > 1);
494 std::ostringstream example;
495 example << page << " / " << _total_pages;
496 _current_page.set_label(example.str());
497
498 // Update the font list with per-page highlighting
499 // XXX Update this psuedo code with real code
500 /*for (auto iter : _font_model->children()) {
501 std::unorderd_list<int> *pages = row[_font_col->pages];
502 row[_font_col->em] = bool(page in pages);
503 }*/
504
505 // Try to get a thumbnail from the PDF if possible
506 if (!_render_thumb) {
507 if (_thumb_data) {
508 gfree(_thumb_data);
509 _thumb_data = nullptr;
510 }
511 if (!_previewed_page->loadThumb(&_thumb_data,
513 return;
514 }
515 // Redraw preview area
516 _preview_area.set_size_request(_thumb_width, _thumb_height + 20);
517 _preview_area.queue_draw();
518 return;
519 }
520#ifdef HAVE_POPPLER_CAIRO
521 // Get page size by accounting for rotation
522 double width, height;
523 int rotate = _previewed_page->getRotate();
524 if ( rotate == 90 || rotate == 270 ) {
525 height = _previewed_page->getCropWidth();
526 width = _previewed_page->getCropHeight();
527 } else {
528 width = _previewed_page->getCropWidth();
529 height = _previewed_page->getCropHeight();
530 }
531 // Calculate the needed scaling for the page
532 double scale_x = (double)_preview_width / width;
533 double scale_y = (double)_preview_height / height;
534 double scale_factor = ( scale_x > scale_y ) ? scale_y : scale_x;
535 // Create new Cairo surface
536 _thumb_width = (int)ceil( width * scale_factor );
537 _thumb_height = (int)ceil( height * scale_factor );
539 // poppler_page_render() isn't safe to call concurrently for multiple
540 // pages, so we render at most one page at a time. We'll restart
541 // rendering in "onfinish" if necessary.
543
544 // Render page asynchronously in a separate thread. The dialog will
545 // become available and responsive even if the rendering takes longer.
546
547 auto cairo_surface = std::shared_ptr<cairo_surface_t>(
548 cairo_image_surface_create(CAIRO_FORMAT_ARGB32, _thumb_width, _thumb_height), //
549 cairo_surface_destroy);
550
551 auto poppler_page = Util::GObjectPtr(poppler_document_get_page(_poppler_doc, page - 1));
552
553 auto [src, dst] = Async::Channel::create();
554
555 Async::fire_and_forget([cairo_surface, scale_factor, poppler_page = std::move(poppler_page),
556 channel = std::move(src), dialog = this, page]() mutable {
557 cairo_t *cr = cairo_create(cairo_surface.get());
558 cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0); // Set fill color to white
559 cairo_paint(cr); // Clear it
560 cairo_scale(cr, scale_factor, scale_factor); // Use Cairo for resizing the image
561 poppler_page_render(poppler_page.get(), cr);
562 cairo_destroy(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) {
567 // Restart rendering for the current page
568 dialog->_setPreviewPage(dialog->_preview_page);
569 }
570 });
571 });
572
573 _channels.push_back(std::move(dst));
574 _cairo_surfaces[page] = std::move(cairo_surface);
575 }
576 // Redraw preview area
577 _preview_area.queue_draw();
578#endif
579}
580
582
583#ifdef HAVE_POPPLER_CAIRO
585static cairo_status_t
586 _write_ustring_cb(void *closure, const unsigned char *data, unsigned int length)
587{
588 Glib::ustring* stream = static_cast<Glib::ustring*>(closure);
589 stream->append(reinterpret_cast<const char*>(data), length);
590
591 return CAIRO_STATUS_SUCCESS;
592}
593#endif
594
598std::unique_ptr<SPDocument> PdfInput::open(Input *mod, char const *uri, bool)
599{
600 // Initialize the globalParams variable for poppler
601 if (!globalParams) {
602 globalParams = _POPPLER_NEW_GLOBAL_PARAMS();
603 }
604
605 // Open the file using poppler
606 // PDFDoc is from poppler. PDFDoc is used for preview and for native import.
607 std::shared_ptr<PDFDoc> pdf_doc;
608
609 // poppler does not use glib g_open. So on win32 we must use unicode call. code was copied from
610 // glib gstdio.c
611 pdf_doc = _POPPLER_MAKE_SHARED_PDFDOC(uri); // TODO: Could ask for password
612
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.");
635 } else {
636 g_message("Failed to load document from data (error %d)", error);
637 }
638
639 return nullptr;
640 }
641
642 std::optional<PdfImportDialog> dlg;
643 if (INKSCAPE.use_gui()) {
644 dlg.emplace(pdf_doc, uri, mod);
645 if (!dlg->showDialog()) {
646 throw Input::open_cancelled();
647 }
648 }
649
650 // Get options
651 std::string page_nums = "1";
653 FontStrategies font_strats;
654 if (dlg) {
655 page_nums = dlg->getSelectedPages();
656 import_method = dlg->getImportMethod();
657 font_strats = dlg->getFontStrategies();
658 } else {
659 page_nums = INKSCAPE.get_pages();
660 auto strat = (FontStrategy)INKSCAPE.get_pdf_font_strategy();
661 font_strats = SvgBuilder::autoFontStrategies(strat, getPdfFonts(pdf_doc));
662#ifdef HAVE_POPPLER_CAIRO
663 import_method = (PdfImportType)INKSCAPE.get_pdf_poppler();
664#endif
665 }
666 // Both poppler and poppler+cairo can get page num info from poppler.
667 auto pages = parseIntRange(page_nums, 1, pdf_doc->getCatalog()->getNumPages());
668
669 if (pages.empty()) {
670 g_warning("No pages selected, getting first page only.");
671 pages.insert(1);
672 }
673
674 // Create Inkscape document from file
675 std::unique_ptr<SPDocument> doc;
676 bool saved = false;
677 if (import_method == PdfImportType::PDF_IMPORT_INTERNAL) {
678 // Create document
679 doc = SPDocument::createNewDoc(nullptr, true, true);
680 saved = DocumentUndo::getUndoSensitive(doc.get());
681 DocumentUndo::setUndoSensitive(doc.get(), false); // No need to undo in this temporary document
682
683 // Create builder
684 gchar *docname = g_path_get_basename(uri);
685 gchar *dot = g_strrstr(docname, ".");
686 if (dot) {
687 *dot = 0;
688 }
689 SvgBuilder *builder = new SvgBuilder(doc.get(), docname, pdf_doc->getXRef());
690 builder->setFontStrategies(font_strats);
691
692 // Get preferences
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);
697
698 for (auto p : pages) {
699 // And then add each of the pages
700 add_builder_page(pdf_doc, builder, doc.get(), p, crop_to, color_delta);
701 }
702
703 delete builder;
704 g_free(docname);
705#ifdef HAVE_POPPLER_CAIRO
706 } else if (import_method == PdfImportType::PDF_IMPORT_CAIRO) {
707 // the poppler import
708
709 std::string full_path = uri;
710 if (!Glib::path_is_absolute(uri)) {
711 full_path = Glib::build_filename(Glib::get_current_dir(),uri);
712 }
713 Glib::ustring full_uri = Glib::filename_to_uri(full_path);
714
715 GError *error = NULL;
718 PopplerDocument* document = poppler_document_new_from_file(full_uri.c_str(), NULL, &error);
719
720 if(error != NULL) {
721 std::cerr << "PDFInput::open: error opening document: " << full_uri.raw() << std::endl;
722 g_error_free (error);
723 return nullptr;
724 }
725
726 int page_num = *pages.begin();
727 if (PopplerPage* page = poppler_document_get_page(document, page_num - 1)) {
728 double width, height;
729 poppler_page_get_size(page, &width, &height);
730
731 Glib::ustring output;
732 cairo_surface_t* surface = cairo_svg_surface_create_for_stream(Inkscape::Extension::Internal::_write_ustring_cb,
733 &output, width, height);
734
735 // Reset back to PT for cairo 1.17.6 and above which sets to UNIT_USER
736 cairo_svg_surface_set_document_unit(surface, CAIRO_SVG_UNIT_PT);
737
738 // This magical function results in more fine-grain fallbacks. In particular, a mesh
739 // gradient won't necessarily result in the whole PDF being rasterized. Of course, SVG
740 // 1.2 never made it as a standard, but hey, we'll take what we can get. This trick was
741 // found by examining the 'pdftocairo' code.
742 cairo_svg_surface_restrict_to_version( surface, CAIRO_SVG_VERSION_1_2 );
743
744 cairo_t* cr = cairo_create(surface);
745
746 poppler_page_render_for_printing(page, cr);
747 cairo_show_page(cr);
748
749 cairo_destroy(cr);
750 cairo_surface_destroy(surface);
751
752 doc = SPDocument::createNewDocFromMem(output.raw(), true);
753
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;
757 }
758 g_object_unref(G_OBJECT(document));
759
760 if (!doc) {
761 return nullptr;
762 }
763
764 saved = DocumentUndo::getUndoSensitive(doc.get());
765 DocumentUndo::setUndoSensitive(doc.get(), false); // No need to undo in this temporary document
766#endif
767 }
768
769 // Set viewBox if it doesn't exist
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())));
772 }
773
774 // Restore undo
775 DocumentUndo::setUndoSensitive(doc.get(), saved);
776
777 return doc;
778}
779
783void
784PdfInput::add_builder_page(std::shared_ptr<PDFDoc>pdf_doc, SvgBuilder *builder, SPDocument *doc, int page_num, std::string const &crop_to, double color_delta)
785{
786 // Check page exists
787 Catalog *catalog = pdf_doc->getCatalog();
788 sanitize_page_number(page_num, catalog->getNumPages());
789 Page *page = catalog->getPage(page_num);
790 if (!page) {
791 std::cerr << "PDFInput::open: error opening page " << page_num << std::endl;
792 return;
793 }
794
795 // Apply crop settings
796 _POPPLER_CONST PDFRectangle *clipToBox = nullptr;
797
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();
808 }
809
810 // Create parser (extension/internal/pdfinput/pdf-parser.h)
811 auto pdf_parser = PdfParser(pdf_doc, builder, page, clipToBox);
812
813 // Set up approximation precision for parser. Used for converting Mesh Gradients into tiles.
814 if ( color_delta <= 0.0 ) {
815 color_delta = 1.0 / 2.0;
816 } else {
817 color_delta = 1.0 / color_delta;
818 }
819 for ( int i = 1 ; i <= pdfNumShadingTypes ; i++ ) {
820 pdf_parser.setApproximationPrecision(i, color_delta, 6);
821 }
822
823 // Parse the document structure
824 Object obj = page->getContents();
825 if (!obj.isNull()) {
826 pdf_parser.parse(&obj);
827 }
828
829 // Parse the annotations
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);
834 }
835 }
836}
837
838#include "../clear-n_.h"
839
841 /* PDF in */
842 // clang-format off
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
848 "<input>\n"
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"
853 "</input>\n"
854 "</inkscape-extension>", std::make_unique<PdfInput>());
855 // clang-format on
856
857 /* AI in */
858 // clang-format off
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
864 "<input>\n"
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"
869 "</input>\n"
870 "</inkscape-extension>", std::make_unique<PdfInput>());
871 // clang-format on
872} // init
873
874} } } /* namespace Inkscape, Extension, Implementation */
875
876/*
877 Local Variables:
878 mode:c++
879 c-file-style:"stroustrup"
880 c-file-offsets:((innamespace . 0)(inline-open . 0))
881 indent-tabs-mode:nil
882 fill-column:99
883 End:
884*/
885// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
Gtk builder utilities.
struct _GdkPixbuf GdkPixbuf
Definition cairo-utils.h:20
Cairo::RefPtr< Cairo::ImageSurface > surface
Definition canvas.cpp:137
uint64_t page
Definition canvas.cpp:171
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.
void _setFonts(const FontList &fonts)
Set a full list of all fonts in use for the whole PDF document.
void setFontStrategies(const FontStrategies &fs)
Update the font strats.
std::vector< Async::Channel::Dest > _channels
Definition pdf-input.h:153
Glib::RefPtr< Gtk::Builder > _builder
Definition pdf-input.h:124
FontStrategies getFontStrategies()
Saves each decided font strategy to the Svg Builder object.
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
Definition pdf-input.h:136
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
Definition pdf-input.h:152
std::unique_ptr< SPDocument > open(Inkscape::Extension::Input *mod, char const *uri, bool is_importing) override
Parses the selected page of the given PDF document using PdfParser.
void add_builder_page(std::shared_ptr< PDFDoc > pdf_doc, SvgBuilder *builder, SPDocument *doc, int page_num, std::string const &crop_to, double color_delta)
Parses the selected page object of the given PDF document using PdfParser.
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.
Definition gobjectptr.h:18
PDF parsing module using libpoppler's facilities.
Definition pdf-parser.h:112
Typed SVG document implementation.
Definition document.h:101
static std::unique_ptr< SPDocument > createNewDocFromMem(std::span< char const > buffer, bool keepalive, std::string const &filename="")
Definition document.cpp:708
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.
Definition document.cpp:658
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
std::map< int, FontFallback > FontStrategies
Definition enums.h:32
FontStrategy
Definition enums.h:17
FontFallback
Definition enums.h:26
auto floor(Geom::Rect const &rect)
Definition geom.h:131
Definition desktop.h:50
std::pair< Source, Dest > create()
Create a linked Source - Destination pair forming a thread-safe communication channel.
Definition channel.h:175
void fire_and_forget(F &&f)
Launch an async which will delay program exit until its termination.
Definition async.h:28
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.
Definition system.cpp:459
User interface code.
Definition desktop.h:113
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.
Definition pack.cpp:141
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.
STL namespace.
Helpers for using Gtk::Boxes, encapsulating large changes between GTK3 & GTK4.
struct _cairo cairo_t
Definition path-cairo.h:16
struct _PopplerDocument PopplerDocument
Definition pdf-input.h:63
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.
unsigned long weight
Definition quantize.cpp:37
Ocnode * child[8]
Definition quantize.cpp:33
SPRoot: SVG <svg> implementation.
static const Point data[]
void dot(Cairo::RefPtr< Cairo::Context > &cr, double x, double y)
double height
double width
void cairo_set_source_rgba(cairo_t *cr, colour c)
Glib::RefPtr< Gtk::Builder > builder