Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
svg-builder.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 * Jon A. Cruz <jon@joncruz.org>
8 * Tavmjong Bah
9 *
10 * Copyright (C) 2007 Authors
11 * 2024 Tavmjong Bah
12 *
13 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
14 *
15 */
16
17#include "svg-builder.h"
18
19#ifdef HAVE_CONFIG_H
20# include "config.h" // only include where actually required!
21#endif
22
23#include <string>
24#include <locale>
25#include <codecvt>
26
27#include <poppler/Function.h>
28#include <poppler/GfxFont.h>
29#include <poppler/GfxState.h>
30#include <poppler/Page.h>
31#include <poppler/Stream.h>
32#include "document.h"
33#include "extract-uri.h"
34#include "pdf-parser.h"
35#include "pdf-utils.h"
36#include <png.h>
38#include "rdf.h"
39
40#include "colors/cms/profile.h"
41#include "colors/document-cms.h"
42#include "display/cairo-utils.h"
44#include "object/sp-defs.h"
45#include "object/sp-namedview.h"
46#include "object/sp-text.h"
48#include "svg/path-string.h"
49#include "svg/svg.h"
50#include "util/units.h"
51#include "xml/document.h"
52#include "xml/node.h"
53#include "xml/repr.h"
54#include "xml/sp-css-attr.h"
55#include "helper/geom.h"
56
57namespace Inkscape {
58namespace Extension {
59namespace Internal {
60
61//#define IFTRACE(_code) _code
62#define IFTRACE(_code)
63
64#define TRACE(_args) IFTRACE(g_print _args)
65
66
72SvgBuilder::SvgBuilder(SPDocument *document, gchar *docname, XRef *xref)
73{
74 _is_top_level = true;
75 _doc = document;
76 _docname = docname;
77 _xref = xref;
80 _init();
81
82 // Set default preference settings
83 _preferences = _xml_doc->createElement("svgbuilder:prefs");
84 _preferences->setAttribute("embedImages", "1");
85}
86
88 _is_top_level = false;
89 _doc = parent->_doc;
90 _docname = parent->_docname;
91 _xref = parent->_xref;
92 _xml_doc = parent->_xml_doc;
93 _preferences = parent->_preferences;
94 _container = this->_root = root;
95 _init();
96}
97
99{
100 if (_clip_history) {
101 delete _clip_history;
102 _clip_history = nullptr;
103 }
104}
105
108 _css_font = nullptr;
109 _in_text_object = false;
110 _invalidated_style = true;
111 _width = 0;
112 _height = 0;
113
114 _node_stack.push_back(_container);
115}
116
120void SvgBuilder::pushPage(const std::string &label, GfxState *state)
121{
122 // Move page over by the last page width
123 if (_page_offset && this->_width) {
124 int gap = 20;
125 _page_left += this->_width + gap;
126 // TODO: A more interesting page layout could be implemented here.
127 }
128 _page_num += 1;
129 _page_offset = true;
130
131 if (_page) {
133 }
134
135 if (_as_pages) {
136 _page = _xml_doc->createElement("inkscape:page");
139
140 if (!label.empty()) {
141 _page->setAttribute("inkscape:label", validateString(label));
142 }
143 auto _nv = _doc->getNamedView()->getRepr();
144 _nv->appendChild(_page);
145 }
146
147 // Page translation is somehow lost in the way we're using poppler and the state management
148 // Applying the state directly doesn't work as many of the flips/rotates are baked in already.
149 // The translation alone must be added back to the page position so items end up in the
150 // right places. If a better method is found, please replace this code.
151 auto st = stateToAffine(state);
152 auto tr = st.translation();
153 if (st[0] < 0 || st[2] < 0) { // Flip or rotate in X
154 tr[Geom::X] = -tr[Geom::X] + state->getPageWidth();
155 }
156 if (st[1] < 0 || st[3] < 0) { // Flip or rotate in Y
157 tr[Geom::Y] = -tr[Geom::Y] + state->getPageHeight();
158 }
159 // Note: This translation is very rare in pdf files, most of the time their initial state doesn't contain
160 // any real translations, just a flip and the because of our GfxState constructor, the pt/px scale.
161 // Please use an example pdf which produces a non-zero translation in order to change this code!
163
164 // No OptionalContentGroups means no layers, so make a default layer for this page.
165 if (_ocgs.empty()) {
166 // Reset to root
167 while (_container != _root) {
168 _popGroup();
169 }
170 _pushGroup();
171 setAsLayer(label.c_str(), true);
172 }
173}
174
176 this->_width = width;
177 this->_height = height;
178
179 // Build the document size to include all page widths together.
180 if (!_as_pages) {
181 width += _page_left;
182 }
183 if (_page_num < 2 || !_as_pages) {
186 }
187 if (_page) {
190 }
191}
192
197{
198 if (_container == _root) {
199 // We're not going to crop when there's PDF Layers
200 return;
201 }
202 auto box = bbox * _page_affine;
204 val << "M" << box.left() << " " << box.top()
205 << "H" << box.right() << "V" << box.bottom()
206 << "H" << box.left() << "Z";
207 auto clip_path = _createClip(val.str(), Geom::identity(), false);
208 gchar *urltext = g_strdup_printf("url(#%s)", clip_path->attribute("id"));
209 _container->setAttribute("clip-path", urltext);
210 g_free(urltext);
211}
212
216void SvgBuilder::setMargins(const Geom::Rect &page, const Geom::Rect &margins, const Geom::Rect &bleed)
217{
218 if (page.width() != _width || page.height() != _height) {
219 // We need to re-set the page size and change the page_affine.
220 _page_affine *= Geom::Translate(-page.left(), -page.top());
221 setDocumentSize(page.width(), page.height());
222 }
223 if (_as_pages && page != margins) {
224 if (!_page) {
225 g_warning("Can not store PDF margins in bare document.");
226 return;
227 }
228 // Calculate the margins from the pdf art box.
230 val << margins.top() - page.top() << " "
231 << page.right() - margins.right() << " "
232 << page.bottom() - margins.bottom() << " "
233 << margins.left() - page.left();
234 _page->setAttribute("margin", val.str());
235 }
236 if (_as_pages && page != bleed) {
237 if (!_page) {
238 g_warning("Can not store PDF bleed in bare document.");
239 return;
240 }
242 val << page.top() - bleed.top() << " "
243 << bleed.right() - page.right() << " "
244 << bleed.bottom() - page.bottom() << " "
245 << page.left() - bleed.left();
246 _page->setAttribute("bleed", val.str());
247 }
248}
249
250void SvgBuilder::setMetadata(char const *name, const std::string &content)
251{
252 if (name && !content.empty()) {
254 }
255}
256
260void SvgBuilder::setAsLayer(const char *layer_name, bool visible)
261{
262 _container->setAttribute("inkscape:groupmode", "layer");
263 if (layer_name) {
264 _container->setAttribute("inkscape:label", validateString(layer_name));
265 }
266 if (!visible) {
268 sp_repr_css_set_property(css, "display", "none");
270 }
271}
272
276void SvgBuilder::setGroupOpacity(double opacity) {
277 _container->setAttributeSvgDouble("opacity", CLAMP(opacity, 0.0, 1.0));
278}
279
280void SvgBuilder::saveState(GfxState *state)
281{
283}
284
285void SvgBuilder::restoreState(GfxState *state) {
287
288 if (!_mask_groups.empty()) {
289 GfxState *mask_state = _mask_groups.back();
290 if (state == mask_state) {
291 popGroup(state);
292 _mask_groups.pop_back();
293 }
294 }
295 while (_clip_groups > 0) {
296 popGroup(nullptr);
297 _clip_groups--;
298 }
299}
300
305
307{
308 _node_stack.push_back(node);
310 // Clear the clip history
312 return node;
313}
314
316{
317 Inkscape::XML::Node *node = nullptr;
318 if ( _node_stack.size() > 1 ) {
319 node = _node_stack.back();
320 _node_stack.pop_back();
321 _container = _node_stack.back(); // Re-set container
323 } else {
324 TRACE(("_popContainer() called when stack is empty\n"));
325 node = _root;
326 }
327 return node;
328}
329
339
346{
347 if (!node->parent()) {
349 }
350 if (release) {
352 }
353}
354
356{
358 if (auto clip_path = _getClip(node)) {
359 gchar *urltext = g_strdup_printf("url(#%s)", clip_path->attribute("id"));
360 node->setAttribute("clip-path", urltext);
361 g_free(urltext);
362 }
363 }
364}
365
374
376{
377 if (_container != _root) { // Pop if the current container isn't root
379 }
380 return _container;
381}
382
383static gchar *svgConvertRGBToText(double r, double g, double b) {
385 static gchar tmp[1023] = {0};
386 snprintf(tmp, 1023,
387 "#%02x%02x%02x",
388 clamp(SP_COLOR_F_TO_U(r)),
389 clamp(SP_COLOR_F_TO_U(g)),
390 clamp(SP_COLOR_F_TO_U(b)));
391 return (gchar *)&tmp;
392}
393
394static std::string svgConvertGfxRGB(GfxRGB *color)
395{
396 double r = (double)color->r / 65535.0;
397 double g = (double)color->g / 65535.0;
398 double b = (double)color->b / 65535.0;
399 return svgConvertRGBToText(r, g, b);
400}
401
402std::string SvgBuilder::convertGfxColor(const GfxColor *color, GfxColorSpace *space)
403{
404 std::string icc = "";
405 switch (space->getMode()) {
406 case csDeviceGray:
407 case csDeviceRGB:
408 case csDeviceCMYK:
409 icc = _icc_profile;
410 break;
411 case csICCBased:
412#if POPPLER_CHECK_VERSION(0, 90, 0)
413 auto icc_space = dynamic_cast<GfxICCBasedColorSpace *>(space);
414 icc = _getColorProfile(icc_space->getProfile().get());
415#else
416 g_warning("ICC profile ignored; libpoppler >= 0.90.0 required.");
417#endif
418 break;
419 }
420
421 GfxRGB rgb;
422 space->getRGB(color, &rgb);
423 auto rgb_color = svgConvertGfxRGB(&rgb);
424
425 if (!icc.empty()) {
427 icc_color << rgb_color << " icc-color(" << icc;
428 for (int i = 0; i < space->getNComps(); ++i) {
429 icc_color << ", " << colToDbl((*color).c[i]);
430 }
431 icc_color << ");";
432 return icc_color.str();
433 }
434
435 return rgb_color;
436}
437
439 if (node->attribute("clip-path")) {
440 g_error("Adding transform AFTER clipping path.");
441 }
443}
444
448static gchar *svgInterpretPath(_POPPLER_CONST_83 GfxPath *path) {
449 Inkscape::SVG::PathString pathString;
450 for (int i = 0 ; i < path->getNumSubpaths() ; ++i ) {
451 _POPPLER_CONST_83 GfxSubpath *subpath = path->getSubpath(i);
452 if (subpath->getNumPoints() > 0) {
453 pathString.moveTo(subpath->getX(0), subpath->getY(0));
454 int j = 1;
455 while (j < subpath->getNumPoints()) {
456 if (subpath->getCurve(j)) {
457 pathString.curveTo(subpath->getX(j), subpath->getY(j),
458 subpath->getX(j+1), subpath->getY(j+1),
459 subpath->getX(j+2), subpath->getY(j+2));
460
461 j += 3;
462 } else {
463 pathString.lineTo(subpath->getX(j), subpath->getY(j));
464 ++j;
465 }
466 }
467 if (subpath->isClosed()) {
468 pathString.closePath();
469 }
470 }
471 }
472
473 return g_strdup(pathString.c_str());
474}
475
481 // Stroke color/pattern
482 auto space = state->getStrokeColorSpace();
483 if (space->getMode() == csPattern) {
484 gchar *urltext = _createPattern(state->getStrokePattern(), state, true);
485 sp_repr_css_set_property(css, "stroke", urltext);
486 if (urltext) {
487 g_free(urltext);
488 }
489 } else {
490 sp_repr_css_set_property(css, "stroke", convertGfxColor(state->getStrokeColor(), space).c_str());
491 }
492
493 // Opacity
495 os_opacity << state->getStrokeOpacity();
496 sp_repr_css_set_property(css, "stroke-opacity", os_opacity.str().c_str());
497
498 // Line width
500 double lw = state->getLineWidth();
501 // emit a stroke which is 1px in toplevel user units
502 os_width << (lw > 0.0 ? lw : 1.0);
503 sp_repr_css_set_property(css, "stroke-width", os_width.str().c_str());
504
505 // Line cap
506 switch (state->getLineCap()) {
507 case 0:
508 sp_repr_css_set_property(css, "stroke-linecap", "butt");
509 break;
510 case 1:
511 sp_repr_css_set_property(css, "stroke-linecap", "round");
512 break;
513 case 2:
514 sp_repr_css_set_property(css, "stroke-linecap", "square");
515 break;
516 }
517
518 // Line join
519 switch (state->getLineJoin()) {
520 case 0:
521 sp_repr_css_set_property(css, "stroke-linejoin", "miter");
522 break;
523 case 1:
524 sp_repr_css_set_property(css, "stroke-linejoin", "round");
525 break;
526 case 2:
527 sp_repr_css_set_property(css, "stroke-linejoin", "bevel");
528 break;
529 }
530
531 // Miterlimit
533 os_ml << state->getMiterLimit();
534 sp_repr_css_set_property(css, "stroke-miterlimit", os_ml.str().c_str());
535
536 // Line dash
537 int dash_length;
538 double dash_start;
539#if POPPLER_CHECK_VERSION(22, 9, 0)
540 const double *dash_pattern;
541 const std::vector<double> &dash = state->getLineDash(&dash_start);
542 dash_pattern = dash.data();
543 dash_length = dash.size();
544#else
545 double *dash_pattern;
546 state->getLineDash(&dash_pattern, &dash_length, &dash_start);
547#endif
548 if ( dash_length > 0 ) {
550 for ( int i = 0 ; i < dash_length ; i++ ) {
551 os_array << dash_pattern[i];
552 if (i < (dash_length - 1)) {
553 os_array << ",";
554 }
555 }
556 sp_repr_css_set_property(css, "stroke-dasharray", os_array.str().c_str());
557
559 os_offset << dash_start;
560 sp_repr_css_set_property(css, "stroke-dashoffset", os_offset.str().c_str());
561 } else {
562 sp_repr_css_set_property(css, "stroke-dasharray", "none");
563 sp_repr_css_set_property(css, "stroke-dashoffset", nullptr);
564 }
565}
566
571void SvgBuilder::_setFillStyle(SPCSSAttr *css, GfxState *state, bool even_odd) {
572
573 // Fill color/pattern
574 auto space = state->getFillColorSpace();
575 if (space->getMode() == csPattern) {
576 gchar *urltext = _createPattern(state->getFillPattern(), state);
577 sp_repr_css_set_property(css, "fill", urltext);
578 if (urltext) {
579 g_free(urltext);
580 }
581 } else {
582 sp_repr_css_set_property(css, "fill", convertGfxColor(state->getFillColor(), space).c_str());
583 }
584
585 // Opacity
587 os_opacity << state->getFillOpacity();
588 sp_repr_css_set_property(css, "fill-opacity", os_opacity.str().c_str());
589
590 // Fill rule
591 sp_repr_css_set_property(css, "fill-rule", even_odd ? "evenodd" : "nonzero");
592}
593
599{
600 SPCSSAttr *css = sp_repr_css_attr(node, "style");
601 GfxBlendMode blendmode = state->getBlendMode();
602 if (blendmode) {
603 sp_repr_css_set_property(css, "mix-blend-mode", enum_blend_mode[blendmode].key);
604 }
605 Glib::ustring value;
607 node->setAttributeOrRemoveIfEmpty("style", value);
609}
610
612{
614}
615
620SPCSSAttr *SvgBuilder::_setStyle(GfxState *state, bool fill, bool stroke, bool even_odd) {
622 if (fill) {
623 _setFillStyle(css, state, even_odd);
624 } else {
625 sp_repr_css_set_property(css, "fill", "none");
626 }
627
628 if (stroke) {
629 _setStrokeStyle(css, state);
630 } else {
631 sp_repr_css_set_property(css, "stroke", "none");
632 }
633
634 return css;
635}
636
641bool SvgBuilder::shouldMergePath(bool is_fill, const std::string &path)
642{
643 auto prev = _container->lastChild();
644 if (!prev || prev->attribute("mask"))
645 return false;
646
647 auto prev_d = prev->attribute("d");
648 if (!prev_d)
649 return false;
650
651 if (path != prev_d && path != std::string(prev_d) + " Z")
652 return false;
653
654 auto prev_css = sp_repr_css_attr(prev, "style");
655 std::string prev_val = sp_repr_css_property(prev_css, is_fill ? "fill" : "stroke", "");
656 // Very specific check excludes paths created elsewhere who's fill/stroke was unset.
657 return prev_val == "none";
658}
659
666bool SvgBuilder::mergePath(GfxState *state, bool is_fill, const std::string &path, bool even_odd)
667{
668 if (shouldMergePath(is_fill, path)) {
669 auto prev = _container->lastChild();
671 if (is_fill) {
672 _setFillStyle(css, state, even_odd);
673 // Fill after stroke indicates a different paint order.
674 sp_repr_css_set_property(css, "paint-order", "stroke fill markers");
675 } else {
676 _setStrokeStyle(css, state);
677 }
678 sp_repr_css_change(prev, css, "style");
680 return true;
681 }
682 return false;
683}
684
693void SvgBuilder::addPath(GfxState *state, bool fill, bool stroke, bool even_odd) {
694 gchar *pathtext = svgInterpretPath(state->getPath());
695
696 if (!pathtext)
697 return;
698
699 if (!strlen(pathtext) || (fill != stroke && mergePath(state, fill, pathtext, even_odd))) {
700 g_free(pathtext);
701 return;
702 }
703
704 Inkscape::XML::Node *path = _addToContainer("svg:path");
705 path->setAttribute("d", pathtext);
706 g_free(pathtext);
707
708 // Set style
709 SPCSSAttr *css = _setStyle(state, fill, stroke, even_odd);
710 sp_repr_css_change(path, css, "style");
712 _setBlendMode(path, state);
713 _setTransform(path, state);
714 _setClipPath(path);
715}
716
717void SvgBuilder::addClippedFill(GfxShading *shading, const Geom::Affine shading_tr)
718{
719 if (_clip_history->getClipPath()) {
721 _clip_history->getClipType() == clipEO);
722 }
723}
724
729void SvgBuilder::addShadedFill(GfxShading *shading, const Geom::Affine shading_tr, GfxPath *path, const Geom::Affine tr,
730 bool even_odd)
731{
732 auto prev = _container->lastChild();
733 gchar *pathtext = svgInterpretPath(path);
734
735 // Create a new gradient object before comitting to creating a path for it
736 // And package it into a css bundle which can be applied
738 // We remove the shape's affine to adjust the gradient back into place
739 gchar *id = _createGradient(shading, shading_tr * tr.inverse(), true);
740 if (id) {
741 gchar *urltext = g_strdup_printf ("url(#%s)", id);
742 sp_repr_css_set_property(css, "fill", urltext);
743 g_free(urltext);
744 g_free(id);
745 } else {
747 return;
748 }
749 if (even_odd) {
750 sp_repr_css_set_property(css, "fill-rule", "evenodd");
751 }
752 // Merge the style with the previous shape
753 if (shouldMergePath(true, pathtext)) {
754 // POSSIBLE: The gradientTransform might now incorrect if the
755 // state of the transformation was different between the two paths.
756 sp_repr_css_change(prev, css, "style");
757 g_free(pathtext);
758 return;
759 }
760
761 Inkscape::XML::Node *path_node = _addToContainer("svg:path");
762 path_node->setAttribute("d", pathtext);
763 g_free(pathtext);
764
765 // Don't add transforms to mask children.
766 if (std::string("svg:mask") != _container->name()) {
767 svgSetTransform(path_node, tr * _page_affine);
768 }
769
770 // Set the gradient into this new path.
771 sp_repr_css_set_property(css, "stroke", "none");
772 sp_repr_css_change(path_node, css, "style");
774}
775
781void SvgBuilder::setClip(GfxState *state, GfxClipType clip, bool is_bbox)
782{
783 // When there's already a clip path, we add clipping groups to handle them.
784 if (!is_bbox && _clip_history->hasClipPath()) {
785 _pushContainer("svg:g");
786 _clip_groups++;
787 }
788 if (clip == clipNormal) {
789 _clip_history->setClip(state, clipNormal, is_bbox);
790 } else {
791 _clip_history->setClip(state, clipEO);
792 }
793}
794
799{
800 // In SVG the path-clip transforms are compounded, so we have to do extra work to
801 // pull transforms back out of the clipping object and set them. Otherwise this
802 // would all be a lot simpler.
803 Geom::Affine node_tr = Geom::identity();
804 if (auto attr = node->attribute("transform")) {
805 sp_svg_transform_read(attr, &node_tr);
806 }
807
808 if (_clip_text) {
809 auto clip_node = _clip_text;
810
811 auto text_tr = Geom::identity();
812 if (auto attr = clip_node->attribute("transform")) {
813 sp_svg_transform_read(attr, &text_tr);
814 clip_node->removeAttribute("transform");
815 }
816
817 for (auto child = clip_node->firstChild(); child; child = child->next()) {
818 Geom::Affine child_tr = text_tr * _page_affine * node_tr.inverse();
819 svgSetTransform(child, child_tr);
820 }
821
822 _clip_text = nullptr;
823 return clip_node;
824 }
825 if (_shouldClip(node)) {
826 std::string clip_d = svgInterpretPath(_clip_history->getClipPath());
828 return _createClip(clip_d, tr, _clip_history->evenOdd());
829 }
830 return nullptr;
831}
832
834{
835 if (!_clip_history->hasClipPath()) {
836 return false;
837 }
838
839 // Calculate bounding boxes for both the node and the clip path
841
842 if (node_vec.empty()) {
843 // Non-path node (text, image, etc)
844 // Create a PathVector of the bounding box instead
846 auto item = cast<SPItem>(_doc->getObjectByRepr(const_cast<Inkscape::XML::Node *>(node)));
847 // transform will be applied later, so default identity is good
850
851 if (!bounds.empty()) {
852 node_vec.push_back(Geom::Path(*bounds));
853 } else {
854 // not sure what this is, default to clipping it
855 return true;
856 }
857 }
858
860
861 // Clip transform is compounded with page, node inverse, and node transforms
862 // Skip the node inverse * node part as it's just identity.
863 // Also skip the page transform as it should be applied equally to both.
865 Geom::Affine node_tr = Geom::identity();
866 if (auto attr = node->attribute("transform")) {
867 sp_svg_transform_read(attr, &node_tr);
868 }
869
870 node_vec *= node_tr;
871 clip_vec *= clip_tr;
872
873 return !pathv_fully_contains(clip_vec, node_vec);
874}
875
876Inkscape::XML::Node *SvgBuilder::_createClip(const std::string &d, const Geom::Affine tr, bool even_odd)
877{
878 if (_prev_clip) {
879 // Check if the previous clipping path would be identical to the new one.
880 auto prev_path = _prev_clip->firstChild();
881 std::string prev_d = prev_path->attribute("d");
882 std::string prev_tr = prev_path->attribute("transform") ? prev_path->attribute("transform")
884 bool prev_even_odd =
885 prev_path->attribute("clip-rule") ? std::string(prev_path->attribute("clip-rule")) == "evenodd" : false;
886
887 // Don't create an identical new clipping path
888 if (prev_d == d && prev_tr == sp_svg_transform_write(tr) && prev_even_odd == even_odd) {
889 return _prev_clip;
890 }
891 }
892
893 Inkscape::XML::Node *clip_path = _xml_doc->createElement("svg:clipPath");
894 clip_path->setAttribute("clipPathUnits", "userSpaceOnUse");
895
896 // Create the path
897 Inkscape::XML::Node *path = _xml_doc->createElement("svg:path");
898 path->setAttribute("d", d);
899 svgSetTransform(path, tr);
900
901 if (even_odd) {
902 path->setAttribute("clip-rule", "evenodd");
903 }
904 clip_path->appendChild(path);
906
907 // Append clipPath to defs and get id
908 _doc->getDefs()->getRepr()->appendChild(clip_path);
909 Inkscape::GC::release(clip_path);
910
911 // update the previous clip path
912 _prev_clip = clip_path;
913
914 return clip_path;
915}
916
917void SvgBuilder::beginMarkedContent(const char *name, const char *group)
918{
919 if (name && group && std::string(name) == "OC") {
920 auto layer_id = std::string("layer-") + sanitizeId(group);
921 if (auto existing = _doc->getObjectById(layer_id)) {
922 if (existing->getRepr()->parent() == _container) {
923 _container = existing->getRepr();
924 _node_stack.push_back(_container);
925 } else {
926 g_warning("Unexpected marked content group in PDF!");
927 _pushGroup();
928 }
929 } else {
930 auto node = _pushGroup();
931 node->setAttribute("id", layer_id);
932 if (_ocgs.find(group) != _ocgs.end()) {
933 auto pair = _ocgs[group];
934 setAsLayer(pair.first.c_str(), pair.second);
935 }
936 }
937 } else {
938 auto node = _pushGroup();
939 if (group) {
940 node->setAttribute("id", std::string("group-") + sanitizeId(group));
941 }
942 }
943}
944
945void SvgBuilder::addOptionalGroup(const std::string &oc, const std::string &label, bool visible)
946{
947 _ocgs[oc] = {label, visible};
948}
949
951{
952 auto id = sanitizeId(label);
953 Inkscape::XML::Node *save_current_location = _container;
954 if (auto existing = _doc->getObjectById(id)) {
955 _container = existing->getRepr();
956 _node_stack.push_back(_container);
957 } else {
958 while (_container != _root) {
959 _popGroup();
960 }
961 auto node = _pushGroup();
962 node->setAttribute("id", id);
963 setAsLayer(label.c_str(), visible);
964 }
965 return save_current_location;
966}
967
973
978
979void SvgBuilder::addColorProfile(unsigned char *profBuf, int length)
980{
981 cmsHPROFILE hp = cmsOpenProfileFromMem(profBuf, length);
982 if (!hp) {
983 g_warning("Failed to read ICCBased color space profile from PDF file.");
984 return;
985 }
987}
988
992std::string SvgBuilder::_getColorProfile(cmsHPROFILE hp)
993{
994 if (!hp)
995 return "";
996
997 // Cached name of this profile by reference
998 if (_icc_profiles.find(hp) != _icc_profiles.end())
999 return _icc_profiles[hp];
1000
1001 auto profile = Colors::CMS::Profile::create(hp);
1002 std::string name = validateString(profile->getName());
1003
1004 // Find the named profile in the document (if already added)
1006 return name;
1007
1008 // Add the profile, we've never seen it before.
1009 Inkscape::XML::Node *icc_node = _xml_doc->createElement("svg:color-profile");
1010 icc_node->setAttribute("inkscape:label", name);
1011 icc_node->setAttribute("name", name);
1012
1013 auto icc_data = std::string("data:application/vnd.iccprofile;base64,") + profile->dumpBase64();
1014 icc_node->setAttributeOrRemoveIfEmpty("xlink:href", icc_data);
1015 _doc->getDefs()->getRepr()->appendChild(icc_node);
1016 Inkscape::GC::release(icc_node);
1017
1018 _icc_profiles[hp] = name;
1019 return name;
1020}
1021
1026bool SvgBuilder::isPatternTypeSupported(GfxPattern *pattern) {
1027 if ( pattern != nullptr ) {
1028 if ( pattern->getType() == 2 ) { // shading pattern
1029 GfxShading *shading = (static_cast<GfxShadingPattern *>(pattern))->getShading();
1030 int shadingType = shading->getType();
1031 if ( shadingType == 2 || // axial shading
1032 shadingType == 3 ) { // radial shading
1033 return true;
1034 }
1035 return false;
1036 } else if ( pattern->getType() == 1 ) { // tiling pattern
1037 return true;
1038 }
1039 }
1040
1041 return false;
1042}
1043
1050gchar *SvgBuilder::_createPattern(GfxPattern *pattern, GfxState *state, bool is_stroke) {
1051 gchar *id = nullptr;
1052 if ( pattern != nullptr ) {
1053 if ( pattern->getType() == 2 ) { // Shading pattern
1054 GfxShadingPattern *shading_pattern = static_cast<GfxShadingPattern *>(pattern);
1055 // construct a (pattern space) -> (current space) transform matrix
1056 auto flip = Geom::Affine(1.0, 0.0, 0.0, -1.0, 0.0, _height);
1057 auto pt = Geom::Scale(Inkscape::Util::Quantity::convert(1.0, "pt", "px"));
1058 auto grad_affine = ctmToAffine(shading_pattern->getMatrix());
1059 auto obj_affine = stateToAffine(state);
1060 // SVG applies the object's affine on top of the gradient's affine,
1061 // So we must remove the object affine to move it back into place.
1062 auto affine = (grad_affine * pt * flip) * obj_affine.inverse();
1063 id = _createGradient(shading_pattern->getShading(), affine, !is_stroke);
1064 } else if ( pattern->getType() == 1 ) { // Tiling pattern
1065 id = _createTilingPattern(static_cast<GfxTilingPattern*>(pattern), state, is_stroke);
1066 }
1067 } else {
1068 return nullptr;
1069 }
1070 gchar *urltext = g_strdup_printf ("url(#%s)", id);
1071 g_free(id);
1072 return urltext;
1073}
1074
1080gchar *SvgBuilder::_createTilingPattern(GfxTilingPattern *tiling_pattern,
1081 GfxState *state, bool is_stroke) {
1082
1083 Inkscape::XML::Node *pattern_node = _xml_doc->createElement("svg:pattern");
1084 // Set pattern transform matrix
1085 auto pat_matrix = ctmToAffine(tiling_pattern->getMatrix());
1086 pattern_node->setAttributeOrRemoveIfEmpty("patternTransform", sp_svg_transform_write(pat_matrix));
1087 pattern_node->setAttribute("patternUnits", "userSpaceOnUse");
1088 // Set pattern tiling
1089 // FIXME: don't ignore XStep and YStep
1090 const double *bbox = tiling_pattern->getBBox();
1091 pattern_node->setAttributeSvgDouble("x", 0.0);
1092 pattern_node->setAttributeSvgDouble("y", 0.0);
1093 pattern_node->setAttributeSvgDouble("width", bbox[2] - bbox[0]);
1094 pattern_node->setAttributeSvgDouble("height", bbox[3] - bbox[1]);
1095
1096 // Convert BBox for PdfParser
1097 PDFRectangle box;
1098 box.x1 = bbox[0];
1099 box.y1 = bbox[1];
1100 box.x2 = bbox[2];
1101 box.y2 = bbox[3];
1102 // Create new SvgBuilder and sub-page PdfParser
1103 SvgBuilder *pattern_builder = new SvgBuilder(this, pattern_node);
1104 PdfParser *pdf_parser = new PdfParser(_xref, pattern_builder, tiling_pattern->getResDict(),
1105 &box);
1106 // Get pattern color space
1107 GfxPatternColorSpace *pat_cs = (GfxPatternColorSpace *)( is_stroke ? state->getStrokeColorSpace()
1108 : state->getFillColorSpace() );
1109 // Set fill/stroke colors if this is an uncolored tiling pattern
1110 GfxColorSpace *cs = nullptr;
1111 if ( tiling_pattern->getPaintType() == 2 && ( cs = pat_cs->getUnder() ) ) {
1112 GfxState *pattern_state = pdf_parser->getState();
1113 pattern_state->setFillColorSpace(cs->copy());
1114 pattern_state->setFillColor(state->getFillColor());
1115 pattern_state->setStrokeColorSpace(cs->copy());
1116 pattern_state->setStrokeColor(state->getFillColor());
1117 }
1118
1119 // Generate the SVG pattern
1120 pdf_parser->parse(tiling_pattern->getContentStream());
1121
1122 // Cleanup
1123 delete pdf_parser;
1124 delete pattern_builder;
1125
1126 // Append the pattern to defs
1127 _doc->getDefs()->getRepr()->appendChild(pattern_node);
1128 gchar *id = g_strdup(pattern_node->attribute("id"));
1129 Inkscape::GC::release(pattern_node);
1130
1131 return id;
1132}
1133
1141gchar *SvgBuilder::_createGradient(GfxShading *shading, const Geom::Affine pat_matrix, bool for_shading)
1142{
1143 Inkscape::XML::Node *gradient;
1144 _POPPLER_CONST Function *func;
1145 int num_funcs;
1146 bool extend0, extend1;
1147
1148 if ( shading->getType() == 2 ) { // Axial shading
1149 gradient = _xml_doc->createElement("svg:linearGradient");
1150 GfxAxialShading *axial_shading = static_cast<GfxAxialShading*>(shading);
1151 double x1, y1, x2, y2;
1152 axial_shading->getCoords(&x1, &y1, &x2, &y2);
1153 gradient->setAttributeSvgDouble("x1", x1);
1154 gradient->setAttributeSvgDouble("y1", y1);
1155 gradient->setAttributeSvgDouble("x2", x2);
1156 gradient->setAttributeSvgDouble("y2", y2);
1157 extend0 = axial_shading->getExtend0();
1158 extend1 = axial_shading->getExtend1();
1159 num_funcs = axial_shading->getNFuncs();
1160 func = axial_shading->getFunc(0);
1161 } else if (shading->getType() == 3) { // Radial shading
1162 gradient = _xml_doc->createElement("svg:radialGradient");
1163 GfxRadialShading *radial_shading = static_cast<GfxRadialShading*>(shading);
1164 double x1, y1, r1, x2, y2, r2;
1165 radial_shading->getCoords(&x1, &y1, &r1, &x2, &y2, &r2);
1166 // FIXME: the inner circle's radius is ignored here
1167 gradient->setAttributeSvgDouble("fx", x1);
1168 gradient->setAttributeSvgDouble("fy", y1);
1169 gradient->setAttributeSvgDouble("cx", x2);
1170 gradient->setAttributeSvgDouble("cy", y2);
1171 gradient->setAttributeSvgDouble("r", r2);
1172 extend0 = radial_shading->getExtend0();
1173 extend1 = radial_shading->getExtend1();
1174 num_funcs = radial_shading->getNFuncs();
1175 func = radial_shading->getFunc(0);
1176 } else { // Unsupported shading type
1177 return nullptr;
1178 }
1179 gradient->setAttribute("gradientUnits", "userSpaceOnUse");
1180 // If needed, flip the gradient transform around the y axis
1181 if (pat_matrix != Geom::identity()) {
1182 gradient->setAttributeOrRemoveIfEmpty("gradientTransform", sp_svg_transform_write(pat_matrix));
1183 }
1184
1185 if ( extend0 && extend1 ) {
1186 gradient->setAttribute("spreadMethod", "pad");
1187 }
1188
1189 if ( num_funcs > 1 || !_addGradientStops(gradient, shading, func) ) {
1190 Inkscape::GC::release(gradient);
1191 return nullptr;
1192 }
1193
1194 _doc->getDefs()->getRepr()->appendChild(gradient);
1195 gchar *id = g_strdup(gradient->attribute("id"));
1196 Inkscape::GC::release(gradient);
1197
1198 return id;
1199}
1200
1201#define EPSILON 0.0001
1205void SvgBuilder::_addStopToGradient(Inkscape::XML::Node *gradient, double offset, GfxColor *color, GfxColorSpace *space,
1206 double opacity)
1207{
1208 Inkscape::XML::Node *stop = _xml_doc->createElement("svg:stop");
1210 Inkscape::CSSOStringStream os_opacity;
1211 std::string color_text = "#ffffff";
1212 if (space->getMode() == csDeviceGray) {
1213 // This is a transparency mask.
1214 GfxRGB rgb;
1215 space->getRGB(color, &rgb);
1216 double gray = (double)rgb.r / 65535.0;
1217 gray = CLAMP(gray, 0.0, 1.0);
1218 os_opacity << gray;
1219 } else {
1220 os_opacity << opacity;
1221 color_text = convertGfxColor(color, space);
1222 }
1223 sp_repr_css_set_property(css, "stop-opacity", os_opacity.str().c_str());
1224 sp_repr_css_set_property(css, "stop-color", color_text.c_str());
1225
1226 sp_repr_css_change(stop, css, "style");
1228 stop->setAttributeCssDouble("offset", offset);
1229
1230 gradient->appendChild(stop);
1232}
1233
1234static bool svgGetShadingColor(GfxShading *shading, double offset, GfxColor *result)
1235{
1236 if ( shading->getType() == 2 ) { // Axial shading
1237 (static_cast<GfxAxialShading *>(shading))->getColor(offset, result);
1238 } else if ( shading->getType() == 3 ) { // Radial shading
1239 (static_cast<GfxRadialShading *>(shading))->getColor(offset, result);
1240 } else {
1241 return false;
1242 }
1243 return true;
1244}
1245
1246#define INT_EPSILON 8
1247bool SvgBuilder::_addGradientStops(Inkscape::XML::Node *gradient, GfxShading *shading,
1248 _POPPLER_CONST Function *func) {
1249 auto type = func->getType();
1250 auto space = shading->getColorSpace();
1251 if (type == _POPPLER_FUNCTION_TYPE_SAMPLED || type == _POPPLER_FUNCTION_TYPE_EXPONENTIAL) {
1252 GfxColor stop1, stop2;
1253 if (!svgGetShadingColor(shading, 0.0, &stop1) || !svgGetShadingColor(shading, 1.0, &stop2)) {
1254 return false;
1255 } else {
1256 _addStopToGradient(gradient, 0.0, &stop1, space, 1.0);
1257 _addStopToGradient(gradient, 1.0, &stop2, space, 1.0);
1258 }
1259 } else if (type == _POPPLER_FUNCTION_TYPE_STITCHING) {
1260 auto stitchingFunc = static_cast<_POPPLER_CONST StitchingFunction*>(func);
1261 const double *bounds = stitchingFunc->getBounds();
1262 const double *encode = stitchingFunc->getEncode();
1263 int num_funcs = stitchingFunc->getNumFuncs();
1264 // Adjust gradient so it's always between 0.0 - 1.0
1265 double max_bound = std::max({1.0, bounds[num_funcs]});
1266
1267 // Add stops from all the stitched functions
1268 GfxColor prev_color, color;
1269 svgGetShadingColor(shading, bounds[0], &prev_color);
1270 _addStopToGradient(gradient, bounds[0], &prev_color, space, 1.0);
1271 for ( int i = 0 ; i < num_funcs ; i++ ) {
1272 svgGetShadingColor(shading, bounds[i + 1], &color);
1273 // Add stops
1274 if (stitchingFunc->getFunc(i)->getType() == _POPPLER_FUNCTION_TYPE_EXPONENTIAL) {
1275 double expE = (static_cast<_POPPLER_CONST ExponentialFunction*>(stitchingFunc->getFunc(i)))->getE();
1276 if (expE > 1.0) {
1277 expE = (bounds[i + 1] - bounds[i])/expE; // approximate exponential as a single straight line at x=1
1278 if (encode[2*i] == 0) { // normal sequence
1279 auto offset = (bounds[i + 1] - expE) / max_bound;
1280 _addStopToGradient(gradient, offset, &prev_color, space, 1.0);
1281 } else { // reflected sequence
1282 auto offset = (bounds[i] + expE) / max_bound;
1283 _addStopToGradient(gradient, offset, &color, space, 1.0);
1284 }
1285 }
1286 }
1287 _addStopToGradient(gradient, bounds[i + 1] / max_bound, &color, space, 1.0);
1288 prev_color = color;
1289 }
1290 } else { // Unsupported function type
1291 return false;
1292 }
1293
1294 return true;
1295}
1296
1301void SvgBuilder::updateStyle(GfxState *state) {
1302 if (_in_text_object) {
1303 _invalidated_style = true;
1304 }
1305}
1306
1310void SvgBuilder::updateFont(GfxState *state, std::shared_ptr<CairoFont> cairo_font, bool flip)
1311{
1312 TRACE(("updateFont()\n"));
1313 updateTextMatrix(state, flip); // Ensure that we have a text matrix built
1314
1315 auto font = state->getFont(); // GfxFont
1316 auto font_id = font->getID()->num;
1317
1318 auto new_font_size = state->getFontSize();
1319 if (font->getType() == fontType3) {
1320 const double *font_matrix = font->getFontMatrix();
1321 if (font_matrix[0] != 0.0) {
1322 new_font_size *= font_matrix[3] / font_matrix[0];
1323 }
1324 }
1325 if (new_font_size != _css_font_size) {
1326 _css_font_size = new_font_size;
1327 _invalidated_style = true;
1328 }
1329
1330 bool was_css_font = (bool)_css_font;
1331 // Clean up any previous css font
1332 if (_css_font) {
1334 _css_font = nullptr;
1335 }
1336
1337 auto font_strategy = FontFallback::AS_TEXT;
1338 if (_font_strategies.find(font_id) != _font_strategies.end()) {
1339 font_strategy = _font_strategies[font_id];
1340 }
1341
1342 if (font_strategy == FontFallback::DELETE_TEXT) {
1343 // Delete all text when font is missing.
1344 _invalidated_strategy = true; // Flush any text in buffer.
1345 _cairo_font = nullptr;
1346 return;
1347 }
1348
1349 if (font_strategy == FontFallback::AS_SHAPES) {
1350 // Render text as paths when font is missing.
1352 _invalidated_style = (_cairo_font != cairo_font);
1353 _cairo_font = cairo_font;
1354 return;
1355 }
1356
1357 auto font_data = FontData(font);
1358 auto new_font_specification = font_data.getSpecification();
1359 TRACE(("FontSpecification: %s\n", new_font_specification.c_str()));
1360 if (_font_specification != new_font_specification) {
1361 // If any font property changes, we need a new <tspan> or <path>.
1362 _font_specification = new_font_specification;
1363 _invalidated_strategy = false; // We don't need to flush text which creates a <text> element,
1364 // we will just create new <tspan>.
1365 _invalidated_style = true; // Changed style
1366 }
1367
1368 // Font family
1369 _cairo_font = nullptr;
1371 if (font_data.found) {
1372 sp_repr_css_set_property(_css_font, "font-family", font_data.family.c_str());
1373 } else if (font_strategy == FontFallback::AS_SUB) {
1374 sp_repr_css_set_property(_css_font, "font-family", font_data.getSubstitute().c_str());
1375 } else {
1376 auto keep_name = font_data.family.size() ? font_data.family : font_data.name;
1377 sp_repr_css_set_property(_css_font, "font-family", keep_name.c_str());
1378 }
1379
1380 // Set the font data (are these really necessary if they have default values?).
1381 sp_repr_css_set_property(_css_font, "font-style", font_data.style.c_str());
1382 sp_repr_css_set_property(_css_font, "font-weight", font_data.weight.c_str());
1383 sp_repr_css_set_property(_css_font, "font-stretch", font_data.stretch.c_str());
1384 sp_repr_css_set_property(_css_font, "font-variant", "normal");
1385
1386 // Writing mode
1387 if ( font->getWMode() == 0 ) {
1388 sp_repr_css_set_property(_css_font, "writing-mode", "lr");
1389 } else {
1390 sp_repr_css_set_property(_css_font, "writing-mode", "tb");
1391 }
1392}
1393
1397void SvgBuilder::updateTextShift(GfxState *state, double shift) {
1398 double shift_value = -shift * 0.001 * fabs(state->getFontSize());
1399 if (state->getFont()->getWMode()) {
1400 _text_position[1] += shift_value;
1401 } else {
1402 _text_position[0] += shift_value;
1403 }
1404}
1405
1409void SvgBuilder::updateTextPosition(double tx, double ty) {
1410 _text_position = Geom::Point(tx, ty);
1411}
1412
1416void SvgBuilder::updateTextMatrix(GfxState *state, bool flip) {
1417 // Update text matrix, it contains an extra flip which we must undo.
1418 auto new_matrix = Geom::Scale(1, flip ? -1 : 1) * ctmToAffine(state->getTextMat());
1419 // TODO: Detect if the text matrix is actually just a rotational kern
1420 // this can help stich back together texts where letters are rotated
1421 if (new_matrix != _text_matrix) {
1422 _flushText(state);
1423 _text_matrix = new_matrix;
1424 }
1425}
1426
1434void SvgBuilder::beforeStateChange(GfxState *old_state) {
1435 if (_in_text_object) {
1436 _flushText(old_state);
1437 }
1438}
1439
1443Inkscape::XML::Node* SvgBuilder::_flushTextText(GfxState *state, double text_scale, const Geom::Affine& text_transform)
1444{
1445 auto text_node = _addToContainer("svg:text");
1446 assert (text_node);
1447
1448 // We preserve spaces in the text objects we create, this applies to any descendant.
1449 text_node->setAttribute("xml:space", "preserve");
1450
1451 // Text direction is a property of the <text> element.
1452 auto font = state->getFont();
1453 if (font->getWMode() == 1) {
1454 // Only set if vertical.
1455 auto css_text = sp_repr_css_attr_new();
1456 sp_repr_css_set_property(css_text, "writing-mode", "tb");
1457 sp_repr_css_change(text_node, css_text, "style");
1458 sp_repr_css_attr_unref(css_text);
1459 }
1460
1461 // Prepare to loop over all glyphs in buffer.
1462 unsigned int glyphs_in_tspan = 0;
1463 Glib::ustring text_buffer;
1464
1465 // SVG attributes, only spaces and digits.
1466 std::string x_coords;
1467 std::string y_coords;
1468 std::string dx_coords;
1469 std::string dy_coords;
1470
1471 auto first_glyph = _glyphs.front();
1472 auto prev_glyph = _glyphs.front();
1473 for (auto it = _glyphs.begin(); it != _glyphs.end(); ++it ) {
1474
1475 // Add glyph
1476 auto glyph = *it;
1477
1478 // Absolute position (used to position tspan, only on first character).
1479 if (glyphs_in_tspan == 0) {
1480 //first_glyph = glyph;
1481 prev_glyph = glyph; // So dx and dy for first glyph in tspan are both zero.
1482 Geom::Point delta_pos(glyph.text_position - first_glyph.text_position);
1483 delta_pos[1] += glyph.rise;
1484 delta_pos[1] *= -1.0; // flip it
1485 delta_pos *= Geom::Scale(text_scale);
1486 delta_pos += glyph.origin; // Corrects vertical text position.
1487
1489 os_x << delta_pos[0];
1490 x_coords.append(os_x.str());
1491
1493 os_y << delta_pos[1];
1494 y_coords.append(os_y.str());
1495 }
1496
1497 // Relative position (used to position characters within tspan).
1498 Geom::Point delta_dpos;
1499 if (glyphs_in_tspan != 0) {
1500 // Subtract off previous glyph position and advance.
1501 delta_dpos = glyph.text_position - prev_glyph.text_position - prev_glyph.advance;
1502 }
1503
1504 // Eliminate small rounding errors.
1505 if (std::abs(delta_dpos[0]) < 0.005) {
1506 delta_dpos[0] = 0.0;
1507 }
1508 if (std::abs(delta_dpos[1]) < 0.005) {
1509 delta_dpos[1] = 0.0;
1510 }
1511
1512 delta_dpos[1] += glyph.rise;
1513 delta_dpos[1] *= -1.0; // flip it
1514
1515 delta_dpos *= Geom::Scale(text_scale);
1516
1517
1519 os_dx << delta_dpos[0] << " ";
1520 dx_coords.append(os_dx.str());
1521
1523 os_dy << delta_dpos[1] << " ";
1524 dy_coords.append(os_dy.str());
1525
1526 // Add Unicode points to buffer.
1527 // There may be a glyph to many Unicode point mapping (e.g. a ligature).
1528 for (int i = 0; i < glyph.code.size(); i++) {
1529 text_buffer.append(1, glyph.code[i]);
1530 if (i != 0) {
1531 dx_coords.append("0 ");
1532 dy_coords.append("0 ");
1533 }
1534 }
1535
1536 // Check to see if we need to output <tspan>.
1537 // We output if:
1538 // 1. Last glyph.
1539 // 2. Next glyph has different style.
1540 // 3. Next glyph on new line. TODO: remove this as we can have multiline text now without <tspan>s.
1541
1542 auto writing_mode = state->getFont()->getWMode(); // Horizontal or vertical text.
1543 auto next_it = it + 1;
1544 bool output_tspan =
1545 next_it == _glyphs.end() ||
1546 next_it->style_changed ||
1547 (writing_mode == 0 && std::abs(glyph.text_position[1] - next_it->text_position[1]) > 0.1) ||
1548 (writing_mode == 1 && std::abs(glyph.text_position[0] - next_it->text_position[0]) > 0.1);
1549
1550 if (output_tspan) {
1551
1552 // Create and add new <tspan> to <text>.
1553 auto tspan_node = _xml_doc->createElement("svg:tspan");
1554 text_node->appendChild(tspan_node);
1555 Inkscape::GC::release(tspan_node);
1556
1557 // Create and add text content node to <tspan>.
1558 Inkscape::XML::Node *text_content = _xml_doc->createTextNode(text_buffer.c_str());
1559 tspan_node->appendChild(text_content);
1560 Inkscape::GC::release(text_content);
1561
1562 // Set style.
1563 double text_size = text_scale * glyph.text_size;
1564 sp_repr_css_set_property_double(glyph.css_font, "font-size", text_size);
1565 _setTextStyle(tspan_node, glyph.state, glyph.css_font, text_transform);
1566
1567 // Unref SPCSSAttr if it won't be needed.
1568 // TODO: Remove 'if' wraper once we don't use <tspans> for new lines.
1569 // (Style is the same for all glyphs in a tspan.)
1570 if (next_it == _glyphs.end() ||
1571 next_it->style_changed ) {
1572 sp_repr_css_attr_unref(glyph.css_font);
1573 }
1574
1575 // Remove ' 0's at end.
1576 while (dx_coords.ends_with(" 0 ")) {
1577 dx_coords.erase(dx_coords.length() - 2);
1578 }
1579
1580 while (dy_coords.ends_with(" 0 ")) {
1581 dy_coords.erase(dy_coords.length() - 2);
1582 }
1583
1584 // Remove last entry if 0.
1585 if (dx_coords == "0 ") {
1586 dx_coords.clear();
1587 }
1588
1589 if (dy_coords == "0 ") {
1590 dy_coords.clear();
1591 }
1592
1593 // Remove space at end.
1594 if (dx_coords.length() > 0) {
1595 dx_coords.pop_back();
1596 }
1597
1598 if (dy_coords.length() > 0) {
1599 dy_coords.pop_back();
1600 }
1601
1602 tspan_node->setAttributeOrRemoveIfEmpty("x", x_coords);
1603 tspan_node->setAttributeOrRemoveIfEmpty("dx", dx_coords);
1604
1605 tspan_node->setAttributeOrRemoveIfEmpty("y", y_coords);
1606 tspan_node->setAttributeOrRemoveIfEmpty("dy", dy_coords);
1607
1608 // Reset.
1609 x_coords.clear();
1610 y_coords.clear();
1611 dx_coords.clear();
1612 dy_coords.clear();
1613 text_buffer.clear();
1614 glyphs_in_tspan = 0;
1615
1616 TRACE(("tspan content: %s\n", text_buffer.c_str()));
1617 } else {
1618 glyphs_in_tspan++;
1619 }
1620 prev_glyph = glyph;
1621 }
1622
1623 return text_node;
1624}
1625
1629Inkscape::XML::Node* SvgBuilder::_flushTextPath(GfxState *state, double text_scale, const Geom::Affine& text_transform)
1630{
1631 auto cairo_glyphs = (cairo_glyph_t *)gmallocn(_glyphs.size(), sizeof(cairo_glyph_t));
1632 unsigned int cairo_glyph_count = 0;
1633
1634 Inkscape::XML::Node *node = nullptr;
1635 Inkscape::XML::Node *text_group = nullptr; // Used to wrap paths if more that one path needed due
1636 // to style changes.
1637
1638 auto first_glyph = _glyphs.front();
1639 for (auto it = _glyphs.begin(); it != _glyphs.end(); ++it ) {
1640
1641 auto glyph = *it;
1642
1643 // Append the coordinates to their respective strings
1644 Geom::Point delta_pos(glyph.text_position - first_glyph.text_position);
1645 delta_pos[1] += glyph.rise;
1646 delta_pos[1] *= -1.0; // flip it
1647 delta_pos *= Geom::Scale(text_scale);
1648
1649 // Push the data into the cairo glyph list for later rendering.
1650 cairo_glyphs[cairo_glyph_count].index = glyph.cairo_index;
1651 cairo_glyphs[cairo_glyph_count].x = delta_pos[Geom::X];
1652 cairo_glyphs[cairo_glyph_count].y = delta_pos[Geom::Y];
1653 cairo_glyph_count++;
1654
1655 bool is_last_glyph = (it + 1) == _glyphs.end();
1656 bool flush_text = is_last_glyph ? true : (it+1)->style_changed;
1657
1658 if (flush_text) {
1659 if (!is_last_glyph && !text_group) {
1660 text_group = _pushGroup(); // Create <g> wrapper if we have a style change mid-stream.
1661 }
1662
1663 double text_size = text_scale * glyph.text_size;
1664
1665 // Set to 'node' because if the style does NOT change, we won't have a group
1666 // but still need to set this text's position and blend modes.
1667 node = _renderText(glyph.cairo_font, text_size, text_transform, cairo_glyphs, cairo_glyph_count);
1668 if (!node) {
1669 g_warning("Empty or broken text in PDF file.");
1670 return nullptr;
1671 }
1672 _setTextStyle(node, glyph.state, nullptr, text_transform);
1673
1674 if (text_group) {
1675 // Handled by _renderText
1676 // text_group->appendChild(node);
1677 // Inkscape::GC::release(node);
1678 }
1679
1680 cairo_glyph_count = 0;
1681
1682 if (is_last_glyph) {
1683 break;
1684 }
1685 }
1686 }
1687
1688 // Clean up
1689 gfree(cairo_glyphs);
1690 cairo_glyphs = nullptr;
1691
1692 if (text_group) {
1693 node = text_group;
1694 _popGroup();
1695 }
1696
1697 node->setAttribute("aria-label", _aria_label);
1698 _aria_label = "";
1699
1700 return node;
1701}
1702
1709void SvgBuilder::_flushText(GfxState *state)
1710{
1711 // Ignore empty strings
1712 if (_glyphs.empty()) {
1713 return;
1714 }
1715
1716 // Set up a clipPath group (if required).
1717 if (state->getRender() & 4 && !_clip_text_group) {
1718 auto defs = _doc->getDefs()->getRepr();
1719 _clip_text_group = _pushContainer("svg:clipPath");
1720 _clip_text_group->setAttribute("clipPathUnits", "userSpaceOnUse");
1721 defs->appendChild(_clip_text_group);
1723 }
1724
1725 // Ignore invisible characters.
1726 if (state->getRender() == 3) {
1727 std::cerr << "SVGBuilder::_flushText: Invisible pdf glyphs removed!" << std::endl;
1728 _glyphs.clear();
1729 return;
1730 }
1731
1732 // Strip out text size from text_matrix and remove from text_transform
1733 double text_scale = _text_matrix.expansionX();
1734 Geom::Affine tr = stateToAffine(state);
1735 Geom::Affine text_transform = _text_matrix * tr * Geom::Scale(text_scale).inverse();
1736 std::vector<SvgGlyph>::iterator i = _glyphs.begin();
1737 const SvgGlyph& first_glyph = (*i);
1738
1739 // The glyph position must be moved by the document scale without flipping
1740 // the text object itself. This is why the text affine is applied to the
1741 // translation point and not simply used in the text element directly.
1742 auto pos = first_glyph.position * tr;
1743 text_transform.setTranslation(pos);
1744
1745 // Cache the text transform when clipping
1746 if (_clip_text_group) {
1747 svgSetTransform(_clip_text_group, text_transform);
1748 }
1749
1750 Inkscape::XML::Node *text_node = nullptr; // The text node or the path node.
1751 if (first_glyph.cairo_font) {
1752 text_node = _flushTextPath(state, text_scale, text_transform);
1753 } else {
1754 text_node = _flushTextText(state, text_scale, text_transform);
1755 }
1756
1757 if (text_node) {
1758 _setBlendMode(text_node, state);
1759 svgSetTransform(text_node, text_transform * _page_affine);
1760 _setClipPath(text_node);
1761 }
1762
1763 _aria_label = "";
1764 _glyphs.clear();
1765}
1766
1772{
1773 int render_mode = state->getRender();
1774 bool has_fill = !(render_mode & 1);
1775 bool has_stroke = ( render_mode & 3 ) == 1 || ( render_mode & 3 ) == 2;
1776
1777 state = state->save();
1778 state->setCTM(ta[0], ta[1], ta[2], ta[3], ta[4], ta[5]);
1779 auto style = _setStyle(state, has_fill, has_stroke);
1780 sp_repr_css_change(node, style, "style");
1781 state = state->restore();
1782 if (font_style) {
1783 sp_repr_css_merge(style, font_style);
1784 }
1785 sp_repr_css_change(node, style, "style");
1787}
1788
1803Inkscape::XML::Node *SvgBuilder::_renderText(std::shared_ptr<CairoFont> cairo_font, double font_size,
1804 const Geom::Affine &transform,
1805 cairo_glyph_t *cairo_glyphs, unsigned int count)
1806{
1807 Inkscape::XML::Node *path = _addToContainer("svg:path");
1808 path->setAttribute("d", "");
1809
1810 if (!cairo_glyphs || !cairo_font || _aria_label.empty()) {
1811 std::cerr << "SvgBuilder::_renderText: Invalid argument!" << std::endl;
1812 return path;
1813 }
1814
1815 // The surface isn't actually used, no rendering in cairo takes place.
1816 cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, _width, _height);
1817 cairo_t *cairo = cairo_create(surface);
1818 cairo_set_font_face(cairo, cairo_font->getFontFace());
1819 cairo_set_font_size(cairo, font_size);
1820 ink_cairo_transform(cairo, transform);
1821 cairo_glyph_path(cairo, cairo_glyphs, count);
1822 auto pathv = extract_pathvector_from_cairo(cairo);
1823 cairo_destroy(cairo);
1824 cairo_surface_destroy(surface);
1825
1826 // Failing to render text.
1827 if (!pathv) {
1828 std::cerr << "SvgBuilder::_renderText: Failed to render PDF text! " << _aria_label << std::endl;
1829 return path;
1830 }
1831
1832 auto textpath = sp_svg_write_path(*pathv);
1833 path->setAttribute("d", textpath);
1834
1835 if (textpath.empty()) {
1836 std::cerr << "SvgBuilder::_renderText: Empty path! " << _aria_label << std::endl;
1837 }
1838
1839 return path;
1840}
1841
1846void SvgBuilder::beginString(GfxState *state, int len)
1847{
1848 if (!_glyphs.empty()) {
1849 // What to do about unflushed text in the buffer.
1851 _flushText(state);
1852 _invalidated_strategy = false;
1853 } else {
1854 // Add seperator for aria text.
1855 _aria_space = true;
1856 }
1857 }
1858 IFTRACE(double *m = state->getTextMat());
1859 TRACE(("tm: %f %f %f %f %f %f\n",m[0], m[1],m[2], m[3], m[4], m[5]));
1860 IFTRACE(m = state->getCTM());
1861 TRACE(("ctm: %f %f %f %f %f %f\n",m[0], m[1],m[2], m[3], m[4], m[5]));
1862}
1863void SvgBuilder::endString(GfxState *state)
1864{
1865}
1866
1878void SvgBuilder::addChar(GfxState *state,
1879 double x, double y,
1880 double dx, double dy,
1881 double ax, double ay,
1882 double originX, double originY,
1883 CharCode code, int /*nBytes*/, Unicode const *u, int uLen)
1884{
1885 assert (state);
1886
1887 if (_aria_space && !_glyphs.empty()) {
1888 const SvgGlyph& prev_glyph = _glyphs.back();
1889 // This helps reconstruct the aria text, though it could be made better.
1890 if (prev_glyph.position[Geom::Y] != (y - originY)) {
1891 _aria_label += "\n";
1892 }
1893 }
1894 _aria_space = false;
1895
1896 std::string utf8_code;
1897 static std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> conv1;
1898 // Note std::wstring_convert and std::codecvt_utf are deprecated and will be removed in C++26.
1899 if (u) {
1900 // 'u' maybe null if there is not a "ToUnicode" table in the PDF!
1901 utf8_code = conv1.to_bytes(*u);
1902 _aria_label += utf8_code;
1903 }
1904
1905 // Skip control characters, found in LaTeX generated PDFs
1906 // https://gitlab.com/inkscape/inkscape/-/issues/1369
1907 if (uLen > 0 && u[0] < 0x80 && g_ascii_iscntrl(u[0]) && !g_ascii_isspace(u[0])) {
1908 g_warning("Skipping ASCII control character %u", u[0]);
1909 _text_position += Geom::Point(dx, dy);
1910 return;
1911 }
1912
1913 if (!_css_font && !_cairo_font) {
1914 // Deleted text.
1915 return;
1916 }
1917
1918 Geom::Point delta(dx, dy);
1919 Geom::Point advance(ax, ay);
1920
1921 bool is_space = ( uLen == 1 && u[0] == 32 );
1922
1923 SvgGlyph new_glyph;
1924 new_glyph.code = utf8_code;
1925 new_glyph.is_space = is_space;
1926 new_glyph.delta = delta;
1927 new_glyph.advance = advance;
1928 new_glyph.position = Geom::Point( x - originX, y - originY );
1929 new_glyph.origin = Geom::Point(originX, -originY);
1930 new_glyph.text_position = _text_position;
1931 new_glyph.text_size = _css_font_size;
1932 new_glyph.state = state;
1933 if (_cairo_font) {
1934 // We are rendering text as a path.
1935 new_glyph.cairo_font = _cairo_font;
1936 new_glyph.cairo_index = _cairo_font->getGlyph(code, u, uLen);
1937 }
1939
1940 // Copy current style if it has changed since the previous glyph
1941 if (_invalidated_style || _glyphs.empty()) {
1942 _invalidated_style = false;
1943 new_glyph.style_changed = true;
1944 if (_css_font) {
1945 new_glyph.css_font = sp_repr_css_attr_new();
1947 }
1948 } else {
1949 new_glyph.style_changed = false;
1950 // Point to previous glyph's style information
1951 const SvgGlyph& prev_glyph = _glyphs.back();
1952 new_glyph.css_font = prev_glyph.css_font;
1953 }
1955 new_glyph.rise = state->getRise();
1956 new_glyph.char_space = state->getCharSpace();
1957 new_glyph.word_space = state->getWordSpace();
1958 new_glyph.horiz_scaling = state->getHorizScaling() / 100.0;
1959 _glyphs.push_back(new_glyph);
1960
1961 IFTRACE(
1962 std::cout << "SVGBuilder::addChar: " << new_glyph.code
1963 << " style changed: " << std::boolalpha << new_glyph.style_changed
1964 << std::setprecision(4)
1965 << " position: " << new_glyph.position
1966 << " delta: " << new_glyph.delta
1967 << " x,y: (" << x << ", " << y << ") "
1968 << " origin: (" << originX << ", " << originY << ") "
1969 << " rise: " << new_glyph.rise
1970 << " text_position: " << new_glyph.text_position
1971 << " state: " << (void*)new_glyph.state
1972 << std::endl;
1973 );
1974}
1975
1980void SvgBuilder::beginTextObject(GfxState *state) {
1981 _in_text_object = true;
1982 _invalidated_style = true; // Force copying of current state
1983}
1984
1985void SvgBuilder::endTextObject(GfxState *state)
1986{
1987 _in_text_object = false;
1988 _flushText(state);
1989
1990 if (_clip_text_group) {
1991 // Use the clip as a real clip path
1993 _clip_text_group = nullptr;
1994 }
1995}
1996
2000void png_write_vector(png_structp png_ptr, png_bytep data, png_size_t length)
2001{
2002 auto *v_ptr = reinterpret_cast<std::vector<guchar> *>(png_get_io_ptr(png_ptr)); // Get pointer to stream
2003 for ( unsigned i = 0 ; i < length ; i++ ) {
2004 v_ptr->push_back(data[i]);
2005 }
2006}
2007
2013 GfxImageColorMap *color_map, bool interpolate,
2014 int *mask_colors, bool alpha_only,
2015 bool invert_alpha) {
2016
2017 // Create PNG write struct
2018 png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
2019 if ( png_ptr == nullptr ) {
2020 return nullptr;
2021 }
2022 // Create PNG info struct
2023 png_infop info_ptr = png_create_info_struct(png_ptr);
2024 if ( info_ptr == nullptr ) {
2025 png_destroy_write_struct(&png_ptr, nullptr);
2026 return nullptr;
2027 }
2028 // Set error handler
2029 if (setjmp(png_jmpbuf(png_ptr))) {
2030 png_destroy_write_struct(&png_ptr, &info_ptr);
2031 return nullptr;
2032 }
2033 // Decide whether we should embed this image
2034 bool embed_image = _preferences->getAttributeBoolean("embedImages", true);
2035
2036 // Set read/write functions
2037 std::vector<guchar> png_buffer;
2038 FILE *fp = nullptr;
2039 gchar *file_name = nullptr;
2040 if (embed_image) {
2041 png_set_write_fn(png_ptr, &png_buffer, png_write_vector, nullptr);
2042 } else {
2043 static int counter = 0;
2044 file_name = g_strdup_printf("%s_img%d.png", _docname, counter++);
2045 fp = fopen(file_name, "wb");
2046 if ( fp == nullptr ) {
2047 png_destroy_write_struct(&png_ptr, &info_ptr);
2048 g_free(file_name);
2049 return nullptr;
2050 }
2051 png_init_io(png_ptr, fp);
2052 }
2053
2054 // Set header data
2055 if ( !invert_alpha && !alpha_only ) {
2056 png_set_invert_alpha(png_ptr);
2057 }
2058 png_color_8 sig_bit;
2059 if (alpha_only) {
2060 png_set_IHDR(png_ptr, info_ptr,
2061 width,
2062 height,
2063 8, /* bit_depth */
2064 PNG_COLOR_TYPE_GRAY,
2065 PNG_INTERLACE_NONE,
2066 PNG_COMPRESSION_TYPE_BASE,
2067 PNG_FILTER_TYPE_BASE);
2068 sig_bit.red = 0;
2069 sig_bit.green = 0;
2070 sig_bit.blue = 0;
2071 sig_bit.gray = 8;
2072 sig_bit.alpha = 0;
2073 } else {
2074 png_set_IHDR(png_ptr, info_ptr,
2075 width,
2076 height,
2077 8, /* bit_depth */
2078 PNG_COLOR_TYPE_RGB_ALPHA,
2079 PNG_INTERLACE_NONE,
2080 PNG_COMPRESSION_TYPE_BASE,
2081 PNG_FILTER_TYPE_BASE);
2082 sig_bit.red = 8;
2083 sig_bit.green = 8;
2084 sig_bit.blue = 8;
2085 sig_bit.alpha = 8;
2086 }
2087 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
2088 png_set_bgr(png_ptr);
2089 // Write the file header
2090 png_write_info(png_ptr, info_ptr);
2091
2092 // Convert pixels
2093 ImageStream *image_stream;
2094 if (alpha_only) {
2095 if (color_map) {
2096 image_stream = new ImageStream(str, width, color_map->getNumPixelComps(),
2097 color_map->getBits());
2098 } else {
2099 image_stream = new ImageStream(str, width, 1, 1);
2100 }
2101 image_stream->reset();
2102
2103 // Convert grayscale values
2104 unsigned char *buffer = new unsigned char[width];
2105 int invert_bit = invert_alpha ? 1 : 0;
2106 for ( int y = 0 ; y < height ; y++ ) {
2107 unsigned char *row = image_stream->getLine();
2108 if (color_map) {
2109 color_map->getGrayLine(row, buffer, width);
2110 } else {
2111 unsigned char *buf_ptr = buffer;
2112 for ( int x = 0 ; x < width ; x++ ) {
2113 if ( row[x] ^ invert_bit ) {
2114 *buf_ptr++ = 0;
2115 } else {
2116 *buf_ptr++ = 255;
2117 }
2118 }
2119 }
2120 png_write_row(png_ptr, (png_bytep)buffer);
2121 }
2122 delete [] buffer;
2123 } else if (color_map) {
2124 image_stream = new ImageStream(str, width,
2125 color_map->getNumPixelComps(),
2126 color_map->getBits());
2127 image_stream->reset();
2128
2129 // Convert RGB values
2130 unsigned int *buffer = new unsigned int[width];
2131 if (mask_colors) {
2132 for ( int y = 0 ; y < height ; y++ ) {
2133 unsigned char *row = image_stream->getLine();
2134 color_map->getRGBLine(row, buffer, width);
2135
2136 unsigned int *dest = buffer;
2137 for ( int x = 0 ; x < width ; x++ ) {
2138 // Check each color component against the mask
2139 for ( int i = 0; i < color_map->getNumPixelComps() ; i++) {
2140 if ( row[i] < mask_colors[2*i] * 255 ||
2141 row[i] > mask_colors[2*i + 1] * 255 ) {
2142 *dest = *dest | 0xff000000;
2143 break;
2144 }
2145 }
2146 // Advance to the next pixel
2147 row += color_map->getNumPixelComps();
2148 dest++;
2149 }
2150 // Write it to the PNG
2151 png_write_row(png_ptr, (png_bytep)buffer);
2152 }
2153 } else {
2154 for ( int i = 0 ; i < height ; i++ ) {
2155 unsigned char *row = image_stream->getLine();
2156 memset((void*)buffer, 0xff, sizeof(int) * width);
2157 color_map->getRGBLine(row, buffer, width);
2158 png_write_row(png_ptr, (png_bytep)buffer);
2159 }
2160 }
2161 delete [] buffer;
2162
2163 } else { // A colormap must be provided, so quit
2164 png_destroy_write_struct(&png_ptr, &info_ptr);
2165 if (!embed_image) {
2166 fclose(fp);
2167 g_free(file_name);
2168 }
2169 return nullptr;
2170 }
2171 delete image_stream;
2172 str->close();
2173 // Close PNG
2174 png_write_end(png_ptr, info_ptr);
2175 png_destroy_write_struct(&png_ptr, &info_ptr);
2176
2177 // Create repr
2178 Inkscape::XML::Node *image_node = _xml_doc->createElement("svg:image");
2179 image_node->setAttributeSvgDouble("width", 1);
2180 image_node->setAttributeSvgDouble("height", 1);
2181 if( !interpolate ) {
2183 // This should be changed after CSS4 Images widely supported.
2184 sp_repr_css_set_property(css, "image-rendering", "optimizeSpeed");
2185 sp_repr_css_change(image_node, css, "style");
2187 }
2188
2189 // PS/PDF images are placed via a transformation matrix, no preserveAspectRatio used
2190 image_node->setAttribute("preserveAspectRatio", "none");
2191
2192 // Create href
2193 if (embed_image) {
2194 // Append format specification to the URI
2195 auto *base64String = g_base64_encode(png_buffer.data(), png_buffer.size());
2196 auto png_data = std::string("data:image/png;base64,") + base64String;
2197 g_free(base64String);
2198 image_node->setAttributeOrRemoveIfEmpty("xlink:href", png_data);
2199 } else {
2200 fclose(fp);
2201 image_node->setAttribute("xlink:href", file_name);
2202 g_free(file_name);
2203 }
2204
2205 return image_node;
2206}
2207
2214 Inkscape::XML::Node *mask_node = _xml_doc->createElement("svg:mask");
2215 mask_node->setAttribute("maskUnits", "userSpaceOnUse");
2216 mask_node->setAttributeSvgDouble("x", 0.0);
2217 mask_node->setAttributeSvgDouble("y", 0.0);
2218 mask_node->setAttributeSvgDouble("width", width);
2219 mask_node->setAttributeSvgDouble("height", height);
2220 // Append mask to defs
2221 if (_is_top_level) {
2222 _doc->getDefs()->getRepr()->appendChild(mask_node);
2223 Inkscape::GC::release(mask_node);
2224 return _doc->getDefs()->getRepr()->lastChild();
2225 } else { // Work around for renderer bug when mask isn't defined in pattern
2226 static int mask_count = 0;
2227 gchar *mask_id = g_strdup_printf("_mask%d", mask_count++);
2228 mask_node->setAttribute("id", mask_id);
2229 g_free(mask_id);
2230 _doc->getDefs()->getRepr()->appendChild(mask_node);
2231 Inkscape::GC::release(mask_node);
2232 return mask_node;
2233 }
2234}
2235
2236void SvgBuilder::addImage(GfxState *state, Stream *str, int width, int height, GfxImageColorMap *color_map,
2237 bool interpolate, int *mask_colors)
2238{
2239 Inkscape::XML::Node *image_node = _createImage(str, width, height, color_map, interpolate, mask_colors);
2240 if (image_node) {
2241 _setBlendMode(image_node, state);
2242 _setTransform(image_node, state, Geom::Affine(1.0, 0.0, 0.0, -1.0, 0.0, 1.0));
2243 _addToContainer(image_node);
2244 _setClipPath(image_node);
2245 }
2246}
2247
2248void SvgBuilder::addImageMask(GfxState *state, Stream *str, int width, int height,
2249 bool invert, bool interpolate) {
2250
2251 // Create a rectangle
2252 Inkscape::XML::Node *rect = _addToContainer("svg:rect");
2253 rect->setAttributeSvgDouble("x", 0.0);
2254 rect->setAttributeSvgDouble("y", 0.0);
2255 rect->setAttributeSvgDouble("width", 1.0);
2256 rect->setAttributeSvgDouble("height", 1.0);
2257
2258 // Get current fill style and set it on the rectangle
2260 _setFillStyle(css, state, false);
2261 sp_repr_css_change(rect, css, "style");
2263 _setBlendMode(rect, state);
2264 _setTransform(rect, state, Geom::Affine(1.0, 0.0, 0.0, -1.0, 0.0, 1.0));
2265 _setClipPath(rect);
2266
2267 // Scaling 1x1 surfaces might not work so skip setting a mask with this size
2268 if ( width > 1 || height > 1 ) {
2269 Inkscape::XML::Node *mask_image_node =
2270 _createImage(str, width, height, nullptr, interpolate, nullptr, true, invert);
2271 if (mask_image_node) {
2272 // Create the mask
2273 Inkscape::XML::Node *mask_node = _createMask(1.0, 1.0);
2274 // Remove unnecessary transformation from the mask image
2275 mask_image_node->removeAttribute("transform");
2276 mask_node->appendChild(mask_image_node);
2277 Inkscape::GC::release(mask_image_node);
2278 gchar *mask_url = g_strdup_printf("url(#%s)", mask_node->attribute("id"));
2279 rect->setAttribute("mask", mask_url);
2280 g_free(mask_url);
2281 }
2282 }
2283}
2284
2285void SvgBuilder::addMaskedImage(GfxState *state, Stream *str, int width, int height, GfxImageColorMap *color_map,
2286 bool interpolate, Stream *mask_str, int mask_width, int mask_height, bool invert_mask,
2287 bool mask_interpolate)
2288{
2289 Inkscape::XML::Node *mask_image_node = _createImage(mask_str, mask_width, mask_height,
2290 nullptr, mask_interpolate, nullptr, true, invert_mask);
2291 Inkscape::XML::Node *image_node = _createImage(str, width, height, color_map, interpolate, nullptr);
2292 if ( mask_image_node && image_node ) {
2293 // Create mask for the image
2294 Inkscape::XML::Node *mask_node = _createMask(1.0, 1.0);
2295 // Remove unnecessary transformation from the mask image
2296 mask_image_node->removeAttribute("transform");
2297 mask_node->appendChild(mask_image_node);
2298 // Scale the mask to the size of the image
2299 Geom::Affine mask_transform((double)width, 0.0, 0.0, (double)height, 0.0, 0.0);
2300 mask_node->setAttributeOrRemoveIfEmpty("maskTransform", sp_svg_transform_write(mask_transform));
2301 // Set mask and add image
2302 gchar *mask_url = g_strdup_printf("url(#%s)", mask_node->attribute("id"));
2303 image_node->setAttribute("mask", mask_url);
2304 g_free(mask_url);
2305 _setBlendMode(image_node, state);
2306 _setTransform(image_node, state, Geom::Affine(1.0, 0.0, 0.0, -1.0, 0.0, 1.0));
2307 _addToContainer(image_node);
2308 _setClipPath(image_node);
2309 } else if (image_node) {
2310 Inkscape::GC::release(image_node);
2311 }
2312 if (mask_image_node) {
2313 Inkscape::GC::release(mask_image_node);
2314 }
2315}
2316
2317void SvgBuilder::addSoftMaskedImage(GfxState *state, Stream *str, int width, int height, GfxImageColorMap *color_map,
2318 bool interpolate, Stream *mask_str, int mask_width, int mask_height,
2319 GfxImageColorMap *mask_color_map, bool mask_interpolate)
2320{
2321 Inkscape::XML::Node *mask_image_node = _createImage(mask_str, mask_width, mask_height,
2322 mask_color_map, mask_interpolate, nullptr, true);
2323 Inkscape::XML::Node *image_node = _createImage(str, width, height, color_map, interpolate, nullptr);
2324 if ( mask_image_node && image_node ) {
2325 // Create mask for the image
2326 Inkscape::XML::Node *mask_node = _createMask(1.0, 1.0);
2327 // Remove unnecessary transformation from the mask image
2328 mask_image_node->removeAttribute("transform");
2329 mask_node->appendChild(mask_image_node);
2330 // Set mask and add image
2331 gchar *mask_url = g_strdup_printf("url(#%s)", mask_node->attribute("id"));
2332 image_node->setAttribute("mask", mask_url);
2333 g_free(mask_url);
2334 _addToContainer(image_node);
2335 _setBlendMode(image_node, state);
2336 _setTransform(image_node, state, Geom::Affine(1.0, 0.0, 0.0, -1.0, 0.0, 1.0));
2337 _setClipPath(image_node);
2338 } else if (image_node) {
2339 Inkscape::GC::release(image_node);
2340 }
2341 if (mask_image_node) {
2342 Inkscape::GC::release(mask_image_node);
2343 }
2344}
2345
2350{
2351 auto css = sp_repr_css_attr(node, "style");
2352 if (auto id = try_extract_uri_id(css->attribute(is_fill ? "fill" : "stroke"))) {
2353 if (auto obj = _doc->getObjectById(*id)) {
2354 return obj->getRepr();
2355 }
2356 }
2357 return nullptr;
2358}
2359
2361{
2362 return (!a->attribute(attr) && !b->attribute(attr)) || std::string(a->attribute(attr)) == b->attribute(attr);
2363}
2364
2369{
2370 // Merge transparency gradient back into real gradient if possible
2371 if (mask->childCount() == 1) {
2372 auto source = mask->firstChild();
2373 auto source_gr = _getGradientNode(source, true);
2374 auto target_gr = _getGradientNode(target, true);
2375 // Both objects have a gradient, try and merge them
2376 if (source_gr && target_gr && source_gr->childCount() == target_gr->childCount()) {
2377 bool same_pos = _attrEqual(source_gr, target_gr, "x1") && _attrEqual(source_gr, target_gr, "x2")
2378 && _attrEqual(source_gr, target_gr, "y1") && _attrEqual(source_gr, target_gr, "y2");
2379
2380 bool white_mask = false;
2381 for (auto source_st = source_gr->firstChild(); source_st != nullptr; source_st = source_st->next()) {
2382 auto source_css = sp_repr_css_attr(source_st, "style");
2383 white_mask = white_mask or source_css->getAttributeDouble("stop-opacity") != 1.0;
2384 if (std::string(source_css->attribute("stop-color")) != "#ffffff") {
2385 white_mask = false;
2386 break;
2387 }
2388 }
2389
2390 if (same_pos && white_mask) {
2391 // We move the stop-opacity from the source to the target
2392 auto target_st = target_gr->firstChild();
2393 for (auto source_st = source_gr->firstChild(); source_st != nullptr; source_st = source_st->next()) {
2394 auto target_css = sp_repr_css_attr(target_st, "style");
2395 auto source_css = sp_repr_css_attr(source_st, "style");
2396 sp_repr_css_set_property(target_css, "stop-opacity", source_css->attribute("stop-opacity"));
2397 sp_repr_css_change(target_st, target_css, "style");
2398 target_st = target_st->next();
2399 }
2400 // Remove mask and gradient xml objects
2401 mask->parent()->removeChild(mask);
2402 source_gr->parent()->removeChild(source_gr);
2403 return;
2404 }
2405 }
2406 }
2407 gchar *mask_url = g_strdup_printf("url(#%s)", mask->attribute("id"));
2408 target->setAttribute("mask", mask_url);
2409 g_free(mask_url);
2410}
2411
2412
2416void SvgBuilder::startGroup(GfxState *state, double *bbox, GfxColorSpace * /*blending_color_space*/, bool isolated,
2417 bool knockout, bool for_softmask)
2418{
2419 // Push group node, but don't attach to previous container yet
2420 _pushContainer("svg:g");
2421
2422 if (for_softmask) {
2423 _mask_groups.push_back(state);
2424 // Create a container for the mask
2425 _pushContainer(_createMask(1.0, 1.0));
2426 }
2427
2428 // TODO: In the future we could use state to insert transforms
2429 // and then remove the inverse from the items added into the children
2430 // to reduce the transformational duplication.
2431}
2432
2433void SvgBuilder::finishGroup(GfxState *state, bool for_softmask)
2434{
2435 if (for_softmask) {
2436 // Create mask
2437 auto mask_node = _popContainer();
2438 applyOptionalMask(mask_node, _container);
2439 } else {
2440 popGroup(state);
2441 }
2442}
2443
2444void SvgBuilder::popGroup(GfxState *state)
2445{
2446 // Restore node stack
2447 auto parent = _popContainer();
2448 bool will_clip = _clip_history->hasClipPath() && !_clip_history->isBoundingBox();
2449
2450 if (parent->childCount() == 1 && !parent->attribute("transform")) {
2451 // Merge this opacity and remove unnecessary group
2452 auto child = parent->firstChild();
2453
2454 if (will_clip && child->attribute("d")) {
2455 // Note to future: this means the group contains a single path, this path is likely
2456 // a fake bounding box path and the real path is contained within the clipping region
2457 // Moving the clipping region out into the path object and deleting the group would
2458 // improve output here.
2459 }
2460
2461 // Do not merge masked or clipped groups, to avoid clobering
2462 if (!will_clip && !child->attribute("mask") && !child->attribute("clip-path")) {
2463 auto orig = child->getAttributeDouble("opacity", 1.0);
2464 auto grp = parent->getAttributeDouble("opacity", 1.0);
2465 child->setAttributeSvgDouble("opacity", orig * grp);
2466
2467 if (auto mask_id = try_extract_uri_id(parent->attribute("mask"))) {
2468 if (auto obj = _doc->getObjectById(*mask_id)) {
2469 applyOptionalMask(obj->getRepr(), child);
2470 }
2471 }
2472 if (auto clip = parent->attribute("clip-path")) {
2473 child->setAttribute("clip-path", clip);
2474 }
2475
2476 // This duplicate child will get applied in the place of the group
2477 parent->removeChild(child);
2479 parent = child;
2480 }
2481 }
2482
2483 // Add the parent to the last container
2486}
2487
2492{
2493 FontStrategies ret;
2494 for (auto font : *fonts.get()) {
2495 int id = font.first->getID()->num;
2496 bool found = font.second.found;
2497 switch (s) {
2499 ret[id] = FontFallback::AS_SHAPES;
2500 break;
2502 ret[id] = FontFallback::DELETE_TEXT;
2503 break;
2506 break;
2508 ret[id] = found ? FontFallback::AS_TEXT : FontFallback::AS_SUB;
2509 break;
2511 ret[id] = FontFallback::AS_TEXT;
2512 break;
2515 break;
2516 }
2517 }
2518 return ret;
2519}
2520} } } /* namespace Inkscape, Extension, Internal */
2521
2522/*
2523 Local Variables:
2524 mode:c++
2525 c-file-style:"stroustrup"
2526 c-file-offsets:((innamespace . 0)(inline-open . 0))
2527 indent-tabs-mode:nil
2528 fill-column:99
2529 End:
2530*/
2531// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
void ink_cairo_transform(cairo_t *ct, Geom::Affine const &m)
std::optional< Geom::PathVector > extract_pathvector_from_cairo(cairo_t *ct)
Cairo integration helpers.
Cairo::RefPtr< Cairo::ImageSurface > surface
Definition canvas.cpp:137
uint64_t page
Definition canvas.cpp:171
Geom::IntRect visible
Definition canvas.cpp:154
Geom::IntRect bounds
Definition canvas.cpp:182
ClipHistoryEntry * save(bool cleared=false)
Create a new clip-history, appending it to the stack.
Definition pdf-utils.cpp:69
const Geom::Affine & getAffine()
Definition pdf-utils.h:37
GfxClipType getClipType()
Definition pdf-utils.h:36
void setClip(GfxState *state, GfxClipType newClipType=clipNormal, bool bbox=false)
Definition pdf-utils.cpp:34
ClipHistoryEntry * restore()
Definition pdf-utils.cpp:76
bool hasClipPath()
Definition pdf-utils.h:31
bool isBoundingBox()
Definition pdf-utils.h:33
GfxPath * getClipPath()
Definition pdf-utils.h:35
3x3 matrix representing an affine transformation.
Definition affine.h:70
void setTranslation(Point const &loc)
Sets the translation imparted by the Affine.
Definition affine.cpp:56
Coord expansionX() const
Calculates the amount of x-scaling imparted by the Affine.
Definition affine.cpp:64
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
C right() const
Return rightmost coordinate of the rectangle (+X is to the right).
C top() const
Return top coordinate of the rectangle (+Y is downwards).
C left() const
Return leftmost coordinate of the rectangle (+X is to the right).
C bottom() const
Return bottom coordinate of the rectangle (+Y is downwards).
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Sequence of subpaths.
Definition pathvector.h:122
void push_back(Path const &path)
Append a path at the end.
Definition pathvector.h:172
bool empty() const
Check whether the vector contains any paths.
Definition pathvector.h:145
Sequence of contiguous curves, aka spline.
Definition path.h:353
Two-dimensional point that doubles as a vector.
Definition point.h:66
constexpr Coord y() const noexcept
Definition point.h:106
Axis aligned, non-empty rectangle.
Definition rect.h:92
Scaling from the origin.
Definition transforms.h:150
Scale inverse() const
Definition transforms.h:172
Translation by a vector.
Definition transforms.h:115
Translate inverse() const
Get the inverse translation.
Definition transforms.h:133
A thin wrapper around std::ostringstream, but writing floating point numbers in the format required b...
static std::shared_ptr< Profile > create(cmsHPROFILE handle, std::string path="", bool in_home=false)
Construct a color profile object from the lcms2 object.
Definition profile.cpp:29
std::shared_ptr< Space::CMS > getSpace(std::string const &name) const
Get the specific color space from the list of available spaces.
Builds the inner SVG representation using libpoppler from the calls of PdfParser.
void updateTextShift(GfxState *state, double shift)
Shifts the current text position by the given amount (specified in text space)
std::string convertGfxColor(const GfxColor *color, GfxColorSpace *space)
void updateTextMatrix(GfxState *state, bool flip)
Flushes the buffered characters.
void applyOptionalMask(Inkscape::XML::Node *mask, Inkscape::XML::Node *target)
Take a constructed mask and decide how to apply it to the target.
void beginTextObject(GfxState *state)
These text object functions are the outer most calls for begining and ending text.
void updateTextPosition(double tx, double ty)
Updates current text position.
void setDocumentSize(double width, double height)
Inkscape::XML::Node * _getClip(const Inkscape::XML::Node *node)
Return the active clip as a new xml node.
void cropPage(const Geom::Rect &bbox)
Crop to this bounding box, do this before setMargins() but after setDocumentSize.
void addImageMask(GfxState *state, Stream *str, int width, int height, bool invert, bool interpolate)
gchar * _createTilingPattern(GfxTilingPattern *tiling_pattern, GfxState *state, bool is_stroke=false)
Creates a tiling pattern from poppler's data structure Creates a sub-page PdfParser and uses it to pa...
void setClip(GfxState *state, GfxClipType clip, bool is_bbox=false)
Clips to the current path set in GfxState.
void setMetadata(char const *name, const std::string &content)
std::shared_ptr< CairoFont > _cairo_font
void _flushText(GfxState *state)
Writes the buffered characters to the SVG document.
std::string _getColorProfile(cmsHPROFILE hp)
Return the color profile name if it's already been added.
Inkscape::XML::Node * _addToContainer(const char *name)
Create an svg element and append it to the current container object.
static FontStrategies autoFontStrategies(FontStrategy s, FontList fonts)
Decide what to do for each font in the font list, with the given strategy.
bool _shouldClip(const Inkscape::XML::Node *node) const
void _setClipPath(Inkscape::XML::Node *node)
gchar * _createGradient(GfxShading *shading, const Geom::Affine pat_matrix, bool for_shading=false)
Creates a linear or radial gradient from poppler's data structure.
void addColorProfile(unsigned char *profBuf, int length)
void beginMarkedContent(const char *name=nullptr, const char *group=nullptr)
bool _addGradientStops(Inkscape::XML::Node *gradient, GfxShading *shading, _POPPLER_CONST Function *func)
void setGroupOpacity(double opacity)
Sets the current container's opacity.
void setAsLayer(const char *layer_name=nullptr, bool visible=true)
Sets groupmode of the current container to 'layer' and sets its label if given.
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)
Renders the text as a path object using cairo and returns the node object.
std::map< cmsHPROFILE, std::string > _icc_profiles
bool isPatternTypeSupported(GfxPattern *pattern)
Checks whether the given pattern type can be represented in SVG Used by PdfParser to decide when to d...
void endLayer(Inkscape::XML::Node *save)
Inkscape::XML::Node * _pushContainer(const char *name)
void _setTextStyle(Inkscape::XML::Node *node, GfxState *state, SPCSSAttr *font_style, Geom::Affine text_affine)
Sets the style for the text, rendered or un-rendered, preserving the text_transform for any gradients...
bool shouldMergePath(bool is_fill, const std::string &path)
Returns the CSSAttr of the previously added path if it's exactly the same path AND is missing the fil...
void addOptionalGroup(const std::string &oc, const std::string &label, bool visible=true)
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)
std::vector< GfxState * > _mask_groups
void updateStyle(GfxState *state)
Sets _invalidated_style to true to indicate that styles have to be updated Used for text output when ...
void addPath(GfxState *state, bool fill, bool stroke, bool even_odd=false)
Emits the current path in poppler's GfxState data structure Can be used to do filling and stroking at...
bool mergePath(GfxState *state, bool is_fill, const std::string &path, bool even_odd=false)
Set the fill XOR stroke of the previously added path, if that path is missing the given attribute AND...
void addClippedFill(GfxShading *shading, const Geom::Affine shading_tr)
Inkscape::XML::Node * _flushTextPath(GfxState *state, double text_scale, const Geom::Affine &text_transform)
Create path node(s) for text.
std::map< std::string, std::pair< std::string, bool > > _ocgs
static bool _attrEqual(Inkscape::XML::Node *a, Inkscape::XML::Node *b, char const *attr)
void updateFont(GfxState *state, std::shared_ptr< CairoFont > cairo_font, bool flip)
Updates _css_font according to the font set in parameter state.
void setMargins(const Geom::Rect &page, const Geom::Rect &margins, const Geom::Rect &bleed)
Calculate the page margin size based on the pdf settings.
std::vector< Inkscape::XML::Node * > _node_stack
Inkscape::XML::Node * _getGradientNode(Inkscape::XML::Node *node, bool is_fill)
Find the fill or stroke gradient we previously set on this node.
void addImage(GfxState *state, Stream *str, int width, int height, GfxImageColorMap *color_map, bool interpolate, int *mask_colors)
gchar * _createPattern(GfxPattern *pattern, GfxState *state, bool is_stroke=false)
Creates a pattern from poppler's data structure Handles linear and radial gradients.
void beforeStateChange(GfxState *old_state)
Notifies the svg builder the state will change.
void _setStrokeStyle(SPCSSAttr *css, GfxState *state)
Sets stroke style from poppler's GfxState data structure Uses the given SPCSSAttr for storing the sty...
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 _addStopToGradient(Inkscape::XML::Node *gradient, double offset, GfxColor *color, GfxColorSpace *space, double opacity)
Adds a stop with the given properties to the gradient's representation.
Inkscape::XML::Node * _createMask(double width, double height)
Creates a <mask> with the specified width and height and adds to <defs> If we're not the top-level Sv...
Inkscape::XML::Node * _createClip(const std::string &d, const Geom::Affine tr, bool even_odd)
void addShadedFill(GfxShading *shading, const Geom::Affine shading_tr, GfxPath *path, const Geom::Affine tr, bool even_odd=false)
Emits the current path in poppler's GfxState data structure The path is set to be filled with the giv...
SvgBuilder(SPDocument *document, gchar *docname, XRef *xref)
void _setBlendMode(Inkscape::XML::Node *node, GfxState *state)
Sets blend style properties from poppler's GfxState data structure \update a SPCSSAttr with all mix-b...
SPCSSAttr * _setStyle(GfxState *state, bool fill, bool stroke, bool even_odd=false)
Sets style properties from poppler's GfxState data structure.
void pushPage(const std::string &label, GfxState *state)
We're creating a multi-page document, push page number.
Inkscape::XML::Node * _flushTextText(GfxState *state, double text_scale, const Geom::Affine &text_transform)
Create text node for text.
void _setTransform(Inkscape::XML::Node *node, GfxState *state, Geom::Affine extra=Geom::identity())
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)
Creates an <image> element containing the given ImageStream as a PNG.
void addChar(GfxState *state, double x, double y, double dx, double dy, double ax, double ay, double originX, double originY, CharCode code, int nBytes, Unicode const *u, int uLen)
Adds the specified character to the text buffer Takes care of converting it to UTF-8 and generates a ...
Inkscape::XML::Node * beginLayer(const std::string &label, bool visible)
void beginString(GfxState *state, int len)
Begin and end string is the inner most text processing step which tells us we're about to have a cert...
void finishGroup(GfxState *state, bool for_softmask)
void _setFillStyle(SPCSSAttr *css, GfxState *state, bool even_odd)
Sets fill style from poppler's GfxState data structure Uses the given SPCSSAttr for storing the style...
void startGroup(GfxState *state, double *bbox, GfxColorSpace *blending_color_space, bool isolated, bool knockout, bool for_softmask)
Starts building a new transparency group.
Builder for SVG path strings.
Definition path-string.h:38
PathString & curveTo(Geom::Coord x0, Geom::Coord y0, Geom::Coord x1, Geom::Coord y1, Geom::Coord x, Geom::Coord y)
PathString & moveTo(Geom::Coord x, Geom::Coord y)
Definition path-string.h:67
PathString & lineTo(Geom::Coord x, Geom::Coord y)
Definition path-string.h:79
static double convert(double from_dist, Unit const *from, Unit const *to)
Convert distances.
Definition units.cpp:525
Interface for refcounted XML nodes.
Definition node.h:80
virtual Node * parent()=0
Get the parent of this node.
virtual void appendChild(Node *child)=0
Append a node as the last child of this node.
virtual char const * name() const =0
Get the name of the element node.
void setAttributeOrRemoveIfEmpty(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:167
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
virtual Node * firstChild()=0
Get the first child of this node.
bool setAttributeCssDouble(Util::const_char_ptr key, double val)
Set a property attribute to val [slightly rounded], in the format required for CSS properties: in par...
Definition node.cpp:102
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
void removeAttribute(Inkscape::Util::const_char_ptr key)
Remove an attribute of this node.
Definition node.h:280
virtual void removeChild(Node *child)=0
Remove a child of this node.
virtual unsigned childCount() const =0
Get the number of children of this node.
bool setAttributeSvgDouble(Util::const_char_ptr key, double val)
For attributes where an exponent is allowed.
Definition node.cpp:111
virtual Node * lastChild()=0
Get the last child of this node.
bool getAttributeBoolean(Util::const_char_ptr key, bool default_value=false) const
Parses the boolean value of an attribute "key" in repr and sets val accordingly, or to false if the a...
Definition node.cpp:49
PDF parsing module using libpoppler's facilities.
Definition pdf-parser.h:112
GfxState * getState()
Definition pdf-parser.h:132
void parse(Object *obj, GBool topLevel=gTrue)
Typed SVG document implementation.
Definition document.h:103
SPObject * getObjectById(std::string const &id) const
Inkscape::XML::Node * getReprRoot()
Definition document.h:208
SPDefs * getDefs()
Return the main defs object for the document.
Definition document.cpp:245
Inkscape::XML::Document * getReprDoc()
Our Inkscape::XML::Document.
Definition document.h:213
int ensureUpToDate(unsigned int object_modified_tag=0)
Repeatedly works on getting the document updated, since sometimes it takes more than one pass to get ...
SPObject * getObjectByRepr(Inkscape::XML::Node *repr) const
SPNamedView * getNamedView()
Get the namedview for this document, creates it if it's not found.
Definition document.cpp:233
Inkscape::Colors::DocumentCMS & getDocumentCMS()
Definition document.h:167
Geom::OptRect visualBounds(Geom::Affine const &transform=Geom::identity(), bool wfilter=true, bool wclip=true, bool wmask=true) const
Get item's visual bounding box in this item's coordinate system.
Definition sp-item.cpp:925
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
constexpr uint32_t SP_COLOR_F_TO_U(double v)
Definition utils.h:23
RootCluster root
TODO: insert short description here.
std::shared_ptr< Css const > css
Css & result
Geom::Point orig
std::vector< double > dash_pattern
static char const *const parent
Definition dir-util.cpp:70
struct _cairo_surface cairo_surface_t
std::map< int, FontFallback > FontStrategies
Definition enums.h:32
FontStrategy
Definition enums.h:17
std::optional< std::string > try_extract_uri_id(const char *url)
Try extracting the object id from "url(#obj_id)" string using extract_uri.
TODO: insert short description here.
static bool has_fill(SPObject *source)
static bool has_stroke(SPObject *source)
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
bool pathv_fully_contains(Geom::PathVector const &a, Geom::PathVector const &b)
Definition geom.cpp:549
Specific geometry functions for Inkscape, not provided my lib2geom.
SPItem * item
Inkscape::XML::Node * node
void shift(T &a, T &b, T const &c)
double offset
Glib::ustring label
Affine identity()
Create an identity matrix.
Definition affine.h:210
void png_write_vector(png_structp png_ptr, png_bytep data, png_size_t length)
Helper functions for supporting direct PNG output into a base64 encoded stream.
static gchar * svgConvertRGBToText(double r, double g, double b)
static std::string svgConvertGfxRGB(GfxRGB *color)
static void svgSetTransform(Inkscape::XML::Node *node, Geom::Affine matrix)
static bool svgGetShadingColor(GfxShading *shading, double offset, GfxColor *result)
static gchar * svgInterpretPath(_POPPLER_CONST_83 GfxPath *path)
Generates a SVG path string from poppler's data structure.
void save(Extension *key, SPDocument *doc, gchar const *filename, bool check_overwrite, bool official, Inkscape::Extension::FileSaveMethod save_method)
This is a generic function to use the save function of a module (including Autodetect)
Definition system.cpp:162
int clamp(int const val)
Clamps an integer value to a value between 0 and 255.
static R & anchor(R &r)
Increments the reference count of a anchored object.
Definition gc-anchored.h:92
static R & release(R &r)
Decrements the reference count of a anchored object.
Helper class to stream background task notifications as a series of messages.
static T clip(T const &v, T const &a, T const &b)
Definition of functions needed by several filters.
static cairo_user_data_key_t key
static gint counter
Definition box3d.cpp:39
struct _cairo cairo_t
Definition path-cairo.h:16
Inkscape::SVG::PathString - builder for SVG path strings.
PDF parsing using libpoppler.
PDF Parsing utility functions and classes.
Geom::Affine stateToAffine(GfxState *state)
Get the default transformation state from the GfxState.
std::string sanitizeId(std::string const &in)
Convert arbitrary string (e.g.
std::string validateString(std::string const &in)
Ensure string is valid UTF8.
Geom::Affine ctmToAffine(const double *ctm)
Convert a transformation matrix to a lib2geom affine object.
std::shared_ptr< std::map< FontPtr, FontData > > FontList
Authors: see git history.
Ocnode * child[8]
Definition quantize.cpp:33
RGB rgb
Definition quantize.cpp:36
unsigned int rdf_set_work_entity(SPDocument *doc, struct rdf_work_entity_t *entity, const gchar *text)
Definition rdf.cpp:914
struct rdf_work_entity_t * rdf_find_entity(gchar const *name)
Retrieves a known RDF/Work entity by name.
Definition rdf.cpp:364
headers for RDF types
SPCSSAttr * sp_repr_css_attr_new()
Creates an empty SPCSSAttr (a class for manipulating CSS style properties).
Definition repr-css.cpp:67
void sp_repr_css_write_string(SPCSSAttr *css, Glib::ustring &str)
Write a style attribute string from a list of properties stored in an SPCSAttr object.
Definition repr-css.cpp:243
void sp_repr_css_merge(SPCSSAttr *dst, SPCSSAttr *src)
Merges two SPCSSAttr's.
Definition repr-css.cpp:299
void sp_repr_css_change(Node *repr, SPCSSAttr *css, gchar const *attr)
Creates a new SPCSAttr with the values filled from a repr, merges in properties from the given SPCSAt...
Definition repr-css.cpp:358
void sp_repr_css_set_property_double(SPCSSAttr *css, gchar const *name, double value)
Set a style property to a new float value (e.g.
Definition repr-css.cpp:224
void sp_repr_css_attr_unref(SPCSSAttr *css)
Unreferences an SPCSSAttr (will be garbage collected if no references remain).
Definition repr-css.cpp:76
SPCSSAttr * sp_repr_css_attr(Node const *repr, gchar const *attr)
Creates a new SPCSSAttr with one attribute (i.e.
Definition repr-css.cpp:88
char const * sp_repr_css_property(SPCSSAttr *css, gchar const *name, gchar const *defval)
Returns a character string of the value of a given style property or a default value if the attribute...
Definition repr-css.cpp:147
void sp_repr_css_set_property(SPCSSAttr *css, gchar const *name, gchar const *value)
Set a style property to a new value (e.g.
Definition repr-css.cpp:191
C facade to Inkscape::XML::Node.
auto len
Definition safe-printf.h:21
SPCSSAttr - interface for CSS Attributes.
void invert(const double v[16], double alpha[16])
static const Point data[]
Holds information about glyphs added by PdfParser which haven't been added to the document yet.
Definition svg-builder.h:75
std::shared_ptr< CairoFont > cairo_font
Definition svg-builder.h:95
virtual Node * createTextNode(char const *content)=0
virtual Node * createElement(char const *name)=0
static SPStyleEnum const enum_blend_mode[]
bool sp_svg_transform_read(gchar const *str, Geom::Affine *transform)
std::string sp_svg_transform_write(Geom::Affine const &transform)
Geom::PathVector sp_svg_read_pathv(char const *str)
Definition svg-path.cpp:37
static void sp_svg_write_path(Inkscape::SVG::PathString &str, Geom::Path const &p, bool normalize=false)
Definition svg-path.cpp:109
int delta
double height
double width
Glib::ustring name
Definition toolbars.cpp:55
Interface for XML documents.
Interface for XML nodes.