1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /**
3  * @file
4  * SVG stylesheets implementation.
5  */
6 /* Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   Peter Moulder <pmoulder@mail.csse.monash.edu.au>
9  *   bulia byak <buliabyak@users.sf.net>
10  *   Abhishek Sharma
11  *   Tavmjong Bah <tavmjong@free.fr>
12  *   Kris De Gussem <Kris.DeGussem@gmail.com>
13  *
14  * Copyright (C) 2001-2002 Lauris Kaplinski
15  * Copyright (C) 2001 Ximian, Inc.
16  * Copyright (C) 2005 Monash University
17  * Copyright (C) 2012 Kris De Gussem
18  * Copyright (C) 2014-2015 Tavmjong Bah
19  *
20  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
21  */
22 
23 #include "style.h"
24 
25 #include <cstring>
26 #include <string>
27 #include <algorithm>
28 #include <unordered_map>
29 #include <vector>
30 
31 #include <glibmm/regex.h>
32 
33 #include "attributes.h"
34 #include "bad-uri-exception.h"
35 #include "document.h"
36 #include "preferences.h"
37 
38 #include "3rdparty/libcroco/cr-sel-eng.h"
39 
40 #include "object/sp-paint-server.h"
41 #include "object/uri-references.h"
42 #include "object/uri.h"
43 
44 #include "svg/css-ostringstream.h"
45 #include "svg/svg.h"
46 
47 #include "util/units.h"
48 
49 #include "xml/croco-node-iface.h"
50 #include "xml/simple-document.h"
51 
52 #if !GLIB_CHECK_VERSION(2, 64, 0)
53 #define g_warning_once g_warning
54 #endif
55 
56 using Inkscape::CSSOStringStream;
57 
58 #define BMAX 8192
59 
60 struct SPStyleEnum;
61 
62 int SPStyle::_count = 0;
63 
64 /*#########################
65 ## FORWARD DECLARATIONS
66 #########################*/
67 void sp_style_filter_ref_changed(SPObject *old_ref, SPObject *ref, SPStyle *style);
68 void sp_style_fill_paint_server_ref_changed(SPObject *old_ref, SPObject *ref, SPStyle *style);
69 void sp_style_stroke_paint_server_ref_changed(SPObject *old_ref, SPObject *ref, SPStyle *style);
70 
71 static void sp_style_object_release(SPObject *object, SPStyle *style);
72 static CRSelEng *sp_repr_sel_eng();
73 
74 /**
75  * Helper class for SPStyle property member lookup by SPAttr or
76  * by name, and for iterating over ordered members.
77  */
78 class SPStylePropHelper {
SPStylePropHelper()79     SPStylePropHelper() {
80 #define REGISTER_PROPERTY(id, member, name) \
81         g_assert(decltype(SPStyle::member)::static_id() == id); \
82         _register(reinterpret_cast<SPIBasePtr>(&SPStyle::member), id) /* name unused */
83 
84         // SVG 2: Attributes promoted to properties
85         REGISTER_PROPERTY(SPAttr::D, d, "d");
86 
87         // 'color' must be before 'fill', 'stroke', 'text-decoration-color', ...
88         REGISTER_PROPERTY(SPAttr::COLOR, color, "color");
89 
90         // 'font-size'/'font' must be before properties that need to know em, ex size (SPILength,
91         // SPILengthOrNormal)
92         REGISTER_PROPERTY(SPAttr::FONT_STYLE, font_style, "font-style");
93         REGISTER_PROPERTY(SPAttr::FONT_VARIANT, font_variant, "font-variant");
94         REGISTER_PROPERTY(SPAttr::FONT_WEIGHT, font_weight, "font-weight");
95         REGISTER_PROPERTY(SPAttr::FONT_STRETCH, font_stretch, "font-stretch");
96         REGISTER_PROPERTY(SPAttr::FONT_SIZE, font_size, "font-size");
97         REGISTER_PROPERTY(SPAttr::LINE_HEIGHT, line_height, "line-height");
98         REGISTER_PROPERTY(SPAttr::FONT_FAMILY, font_family, "font-family");
99         REGISTER_PROPERTY(SPAttr::FONT, font, "font");
100         REGISTER_PROPERTY(SPAttr::INKSCAPE_FONT_SPEC, font_specification, "-inkscape-font-specification");
101 
102         // Font variants
103         REGISTER_PROPERTY(SPAttr::FONT_VARIANT_LIGATURES, font_variant_ligatures, "font-variant-ligatures");
104         REGISTER_PROPERTY(SPAttr::FONT_VARIANT_POSITION, font_variant_position, "font-variant-position");
105         REGISTER_PROPERTY(SPAttr::FONT_VARIANT_CAPS, font_variant_caps, "font-variant-caps");
106         REGISTER_PROPERTY(SPAttr::FONT_VARIANT_NUMERIC, font_variant_numeric, "font-variant-numeric");
107         REGISTER_PROPERTY(SPAttr::FONT_VARIANT_ALTERNATES, font_variant_alternates, "font-variant-alternates");
108         REGISTER_PROPERTY(SPAttr::FONT_VARIANT_EAST_ASIAN, font_variant_east_asian, "font-variant-east-asian");
109         REGISTER_PROPERTY(SPAttr::FONT_FEATURE_SETTINGS, font_feature_settings, "font-feature-settings");
110 
111         // Variable Fonts
112         REGISTER_PROPERTY(SPAttr::FONT_VARIATION_SETTINGS, font_variation_settings, "font-variation-settings");
113 
114         REGISTER_PROPERTY(SPAttr::TEXT_INDENT, text_indent, "text-indent");
115         REGISTER_PROPERTY(SPAttr::TEXT_ALIGN, text_align, "text-align");
116 
117         REGISTER_PROPERTY(SPAttr::TEXT_DECORATION, text_decoration, "text-decoration");
118         REGISTER_PROPERTY(SPAttr::TEXT_DECORATION_LINE, text_decoration_line, "text-decoration-line");
119         REGISTER_PROPERTY(SPAttr::TEXT_DECORATION_STYLE, text_decoration_style, "text-decoration-style");
120         REGISTER_PROPERTY(SPAttr::TEXT_DECORATION_COLOR, text_decoration_color, "text-decoration-color");
121         REGISTER_PROPERTY(SPAttr::TEXT_DECORATION_FILL, text_decoration_fill, "text-decoration-fill");
122         REGISTER_PROPERTY(SPAttr::TEXT_DECORATION_STROKE, text_decoration_stroke, "text-decoration-stroke");
123 
124         REGISTER_PROPERTY(SPAttr::LETTER_SPACING, letter_spacing, "letter-spacing");
125         REGISTER_PROPERTY(SPAttr::WORD_SPACING, word_spacing, "word-spacing");
126         REGISTER_PROPERTY(SPAttr::TEXT_TRANSFORM, text_transform, "text-transform");
127 
128         REGISTER_PROPERTY(SPAttr::WRITING_MODE, writing_mode, "writing-mode");
129         REGISTER_PROPERTY(SPAttr::DIRECTION, direction, "direction");
130         REGISTER_PROPERTY(SPAttr::TEXT_ORIENTATION, text_orientation, "text-orientation");
131         REGISTER_PROPERTY(SPAttr::DOMINANT_BASELINE, dominant_baseline, "dominant-baseline");
132         REGISTER_PROPERTY(SPAttr::BASELINE_SHIFT, baseline_shift, "baseline-shift");
133         REGISTER_PROPERTY(SPAttr::TEXT_ANCHOR, text_anchor, "text-anchor");
134         REGISTER_PROPERTY(SPAttr::WHITE_SPACE, white_space, "white-space");
135 
136         REGISTER_PROPERTY(SPAttr::SHAPE_INSIDE, shape_inside, "shape-inside");
137         REGISTER_PROPERTY(SPAttr::SHAPE_SUBTRACT, shape_subtract, "shape-subtract");
138         REGISTER_PROPERTY(SPAttr::SHAPE_PADDING, shape_padding, "shape-padding");
139         REGISTER_PROPERTY(SPAttr::SHAPE_MARGIN, shape_margin, "shape-margin");
140         REGISTER_PROPERTY(SPAttr::INLINE_SIZE, inline_size, "inline-size");
141 
142         REGISTER_PROPERTY(SPAttr::CLIP_RULE, clip_rule, "clip-rule");
143         REGISTER_PROPERTY(SPAttr::DISPLAY, display, "display");
144         REGISTER_PROPERTY(SPAttr::OVERFLOW_, overflow, "overflow");
145         REGISTER_PROPERTY(SPAttr::VISIBILITY, visibility, "visibility");
146         REGISTER_PROPERTY(SPAttr::OPACITY, opacity, "opacity");
147 
148         REGISTER_PROPERTY(SPAttr::ISOLATION, isolation, "isolation");
149         REGISTER_PROPERTY(SPAttr::MIX_BLEND_MODE, mix_blend_mode, "mix-blend-mode");
150 
151         REGISTER_PROPERTY(SPAttr::COLOR_INTERPOLATION, color_interpolation, "color-interpolation");
152         REGISTER_PROPERTY(SPAttr::COLOR_INTERPOLATION_FILTERS, color_interpolation_filters, "color-interpolation-filters");
153 
154         REGISTER_PROPERTY(SPAttr::SOLID_COLOR, solid_color, "solid-color");
155         REGISTER_PROPERTY(SPAttr::SOLID_OPACITY, solid_opacity, "solid-opacity");
156 
157         REGISTER_PROPERTY(SPAttr::VECTOR_EFFECT, vector_effect, "vector-effect");
158 
159         REGISTER_PROPERTY(SPAttr::FILL, fill, "fill");
160         REGISTER_PROPERTY(SPAttr::FILL_OPACITY, fill_opacity, "fill-opacity");
161         REGISTER_PROPERTY(SPAttr::FILL_RULE, fill_rule, "fill-rule");
162 
163         REGISTER_PROPERTY(SPAttr::STROKE, stroke, "stroke");
164         REGISTER_PROPERTY(SPAttr::STROKE_WIDTH, stroke_width, "stroke-width");
165         REGISTER_PROPERTY(SPAttr::STROKE_LINECAP, stroke_linecap, "stroke-linecap");
166         REGISTER_PROPERTY(SPAttr::STROKE_LINEJOIN, stroke_linejoin, "stroke-linejoin");
167         REGISTER_PROPERTY(SPAttr::STROKE_MITERLIMIT, stroke_miterlimit, "stroke-miterlimit");
168         REGISTER_PROPERTY(SPAttr::STROKE_DASHARRAY, stroke_dasharray, "stroke-dasharray");
169         REGISTER_PROPERTY(SPAttr::STROKE_DASHOFFSET, stroke_dashoffset, "stroke-dashoffset");
170         REGISTER_PROPERTY(SPAttr::STROKE_OPACITY, stroke_opacity, "stroke-opacity");
171         REGISTER_PROPERTY(SPAttr::STROKE_EXTENSIONS, stroke_extensions, "-inkscape-stroke");
172 
173         REGISTER_PROPERTY(SPAttr::MARKER, marker, "marker");
174         REGISTER_PROPERTY(SPAttr::MARKER_START, marker_start, "marker-start");
175         REGISTER_PROPERTY(SPAttr::MARKER_MID, marker_mid, "marker-mid");
176         REGISTER_PROPERTY(SPAttr::MARKER_END, marker_end, "marker-end");
177 
178         REGISTER_PROPERTY(SPAttr::PAINT_ORDER, paint_order, "paint-order");
179 
180         REGISTER_PROPERTY(SPAttr::FILTER, filter, "filter");
181 
182         REGISTER_PROPERTY(SPAttr::COLOR_RENDERING, color_rendering, "color-rendering");
183         REGISTER_PROPERTY(SPAttr::IMAGE_RENDERING, image_rendering, "image-rendering");
184         REGISTER_PROPERTY(SPAttr::SHAPE_RENDERING, shape_rendering, "shape-rendering");
185         REGISTER_PROPERTY(SPAttr::TEXT_RENDERING, text_rendering, "text-rendering");
186 
187         REGISTER_PROPERTY(SPAttr::ENABLE_BACKGROUND, enable_background, "enable-background");
188 
189         REGISTER_PROPERTY(SPAttr::STOP_COLOR, stop_color, "stop-color");
190         REGISTER_PROPERTY(SPAttr::STOP_OPACITY, stop_opacity, "stop-opacity");
191     }
192 
193     // this is a singleton, copy not allowed
194     SPStylePropHelper(SPStylePropHelper const&) = delete;
195 public:
196 
197     /**
198      * Singleton instance
199      */
instance()200     static SPStylePropHelper &instance() {
201         static SPStylePropHelper _instance;
202         return _instance;
203     }
204 
205     /**
206      * Get property pointer by enum
207      */
get(SPStyle * style,SPAttr id)208     SPIBase *get(SPStyle *style, SPAttr id) {
209         auto it = m_id_map.find(id);
210         if (it != m_id_map.end()) {
211             return _get(style, it->second);
212         }
213         return nullptr;
214     }
215 
216     /**
217      * Get property pointer by name
218      */
get(SPStyle * style,const std::string & name)219     SPIBase *get(SPStyle *style, const std::string &name) {
220         return get(style, sp_attribute_lookup(name.c_str()));
221     }
222 
223     /**
224      * Get a vector of property pointers
225      * \todo provide iterator instead
226      */
get_vector(SPStyle * style)227     std::vector<SPIBase *> get_vector(SPStyle *style) {
228         std::vector<SPIBase *> v;
229         v.reserve(m_vector.size());
230         for (auto ptr : m_vector) {
231             v.push_back(_get(style, ptr));
232         }
233         return v;
234     }
235 
236 private:
_get(SPStyle * style,SPIBasePtr ptr)237     SPIBase *_get(SPStyle *style, SPIBasePtr ptr) { return &(style->*ptr); }
238 
_register(SPIBasePtr ptr,SPAttr id)239     void _register(SPIBasePtr ptr, SPAttr id) {
240         m_vector.push_back(ptr);
241 
242         if (id != SPAttr::INVALID) {
243             m_id_map[id] = ptr;
244         }
245     }
246 
247     std::unordered_map<SPAttr, SPIBasePtr> m_id_map;
248     std::vector<SPIBasePtr> m_vector;
249 };
250 
251 auto &_prop_helper = SPStylePropHelper::instance();
252 
253 // C++11 allows one constructor to call another... might be useful. The original C code
254 // had separate calls to create SPStyle, one with only SPDocument and the other with only
255 // SPObject as parameters.
SPStyle(SPDocument * document_in,SPObject * object_in)256 SPStyle::SPStyle(SPDocument *document_in, SPObject *object_in) :
257 
258     // Unimplemented SVG 1.1: alignment-baseline, clip, clip-path, color-profile, cursor,
259     // dominant-baseline, flood-color, flood-opacity, font-size-adjust,
260     // glyph-orientation-horizontal, glyph-orientation-vertical, kerning, lighting-color,
261     // pointer-events, unicode-bidi
262 
263     // 'font', 'font-size', and 'font-family' must come first as other properties depend on them
264     // for calculated values (through 'em' and 'ex'). ('ex' is currently not read.)
265     // The following properties can depend on 'em' and 'ex':
266     //   baseline-shift, kerning, letter-spacing, stroke-dash-offset, stroke-width, word-spacing,
267     //   Non-SVG 1.1: text-indent, line-spacing
268 
269     // Hidden in SPIFontStyle:  (to be refactored)
270     //   font-family
271     //   font-specification
272 
273 
274     // SVG 2 attributes promoted to properties. (When geometry properties are added, move after font.)
275     d(                      false),  // SPIString Not inherited!
276 
277     // Font related properties and 'font' shorthand
278     font_style(             SP_CSS_FONT_STYLE_NORMAL),
279     font_variant(           SP_CSS_FONT_VARIANT_NORMAL),
280     font_weight(            SP_CSS_FONT_WEIGHT_NORMAL),
281     font_stretch(           SP_CSS_FONT_STRETCH_NORMAL),
282     font_size(),
283     line_height(            1.25 ),         // SPILengthOrNormal
284     font_family(            ),  // SPIString w/default
285     font(),                                                      // SPIFont
286     font_specification(     ),              // SPIString
287 
288     // Font variants (Features)
289     font_variant_ligatures( ),
290     font_variant_position(  SP_CSS_FONT_VARIANT_POSITION_NORMAL),
291     font_variant_caps(      SP_CSS_FONT_VARIANT_CAPS_NORMAL),
292     font_variant_numeric(   ),
293     font_variant_alternates(SP_CSS_FONT_VARIANT_ALTERNATES_NORMAL),
294     font_variant_east_asian(),
295     font_feature_settings(  ),
296 
297     // Variable Fonts
298     font_variation_settings(),  // SPIFontVariationSettings
299 
300     // Text related properties
301     text_indent(            ),  // SPILength
302     text_align(             SP_CSS_TEXT_ALIGN_START),
303 
304     letter_spacing(         ),  // SPILengthOrNormal
305     word_spacing(           ),  // SPILengthOrNormal
306     text_transform(         SP_CSS_TEXT_TRANSFORM_NONE),
307 
308     direction(              SP_CSS_DIRECTION_LTR),
309     writing_mode(           SP_CSS_WRITING_MODE_LR_TB),
310     text_orientation(       SP_CSS_TEXT_ORIENTATION_MIXED),
311     dominant_baseline(      SP_CSS_BASELINE_AUTO),
312     baseline_shift(),
313     text_anchor(            SP_CSS_TEXT_ANCHOR_START),
314     white_space(            SP_CSS_WHITE_SPACE_NORMAL),
315 
316     // SVG 2 Text Wrapping
317     shape_inside(           ), // SPIString
318     shape_subtract(         ), // SPIString
319     shape_padding(          ), // SPILength for now
320     shape_margin(           ), // SPILength for now
321     inline_size(            ), // SPILength for now
322 
323     text_decoration(),
324     text_decoration_line(),
325     text_decoration_style(),
326     text_decoration_color(  ),  // SPIColor
327     text_decoration_fill(   ),  // SPIPaint
328     text_decoration_stroke( ),  // SPIPaint
329 
330     // General visual properties
331     clip_rule(              SP_WIND_RULE_NONZERO),
332     display(                SP_CSS_DISPLAY_INLINE,   false),
333     overflow(               SP_CSS_OVERFLOW_VISIBLE, false),
334     visibility(             SP_CSS_VISIBILITY_VISIBLE),
335     opacity(                false),
336 
337     isolation(              SP_CSS_ISOLATION_AUTO),
338     mix_blend_mode(         SP_CSS_BLEND_NORMAL),
339 
340     paint_order(), // SPIPaintOrder
341 
342     // Color properties
343     color(                  ),  // SPIColor
344     color_interpolation(    SP_CSS_COLOR_INTERPOLATION_SRGB),
345     color_interpolation_filters(SP_CSS_COLOR_INTERPOLATION_LINEARRGB),
346 
347     // Solid color properties
348     solid_color(            ), // SPIColor
349     solid_opacity(          ),
350 
351     // Vector effects
352     vector_effect(),
353 
354     // Fill properties
355     fill(                   ),  // SPIPaint
356     fill_opacity(           ),
357     fill_rule(              SP_WIND_RULE_NONZERO),
358 
359     // Stroke properites
360     stroke(                 ),  // SPIPaint
361     stroke_width(           1.0),  // SPILength
362     stroke_linecap(         SP_STROKE_LINECAP_BUTT),
363     stroke_linejoin(        SP_STROKE_LINEJOIN_MITER),
364     stroke_miterlimit(      4), // SPIFloat (only use of float!)
365     stroke_dasharray(),                                          // SPIDashArray
366     stroke_dashoffset(      ),  // SPILength for now
367 
368     stroke_opacity(         ),
369 
370     stroke_extensions(      ),
371 
372     marker(                 ),  // SPIString
373     marker_start(           ),  // SPIString
374     marker_mid(             ),  // SPIString
375     marker_end(             ),  // SPIString
376 
377     // Filter properties
378     filter(),
379     enable_background(      SP_CSS_BACKGROUND_ACCUMULATE, false),
380 
381     // Rendering hint properties
382     color_rendering(        SP_CSS_COLOR_RENDERING_AUTO),
383     image_rendering(        SP_CSS_IMAGE_RENDERING_AUTO),
384     shape_rendering(        SP_CSS_SHAPE_RENDERING_AUTO),
385     text_rendering(         SP_CSS_TEXT_RENDERING_AUTO),
386 
387     // Stop color and opacity
388     stop_color(             false),       // SPIColor, does not inherit
389     stop_opacity(           false)        // Does not inherit
390 {
391     // std::cout << "SPStyle::SPStyle( SPDocument ): Entrance: (" << _count << ")" << std::endl;
392     // std::cout << "                      Document: " << (document_in?"present":"null") << std::endl;
393     // std::cout << "                        Object: "
394     //           << (object_in?(object_in->getId()?object_in->getId():"id null"):"object null") << std::endl;
395 
396     // static bool first = true;
397     // if( first ) {
398     //     std::cout << "Size of SPStyle: " << sizeof(SPStyle) << std::endl;
399     //     std::cout << "        SPIBase: " << sizeof(SPIBase) << std::endl;
400     //     std::cout << "       SPIFloat: " << sizeof(SPIFloat) << std::endl;
401     //     std::cout << "     SPIScale24: " << sizeof(SPIScale24) << std::endl;
402     //     std::cout << "      SPILength: " << sizeof(SPILength) << std::endl;
403     //     std::cout << "  SPILengthOrNormal: " << sizeof(SPILengthOrNormal) << std::endl;
404     //     std::cout << "       SPIColor: " << sizeof(SPIColor) << std::endl;
405     //     std::cout << "       SPIPaint: " << sizeof(SPIPaint) << std::endl;
406     //     std::cout << "  SPITextDecorationLine" << sizeof(SPITextDecorationLine) << std::endl;
407     //     std::cout << "   Glib::ustring:" << sizeof(Glib::ustring) << std::endl;
408     //     std::cout << "        SPColor: " << sizeof(SPColor) << std::endl;
409     //     first = false;
410     // }
411 
412     ++_count; // Poor man's memory leak detector
413 
414     _refcount = 1;
415 
416     cloned = false;
417 
418     object = object_in;
419     if( object ) {
420         g_assert( SP_IS_OBJECT(object) );
421         document = object->document;
422         release_connection =
423             object->connectRelease(sigc::bind<1>(sigc::ptr_fun(&sp_style_object_release), this));
424 
425         cloned = object->cloned;
426 
427     } else {
428         document = document_in;
429     }
430 
431     // 'font' shorthand requires access to included properties.
432     font.setStylePointer(              this );
433 
434     // Properties that depend on 'font-size' for calculating lengths.
435     baseline_shift.setStylePointer(    this );
436     text_indent.setStylePointer(       this );
437     line_height.setStylePointer(       this );
438     letter_spacing.setStylePointer(    this );
439     word_spacing.setStylePointer(      this );
440     stroke_width.setStylePointer(      this );
441     stroke_dashoffset.setStylePointer( this );
442     shape_padding.setStylePointer(     this );
443     shape_margin.setStylePointer(      this );
444     inline_size.setStylePointer(       this );
445 
446     // Properties that depend on 'color'
447     text_decoration_color.setStylePointer(this);
448     fill.setStylePointer(this);
449     stroke.setStylePointer(this);
450     color.setStylePointer(this);
451     stop_color.setStylePointer(this);
452     solid_color.setStylePointer(this);
453 
454     // 'text_decoration' shorthand requires access to included properties.
455     text_decoration.setStylePointer( this );
456 
457     // SPIPaint, SPIFilter needs access to 'this' (SPStyle)
458     // for setting up signals...  'fill', 'stroke' already done
459     filter.setStylePointer( this );
460     shape_inside.setStylePointer( this );
461     shape_subtract.setStylePointer( this );
462 
463     // Used to iterate over markers
464     marker_ptrs[SP_MARKER_LOC]       = &marker;
465     marker_ptrs[SP_MARKER_LOC_START] = &marker_start;
466     marker_ptrs[SP_MARKER_LOC_MID]   = &marker_mid;
467     marker_ptrs[SP_MARKER_LOC_END]   = &marker_end;
468 
469 
470     // This might be too resource hungary... but for now it possible to loop over properties
471     _properties = _prop_helper.get_vector(this);
472 }
473 
~SPStyle()474 SPStyle::~SPStyle() {
475 
476     // std::cout << "SPStyle::~SPStyle" << std::endl;
477     --_count; // Poor man's memory leak detector.
478 
479     // Remove connections
480     release_connection.disconnect();
481     fill_ps_changed_connection.disconnect();
482     stroke_ps_changed_connection.disconnect();
483 
484     // The following should be moved into SPIPaint and SPIFilter
485     if (fill.value.href) {
486         fill_ps_modified_connection.disconnect();
487     }
488 
489     if (stroke.value.href) {
490         stroke_ps_modified_connection.disconnect();
491     }
492 
493     if (filter.href) {
494         filter_modified_connection.disconnect();
495     }
496 
497     // Conjecture: all this SPStyle ref counting is not needed. SPObject creates an instance of
498     // SPStyle when it is constructed and deletes it when it is destructed. The refcount is
499     // incremented and decremented only in the files: display/drawing-item.cpp,
500     // display/nr-filter-primitive.cpp, and libnrtype/Layout-TNG-Input.cpp.
501     if( _refcount > 1 ) {
502         std::cerr << "SPStyle::~SPStyle: ref count greater than 1! " << _refcount << std::endl;
503     }
504     // std::cout << "SPStyle::~SPStyle(): Exit\n" << std::endl;
505 }
506 
properties()507 const std::vector<SPIBase *> SPStyle::properties() { return this->_properties; }
508 
509 void
clear(SPAttr id)510 SPStyle::clear(SPAttr id) {
511     SPIBase *p = _prop_helper.get(this, id);
512     if (p) {
513         p->clear();
514     } else {
515         g_warning("Unimplemented style property %d", (int)id);
516     }
517 }
518 
519 void
clear()520 SPStyle::clear() {
521     for (auto * p : _properties) {
522         p->clear();
523     }
524 
525     // Release connection to object, created in constructor.
526     release_connection.disconnect();
527 
528     // href->detach() called in fill->clear()...
529     fill_ps_modified_connection.disconnect();
530     if (fill.value.href) {
531         delete fill.value.href;
532         fill.value.href = nullptr;
533     }
534     stroke_ps_modified_connection.disconnect();
535     if (stroke.value.href) {
536         delete stroke.value.href;
537         stroke.value.href = nullptr;
538     }
539     filter_modified_connection.disconnect();
540     if (filter.href) {
541         delete filter.href;
542         filter.href = nullptr;
543     }
544 
545     if (document) {
546         filter.href = new SPFilterReference(document);
547         filter.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_filter_ref_changed), this));
548 
549         fill.value.href = new SPPaintServerReference(document);
550         fill_ps_changed_connection = fill.value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_fill_paint_server_ref_changed), this));
551 
552         stroke.value.href = new SPPaintServerReference(document);
553         stroke_ps_changed_connection = stroke.value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_stroke_paint_server_ref_changed), this));
554     }
555 
556     cloned = false;
557 
558 }
559 
560 // Matches void sp_style_read(SPStyle *style, SPObject *object, Inkscape::XML::Node *repr)
561 void
read(SPObject * object,Inkscape::XML::Node * repr)562 SPStyle::read( SPObject *object, Inkscape::XML::Node *repr ) {
563 
564     // std::cout << "SPstyle::read( SPObject, Inkscape::XML::Node ): Entrance: "
565     //           << (object?(object->getId()?object->getId():"id null"):"object null") << " "
566     //           << (repr?(repr->name()?repr->name():"no name"):"repr null")
567     //           << std::endl;
568     g_assert(repr != nullptr);
569     g_assert(!object || (object->getRepr() == repr));
570 
571     // // Uncomment to verify that we don't need to call clear.
572     // std::cout << " Creating temp style for testing" << std::endl;
573     // SPStyle *temp = new SPStyle();
574     // if( !(*temp == *this ) ) std::cout << "SPStyle::read: Need to clear" << std::endl;
575     // delete temp;
576 
577     clear(); // FIXME, If this isn't here, EVERYTHING stops working! Why?
578 
579     if (object && object->cloned) {
580         cloned = true;
581     }
582 
583     /* 1. Style attribute */
584     // std::cout << " MERGING STYLE ATTRIBUTE" << std::endl;
585     gchar const *val = repr->attribute("style");
586     if( val != nullptr && *val ) {
587         _mergeString( val );
588     }
589 
590     /* 2 Style sheet */
591     if (object) {
592         _mergeObjectStylesheet( object );
593     }
594 
595     /* 3 Presentation attributes */
596     for (auto * p : _properties) {
597         // Shorthands are not allowed as presentation properites. Note: text-decoration and
598         // font-variant are converted to shorthands in CSS 3 but can still be read as a
599         // non-shorthand for compatibility with older renders, so they should not be in this list.
600         if (p->id() != SPAttr::FONT && p->id() != SPAttr::MARKER) {
601             p->readAttribute( repr );
602         }
603     }
604 
605     /* 4 Cascade from parent */
606     if( object ) {
607         if( object->parent ) {
608             cascade( object->parent->style );
609         }
610     } else if( repr->parent() ) { // When does this happen?
611         // std::cout << "SPStyle::read(): reading via repr->parent()" << std::endl;
612         SPStyle *parent = new SPStyle();
613         parent->read( nullptr, repr->parent() );
614         cascade( parent );
615         delete parent;
616     }
617 }
618 
619 /**
620  * Read style properties from object's repr.
621  *
622  * 1. Reset existing object style
623  * 2. Load current effective object style
624  * 3. Load i attributes from immediate parent (which has to be up-to-date)
625  */
626 void
readFromObject(SPObject * object)627 SPStyle::readFromObject( SPObject *object ) {
628 
629     // std::cout << "SPStyle::readFromObject: "<< (object->getId()?object->getId():"null")<< std::endl;
630 
631     g_return_if_fail(object != nullptr);
632     g_return_if_fail(SP_IS_OBJECT(object));
633 
634     Inkscape::XML::Node *repr = object->getRepr();
635     g_return_if_fail(repr != nullptr);
636 
637     read( object, repr );
638 }
639 
640 /**
641  * Read style properties from preferences.
642  * @param path Preferences directory from which the style should be read
643  */
644 void
readFromPrefs(Glib::ustring const & path)645 SPStyle::readFromPrefs(Glib::ustring const &path) {
646 
647     g_return_if_fail(!path.empty());
648 
649     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
650 
651     // not optimal: we reconstruct the node based on the prefs, then pass it to
652     // sp_style_read for actual processing.
653     Inkscape::XML::SimpleDocument *tempdoc = new Inkscape::XML::SimpleDocument;
654     Inkscape::XML::Node *tempnode = tempdoc->createElement("prefs");
655 
656     std::vector<Inkscape::Preferences::Entry> attrs = prefs->getAllEntries(path);
657     for (auto & attr : attrs) {
658         tempnode->setAttribute(attr.getEntryName(), attr.getString());
659     }
660 
661     read( nullptr, tempnode );
662 
663     Inkscape::GC::release(tempnode);
664     Inkscape::GC::release(tempdoc);
665     delete tempdoc;
666 }
667 
668 // Matches sp_style_merge_property(SPStyle *style, gint id, gchar const *val)
669 void
readIfUnset(SPAttr id,gchar const * val,SPStyleSrc const & source)670 SPStyle::readIfUnset(SPAttr id, gchar const *val, SPStyleSrc const &source ) {
671 
672     // std::cout << "SPStyle::readIfUnset: Entrance: " << id << ": " << (val?val:"null") << std::endl;
673     // To Do: If it is not too slow, use std::map instead of std::vector inorder to remove switch()
674     // (looking up SPAttr::xxxx already uses a hash).
675     g_return_if_fail(val != nullptr);
676 
677     switch (id) {
678             /* SVG */
679             /* Clip/Mask */
680         case SPAttr::CLIP_PATH:
681             /** \todo
682              * This is a workaround. Inkscape only supports 'clip-path' as SVG attribute, not as
683              * style property. By having both CSS and SVG attribute set, editing of clip-path
684              * will fail, since CSS always overwrites SVG attributes.
685              * Fixes Bug #324849
686              */
687             g_warning_once("attribute 'clip-path' given as CSS");
688 
689             //XML Tree being directly used here.
690             if (object) {
691                 object->setAttribute("clip-path", val);
692             }
693             return;
694         case SPAttr::MASK:
695             /** \todo
696              * See comment for SPAttr::CLIP_PATH
697              */
698             g_warning_once("attribute 'mask' given as CSS");
699 
700             //XML Tree being directly used here.
701             if (object) {
702                 object->setAttribute("mask", val);
703             }
704             return;
705         case SPAttr::FILTER:
706             if( !filter.inherit ) filter.readIfUnset( val, source );
707             return;
708         case SPAttr::COLOR_INTERPOLATION:
709             // We read it but issue warning
710             color_interpolation.readIfUnset( val, source );
711             if( color_interpolation.value != SP_CSS_COLOR_INTERPOLATION_SRGB ) {
712                 g_warning("Inkscape currently only supports color-interpolation = sRGB");
713             }
714             return;
715     }
716 
717     auto p = _prop_helper.get(this, id);
718     if (p) {
719         p->readIfUnset(val, source);
720     } else {
721         g_warning("Unimplemented style property %d", (int)id);
722     }
723 }
724 
725 // return if is seted property
isSet(SPAttr id)726 bool SPStyle::isSet(SPAttr id)
727 {
728     bool set = false;
729     switch (id) {
730         case SPAttr::CLIP_PATH:
731             return set;
732         case SPAttr::MASK:
733             return set;
734         case SPAttr::FILTER:
735             if (!filter.inherit)
736                 set = filter.set;
737             return set;
738         case SPAttr::COLOR_INTERPOLATION:
739             // We read it but issue warning
740             return color_interpolation.set;
741     }
742 
743     auto p = _prop_helper.get(this, id);
744     if (p) {
745         return p->set;
746     } else {
747         g_warning("Unimplemented style property %d", (int)id);
748         return set;
749     }
750 }
751 
752 /**
753  * Outputs the style to a CSS string.
754  *
755  * Use with SP_STYLE_FLAG_ALWAYS for copying an object's complete cascaded style to
756  * style_clipboard.
757  *
758  * Use with SP_STYLE_FLAG_IFDIFF and a pointer to the parent class when you need a CSS string for
759  * an object in the document tree.
760  *
761  * \pre flags in {IFSET, ALWAYS, IFDIFF}.
762  * \pre base.
763  * \post ret != NULL.
764  */
765 Glib::ustring
write(guint const flags,SPStyleSrc const style_src_req,SPStyle const * const base) const766 SPStyle::write( guint const flags, SPStyleSrc const style_src_req, SPStyle const *const base ) const {
767 
768     // std::cout << "SPStyle::write: flags: " << flags << std::endl;
769 
770     // If not excluding this case, we'd end up writing all non-inheritable properties.
771     // Can happen when adding fallback <tspan>s to text like this:
772     // <text style="shape-inside:url(#x)">Hello</text>
773     if (base == this) {
774         assert((flags & SP_STYLE_FLAG_IFDIFF) && !(flags & SP_STYLE_FLAG_ALWAYS));
775         return {};
776     }
777 
778     Glib::ustring style_string;
779     for(std::vector<SPIBase*>::size_type i = 0; i != _properties.size(); ++i) {
780         if( base != nullptr ) {
781             style_string += _properties[i]->write( flags, style_src_req, base->_properties[i] );
782         } else {
783             style_string += _properties[i]->write( flags, style_src_req, nullptr );
784         }
785     }
786 
787     // Extended properties. Cascading not supported.
788     for (auto const &pair : extended_properties) {
789         // std::cout << "extended property: " << pair.first << " = " << pair.second << std::endl;
790         style_string += pair.first + ":" + pair.second + ";";
791     }
792 
793     // Remove trailing ';'
794     if( style_string.size() > 0 ) {
795         style_string.erase( style_string.size() - 1 );
796     }
797     return style_string;
798 }
799 /**
800  * Get CSS string for set properties, or with SP_STYLE_FLAG_ALWAYS, for all properties.
801  */
write(unsigned int flags) const802 Glib::ustring SPStyle::write(unsigned int flags) const
803 {
804     assert(flags & (SP_STYLE_FLAG_IFSET | SP_STYLE_FLAG_ALWAYS));
805     return write(flags, SPStyleSrc::UNSET);
806 }
807 /**
808  * Get CSS string for set properties from the requested source
809  */
write(SPStyleSrc style_src_req) const810 Glib::ustring SPStyle::write(SPStyleSrc style_src_req) const
811 {
812     assert(style_src_req != SPStyleSrc::UNSET);
813     return write(SP_STYLE_FLAG_IFSRC | SP_STYLE_FLAG_IFSET, style_src_req);
814 }
815 /**
816  * Get CSS string for set properties which are different from the given
817  * base style. If base is NULL, all set flags are considered different.
818  */
writeIfDiff(SPStyle const * base) const819 Glib::ustring SPStyle::writeIfDiff(SPStyle const *base) const
820 {
821     return write(SP_STYLE_FLAG_IFDIFF, SPStyleSrc::UNSET, base);
822 }
823 
824 // Corresponds to sp_style_merge_from_parent()
825 /**
826  * Sets computed values in \a style, which may involve inheriting from (or in some other way
827  * calculating from) corresponding computed values of \a parent.
828  *
829  * References: http://www.w3.org/TR/SVG11/propidx.html shows what properties inherit by default.
830  * http://www.w3.org/TR/SVG11/styling.html#Inheritance gives general rules as to what it means to
831  * inherit a value.  http://www.w3.org/TR/REC-CSS2/cascade.html#computed-value is more precise
832  * about what the computed value is (not obvious for lengths).
833  *
834  * \pre \a parent's computed values are already up-to-date.
835  */
836 void
cascade(SPStyle const * const parent)837 SPStyle::cascade( SPStyle const *const parent ) {
838     // std::cout << "SPStyle::cascade: " << (object->getId()?object->getId():"null") << std::endl;
839     for(std::vector<SPIBase*>::size_type i = 0; i != _properties.size(); ++i) {
840         _properties[i]->cascade( parent->_properties[i] );
841     }
842 }
843 
844 // Corresponds to sp_style_merge_from_dying_parent()
845 /**
846  * Combine \a style and \a parent style specifications into a single style specification that
847  * preserves (as much as possible) the effect of the existing \a style being a child of \a parent.
848  *
849  * Called when the parent repr is to be removed (e.g. the parent is a \<use\> element that is being
850  * unlinked), in which case we copy/adapt property values that are explicitly set in \a parent,
851  * trying to retain the same visual appearance once the parent is removed.  Interesting cases are
852  * when there is unusual interaction with the parent's value (opacity, display) or when the value
853  * can be specified as relative to the parent computed value (font-size, font-weight etc.).
854  *
855  * Doesn't update computed values of \a style.  For correctness, you should subsequently call
856  * sp_style_merge_from_parent against the new parent (presumably \a parent's parent) even if \a
857  * style was previously up-to-date wrt \a parent.
858  *
859  * \pre \a parent's computed values are already up-to-date.
860  *   (\a style's computed values needn't be up-to-date.)
861  */
862 void
merge(SPStyle const * const parent)863 SPStyle::merge( SPStyle const *const parent ) {
864     // std::cout << "SPStyle::merge" << std::endl;
865     for(std::vector<SPIBase*>::size_type i = 0; i != _properties.size(); ++i) {
866         _properties[i]->merge( parent->_properties[i] );
867     }
868 }
869 
870 /**
871  * Parses a style="..." string and merges it with an existing SPStyle.
872  */
873 void
mergeString(gchar const * const p)874 SPStyle::mergeString( gchar const *const p ) {
875     _mergeString( p );
876 }
877 
878 /**
879   * Append an existing css statement into this style, used in css editing
880   * always appends declarations as STYLE_SHEET properties.
881   */
882 void
mergeStatement(CRStatement * statement)883 SPStyle::mergeStatement( CRStatement *statement ) {
884     if (statement->type != RULESET_STMT) {
885         return;
886     }
887     CRDeclaration *decl_list = nullptr;
888     cr_statement_ruleset_get_declarations (statement, &decl_list);
889     if (decl_list) {
890         _mergeDeclList(decl_list, SPStyleSrc::STYLE_SHEET);
891     }
892 }
893 
894 // Mostly for unit testing
895 bool
operator ==(const SPStyle & rhs)896 SPStyle::operator==(const SPStyle& rhs) {
897 
898     // Uncomment for testing
899     // for(std::vector<SPIBase*>::size_type i = 0; i != _properties.size(); ++i) {
900     //     if( *_properties[i] != *rhs._properties[i])
901     //     std::cout << _properties[i]->name << ": "
902     //               << _properties[i]->write(SP_STYLE_FLAG_ALWAYS,NULL) << " "
903     //               << rhs._properties[i]->write(SP_STYLE_FLAG_ALWAYS,NULL)
904     //               << (*_properties[i]  == *rhs._properties[i]) << std::endl;
905     // }
906 
907     for(std::vector<SPIBase*>::size_type i = 0; i != _properties.size(); ++i) {
908         if( *_properties[i] != *rhs._properties[i]) return false;
909     }
910     return true;
911 }
912 
913 void
_mergeString(gchar const * const p)914 SPStyle::_mergeString( gchar const *const p ) {
915 
916     // std::cout << "SPStyle::_mergeString: " << (p?p:"null") << std::endl;
917     CRDeclaration *const decl_list
918         = cr_declaration_parse_list_from_buf(reinterpret_cast<guchar const *>(p), CR_UTF_8);
919     if (decl_list) {
920         _mergeDeclList( decl_list, SPStyleSrc::STYLE_PROP );
921         cr_declaration_destroy(decl_list);
922     }
923 }
924 
925 void
_mergeDeclList(CRDeclaration const * const decl_list,SPStyleSrc const & source)926 SPStyle::_mergeDeclList( CRDeclaration const *const decl_list, SPStyleSrc const &source ) {
927 
928     // std::cout << "SPStyle::_mergeDeclList" << std::endl;
929 
930     // In reverse order, as later declarations to take precedence over earlier ones.
931     // (Properties are only set if not previously set. See:
932     // Ref: http://www.w3.org/TR/REC-CSS2/cascade.html#cascading-order point 4.)
933     if (decl_list->next) {
934         _mergeDeclList( decl_list->next, source );
935     }
936     _mergeDecl( decl_list, source );
937 }
938 
939 void
_mergeDecl(CRDeclaration const * const decl,SPStyleSrc const & source)940 SPStyle::_mergeDecl(  CRDeclaration const *const decl, SPStyleSrc const &source ) {
941 
942     // std::cout << "SPStyle::_mergeDecl" << std::endl;
943 
944     auto prop_idx = sp_attribute_lookup(decl->property->stryng->str);
945     if (prop_idx != SPAttr::INVALID) {
946         /** \todo
947          * effic: Test whether the property is already set before trying to
948          * convert to string. Alternatively, set from CRTerm directly rather
949          * than converting to string.
950          */
951         if (!isSet(prop_idx) || decl->important) {
952             guchar *const str_value_unsigned = cr_term_to_string(decl->value);
953             gchar *const str_value = reinterpret_cast<gchar *>(str_value_unsigned);
954 
955             // Add "!important" rule if necessary as this is not handled by cr_term_to_string().
956             gchar const *important = decl->important ? " !important" : "";
957             Inkscape::CSSOStringStream os;
958             os << str_value << important;
959 
960             readIfUnset(prop_idx, os.str().c_str(), source);
961             g_free(str_value);
962         }
963     } else {
964         gchar const *key = decl->property->stryng->str;
965         auto value = reinterpret_cast<gchar *>(cr_term_to_string(decl->value));
966 
967         if (g_str_has_prefix(key, "--")) {
968             g_warning("Ignoring CSS variable: %s", key);
969         } else if (g_str_has_prefix(key, "-")) {
970             extended_properties[key] = value;
971         } else {
972             g_warning("Ignoring unrecognized CSS property: %s", key);
973         }
974 
975         g_free(value);
976     }
977 }
978 
979 void
_mergeProps(CRPropList * const props)980 SPStyle::_mergeProps( CRPropList *const props ) {
981 
982     // std::cout << "SPStyle::_mergeProps" << std::endl;
983 
984     // In reverse order, as later declarations to take precedence over earlier ones.
985     if (props) {
986         _mergeProps( cr_prop_list_get_next( props ) );
987         CRDeclaration *decl = nullptr;
988         cr_prop_list_get_decl(props, &decl);
989         _mergeDecl( decl, SPStyleSrc::STYLE_SHEET );
990     }
991 }
992 
993 void
_mergeObjectStylesheet(SPObject const * const object)994 SPStyle::_mergeObjectStylesheet( SPObject const *const object ) {
995 
996     // std::cout << "SPStyle::_mergeObjectStylesheet: " << (object->getId()?object->getId():"null") << std::endl;
997 
998     static CRSelEng *sel_eng = nullptr;
999     if (!sel_eng) {
1000         sel_eng = sp_repr_sel_eng();
1001     }
1002 
1003     CRPropList *props = nullptr;
1004 
1005     //XML Tree being directly used here while it shouldn't be.
1006     CRStatus status =
1007         cr_sel_eng_get_matched_properties_from_cascade(sel_eng,
1008                                                        object->document->getStyleCascade(),
1009                                                        object->getRepr(),
1010                                                        &props);
1011     g_return_if_fail(status == CR_OK);
1012     /// \todo Check what errors can occur, and handle them properly.
1013     if (props) {
1014         _mergeProps(props);
1015         cr_prop_list_destroy(props);
1016     }
1017 }
1018 
1019 // Used for input into Pango. Must be computed value!
1020 std::string
getFontFeatureString()1021 SPStyle::getFontFeatureString() {
1022 
1023     std::string feature_string;
1024     if ( !(font_variant_ligatures.computed & SP_CSS_FONT_VARIANT_LIGATURES_COMMON) )
1025         feature_string += "liga 0, clig 0, ";
1026     if (   font_variant_ligatures.computed & SP_CSS_FONT_VARIANT_LIGATURES_DISCRETIONARY )
1027         feature_string += "dlig, ";
1028     if (   font_variant_ligatures.computed & SP_CSS_FONT_VARIANT_LIGATURES_HISTORICAL )
1029         feature_string += "hlig, ";
1030     if ( !(font_variant_ligatures.computed & SP_CSS_FONT_VARIANT_LIGATURES_CONTEXTUAL) )
1031         feature_string += "calt 0, ";
1032 
1033     switch (font_variant_position.computed) {
1034         case SP_CSS_FONT_VARIANT_POSITION_SUB:
1035             feature_string += "subs, ";
1036             break;
1037         case SP_CSS_FONT_VARIANT_POSITION_SUPER:
1038             feature_string += "sups, ";
1039     }
1040 
1041     switch (font_variant_caps.computed) {
1042         case SP_CSS_FONT_VARIANT_CAPS_SMALL:
1043             feature_string += "smcp, ";
1044             break;
1045         case SP_CSS_FONT_VARIANT_CAPS_ALL_SMALL:
1046             feature_string += "smcp, c2sc, ";
1047             break;
1048         case SP_CSS_FONT_VARIANT_CAPS_PETITE:
1049             feature_string += "pcap, ";
1050             break;
1051         case SP_CSS_FONT_VARIANT_CAPS_ALL_PETITE:
1052             feature_string += "pcap, c2pc, ";
1053             break;
1054         case SP_CSS_FONT_VARIANT_CAPS_UNICASE:
1055             feature_string += "unic, ";
1056             break;
1057         case SP_CSS_FONT_VARIANT_CAPS_TITLING:
1058             feature_string += "titl, ";
1059     }
1060 
1061     if ( font_variant_numeric.computed & SP_CSS_FONT_VARIANT_NUMERIC_LINING_NUMS )
1062         feature_string += "lnum, ";
1063     if ( font_variant_numeric.computed & SP_CSS_FONT_VARIANT_NUMERIC_OLDSTYLE_NUMS )
1064         feature_string += "onum, ";
1065     if ( font_variant_numeric.computed & SP_CSS_FONT_VARIANT_NUMERIC_PROPORTIONAL_NUMS )
1066         feature_string += "pnum, ";
1067     if ( font_variant_numeric.computed & SP_CSS_FONT_VARIANT_NUMERIC_TABULAR_NUMS )
1068         feature_string += "tnum, ";
1069     if ( font_variant_numeric.computed & SP_CSS_FONT_VARIANT_NUMERIC_DIAGONAL_FRACTIONS )
1070         feature_string += "frac, ";
1071     if ( font_variant_numeric.computed & SP_CSS_FONT_VARIANT_NUMERIC_STACKED_FRACTIONS )
1072         feature_string += "afrc, ";
1073     if ( font_variant_numeric.computed & SP_CSS_FONT_VARIANT_NUMERIC_ORDINAL )
1074         feature_string += "ordn, ";
1075     if ( font_variant_numeric.computed & SP_CSS_FONT_VARIANT_NUMERIC_SLASHED_ZERO )
1076         feature_string += "zero, ";
1077 
1078     if( font_variant_east_asian.computed & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS78 )
1079         feature_string += "jp78, ";
1080     if( font_variant_east_asian.computed & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS83 )
1081         feature_string += "jp83, ";
1082     if( font_variant_east_asian.computed & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS90 )
1083         feature_string += "jp90, ";
1084     if( font_variant_east_asian.computed & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS04 )
1085         feature_string += "jp04, ";
1086     if( font_variant_east_asian.computed & SP_CSS_FONT_VARIANT_EAST_ASIAN_SIMPLIFIED )
1087         feature_string += "smpl, ";
1088     if( font_variant_east_asian.computed & SP_CSS_FONT_VARIANT_EAST_ASIAN_TRADITIONAL )
1089         feature_string += "trad, ";
1090     if( font_variant_east_asian.computed & SP_CSS_FONT_VARIANT_EAST_ASIAN_FULL_WIDTH )
1091         feature_string += "fwid, ";
1092     if( font_variant_east_asian.computed & SP_CSS_FONT_VARIANT_EAST_ASIAN_PROPORTIONAL_WIDTH )
1093         feature_string += "pwid, ";
1094     if( font_variant_east_asian.computed & SP_CSS_FONT_VARIANT_EAST_ASIAN_RUBY )
1095         feature_string += "ruby, ";
1096 
1097     char const *val = font_feature_settings.value();
1098     if (val[0] && strcmp(val, "normal")) {
1099         // We do no sanity checking...
1100         feature_string += val;
1101         feature_string += ", ";
1102     }
1103 
1104     if (feature_string.empty()) {
1105         feature_string = "normal";
1106     } else {
1107         // Remove last ", "
1108         feature_string.resize(feature_string.size() - 2);
1109     }
1110 
1111     return feature_string;
1112 }
1113 
1114 
1115 // Internal
1116 /**
1117  * Release callback.
1118  */
1119 static void
sp_style_object_release(SPObject * object,SPStyle * style)1120 sp_style_object_release(SPObject *object, SPStyle *style)
1121 {
1122     (void)object; // TODO
1123     style->object = nullptr;
1124 }
1125 
1126 // Internal
1127 /**
1128  * Emit style modified signal on style's object if the filter changed.
1129  */
1130 static void
sp_style_filter_ref_modified(SPObject * obj,guint flags,SPStyle * style)1131 sp_style_filter_ref_modified(SPObject *obj, guint flags, SPStyle *style)
1132 {
1133     (void)flags; // TODO
1134     SPFilter *filter=static_cast<SPFilter *>(obj);
1135     if (style->getFilter() == filter)
1136     {
1137         if (style->object) {
1138             style->object->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1139         }
1140     }
1141 }
1142 
1143 // Internal
1144 /**
1145  * Gets called when the filter is (re)attached to the style
1146  */
1147 void
sp_style_filter_ref_changed(SPObject * old_ref,SPObject * ref,SPStyle * style)1148 sp_style_filter_ref_changed(SPObject *old_ref, SPObject *ref, SPStyle *style)
1149 {
1150     if (old_ref) {
1151         (dynamic_cast<SPFilter *>( old_ref ))->_refcount--;
1152         style->filter_modified_connection.disconnect();
1153     }
1154     if ( SP_IS_FILTER(ref))
1155     {
1156        (dynamic_cast<SPFilter *>( ref ))->_refcount++;
1157         style->filter_modified_connection =
1158            ref->connectModified(sigc::bind(sigc::ptr_fun(&sp_style_filter_ref_modified), style));
1159     }
1160 
1161     sp_style_filter_ref_modified(ref, 0, style);
1162 }
1163 
1164 /**
1165  * Emit style modified signal on style's object if server is style's fill
1166  * or stroke paint server.
1167  */
1168 static void
sp_style_paint_server_ref_modified(SPObject * obj,guint flags,SPStyle * style)1169 sp_style_paint_server_ref_modified(SPObject *obj, guint flags, SPStyle *style)
1170 {
1171     (void)flags; // TODO
1172     SPPaintServer *server = static_cast<SPPaintServer *>(obj);
1173 
1174     if ((style->fill.isPaintserver())
1175         && style->getFillPaintServer() == server)
1176     {
1177         if (style->object) {
1178             /** \todo
1179              * fixme: I do not know, whether it is optimal - we are
1180              * forcing reread of everything (Lauris)
1181              */
1182             /** \todo
1183              * fixme: We have to use object_modified flag, because parent
1184              * flag is only available downstreams.
1185              */
1186             style->object->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1187         }
1188     } else if ((style->stroke.isPaintserver())
1189         && style->getStrokePaintServer() == server)
1190     {
1191         if (style->object) {
1192             /// \todo fixme:
1193             style->object->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1194         }
1195     } else if (server) {
1196         g_assert_not_reached();
1197     }
1198 }
1199 
1200 /**
1201  * Gets called when the paintserver is (re)attached to the style
1202  */
1203 void
sp_style_fill_paint_server_ref_changed(SPObject * old_ref,SPObject * ref,SPStyle * style)1204 sp_style_fill_paint_server_ref_changed(SPObject *old_ref, SPObject *ref, SPStyle *style)
1205 {
1206     if (old_ref) {
1207         style->fill_ps_modified_connection.disconnect();
1208     }
1209     if (SP_IS_PAINT_SERVER(ref)) {
1210         style->fill_ps_modified_connection =
1211            ref->connectModified(sigc::bind(sigc::ptr_fun(&sp_style_paint_server_ref_modified), style));
1212     }
1213 
1214     style->signal_fill_ps_changed.emit(old_ref, ref);
1215     sp_style_paint_server_ref_modified(ref, 0, style);
1216 }
1217 
1218 /**
1219  * Gets called when the paintserver is (re)attached to the style
1220  */
1221 void
sp_style_stroke_paint_server_ref_changed(SPObject * old_ref,SPObject * ref,SPStyle * style)1222 sp_style_stroke_paint_server_ref_changed(SPObject *old_ref, SPObject *ref, SPStyle *style)
1223 {
1224     if (old_ref) {
1225         style->stroke_ps_modified_connection.disconnect();
1226     }
1227     if (SP_IS_PAINT_SERVER(ref)) {
1228         style->stroke_ps_modified_connection =
1229           ref->connectModified(sigc::bind(sigc::ptr_fun(&sp_style_paint_server_ref_modified), style));
1230     }
1231 
1232     style->signal_stroke_ps_changed.emit(old_ref, ref);
1233     sp_style_paint_server_ref_modified(ref, 0, style);
1234 }
1235 
1236 // Called in display/drawing-item.cpp, display/nr-filter-primitive.cpp, libnrtype/Layout-TNG-Input.cpp
1237 /**
1238  * Increase refcount of style.
1239  */
1240 SPStyle *
sp_style_ref(SPStyle * style)1241 sp_style_ref(SPStyle *style)
1242 {
1243     g_return_val_if_fail(style != nullptr, NULL);
1244 
1245     style->style_ref(); // Increase ref count
1246 
1247     return style;
1248 }
1249 
1250 // Called in display/drawing-item.cpp, display/nr-filter-primitive.cpp, libnrtype/Layout-TNG-Input.cpp
1251 /**
1252  * Decrease refcount of style with possible destruction.
1253  */
1254 SPStyle *
sp_style_unref(SPStyle * style)1255 sp_style_unref(SPStyle *style)
1256 {
1257     g_return_val_if_fail(style != nullptr, NULL);
1258     if (style->style_unref() < 1) {
1259         delete style;
1260         return nullptr;
1261     }
1262     return style;
1263 }
1264 
1265 static CRSelEng *
sp_repr_sel_eng()1266 sp_repr_sel_eng()
1267 {
1268     CRSelEng *const ret = cr_sel_eng_new(&Inkscape::XML::croco_node_iface);
1269 
1270     /** \todo
1271      * Check whether we need to register any pseudo-class handlers.
1272      * libcroco has its own default handlers for first-child and lang.
1273      *
1274      * We probably want handlers for link and arguably visited (though
1275      * inkscape can't visit links at the time of writing).  hover etc.
1276      * more useful in inkview than the editor inkscape.
1277      *
1278      * http://www.w3.org/TR/SVG11/styling.html#StylingWithCSS says that
1279      * the following should be honoured, at least by inkview:
1280      * :hover, :active, :focus, :visited, :link.
1281      */
1282 
1283     g_assert(ret);
1284     return ret;
1285 }
1286 
1287 // The following functions should be incorporated into SPIPaint. FIXME
1288 // Called in: style.cpp, style-internal.cpp
1289 void
sp_style_set_ipaint_to_uri(SPStyle * style,SPIPaint * paint,const Inkscape::URI * uri,SPDocument * document)1290 sp_style_set_ipaint_to_uri(SPStyle *style, SPIPaint *paint, const Inkscape::URI *uri, SPDocument *document)
1291 {
1292     if (!paint->value.href) {
1293 
1294         if (style->object) {
1295             // Should not happen as href should have been created in SPIPaint. (TODO: Removed code duplication.)
1296             paint->value.href = new SPPaintServerReference(style->object);
1297 
1298         } else if (document) {
1299             // Used by desktop style (no object to attach to!).
1300             paint->value.href = new SPPaintServerReference(document);
1301 
1302         } else {
1303             std::cerr << "sp_style_set_ipaint_to_uri: No valid object or document!" << std::endl;
1304             return;
1305         }
1306 
1307         if (paint == &style->fill) {
1308             style->fill_ps_changed_connection = paint->value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_fill_paint_server_ref_changed), style));
1309         } else {
1310             style->stroke_ps_changed_connection = paint->value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_stroke_paint_server_ref_changed), style));
1311         }
1312     }
1313 
1314     if (paint->value.href){
1315         if (paint->value.href->getObject()){
1316             paint->value.href->detach();
1317         }
1318 
1319         try {
1320             paint->value.href->attach(*uri);
1321         } catch (Inkscape::BadURIException &e) {
1322             g_warning("%s", e.what());
1323             paint->value.href->detach();
1324         }
1325     }
1326 }
1327 
1328 // Called in: style.cpp, style-internal.cpp
1329 void
sp_style_set_ipaint_to_uri_string(SPStyle * style,SPIPaint * paint,const gchar * uri)1330 sp_style_set_ipaint_to_uri_string (SPStyle *style, SPIPaint *paint, const gchar *uri)
1331 {
1332     try {
1333         const Inkscape::URI IURI(uri);
1334         sp_style_set_ipaint_to_uri(style, paint, &IURI, style->document);
1335     } catch (...) {
1336         g_warning("URI failed to parse: %s", uri);
1337     }
1338 }
1339 
1340 // Called in: desktop-style.cpp
sp_style_set_to_uri(SPStyle * style,bool isfill,Inkscape::URI const * uri)1341 void sp_style_set_to_uri(SPStyle *style, bool isfill, Inkscape::URI const *uri)
1342 {
1343     sp_style_set_ipaint_to_uri(style, style->getFillOrStroke(isfill), uri, style->document);
1344 }
1345 
1346 // Called in: widgets/font-selector.cpp, widgets/text-toolbar.cpp, ui/dialog/text-edit.cpp
1347 gchar const *
sp_style_get_css_unit_string(int unit)1348 sp_style_get_css_unit_string(int unit)
1349 {
1350     // specify px by default, see inkscape bug 1221626, mozilla bug 234789
1351     // This is a problematic fix as some properties (e.g. 'line-height') have
1352     // different behaviour if there is no unit.
1353     switch (unit) {
1354 
1355         case SP_CSS_UNIT_NONE: return "px";
1356         case SP_CSS_UNIT_PX: return "px";
1357         case SP_CSS_UNIT_PT: return "pt";
1358         case SP_CSS_UNIT_PC: return "pc";
1359         case SP_CSS_UNIT_MM: return "mm";
1360         case SP_CSS_UNIT_CM: return "cm";
1361         case SP_CSS_UNIT_IN: return "in";
1362         case SP_CSS_UNIT_EM: return "em";
1363         case SP_CSS_UNIT_EX: return "ex";
1364         case SP_CSS_UNIT_PERCENT: return "%";
1365         default: return "px";
1366     }
1367     return "px";
1368 }
1369 
1370 // Called in: style-internal.cpp, widgets/text-toolbar.cpp, ui/dialog/text-edit.cpp
1371 /*
1372  * Convert a size in pixels into another CSS unit size
1373  */
1374 double
sp_style_css_size_px_to_units(double size,int unit,double font_size)1375 sp_style_css_size_px_to_units(double size, int unit, double font_size)
1376 {
1377     double unit_size = size;
1378 
1379     if (font_size == 0) {
1380         g_warning("sp_style_get_css_font_size_units: passed in zero font_size");
1381         font_size = SP_CSS_FONT_SIZE_DEFAULT;
1382     }
1383 
1384     switch (unit) {
1385 
1386         case SP_CSS_UNIT_NONE: unit_size = size; break;
1387         case SP_CSS_UNIT_PX: unit_size = size; break;
1388         case SP_CSS_UNIT_PT: unit_size = Inkscape::Util::Quantity::convert(size, "px", "pt");  break;
1389         case SP_CSS_UNIT_PC: unit_size = Inkscape::Util::Quantity::convert(size, "px", "pc");  break;
1390         case SP_CSS_UNIT_MM: unit_size = Inkscape::Util::Quantity::convert(size, "px", "mm");  break;
1391         case SP_CSS_UNIT_CM: unit_size = Inkscape::Util::Quantity::convert(size, "px", "cm");  break;
1392         case SP_CSS_UNIT_IN: unit_size = Inkscape::Util::Quantity::convert(size, "px", "in");  break;
1393         case SP_CSS_UNIT_EM: unit_size = size / font_size; break;
1394         case SP_CSS_UNIT_EX: unit_size = size * 2.0 / font_size ; break;
1395         case SP_CSS_UNIT_PERCENT: unit_size = size * 100.0 / font_size; break;
1396 
1397         default:
1398             g_warning("sp_style_get_css_font_size_units conversion to %d not implemented.", unit);
1399             break;
1400     }
1401 
1402     return unit_size;
1403 }
1404 
1405 // Called in: widgets/text-toolbar.cpp, ui/dialog/text-edit.cpp
1406 /*
1407  * Convert a size in a CSS unit size to pixels
1408  */
1409 double
sp_style_css_size_units_to_px(double size,int unit,double font_size)1410 sp_style_css_size_units_to_px(double size, int unit, double font_size)
1411 {
1412     if (unit == SP_CSS_UNIT_PX) {
1413         return size;
1414     }
1415     //g_message("sp_style_css_size_units_to_px %f %d = %f px", size, unit, out);
1416     return size * (size / sp_style_css_size_px_to_units(size, unit, font_size));;
1417 }
1418 
1419 
1420 // FIXME: Everything below this line belongs in a different file - css-chemistry?
1421 
1422 void
sp_style_set_property_url(SPObject * item,gchar const * property,SPObject * linked,bool recursive)1423 sp_style_set_property_url (SPObject *item, gchar const *property, SPObject *linked, bool recursive)
1424 {
1425     Inkscape::XML::Node *repr = item->getRepr();
1426 
1427     if (repr == nullptr) return;
1428 
1429     SPCSSAttr *css = sp_repr_css_attr_new();
1430     if (linked) {
1431         gchar *val = g_strdup_printf("url(#%s)", linked->getId());
1432         sp_repr_css_set_property(css, property, val);
1433         g_free(val);
1434     } else {
1435         sp_repr_css_unset_property(css, "filter");
1436     }
1437 
1438     if (recursive) {
1439         sp_repr_css_change_recursive(repr, css, "style");
1440     } else {
1441         sp_repr_css_change(repr, css, "style");
1442     }
1443     sp_repr_css_attr_unref(css);
1444 }
1445 
1446 /**
1447  * \pre style != NULL.
1448  * \pre flags in {IFSET, ALWAYS}.
1449  * Only used by sp_css_attr_from_object() and in splivarot.cpp - sp_item_path_outline().
1450  */
1451 SPCSSAttr *
sp_css_attr_from_style(SPStyle const * const style,guint const flags)1452 sp_css_attr_from_style(SPStyle const *const style, guint const flags)
1453 {
1454     g_return_val_if_fail(style != nullptr, NULL);
1455     g_return_val_if_fail(((flags & SP_STYLE_FLAG_IFSET) ||
1456                           (flags & SP_STYLE_FLAG_ALWAYS)),
1457                          NULL);
1458     Glib::ustring style_str = style->write(flags);
1459     SPCSSAttr *css = sp_repr_css_attr_new();
1460     sp_repr_css_attr_add_from_string(css, style_str.c_str());
1461     return css;
1462 }
1463 
1464 // Called in: selection-chemistry.cpp, widgets/stroke-marker-selector.cpp, widgets/stroke-style.cpp,
1465 // ui/tools/freehand-base.cpp
1466 /**
1467  * \pre object != NULL
1468  * \pre flags in {IFSET, ALWAYS}.
1469  */
sp_css_attr_from_object(SPObject * object,guint const flags)1470 SPCSSAttr *sp_css_attr_from_object(SPObject *object, guint const flags)
1471 {
1472     g_return_val_if_fail(((flags == SP_STYLE_FLAG_IFSET) ||
1473                           (flags == SP_STYLE_FLAG_ALWAYS)  ),
1474                          NULL);
1475     SPCSSAttr * result = nullptr;
1476     if (object->style) {
1477         result = sp_css_attr_from_style(object->style, flags);
1478     }
1479     return result;
1480 }
1481 
1482 // Called in: selection-chemistry.cpp, ui/dialog/inkscape-preferences.cpp
1483 /**
1484  * Unset any text-related properties
1485  */
1486 SPCSSAttr *
sp_css_attr_unset_text(SPCSSAttr * css)1487 sp_css_attr_unset_text(SPCSSAttr *css)
1488 {
1489     sp_repr_css_set_property(css, "font", nullptr);
1490     sp_repr_css_set_property(css, "-inkscape-font-specification", nullptr);
1491     sp_repr_css_set_property(css, "font-size", nullptr);
1492     sp_repr_css_set_property(css, "font-size-adjust", nullptr); // not implemented yet
1493     sp_repr_css_set_property(css, "font-style", nullptr);
1494     sp_repr_css_set_property(css, "font-variant", nullptr);
1495     sp_repr_css_set_property(css, "font-weight", nullptr);
1496     sp_repr_css_set_property(css, "font-stretch", nullptr);
1497     sp_repr_css_set_property(css, "font-family", nullptr);
1498     sp_repr_css_set_property(css, "text-indent", nullptr);
1499     sp_repr_css_set_property(css, "text-align", nullptr);
1500     sp_repr_css_set_property(css, "line-height", nullptr);
1501     sp_repr_css_set_property(css, "letter-spacing", nullptr);
1502     sp_repr_css_set_property(css, "word-spacing", nullptr);
1503     sp_repr_css_set_property(css, "text-transform", nullptr);
1504     sp_repr_css_set_property(css, "direction", nullptr);
1505     sp_repr_css_set_property(css, "writing-mode", nullptr);
1506     sp_repr_css_set_property(css, "text-orientation", nullptr);
1507     sp_repr_css_set_property(css, "text-anchor", nullptr);
1508     sp_repr_css_set_property(css, "white-space", nullptr);
1509     sp_repr_css_set_property(css, "shape-inside", nullptr);
1510     sp_repr_css_set_property(css, "shape-subtract", nullptr);
1511     sp_repr_css_set_property(css, "shape-padding", nullptr);
1512     sp_repr_css_set_property(css, "shape-margin", nullptr);
1513     sp_repr_css_set_property(css, "inline-size", nullptr);
1514     sp_repr_css_set_property(css, "kerning", nullptr); // not implemented yet
1515     sp_repr_css_set_property(css, "dominant-baseline", nullptr); // not implemented yet
1516     sp_repr_css_set_property(css, "alignment-baseline", nullptr); // not implemented yet
1517     sp_repr_css_set_property(css, "baseline-shift", nullptr);
1518 
1519     sp_repr_css_set_property(css, "text-decoration", nullptr);
1520     sp_repr_css_set_property(css, "text-decoration-line", nullptr);
1521     sp_repr_css_set_property(css, "text-decoration-color", nullptr);
1522     sp_repr_css_set_property(css, "text-decoration-style", nullptr);
1523 
1524     sp_repr_css_set_property(css, "font-variant-ligatures", nullptr);
1525     sp_repr_css_set_property(css, "font-variant-position", nullptr);
1526     sp_repr_css_set_property(css, "font-variant-caps", nullptr);
1527     sp_repr_css_set_property(css, "font-variant-numeric", nullptr);
1528     sp_repr_css_set_property(css, "font-variant-alternates", nullptr);
1529     sp_repr_css_set_property(css, "font-variant-east-asian", nullptr);
1530     sp_repr_css_set_property(css, "font-feature-settings", nullptr);
1531 
1532     return css;
1533 }
1534 
1535 // ui/dialog/inkscape-preferences.cpp
1536 /**
1537  * Unset properties that should not be set for default tool style.
1538  * This list needs to be reviewed.
1539  */
1540 SPCSSAttr *
sp_css_attr_unset_blacklist(SPCSSAttr * css)1541 sp_css_attr_unset_blacklist(SPCSSAttr *css)
1542 {
1543     sp_repr_css_set_property(css, "color",               nullptr);
1544     sp_repr_css_set_property(css, "clip-rule",           nullptr);
1545     sp_repr_css_set_property(css, "d",                   nullptr);
1546     sp_repr_css_set_property(css, "display",             nullptr);
1547     sp_repr_css_set_property(css, "overflow",            nullptr);
1548     sp_repr_css_set_property(css, "visibility",          nullptr);
1549     sp_repr_css_set_property(css, "isolation",           nullptr);
1550     sp_repr_css_set_property(css, "mix-blend-mode",      nullptr);
1551     sp_repr_css_set_property(css, "color-interpolation", nullptr);
1552     sp_repr_css_set_property(css, "color-interpolation-filters", nullptr);
1553     sp_repr_css_set_property(css, "solid-color",         nullptr);
1554     sp_repr_css_set_property(css, "solid-opacity",       nullptr);
1555     sp_repr_css_set_property(css, "fill-rule",           nullptr);
1556     sp_repr_css_set_property(css, "color-rendering",     nullptr);
1557     sp_repr_css_set_property(css, "image-rendering",     nullptr);
1558     sp_repr_css_set_property(css, "shape-rendering",     nullptr);
1559     sp_repr_css_set_property(css, "text-rendering",      nullptr);
1560     sp_repr_css_set_property(css, "enable-background",   nullptr);
1561 
1562     return css;
1563 }
1564 
1565 // Called in style.cpp
1566 static bool
is_url(char const * p)1567 is_url(char const *p)
1568 {
1569     if (p == nullptr)
1570         return false;
1571 /** \todo
1572  * FIXME: I'm not sure if this applies to SVG as well, but CSS2 says any URIs
1573  * in property values must start with 'url('.
1574  */
1575     return (g_ascii_strncasecmp(p, "url(", 4) == 0);
1576 }
1577 
1578 // Called in: ui/dialog/inkscape-preferences.cpp, ui/tools/tweek-tool.cpp
1579 /**
1580  * Unset any properties that contain URI values.
1581  *
1582  * Used for storing style that will be reused across documents when carrying
1583  * the referenced defs is impractical.
1584  */
1585 SPCSSAttr *
sp_css_attr_unset_uris(SPCSSAttr * css)1586 sp_css_attr_unset_uris(SPCSSAttr *css)
1587 {
1588 // All properties that may hold <uri> or <paint> according to SVG 1.1
1589     if (is_url(sp_repr_css_property(css, "clip-path", nullptr))) sp_repr_css_set_property(css, "clip-path", nullptr);
1590     if (is_url(sp_repr_css_property(css, "color-profile", nullptr))) sp_repr_css_set_property(css, "color-profile", nullptr);
1591     if (is_url(sp_repr_css_property(css, "cursor", nullptr))) sp_repr_css_set_property(css, "cursor", nullptr);
1592     if (is_url(sp_repr_css_property(css, "filter", nullptr))) sp_repr_css_set_property(css, "filter", nullptr);
1593     if (is_url(sp_repr_css_property(css, "marker", nullptr))) sp_repr_css_set_property(css, "marker", nullptr);
1594     if (is_url(sp_repr_css_property(css, "marker-start", nullptr))) sp_repr_css_set_property(css, "marker-start", nullptr);
1595     if (is_url(sp_repr_css_property(css, "marker-mid", nullptr))) sp_repr_css_set_property(css, "marker-mid", nullptr);
1596     if (is_url(sp_repr_css_property(css, "marker-end", nullptr))) sp_repr_css_set_property(css, "marker-end", nullptr);
1597     if (is_url(sp_repr_css_property(css, "mask", nullptr))) sp_repr_css_set_property(css, "mask", nullptr);
1598     if (is_url(sp_repr_css_property(css, "fill", nullptr))) sp_repr_css_set_property(css, "fill", nullptr);
1599     if (is_url(sp_repr_css_property(css, "stroke", nullptr))) sp_repr_css_set_property(css, "stroke", nullptr);
1600 
1601     return css;
1602 }
1603 
1604 // Called in style.cpp
1605 /**
1606  * Scale a single-value property.
1607  */
1608 static void
sp_css_attr_scale_property_single(SPCSSAttr * css,gchar const * property,double ex,bool only_with_units=false)1609 sp_css_attr_scale_property_single(SPCSSAttr *css, gchar const *property,
1610                                   double ex, bool only_with_units = false)
1611 {
1612     gchar const *w = sp_repr_css_property(css, property, nullptr);
1613     if (w) {
1614         gchar *units = nullptr;
1615         double wd = g_ascii_strtod(w, &units) * ex;
1616         if (w == units) {// nothing converted, non-numeric value
1617             return;
1618         }
1619         if (only_with_units && (units == nullptr || *units == '\0' || *units == '%' || *units == 'e')) {
1620             // only_with_units, but no units found, so do nothing.
1621             // 'e' matches 'em' or 'ex'
1622             return;
1623         }
1624         Inkscape::CSSOStringStream os;
1625         os << wd << units; // reattach units
1626         sp_repr_css_set_property(css, property, os.str().c_str());
1627     }
1628 }
1629 
1630 // Called in style.cpp for stroke-dasharray
1631 /**
1632  * Scale a list-of-values property.
1633  */
1634 static void
sp_css_attr_scale_property_list(SPCSSAttr * css,gchar const * property,double ex)1635 sp_css_attr_scale_property_list(SPCSSAttr *css, gchar const *property, double ex)
1636 {
1637     gchar const *string = sp_repr_css_property(css, property, nullptr);
1638     if (string) {
1639         Inkscape::CSSOStringStream os;
1640         gchar **a = g_strsplit(string, ",", 10000);
1641         bool first = true;
1642         for (gchar **i = a; i != nullptr; i++) {
1643             gchar *w = *i;
1644             if (w == nullptr)
1645                 break;
1646             gchar *units = nullptr;
1647             double wd = g_ascii_strtod(w, &units) * ex;
1648             if (w == units) {// nothing converted, non-numeric value ("none" or "inherit"); do nothing
1649                 g_strfreev(a);
1650                 return;
1651             }
1652             if (!first) {
1653                 os << ",";
1654             }
1655             os << wd << units; // reattach units
1656             first = false;
1657         }
1658         sp_repr_css_set_property(css, property, os.str().c_str());
1659         g_strfreev(a);
1660     }
1661 }
1662 
1663 // Called in: text-editing.cpp,
1664 /**
1665  * Scale any properties that may hold <length> by ex.
1666  */
1667 SPCSSAttr *
sp_css_attr_scale(SPCSSAttr * css,double ex)1668 sp_css_attr_scale(SPCSSAttr *css, double ex)
1669 {
1670     sp_css_attr_scale_property_single(css, "baseline-shift", ex);
1671     sp_css_attr_scale_property_single(css, "stroke-width", ex);
1672     sp_css_attr_scale_property_list  (css, "stroke-dasharray", ex);
1673     sp_css_attr_scale_property_single(css, "stroke-dashoffset", ex);
1674     sp_css_attr_scale_property_single(css, "font-size", ex, true);
1675     sp_css_attr_scale_property_single(css, "kerning", ex);
1676     sp_css_attr_scale_property_single(css, "letter-spacing", ex);
1677     sp_css_attr_scale_property_single(css, "word-spacing", ex);
1678     sp_css_attr_scale_property_single(css, "line-height", ex, true);
1679 
1680     return css;
1681 }
1682 
1683 
1684 /**
1685  * Quote and/or escape string for writing to CSS, changing strings in place.
1686  * See: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
1687  */
1688 void
css_quote(Glib::ustring & val)1689 css_quote(Glib::ustring &val)
1690 {
1691     Glib::ustring out;
1692     bool quote = false;
1693 
1694     // Can't wait for C++11!
1695     for( Glib::ustring::iterator it = val.begin(); it != val.end(); ++it) {
1696         if(g_ascii_isalnum(*it) || *it=='-' || *it=='_' || *it > 0xA0) {
1697             out += *it;
1698         } else if (*it == '\'') {
1699             // Single quotes require escaping and quotes.
1700             out += '\\';
1701             out += *it;
1702             quote = true;
1703         } else {
1704             // Quote everything else including spaces.
1705             // (CSS Fonts Level 3 recommends quoting with spaces.)
1706             out += *it;
1707             quote = true;
1708         }
1709         if( it == val.begin() && !g_ascii_isalpha(*it) ) {
1710             // A non-ASCII/non-alpha initial value on any identifier needs quotes.
1711             // (Actually it's a bit more complicated but as it never hurts to quote...)
1712             quote = true;
1713         }
1714     }
1715     if( quote ) {
1716         out.insert( out.begin(), '\'' );
1717         out += '\'';
1718     }
1719     val = out;
1720 }
1721 
1722 
1723 /**
1724  * Quote font names in font-family lists, changing string in place.
1725  * We use unquoted names internally but some need to be quoted in CSS.
1726  */
1727 void
css_font_family_quote(Glib::ustring & val)1728 css_font_family_quote(Glib::ustring &val)
1729 {
1730     std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("\\s*,\\s*", val );
1731 
1732     val.erase();
1733     for(auto & token : tokens) {
1734         css_quote( token );
1735         val += token + ", ";
1736     }
1737     if( val.size() > 1 )
1738         val.erase( val.size() - 2 ); // Remove trailing ", "
1739 }
1740 
1741 
1742 // Called in style-internal.cpp, xml/repr-css.cpp
1743 /**
1744  * Remove paired single and double quotes from a string, changing string in place.
1745  */
1746 void
css_unquote(Glib::ustring & val)1747 css_unquote(Glib::ustring &val)
1748 {
1749   if( val.size() > 1 &&
1750       ( (val[0] == '"'  && val[val.size()-1] == '"'  ) ||
1751 	(val[0] == '\'' && val[val.size()-1] == '\'' ) ) ) {
1752 
1753     val.erase( 0, 1 );
1754     val.erase( val.size()-1 );
1755   }
1756 }
1757 
1758 // Called in style-internal.cpp, text-toolbar.cpp
1759 /**
1760  * Remove paired single and double quotes from font names in font-family lists,
1761  * changing string in place.
1762  * We use unquoted family names internally but CSS sometimes uses quoted names.
1763  */
1764 void
css_font_family_unquote(Glib::ustring & val)1765 css_font_family_unquote(Glib::ustring &val)
1766 {
1767     std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("\\s*,\\s*", val );
1768 
1769     val.erase();
1770     for(auto & token : tokens) {
1771         css_unquote( token );
1772         val += token + ", ";
1773     }
1774     if( val.size() > 1 )
1775         val.erase( val.size() - 2 ); // Remove trailing ", "
1776 }
1777 
1778 /*
1779   Local Variables:
1780   mode:c++
1781   c-file-style:"stroustrup"
1782   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1783   indent-tabs-mode:nil
1784   fill-column:99
1785   End:
1786 */
1787 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
1788