summaryrefslogtreecommitdiffstats
path: root/src/style.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:50:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:50:49 +0000
commitc853ffb5b2f75f5a889ed2e3ef89b818a736e87a (patch)
tree7d13a0883bb7936b84d6ecdd7bc332b41ed04bee /src/style.cpp
parentInitial commit. (diff)
downloadinkscape-c853ffb5b2f75f5a889ed2e3ef89b818a736e87a.tar.xz
inkscape-c853ffb5b2f75f5a889ed2e3ef89b818a736e87a.zip
Adding upstream version 1.3+ds.upstream/1.3+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/style.cpp1747
1 files changed, 1747 insertions, 0 deletions
diff --git a/src/style.cpp b/src/style.cpp
new file mode 100644
index 0000000..cdc7bdf
--- /dev/null
+++ b/src/style.cpp
@@ -0,0 +1,1747 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * SVG stylesheets implementation.
+ */
+/* Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Peter Moulder <pmoulder@mail.csse.monash.edu.au>
+ * bulia byak <buliabyak@users.sf.net>
+ * Abhishek Sharma
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2001-2002 Lauris Kaplinski
+ * Copyright (C) 2001 Ximian, Inc.
+ * Copyright (C) 2005 Monash University
+ * Copyright (C) 2012 Kris De Gussem
+ * Copyright (C) 2014-2015 Tavmjong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "style.h"
+
+#include <cstring>
+#include <string>
+#include <algorithm>
+#include <unordered_map>
+#include <vector>
+
+#include <glibmm/regex.h>
+
+#include "attributes.h"
+#include "bad-uri-exception.h"
+#include "document.h"
+#include "preferences.h"
+
+#include "3rdparty/libcroco/src/cr-sel-eng.h"
+
+#include "object/sp-paint-server.h"
+#include "object/uri-references.h"
+#include "object/uri.h"
+
+#include "svg/css-ostringstream.h"
+#include "svg/svg.h"
+
+#include "util/units.h"
+
+#include "xml/croco-node-iface.h"
+#include "xml/simple-document.h"
+
+#if !GLIB_CHECK_VERSION(2, 64, 0)
+#define g_warning_once g_warning
+#endif
+
+using Inkscape::CSSOStringStream;
+
+#define BMAX 8192
+
+struct SPStyleEnum;
+
+// static int _count = 0;
+
+/*#########################
+## FORWARD DECLARATIONS
+#########################*/
+void sp_style_filter_ref_changed(SPObject *old_ref, SPObject *ref, SPStyle *style);
+void sp_style_fill_paint_server_ref_changed(SPObject *old_ref, SPObject *ref, SPStyle *style);
+void sp_style_stroke_paint_server_ref_changed(SPObject *old_ref, SPObject *ref, SPStyle *style);
+
+static void sp_style_object_release(SPObject *object, SPStyle *style);
+static CRSelEng *sp_repr_sel_eng();
+
+/**
+ * Helper class for SPStyle property member lookup by SPAttr or
+ * by name, and for iterating over ordered members.
+ */
+class SPStylePropHelper {
+ SPStylePropHelper() {
+#define REGISTER_PROPERTY(id, member, name) \
+ g_assert(decltype(SPStyle::member)::static_id() == id); \
+ _register(reinterpret_cast<SPIBasePtr>(&SPStyle::member), id) /* name unused */
+
+ // SVG 2: Attributes promoted to properties
+ REGISTER_PROPERTY(SPAttr::D, d, "d");
+
+ // 'color' must be before 'fill', 'stroke', 'text-decoration-color', ...
+ REGISTER_PROPERTY(SPAttr::COLOR, color, "color");
+
+ // 'font-size'/'font' must be before properties that need to know em, ex size (SPILength,
+ // SPILengthOrNormal)
+ REGISTER_PROPERTY(SPAttr::FONT_STYLE, font_style, "font-style");
+ REGISTER_PROPERTY(SPAttr::FONT_VARIANT, font_variant, "font-variant");
+ REGISTER_PROPERTY(SPAttr::FONT_WEIGHT, font_weight, "font-weight");
+ REGISTER_PROPERTY(SPAttr::FONT_STRETCH, font_stretch, "font-stretch");
+ REGISTER_PROPERTY(SPAttr::FONT_SIZE, font_size, "font-size");
+ REGISTER_PROPERTY(SPAttr::LINE_HEIGHT, line_height, "line-height");
+ REGISTER_PROPERTY(SPAttr::FONT_FAMILY, font_family, "font-family");
+ REGISTER_PROPERTY(SPAttr::FONT, font, "font");
+ REGISTER_PROPERTY(SPAttr::INKSCAPE_FONT_SPEC, font_specification, "-inkscape-font-specification");
+
+ // Font variants
+ REGISTER_PROPERTY(SPAttr::FONT_VARIANT_LIGATURES, font_variant_ligatures, "font-variant-ligatures");
+ REGISTER_PROPERTY(SPAttr::FONT_VARIANT_POSITION, font_variant_position, "font-variant-position");
+ REGISTER_PROPERTY(SPAttr::FONT_VARIANT_CAPS, font_variant_caps, "font-variant-caps");
+ REGISTER_PROPERTY(SPAttr::FONT_VARIANT_NUMERIC, font_variant_numeric, "font-variant-numeric");
+ REGISTER_PROPERTY(SPAttr::FONT_VARIANT_ALTERNATES, font_variant_alternates, "font-variant-alternates");
+ REGISTER_PROPERTY(SPAttr::FONT_VARIANT_EAST_ASIAN, font_variant_east_asian, "font-variant-east-asian");
+ REGISTER_PROPERTY(SPAttr::FONT_FEATURE_SETTINGS, font_feature_settings, "font-feature-settings");
+
+ // Variable Fonts
+ REGISTER_PROPERTY(SPAttr::FONT_VARIATION_SETTINGS, font_variation_settings, "font-variation-settings");
+
+ REGISTER_PROPERTY(SPAttr::TEXT_INDENT, text_indent, "text-indent");
+ REGISTER_PROPERTY(SPAttr::TEXT_ALIGN, text_align, "text-align");
+
+ REGISTER_PROPERTY(SPAttr::TEXT_DECORATION, text_decoration, "text-decoration");
+ REGISTER_PROPERTY(SPAttr::TEXT_DECORATION_LINE, text_decoration_line, "text-decoration-line");
+ REGISTER_PROPERTY(SPAttr::TEXT_DECORATION_STYLE, text_decoration_style, "text-decoration-style");
+ REGISTER_PROPERTY(SPAttr::TEXT_DECORATION_COLOR, text_decoration_color, "text-decoration-color");
+ REGISTER_PROPERTY(SPAttr::TEXT_DECORATION_FILL, text_decoration_fill, "text-decoration-fill");
+ REGISTER_PROPERTY(SPAttr::TEXT_DECORATION_STROKE, text_decoration_stroke, "text-decoration-stroke");
+
+ REGISTER_PROPERTY(SPAttr::LETTER_SPACING, letter_spacing, "letter-spacing");
+ REGISTER_PROPERTY(SPAttr::WORD_SPACING, word_spacing, "word-spacing");
+ REGISTER_PROPERTY(SPAttr::TEXT_TRANSFORM, text_transform, "text-transform");
+
+ REGISTER_PROPERTY(SPAttr::WRITING_MODE, writing_mode, "writing-mode");
+ REGISTER_PROPERTY(SPAttr::DIRECTION, direction, "direction");
+ REGISTER_PROPERTY(SPAttr::TEXT_ORIENTATION, text_orientation, "text-orientation");
+ REGISTER_PROPERTY(SPAttr::DOMINANT_BASELINE, dominant_baseline, "dominant-baseline");
+ REGISTER_PROPERTY(SPAttr::BASELINE_SHIFT, baseline_shift, "baseline-shift");
+ REGISTER_PROPERTY(SPAttr::TEXT_ANCHOR, text_anchor, "text-anchor");
+ REGISTER_PROPERTY(SPAttr::WHITE_SPACE, white_space, "white-space");
+
+ REGISTER_PROPERTY(SPAttr::SHAPE_INSIDE, shape_inside, "shape-inside");
+ REGISTER_PROPERTY(SPAttr::SHAPE_SUBTRACT, shape_subtract, "shape-subtract");
+ REGISTER_PROPERTY(SPAttr::SHAPE_PADDING, shape_padding, "shape-padding");
+ REGISTER_PROPERTY(SPAttr::SHAPE_MARGIN, shape_margin, "shape-margin");
+ REGISTER_PROPERTY(SPAttr::INLINE_SIZE, inline_size, "inline-size");
+
+ REGISTER_PROPERTY(SPAttr::CLIP_RULE, clip_rule, "clip-rule");
+ REGISTER_PROPERTY(SPAttr::DISPLAY, display, "display");
+ REGISTER_PROPERTY(SPAttr::OVERFLOW_, overflow, "overflow");
+ REGISTER_PROPERTY(SPAttr::VISIBILITY, visibility, "visibility");
+ REGISTER_PROPERTY(SPAttr::OPACITY, opacity, "opacity");
+
+ REGISTER_PROPERTY(SPAttr::ISOLATION, isolation, "isolation");
+ REGISTER_PROPERTY(SPAttr::MIX_BLEND_MODE, mix_blend_mode, "mix-blend-mode");
+
+ REGISTER_PROPERTY(SPAttr::COLOR_INTERPOLATION, color_interpolation, "color-interpolation");
+ REGISTER_PROPERTY(SPAttr::COLOR_INTERPOLATION_FILTERS, color_interpolation_filters, "color-interpolation-filters");
+
+ REGISTER_PROPERTY(SPAttr::SOLID_COLOR, solid_color, "solid-color");
+ REGISTER_PROPERTY(SPAttr::SOLID_OPACITY, solid_opacity, "solid-opacity");
+
+ REGISTER_PROPERTY(SPAttr::VECTOR_EFFECT, vector_effect, "vector-effect");
+
+ REGISTER_PROPERTY(SPAttr::FILL, fill, "fill");
+ REGISTER_PROPERTY(SPAttr::FILL_OPACITY, fill_opacity, "fill-opacity");
+ REGISTER_PROPERTY(SPAttr::FILL_RULE, fill_rule, "fill-rule");
+
+ REGISTER_PROPERTY(SPAttr::STROKE, stroke, "stroke");
+ REGISTER_PROPERTY(SPAttr::STROKE_WIDTH, stroke_width, "stroke-width");
+ REGISTER_PROPERTY(SPAttr::STROKE_LINECAP, stroke_linecap, "stroke-linecap");
+ REGISTER_PROPERTY(SPAttr::STROKE_LINEJOIN, stroke_linejoin, "stroke-linejoin");
+ REGISTER_PROPERTY(SPAttr::STROKE_MITERLIMIT, stroke_miterlimit, "stroke-miterlimit");
+ REGISTER_PROPERTY(SPAttr::STROKE_DASHARRAY, stroke_dasharray, "stroke-dasharray");
+ REGISTER_PROPERTY(SPAttr::STROKE_DASHOFFSET, stroke_dashoffset, "stroke-dashoffset");
+ REGISTER_PROPERTY(SPAttr::STROKE_OPACITY, stroke_opacity, "stroke-opacity");
+ REGISTER_PROPERTY(SPAttr::STROKE_EXTENSIONS, stroke_extensions, "-inkscape-stroke");
+
+ REGISTER_PROPERTY(SPAttr::MARKER, marker, "marker");
+ REGISTER_PROPERTY(SPAttr::MARKER_START, marker_start, "marker-start");
+ REGISTER_PROPERTY(SPAttr::MARKER_MID, marker_mid, "marker-mid");
+ REGISTER_PROPERTY(SPAttr::MARKER_END, marker_end, "marker-end");
+
+ REGISTER_PROPERTY(SPAttr::PAINT_ORDER, paint_order, "paint-order");
+
+ REGISTER_PROPERTY(SPAttr::FILTER, filter, "filter");
+
+ REGISTER_PROPERTY(SPAttr::COLOR_RENDERING, color_rendering, "color-rendering");
+ REGISTER_PROPERTY(SPAttr::IMAGE_RENDERING, image_rendering, "image-rendering");
+ REGISTER_PROPERTY(SPAttr::SHAPE_RENDERING, shape_rendering, "shape-rendering");
+ REGISTER_PROPERTY(SPAttr::TEXT_RENDERING, text_rendering, "text-rendering");
+
+ REGISTER_PROPERTY(SPAttr::ENABLE_BACKGROUND, enable_background, "enable-background");
+
+ REGISTER_PROPERTY(SPAttr::STOP_COLOR, stop_color, "stop-color");
+ REGISTER_PROPERTY(SPAttr::STOP_OPACITY, stop_opacity, "stop-opacity");
+ }
+
+ // this is a singleton, copy not allowed
+ SPStylePropHelper(SPStylePropHelper const&) = delete;
+public:
+
+ /**
+ * Singleton instance
+ */
+ static SPStylePropHelper &instance() {
+ static SPStylePropHelper _instance;
+ return _instance;
+ }
+
+ /**
+ * Get property pointer by enum
+ */
+ SPIBase *get(SPStyle *style, SPAttr id) {
+ auto it = m_id_map.find(id);
+ if (it != m_id_map.end()) {
+ return _get(style, it->second);
+ }
+ return nullptr;
+ }
+
+ /**
+ * Get property pointer by name
+ */
+ SPIBase *get(SPStyle *style, const std::string &name) {
+ return get(style, sp_attribute_lookup(name.c_str()));
+ }
+
+ /**
+ * Get a vector of property pointers
+ * \todo provide iterator instead
+ */
+ std::vector<SPIBase *> get_vector(SPStyle *style) {
+ std::vector<SPIBase *> v;
+ v.reserve(m_vector.size());
+ for (auto ptr : m_vector) {
+ v.push_back(_get(style, ptr));
+ }
+ return v;
+ }
+
+private:
+ SPIBase *_get(SPStyle *style, SPIBasePtr ptr) { return &(style->*ptr); }
+
+ void _register(SPIBasePtr ptr, SPAttr id) {
+ m_vector.push_back(ptr);
+
+ if (id != SPAttr::INVALID) {
+ m_id_map[id] = ptr;
+ }
+ }
+
+ std::unordered_map<SPAttr, SPIBasePtr> m_id_map;
+ std::vector<SPIBasePtr> m_vector;
+};
+
+auto &_prop_helper = SPStylePropHelper::instance();
+
+// C++11 allows one constructor to call another... might be useful. The original C code
+// had separate calls to create SPStyle, one with only SPDocument and the other with only
+// SPObject as parameters.
+SPStyle::SPStyle(SPDocument *document_in, SPObject *object_in) :
+
+ // Unimplemented SVG 1.1: alignment-baseline, clip, clip-path, color-profile, cursor,
+ // dominant-baseline, flood-color, flood-opacity, font-size-adjust,
+ // glyph-orientation-horizontal, glyph-orientation-vertical, kerning, lighting-color,
+ // pointer-events, unicode-bidi
+
+ // 'font', 'font-size', and 'font-family' must come first as other properties depend on them
+ // for calculated values (through 'em' and 'ex'). ('ex' is currently not read.)
+ // The following properties can depend on 'em' and 'ex':
+ // baseline-shift, kerning, letter-spacing, stroke-dash-offset, stroke-width, word-spacing,
+ // Non-SVG 1.1: text-indent, line-spacing
+
+ // Hidden in SPIFontStyle: (to be refactored)
+ // font-family
+ // font-specification
+
+
+ // SVG 2 attributes promoted to properties. (When geometry properties are added, move after font.)
+ d( false), // SPIString Not inherited!
+
+ // Font related properties and 'font' shorthand
+ font_style( SP_CSS_FONT_STYLE_NORMAL),
+ font_variant( SP_CSS_FONT_VARIANT_NORMAL),
+ font_weight( SP_CSS_FONT_WEIGHT_NORMAL),
+ font_stretch( SP_CSS_FONT_STRETCH_NORMAL),
+ font_size(),
+ line_height( 1.25 ), // SPILengthOrNormal
+ font_family( ), // SPIString w/default
+ font(), // SPIFont
+ font_specification( ), // SPIString
+
+ // Font variants (Features)
+ font_variant_ligatures( ),
+ font_variant_position( SP_CSS_FONT_VARIANT_POSITION_NORMAL),
+ font_variant_caps( SP_CSS_FONT_VARIANT_CAPS_NORMAL),
+ font_variant_numeric( ),
+ font_variant_alternates(SP_CSS_FONT_VARIANT_ALTERNATES_NORMAL),
+ font_variant_east_asian(),
+ font_feature_settings( ),
+
+ // Variable Fonts
+ font_variation_settings(), // SPIFontVariationSettings
+
+ // Text related properties
+ text_indent( ), // SPILength
+ text_align( SP_CSS_TEXT_ALIGN_START),
+
+ letter_spacing( ), // SPILengthOrNormal
+ word_spacing( ), // SPILengthOrNormal
+ text_transform( SP_CSS_TEXT_TRANSFORM_NONE),
+
+ direction( SP_CSS_DIRECTION_LTR),
+ writing_mode( SP_CSS_WRITING_MODE_LR_TB),
+ text_orientation( SP_CSS_TEXT_ORIENTATION_MIXED),
+ dominant_baseline( SP_CSS_BASELINE_AUTO),
+ baseline_shift(),
+ text_anchor( SP_CSS_TEXT_ANCHOR_START),
+ white_space( SP_CSS_WHITE_SPACE_NORMAL),
+
+ // SVG 2 Text Wrapping
+ shape_inside( ), // SPIString
+ shape_subtract( ), // SPIString
+ shape_padding( ), // SPILength for now
+ shape_margin( ), // SPILength for now
+ inline_size( ), // SPILength for now
+
+ text_decoration(),
+ text_decoration_line(),
+ text_decoration_style(),
+ text_decoration_color( ), // SPIColor
+ text_decoration_fill( ), // SPIPaint
+ text_decoration_stroke( ), // SPIPaint
+
+ // General visual properties
+ clip_rule( SP_WIND_RULE_NONZERO),
+ display( SP_CSS_DISPLAY_INLINE, false),
+ overflow( SP_CSS_OVERFLOW_VISIBLE, false),
+ visibility( SP_CSS_VISIBILITY_VISIBLE),
+ opacity( false),
+
+ isolation( SP_CSS_ISOLATION_AUTO),
+ mix_blend_mode( SP_CSS_BLEND_NORMAL),
+
+ paint_order(), // SPIPaintOrder
+
+ // Color properties
+ color( ), // SPIColor
+ color_interpolation( SP_CSS_COLOR_INTERPOLATION_SRGB),
+ color_interpolation_filters(SP_CSS_COLOR_INTERPOLATION_LINEARRGB),
+
+ // Solid color properties
+ solid_color( ), // SPIColor
+ solid_opacity( ),
+
+ // Vector effects
+ vector_effect(),
+
+ // Fill properties
+ fill( ), // SPIPaint
+ fill_opacity( ),
+ fill_rule( SP_WIND_RULE_NONZERO),
+
+ // Stroke properties
+ stroke( ), // SPIPaint
+ stroke_width( 1.0), // SPILength
+ stroke_linecap( SP_STROKE_LINECAP_BUTT),
+ stroke_linejoin( SP_STROKE_LINEJOIN_MITER),
+ stroke_miterlimit( 4), // SPIFloat (only use of float!)
+ stroke_dasharray(), // SPIDashArray
+ stroke_dashoffset( ), // SPILength for now
+
+ stroke_opacity( ),
+
+ stroke_extensions( ),
+
+ marker( ), // SPIString
+ marker_start( ), // SPIString
+ marker_mid( ), // SPIString
+ marker_end( ), // SPIString
+
+ // Filter properties
+ filter(),
+ enable_background( SP_CSS_BACKGROUND_ACCUMULATE, false),
+
+ // Rendering hint properties
+ color_rendering( SP_CSS_COLOR_RENDERING_AUTO),
+ image_rendering( SP_CSS_IMAGE_RENDERING_AUTO),
+ shape_rendering( SP_CSS_SHAPE_RENDERING_AUTO),
+ text_rendering( SP_CSS_TEXT_RENDERING_AUTO),
+
+ // Stop color and opacity
+ stop_color( false), // SPIColor, does not inherit
+ stop_opacity( false) // Does not inherit
+{
+ // std::cout << "SPStyle::SPStyle( SPDocument ): Entrance" << std::endl;
+ // std::cout << " Document: " << (document_in?"present":"null") << std::endl;
+ // std::cout << " Object: "
+ // << (object_in?(object_in->getId()?object_in->getId():"id null"):"object null") << std::endl;
+
+ // static bool first = true;
+ // if( first ) {
+ // std::cout << "Size of SPStyle: " << sizeof(SPStyle) << std::endl;
+ // std::cout << " SPIBase: " << sizeof(SPIBase) << std::endl;
+ // std::cout << " SPIFloat: " << sizeof(SPIFloat) << std::endl;
+ // std::cout << " SPIScale24: " << sizeof(SPIScale24) << std::endl;
+ // std::cout << " SPILength: " << sizeof(SPILength) << std::endl;
+ // std::cout << " SPILengthOrNormal: " << sizeof(SPILengthOrNormal) << std::endl;
+ // std::cout << " SPIColor: " << sizeof(SPIColor) << std::endl;
+ // std::cout << " SPIPaint: " << sizeof(SPIPaint) << std::endl;
+ // std::cout << " SPITextDecorationLine" << sizeof(SPITextDecorationLine) << std::endl;
+ // std::cout << " Glib::ustring:" << sizeof(Glib::ustring) << std::endl;
+ // std::cout << " SPColor: " << sizeof(SPColor) << std::endl;
+ // first = false;
+ // }
+
+ // ++_count; // Poor man's memory leak detector
+ // std::cout << "Style count: " << _count << std::endl;
+
+ cloned = false;
+
+ object = object_in;
+ if( object ) {
+ document = object->document;
+ release_connection =
+ object->connectRelease(sigc::bind<1>(sigc::ptr_fun(&sp_style_object_release), this));
+
+ cloned = object->cloned;
+
+ } else {
+ document = document_in;
+ }
+
+ // 'font' shorthand requires access to included properties.
+ font.setStylePointer( this );
+
+ // Properties that depend on 'font-size' for calculating lengths.
+ baseline_shift.setStylePointer( this );
+ text_indent.setStylePointer( this );
+ line_height.setStylePointer( this );
+ letter_spacing.setStylePointer( this );
+ word_spacing.setStylePointer( this );
+ stroke_width.setStylePointer( this );
+ stroke_dashoffset.setStylePointer( this );
+ shape_padding.setStylePointer( this );
+ shape_margin.setStylePointer( this );
+ inline_size.setStylePointer( this );
+
+ // Properties that depend on 'color'
+ text_decoration_color.setStylePointer(this);
+ fill.setStylePointer(this);
+ stroke.setStylePointer(this);
+ color.setStylePointer(this);
+ stop_color.setStylePointer(this);
+ solid_color.setStylePointer(this);
+
+ // 'text_decoration' shorthand requires access to included properties.
+ text_decoration.setStylePointer( this );
+
+ // SPIPaint, SPIFilter needs access to 'this' (SPStyle)
+ // for setting up signals... 'fill', 'stroke' already done
+ filter.setStylePointer( this );
+ shape_inside.setStylePointer( this );
+ shape_subtract.setStylePointer( this );
+
+ // Used to iterate over markers
+ marker_ptrs[SP_MARKER_LOC] = &marker;
+ marker_ptrs[SP_MARKER_LOC_START] = &marker_start;
+ marker_ptrs[SP_MARKER_LOC_MID] = &marker_mid;
+ marker_ptrs[SP_MARKER_LOC_END] = &marker_end;
+
+
+ // This might be too resource hungary... but for now it possible to loop over properties
+ _properties = _prop_helper.get_vector(this);
+}
+
+SPStyle::~SPStyle() {
+
+ // std::cout << "SPStyle::~SPStyle" << std::endl;
+ // --_count; // Poor man's memory leak detector.
+
+ // Remove connections
+ release_connection.disconnect();
+ fill_ps_changed_connection.disconnect();
+ stroke_ps_changed_connection.disconnect();
+ filter_changed_connection.disconnect();
+
+ // The following should be moved into SPIPaint and SPIFilter
+ if (fill.value.href) {
+ fill_ps_modified_connection.disconnect();
+ }
+
+ if (stroke.value.href) {
+ stroke_ps_modified_connection.disconnect();
+ }
+
+ if (filter.href) {
+ filter_modified_connection.disconnect();
+ }
+
+ // std::cout << "SPStyle::~SPStyle(): Exit\n" << std::endl;
+}
+
+const std::vector<SPIBase *> SPStyle::properties() { return this->_properties; }
+
+void
+SPStyle::clear(SPAttr id) {
+ SPIBase *p = _prop_helper.get(this, id);
+ if (p) {
+ p->clear();
+ } else {
+ g_warning("Unimplemented style property %d", (int)id);
+ }
+}
+
+void
+SPStyle::clear() {
+ for (auto * p : _properties) {
+ p->clear();
+ }
+
+ // Release connection to object, created in constructor.
+ release_connection.disconnect();
+
+ fill_ps_modified_connection.disconnect();
+ stroke_ps_modified_connection.disconnect();
+
+ filter_modified_connection.disconnect();
+ if (filter.href) {
+ delete filter.href;
+ filter.href = nullptr;
+ }
+
+ if (document) {
+ filter.href = new SPFilterReference(document);
+ filter_changed_connection = filter.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_filter_ref_changed), this));
+
+ fill.value.href = std::make_shared<SPPaintServerReference>(document);
+ fill_ps_changed_connection = fill.value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_fill_paint_server_ref_changed), this));
+
+ stroke.value.href = std::make_shared<SPPaintServerReference>(document);
+ stroke_ps_changed_connection = stroke.value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_stroke_paint_server_ref_changed), this));
+ }
+
+ cloned = false;
+
+}
+
+// Matches void sp_style_read(SPStyle *style, SPObject *object, Inkscape::XML::Node *repr)
+void
+SPStyle::read( SPObject *object, Inkscape::XML::Node *repr ) {
+
+ // std::cout << "SPStyle::read( SPObject, Inkscape::XML::Node ): Entrance: "
+ // << (object?(object->getId()?object->getId():"id null"):"object null") << " "
+ // << (repr?(repr->name()?repr->name():"no name"):"repr null")
+ // << std::endl;
+ g_assert(repr != nullptr);
+ g_assert(!object || (object->getRepr() == repr));
+
+ // // Uncomment to verify that we don't need to call clear.
+ // std::cout << " Creating temp style for testing" << std::endl;
+ // SPStyle *temp = new SPStyle();
+ // if( !(*temp == *this ) ) std::cout << "SPStyle::read: Need to clear" << std::endl;
+ // delete temp;
+
+ clear(); // FIXME, If this isn't here, EVERYTHING stops working! Why?
+
+ if (object && object->cloned) {
+ cloned = true;
+ }
+
+ /* 1. Style attribute */
+ // std::cout << " MERGING STYLE ATTRIBUTE" << std::endl;
+ gchar const *val = repr->attribute("style");
+ if( val != nullptr && *val ) {
+ _mergeString( val );
+ }
+
+ /* 2 Style sheet */
+ if (object) {
+ _mergeObjectStylesheet( object );
+ }
+
+ /* 3 Presentation attributes */
+ for (auto * p : _properties) {
+ // Shorthands are not allowed as presentation properties. Note: text-decoration and
+ // font-variant are converted to shorthands in CSS 3 but can still be read as a
+ // non-shorthand for compatibility with older renders, so they should not be in this list.
+ if (p->id() != SPAttr::FONT && p->id() != SPAttr::MARKER) {
+ p->readAttribute( repr );
+ }
+ }
+
+ /* 4 Cascade from parent */
+ if( object ) {
+ if( object->parent ) {
+ cascade( object->parent->style );
+ }
+ } else if( repr->parent() ) { // When does this happen?
+ // std::cout << "SPStyle::read(): reading via repr->parent()" << std::endl;
+ SPStyle *parent = new SPStyle();
+ parent->read( nullptr, repr->parent() );
+ cascade( parent );
+ delete parent;
+ }
+}
+
+/**
+ * Read style properties from object's repr.
+ *
+ * 1. Reset existing object style
+ * 2. Load current effective object style
+ * 3. Load i attributes from immediate parent (which has to be up-to-date)
+ */
+void
+SPStyle::readFromObject( SPObject *object ) {
+
+ // std::cout << "SPStyle::readFromObject: "<< (object->getId()?object->getId():"null") << std::endl;
+
+ g_return_if_fail(object != nullptr);
+
+ Inkscape::XML::Node *repr = object->getRepr();
+ g_return_if_fail(repr != nullptr);
+
+ read( object, repr );
+}
+
+/**
+ * Read style properties from preferences.
+ * @param path Preferences directory from which the style should be read
+ */
+void
+SPStyle::readFromPrefs(Glib::ustring const &path) {
+
+ g_return_if_fail(!path.empty());
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ // not optimal: we reconstruct the node based on the prefs, then pass it to
+ // sp_style_read for actual processing.
+ Inkscape::XML::SimpleDocument *tempdoc = new Inkscape::XML::SimpleDocument;
+ Inkscape::XML::Node *tempnode = tempdoc->createElement("prefs");
+
+ std::vector<Inkscape::Preferences::Entry> attrs = prefs->getAllEntries(path);
+ for (auto & attr : attrs) {
+ tempnode->setAttribute(attr.getEntryName(), attr.getString());
+ }
+
+ read( nullptr, tempnode );
+
+ Inkscape::GC::release(tempnode);
+ Inkscape::GC::release(tempdoc);
+ delete tempdoc;
+}
+
+// Matches sp_style_merge_property(SPStyle *style, gint id, gchar const *val)
+void
+SPStyle::readIfUnset(SPAttr id, gchar const *val, SPStyleSrc const &source ) {
+
+ // std::cout << "SPStyle::readIfUnset: Entrance: " << sp_attribute_name(id) << ": " << (val?val:"null") << std::endl;
+ // To Do: If it is not too slow, use std::map instead of std::vector inorder to remove switch()
+ // (looking up SPAttr::xxxx already uses a hash).
+ g_return_if_fail(val != nullptr);
+
+ switch (id) {
+ /* SVG */
+ /* Clip/Mask */
+ case SPAttr::CLIP_PATH:
+ /** \todo
+ * This is a workaround. Inkscape only supports 'clip-path' as SVG attribute, not as
+ * style property. By having both CSS and SVG attribute set, editing of clip-path
+ * will fail, since CSS always overwrites SVG attributes.
+ * Fixes Bug #324849
+ */
+ g_warning_once("attribute 'clip-path' given as CSS");
+
+ //XML Tree being directly used here.
+ if (object) {
+ object->setAttribute("clip-path", val);
+ }
+ return;
+ case SPAttr::MASK:
+ /** \todo
+ * See comment for SPAttr::CLIP_PATH
+ */
+ g_warning_once("attribute 'mask' given as CSS");
+
+ //XML Tree being directly used here.
+ if (object) {
+ object->setAttribute("mask", val);
+ }
+ return;
+ case SPAttr::FILTER:
+ if( !filter.inherit ) filter.readIfUnset( val, source );
+ return;
+ case SPAttr::COLOR_INTERPOLATION:
+ // We read it but issue warning
+ color_interpolation.readIfUnset( val, source );
+ if( color_interpolation.value != SP_CSS_COLOR_INTERPOLATION_SRGB ) {
+ g_warning("Inkscape currently only supports color-interpolation = sRGB");
+ }
+ return;
+ }
+
+ auto p = _prop_helper.get(this, id);
+ if (p) {
+ p->readIfUnset(val, source);
+ } else {
+ g_warning("Unimplemented style property %d", (int)id);
+ }
+}
+
+// return if is seted property
+bool SPStyle::isSet(SPAttr id)
+{
+ bool set = false;
+ switch (id) {
+ case SPAttr::CLIP_PATH:
+ return set;
+ case SPAttr::MASK:
+ return set;
+ case SPAttr::FILTER:
+ if (!filter.inherit)
+ set = filter.set;
+ return set;
+ case SPAttr::COLOR_INTERPOLATION:
+ // We read it but issue warning
+ return color_interpolation.set;
+ }
+
+ auto p = _prop_helper.get(this, id);
+ if (p) {
+ return p->set;
+ } else {
+ g_warning("Unimplemented style property %d", (int)id);
+ return set;
+ }
+}
+
+/**
+ * Outputs the style to a CSS string.
+ *
+ * Use with SP_STYLE_FLAG_ALWAYS for copying an object's complete cascaded style to
+ * style_clipboard.
+ *
+ * Use with SP_STYLE_FLAG_IFDIFF and a pointer to the parent class when you need a CSS string for
+ * an object in the document tree.
+ *
+ * \pre flags in {IFSET, ALWAYS, IFDIFF}.
+ * \pre base.
+ * \post ret != NULL.
+ */
+Glib::ustring
+SPStyle::write( guint const flags, SPStyleSrc const style_src_req, SPStyle const *const base ) const {
+
+ // std::cout << "SPStyle::write: flags: " << flags << std::endl;
+
+ // If not excluding this case, we'd end up writing all non-inheritable properties.
+ // Can happen when adding fallback <tspan>s to text like this:
+ // <text style="shape-inside:url(#x)">Hello</text>
+ if (base == this) {
+ assert((flags & SP_STYLE_FLAG_IFDIFF) && !(flags & SP_STYLE_FLAG_ALWAYS));
+ return {};
+ }
+
+ Glib::ustring style_string;
+ for(std::vector<SPIBase*>::size_type i = 0; i != _properties.size(); ++i) {
+ if( base != nullptr ) {
+ style_string += _properties[i]->write( flags, style_src_req, base->_properties[i] );
+ } else {
+ style_string += _properties[i]->write( flags, style_src_req, nullptr );
+ }
+ }
+
+ // Extended properties. Cascading not supported.
+ for (auto const &pair : extended_properties) {
+ // std::cout << "extended property: " << pair.first << " = " << pair.second << std::endl;
+ style_string += pair.first + ":" + pair.second + ";";
+ }
+
+ // Remove trailing ';'
+ if( style_string.size() > 0 ) {
+ style_string.erase( style_string.size() - 1 );
+ }
+ return style_string;
+}
+/**
+ * Get CSS string for set properties, or with SP_STYLE_FLAG_ALWAYS, for all properties.
+ */
+Glib::ustring SPStyle::write(unsigned int flags) const
+{
+ assert(flags & (SP_STYLE_FLAG_IFSET | SP_STYLE_FLAG_ALWAYS));
+ return write(flags, SPStyleSrc::UNSET);
+}
+/**
+ * Get CSS string for set properties from the requested source
+ */
+Glib::ustring SPStyle::write(SPStyleSrc style_src_req) const
+{
+ assert(style_src_req != SPStyleSrc::UNSET);
+ return write(SP_STYLE_FLAG_IFSRC | SP_STYLE_FLAG_IFSET, style_src_req);
+}
+/**
+ * Get CSS string for set properties which are different from the given
+ * base style. If base is NULL, all set flags are considered different.
+ */
+Glib::ustring SPStyle::writeIfDiff(SPStyle const *base) const
+{
+ return write(SP_STYLE_FLAG_IFDIFF, SPStyleSrc::UNSET, base);
+}
+
+// Corresponds to sp_style_merge_from_parent()
+/**
+ * Sets computed values in \a style, which may involve inheriting from (or in some other way
+ * calculating from) corresponding computed values of \a parent.
+ *
+ * References: http://www.w3.org/TR/SVG11/propidx.html shows what properties inherit by default.
+ * http://www.w3.org/TR/SVG11/styling.html#Inheritance gives general rules as to what it means to
+ * inherit a value. http://www.w3.org/TR/REC-CSS2/cascade.html#computed-value is more precise
+ * about what the computed value is (not obvious for lengths).
+ *
+ * \pre \a parent's computed values are already up-to-date.
+ */
+void
+SPStyle::cascade( SPStyle const *const parent ) {
+ // std::cout << "SPStyle::cascade: " << (object->getId()?object->getId():"null") << std::endl;
+ for(std::vector<SPIBase*>::size_type i = 0; i != _properties.size(); ++i) {
+ _properties[i]->cascade( parent->_properties[i] );
+ }
+}
+
+// Corresponds to sp_style_merge_from_dying_parent()
+/**
+ * Combine \a style and \a parent style specifications into a single style specification that
+ * preserves (as much as possible) the effect of the existing \a style being a child of \a parent.
+ *
+ * Called when the parent repr is to be removed (e.g. the parent is a \<use\> element that is being
+ * unlinked), in which case we copy/adapt property values that are explicitly set in \a parent,
+ * trying to retain the same visual appearance once the parent is removed. Interesting cases are
+ * when there is unusual interaction with the parent's value (opacity, display) or when the value
+ * can be specified as relative to the parent computed value (font-size, font-weight etc.).
+ *
+ * Doesn't update computed values of \a style. For correctness, you should subsequently call
+ * sp_style_merge_from_parent against the new parent (presumably \a parent's parent) even if \a
+ * style was previously up-to-date wrt \a parent.
+ *
+ * \pre \a parent's computed values are already up-to-date.
+ * (\a style's computed values needn't be up-to-date.)
+ */
+void
+SPStyle::merge( SPStyle const *const parent ) {
+ // std::cout << "SPStyle::merge" << std::endl;
+ for(std::vector<SPIBase*>::size_type i = 0; i != _properties.size(); ++i) {
+ _properties[i]->merge( parent->_properties[i] );
+ }
+}
+
+/**
+ * Parses a style="..." string and merges it with an existing SPStyle.
+ */
+void
+SPStyle::mergeString( gchar const *const p ) {
+ _mergeString( p );
+}
+
+void
+SPStyle::mergeCSS(SPCSSAttr *css) {
+ Glib::ustring css_str;
+ sp_repr_css_write_string(css, css_str);
+ _mergeString(css_str.c_str());
+}
+
+/**
+ * Append an existing css statement into this style, used in css editing
+ * always appends declarations as STYLE_SHEET properties.
+ */
+void
+SPStyle::mergeStatement( CRStatement *statement ) {
+ if (statement->type != RULESET_STMT) {
+ return;
+ }
+ CRDeclaration *decl_list = nullptr;
+ cr_statement_ruleset_get_declarations (statement, &decl_list);
+ if (decl_list) {
+ _mergeDeclList(decl_list, SPStyleSrc::STYLE_SHEET);
+ }
+}
+
+// Mostly for unit testing
+bool
+SPStyle::operator==(const SPStyle& rhs) {
+
+ // Uncomment for testing
+ // for(std::vector<SPIBase*>::size_type i = 0; i != _properties.size(); ++i) {
+ // if( *_properties[i] != *rhs._properties[i])
+ // std::cout << _properties[i]->name << ": "
+ // << _properties[i]->write(SP_STYLE_FLAG_ALWAYS,NULL) << " "
+ // << rhs._properties[i]->write(SP_STYLE_FLAG_ALWAYS,NULL)
+ // << (*_properties[i] == *rhs._properties[i]) << std::endl;
+ // }
+
+ for(std::vector<SPIBase*>::size_type i = 0; i != _properties.size(); ++i) {
+ if( *_properties[i] != *rhs._properties[i]) return false;
+ }
+ return true;
+}
+
+void
+SPStyle::_mergeString( gchar const *const p ) {
+
+ // std::cout << "SPStyle::_mergeString: " << (p?p:"null") << std::endl;
+ CRDeclaration *const decl_list
+ = cr_declaration_parse_list_from_buf(reinterpret_cast<guchar const *>(p), CR_UTF_8);
+ if (decl_list) {
+ _mergeDeclList( decl_list, SPStyleSrc::STYLE_PROP );
+ cr_declaration_destroy(decl_list);
+ }
+}
+
+void
+SPStyle::_mergeDeclList( CRDeclaration const *const decl_list, SPStyleSrc const &source ) {
+
+ // std::cout << "SPStyle::_mergeDeclList" << std::endl;
+
+ // In reverse order, as later declarations to take precedence over earlier ones.
+ // (Properties are only set if not previously set. See:
+ // Ref: http://www.w3.org/TR/REC-CSS2/cascade.html#cascading-order point 4.)
+ if (decl_list->next) {
+ _mergeDeclList( decl_list->next, source );
+ }
+ _mergeDecl( decl_list, source );
+}
+
+void
+SPStyle::_mergeDecl( CRDeclaration const *const decl, SPStyleSrc const &source ) {
+
+ // std::cout << "SPStyle::_mergeDecl" << std::endl;
+
+ auto prop_idx = sp_attribute_lookup(decl->property->stryng->str);
+ if (prop_idx != SPAttr::INVALID) {
+ /** \todo
+ * effic: Test whether the property is already set before trying to
+ * convert to string. Alternatively, set from CRTerm directly rather
+ * than converting to string.
+ */
+ if (!isSet(prop_idx) || decl->important) {
+ guchar *const str_value_unsigned = cr_term_to_string(decl->value);
+ gchar *const str_value = reinterpret_cast<gchar *>(str_value_unsigned);
+
+ // Add "!important" rule if necessary as this is not handled by cr_term_to_string().
+ gchar const *important = decl->important ? " !important" : "";
+ Inkscape::CSSOStringStream os;
+ os << str_value << important;
+
+ readIfUnset(prop_idx, os.str().c_str(), source);
+ g_free(str_value);
+ }
+ } else {
+ gchar const *key = decl->property->stryng->str;
+ auto value = reinterpret_cast<gchar *>(cr_term_to_string(decl->value));
+
+ if (g_str_has_prefix(key, "--")) {
+ g_warning("Ignoring CSS variable: %s", key);
+ } else if (g_str_has_prefix(key, "-")) {
+ extended_properties[key] = value;
+ } else {
+ g_warning("Ignoring unrecognized CSS property: %s", key);
+ }
+
+ g_free(value);
+ }
+}
+
+void
+SPStyle::_mergeProps( CRPropList *const props ) {
+
+ // std::cout << "SPStyle::_mergeProps" << std::endl;
+
+ // In reverse order, as later declarations to take precedence over earlier ones.
+ if (props) {
+ _mergeProps( cr_prop_list_get_next( props ) );
+ CRDeclaration *decl = nullptr;
+ cr_prop_list_get_decl(props, &decl);
+ _mergeDecl( decl, SPStyleSrc::STYLE_SHEET );
+ }
+}
+
+void
+SPStyle::_mergeObjectStylesheet( SPObject const *const object ) {
+
+ // std::cout << "SPStyle::_mergeObjectStylesheet: " << (object->getId()?object->getId():"null") << std::endl;
+
+ _mergeObjectStylesheet(object, object->document);
+}
+
+void
+SPStyle::_mergeObjectStylesheet( SPObject const *const object, SPDocument *const document ) {
+
+ static CRSelEng *sel_eng = sp_repr_sel_eng();
+
+ if (auto *const parent = document->getParent()) {
+ _mergeObjectStylesheet(object, parent);
+ } else if (auto *const parent = document->get_reference_document()) {
+ _mergeObjectStylesheet(object, parent);
+ }
+
+ CRPropList *props = nullptr;
+
+ //XML Tree being directly used here while it shouldn't be.
+ CRStatus status =
+ cr_sel_eng_get_matched_properties_from_cascade(sel_eng,
+ document->getStyleCascade(),
+ object->getRepr(),
+ &props);
+ g_return_if_fail(status == CR_OK);
+ /// \todo Check what errors can occur, and handle them properly.
+ if (props) {
+ _mergeProps(props);
+ cr_prop_list_destroy(props);
+ }
+}
+
+// Used for input into Pango. Must be computed value!
+std::string
+SPStyle::getFontFeatureString() {
+
+ std::string feature_string;
+ if ( !(font_variant_ligatures.computed & SP_CSS_FONT_VARIANT_LIGATURES_COMMON) )
+ feature_string += "liga 0, clig 0, ";
+ if ( font_variant_ligatures.computed & SP_CSS_FONT_VARIANT_LIGATURES_DISCRETIONARY )
+ feature_string += "dlig, ";
+ if ( font_variant_ligatures.computed & SP_CSS_FONT_VARIANT_LIGATURES_HISTORICAL )
+ feature_string += "hlig, ";
+ if ( !(font_variant_ligatures.computed & SP_CSS_FONT_VARIANT_LIGATURES_CONTEXTUAL) )
+ feature_string += "calt 0, ";
+
+ switch (font_variant_position.computed) {
+ case SP_CSS_FONT_VARIANT_POSITION_SUB:
+ feature_string += "subs, ";
+ break;
+ case SP_CSS_FONT_VARIANT_POSITION_SUPER:
+ feature_string += "sups, ";
+ }
+
+ switch (font_variant_caps.computed) {
+ case SP_CSS_FONT_VARIANT_CAPS_SMALL:
+ feature_string += "smcp, ";
+ break;
+ case SP_CSS_FONT_VARIANT_CAPS_ALL_SMALL:
+ feature_string += "smcp, c2sc, ";
+ break;
+ case SP_CSS_FONT_VARIANT_CAPS_PETITE:
+ feature_string += "pcap, ";
+ break;
+ case SP_CSS_FONT_VARIANT_CAPS_ALL_PETITE:
+ feature_string += "pcap, c2pc, ";
+ break;
+ case SP_CSS_FONT_VARIANT_CAPS_UNICASE:
+ feature_string += "unic, ";
+ break;
+ case SP_CSS_FONT_VARIANT_CAPS_TITLING:
+ feature_string += "titl, ";
+ }
+
+ if ( font_variant_numeric.computed & SP_CSS_FONT_VARIANT_NUMERIC_LINING_NUMS )
+ feature_string += "lnum, ";
+ if ( font_variant_numeric.computed & SP_CSS_FONT_VARIANT_NUMERIC_OLDSTYLE_NUMS )
+ feature_string += "onum, ";
+ if ( font_variant_numeric.computed & SP_CSS_FONT_VARIANT_NUMERIC_PROPORTIONAL_NUMS )
+ feature_string += "pnum, ";
+ if ( font_variant_numeric.computed & SP_CSS_FONT_VARIANT_NUMERIC_TABULAR_NUMS )
+ feature_string += "tnum, ";
+ if ( font_variant_numeric.computed & SP_CSS_FONT_VARIANT_NUMERIC_DIAGONAL_FRACTIONS )
+ feature_string += "frac, ";
+ if ( font_variant_numeric.computed & SP_CSS_FONT_VARIANT_NUMERIC_STACKED_FRACTIONS )
+ feature_string += "afrc, ";
+ if ( font_variant_numeric.computed & SP_CSS_FONT_VARIANT_NUMERIC_ORDINAL )
+ feature_string += "ordn, ";
+ if ( font_variant_numeric.computed & SP_CSS_FONT_VARIANT_NUMERIC_SLASHED_ZERO )
+ feature_string += "zero, ";
+
+ if( font_variant_east_asian.computed & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS78 )
+ feature_string += "jp78, ";
+ if( font_variant_east_asian.computed & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS83 )
+ feature_string += "jp83, ";
+ if( font_variant_east_asian.computed & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS90 )
+ feature_string += "jp90, ";
+ if( font_variant_east_asian.computed & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS04 )
+ feature_string += "jp04, ";
+ if( font_variant_east_asian.computed & SP_CSS_FONT_VARIANT_EAST_ASIAN_SIMPLIFIED )
+ feature_string += "smpl, ";
+ if( font_variant_east_asian.computed & SP_CSS_FONT_VARIANT_EAST_ASIAN_TRADITIONAL )
+ feature_string += "trad, ";
+ if( font_variant_east_asian.computed & SP_CSS_FONT_VARIANT_EAST_ASIAN_FULL_WIDTH )
+ feature_string += "fwid, ";
+ if( font_variant_east_asian.computed & SP_CSS_FONT_VARIANT_EAST_ASIAN_PROPORTIONAL_WIDTH )
+ feature_string += "pwid, ";
+ if( font_variant_east_asian.computed & SP_CSS_FONT_VARIANT_EAST_ASIAN_RUBY )
+ feature_string += "ruby, ";
+
+ char const *val = font_feature_settings.value();
+ if (val[0] && strcmp(val, "normal")) {
+ // We do no sanity checking...
+ feature_string += val;
+ feature_string += ", ";
+ }
+
+ if (feature_string.empty()) {
+ feature_string = "normal";
+ } else {
+ // Remove last ", "
+ feature_string.resize(feature_string.size() - 2);
+ }
+
+ return feature_string;
+}
+
+
+// Internal
+/**
+ * Release callback.
+ */
+static void
+sp_style_object_release(SPObject *object, SPStyle *style)
+{
+ (void)object; // TODO
+ style->object = nullptr;
+}
+
+// Internal
+/**
+ * Emit style modified signal on style's object if the filter changed.
+ */
+static void sp_style_filter_ref_modified(SPObject *obj, unsigned flags, SPStyle *style)
+{
+ auto filter = static_cast<SPFilter*>(obj);
+
+ g_assert(style->getFilter() == filter);
+
+ if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG)) {
+ if (style->object) {
+ // FIXME: This line results in now-unnecessary filter recreation.
+ style->object->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
+ if (!style->block_filter_bbox_updates) {
+ style->object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); // To update bbox.
+ }
+ }
+ }
+}
+
+// Internal
+/**
+ * Gets called when the filter is (re)attached to the style
+ */
+void sp_style_filter_ref_changed(SPObject *old_ref, SPObject *ref, SPStyle *style)
+{
+ if (old_ref) {
+ static_cast<SPFilter*>(old_ref)->_refcount--;
+ style->filter_modified_connection.disconnect();
+ }
+
+ if (is<SPFilter>(ref)) {
+ static_cast<SPFilter*>(ref)->_refcount++;
+ style->filter_modified_connection = ref->connectModified(sigc::bind(sigc::ptr_fun(&sp_style_filter_ref_modified), style));
+ }
+
+ style->signal_filter_changed.emit(old_ref, ref);
+ sp_style_filter_ref_modified(ref, SP_OBJECT_FLAGS_ALL, style);
+}
+
+/**
+ * Emit style modified signal on style's object if server is style's fill
+ * or stroke paint server.
+ */
+static void sp_style_paint_server_ref_modified(SPObject *obj, unsigned /*flags*/, SPStyle *style)
+{
+ // todo: use flags
+ auto server = static_cast<SPPaintServer*>(obj);
+
+ g_assert((style->fill .isPaintserver() && style->getFillPaintServer() == server) ||
+ (style->stroke.isPaintserver() && style->getStrokePaintServer() == server) ||
+ !server);
+
+ if (style->object) {
+ /** \todo
+ * fixme: I do not know, whether it is optimal - we are
+ * forcing reread of everything (Lauris)
+ */
+ /** \todo
+ * fixme: We have to use object_modified flag, because parent
+ * flag is only available downstreams.
+ */
+ // FIXME: For patterns and hatches, this line results in now-unnecessary pattern recreation.
+ style->object->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
+ }
+}
+
+/**
+ * Gets called when the paintserver is (re)attached to the style
+ */
+void sp_style_fill_paint_server_ref_changed(SPObject *old_ref, SPObject *ref, SPStyle *style)
+{
+ if (old_ref) {
+ style->fill_ps_modified_connection.disconnect();
+ }
+
+ if (is<SPPaintServer>(ref)) {
+ style->fill_ps_modified_connection = ref->connectModified(sigc::bind(sigc::ptr_fun(&sp_style_paint_server_ref_modified), style));
+ }
+
+ style->signal_fill_ps_changed.emit(old_ref, ref);
+ sp_style_paint_server_ref_modified(ref, 0, style);
+}
+
+/**
+ * Gets called when the paintserver is (re)attached to the style
+ */
+void sp_style_stroke_paint_server_ref_changed(SPObject *old_ref, SPObject *ref, SPStyle *style)
+{
+ if (old_ref) {
+ style->stroke_ps_modified_connection.disconnect();
+ }
+
+ if (is<SPPaintServer>(ref)) {
+ style->stroke_ps_modified_connection = ref->connectModified(sigc::bind(sigc::ptr_fun(&sp_style_paint_server_ref_modified), style));
+ }
+
+ style->signal_stroke_ps_changed.emit(old_ref, ref);
+ sp_style_paint_server_ref_modified(ref, 0, style);
+}
+
+static CRSelEng *
+sp_repr_sel_eng()
+{
+ CRSelEng *const ret = cr_sel_eng_new(&Inkscape::XML::croco_node_iface);
+
+ /** \todo
+ * Check whether we need to register any pseudo-class handlers.
+ * libcroco has its own default handlers for first-child and lang.
+ *
+ * We probably want handlers for link and arguably visited (though
+ * inkscape can't visit links at the time of writing). hover etc.
+ * more useful in inkview than the editor inkscape.
+ *
+ * http://www.w3.org/TR/SVG11/styling.html#StylingWithCSS says that
+ * the following should be honoured, at least by inkview:
+ * :hover, :active, :focus, :visited, :link.
+ */
+
+ g_assert(ret);
+ return ret;
+}
+
+// The following functions should be incorporated into SPIPaint. FIXME
+// Called in: style.cpp, style-internal.cpp
+void
+sp_style_set_ipaint_to_uri(SPStyle *style, SPIPaint *paint, const Inkscape::URI *uri, SPDocument *document)
+{
+ if (!paint->value.href) {
+
+ if (style->object) {
+ // Should not happen as href should have been created in SPIPaint. (TODO: Removed code duplication.)
+ paint->value.href = std::make_shared<SPPaintServerReference>(style->object);
+ } else if (document) {
+ // Used by desktop style (no object to attach to!).
+ paint->value.href = std::make_shared<SPPaintServerReference>(document);
+ } else {
+ std::cerr << "sp_style_set_ipaint_to_uri: No valid object or document!" << std::endl;
+ return;
+ }
+
+ if (paint == &style->fill) {
+ style->fill_ps_changed_connection = paint->value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_fill_paint_server_ref_changed), style));
+ } else {
+ style->stroke_ps_changed_connection = paint->value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_stroke_paint_server_ref_changed), style));
+ }
+ }
+
+ if (paint->value.href){
+ if (paint->value.href->getObject()){
+ paint->value.href->detach();
+ }
+
+ try {
+ paint->value.href->attach(*uri);
+ } catch (Inkscape::BadURIException &e) {
+ g_warning("%s", e.what());
+ paint->value.href->detach();
+ }
+ }
+}
+
+// Called in: style.cpp, style-internal.cpp
+void
+sp_style_set_ipaint_to_uri_string (SPStyle *style, SPIPaint *paint, const gchar *uri)
+{
+ try {
+ const Inkscape::URI IURI(uri);
+ sp_style_set_ipaint_to_uri(style, paint, &IURI, style->document);
+ } catch (...) {
+ g_warning("URI failed to parse: %s", uri);
+ }
+}
+
+// Called in: desktop-style.cpp
+void sp_style_set_to_uri(SPStyle *style, bool isfill, Inkscape::URI const *uri)
+{
+ sp_style_set_ipaint_to_uri(style, style->getFillOrStroke(isfill), uri, style->document);
+}
+
+// Called in: widgets/font-selector.cpp, widgets/text-toolbar.cpp, ui/dialog/text-edit.cpp
+gchar const *
+sp_style_get_css_unit_string(int unit)
+{
+ // specify px by default, see inkscape bug 1221626, mozilla bug 234789
+ // This is a problematic fix as some properties (e.g. 'line-height') have
+ // different behaviour if there is no unit.
+ switch (unit) {
+
+ case SP_CSS_UNIT_NONE: return "px";
+ case SP_CSS_UNIT_PX: return "px";
+ case SP_CSS_UNIT_PT: return "pt";
+ case SP_CSS_UNIT_PC: return "pc";
+ case SP_CSS_UNIT_MM: return "mm";
+ case SP_CSS_UNIT_CM: return "cm";
+ case SP_CSS_UNIT_IN: return "in";
+ case SP_CSS_UNIT_EM: return "em";
+ case SP_CSS_UNIT_EX: return "ex";
+ case SP_CSS_UNIT_PERCENT: return "%";
+ default: return "px";
+ }
+ return "px";
+}
+
+// Called in: style-internal.cpp, widgets/text-toolbar.cpp, ui/dialog/text-edit.cpp
+/*
+ * Convert a size in pixels into another CSS unit size
+ */
+double
+sp_style_css_size_px_to_units(double size, int unit, double font_size)
+{
+ double unit_size = size;
+
+ if (font_size == 0) {
+ g_warning("sp_style_get_css_font_size_units: passed in zero font_size");
+ font_size = SP_CSS_FONT_SIZE_DEFAULT;
+ }
+
+ switch (unit) {
+
+ case SP_CSS_UNIT_NONE: unit_size = size; break;
+ case SP_CSS_UNIT_PX: unit_size = size; break;
+ case SP_CSS_UNIT_PT: unit_size = Inkscape::Util::Quantity::convert(size, "px", "pt"); break;
+ case SP_CSS_UNIT_PC: unit_size = Inkscape::Util::Quantity::convert(size, "px", "pc"); break;
+ case SP_CSS_UNIT_MM: unit_size = Inkscape::Util::Quantity::convert(size, "px", "mm"); break;
+ case SP_CSS_UNIT_CM: unit_size = Inkscape::Util::Quantity::convert(size, "px", "cm"); break;
+ case SP_CSS_UNIT_IN: unit_size = Inkscape::Util::Quantity::convert(size, "px", "in"); break;
+ case SP_CSS_UNIT_EM: unit_size = size / font_size; break;
+ case SP_CSS_UNIT_EX: unit_size = size * 2.0 / font_size ; break;
+ case SP_CSS_UNIT_PERCENT: unit_size = size * 100.0 / font_size; break;
+
+ default:
+ g_warning("sp_style_get_css_font_size_units conversion to %d not implemented.", unit);
+ break;
+ }
+
+ return unit_size;
+}
+
+// Called in: widgets/text-toolbar.cpp, ui/dialog/text-edit.cpp
+/*
+ * Convert a size in a CSS unit size to pixels
+ */
+double
+sp_style_css_size_units_to_px(double size, int unit, double font_size)
+{
+ if (unit == SP_CSS_UNIT_PX) {
+ return size;
+ }
+ //g_message("sp_style_css_size_units_to_px %f %d = %f px", size, unit, out);
+ return size * (size / sp_style_css_size_px_to_units(size, unit, font_size));;
+}
+
+
+// FIXME: Everything below this line belongs in a different file - css-chemistry?
+
+void
+sp_style_set_property_url (SPObject *item, gchar const *property, SPObject *linked, bool recursive)
+{
+ Inkscape::XML::Node *repr = item->getRepr();
+
+ if (repr == nullptr) return;
+
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ if (linked) {
+ gchar *val = g_strdup_printf("url(#%s)", linked->getId());
+ sp_repr_css_set_property(css, property, val);
+ g_free(val);
+ } else {
+ sp_repr_css_unset_property(css, "filter");
+ }
+
+ if (recursive) {
+ sp_repr_css_change_recursive(repr, css, "style");
+ } else {
+ sp_repr_css_change(repr, css, "style");
+ }
+ sp_repr_css_attr_unref(css);
+}
+
+/**
+ * \pre style != NULL.
+ * \pre flags in {IFSET, ALWAYS}.
+ * Only used by sp_css_attr_from_object() and in splivarot.cpp - sp_item_path_outline().
+ */
+SPCSSAttr *
+sp_css_attr_from_style(SPStyle const *const style, guint const flags)
+{
+ g_return_val_if_fail(style != nullptr, NULL);
+ g_return_val_if_fail(((flags & SP_STYLE_FLAG_IFSET) ||
+ (flags & SP_STYLE_FLAG_ALWAYS)),
+ NULL);
+ Glib::ustring style_str = style->write(flags);
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ sp_repr_css_attr_add_from_string(css, style_str.c_str());
+ return css;
+}
+
+// Called in: selection-chemistry.cpp, widgets/stroke-marker-selector.cpp, widgets/stroke-style.cpp,
+// ui/tools/freehand-base.cpp
+/**
+ * \pre object != NULL
+ * \pre flags in {IFSET, ALWAYS}.
+ */
+SPCSSAttr *sp_css_attr_from_object(SPObject *object, guint const flags)
+{
+ g_return_val_if_fail(((flags == SP_STYLE_FLAG_IFSET) ||
+ (flags == SP_STYLE_FLAG_ALWAYS) ),
+ NULL);
+ SPCSSAttr * result = nullptr;
+ if (object->style) {
+ result = sp_css_attr_from_style(object->style, flags);
+ }
+ return result;
+}
+
+// Called in: selection-chemistry.cpp, ui/dialog/inkscape-preferences.cpp
+/**
+ * Unset any text-related properties
+ */
+SPCSSAttr *
+sp_css_attr_unset_text(SPCSSAttr *css)
+{
+ sp_repr_css_set_property(css, "font", nullptr);
+ sp_repr_css_set_property(css, "-inkscape-font-specification", nullptr);
+ sp_repr_css_set_property(css, "font-size", nullptr);
+ sp_repr_css_set_property(css, "font-size-adjust", nullptr); // not implemented yet
+ sp_repr_css_set_property(css, "font-style", nullptr);
+ sp_repr_css_set_property(css, "font-variant", nullptr);
+ sp_repr_css_set_property(css, "font-weight", nullptr);
+ sp_repr_css_set_property(css, "font-stretch", nullptr);
+ sp_repr_css_set_property(css, "font-family", nullptr);
+ sp_repr_css_set_property(css, "text-indent", nullptr);
+ sp_repr_css_set_property(css, "text-align", nullptr);
+ sp_repr_css_set_property(css, "line-height", nullptr);
+ sp_repr_css_set_property(css, "letter-spacing", nullptr);
+ sp_repr_css_set_property(css, "word-spacing", nullptr);
+ sp_repr_css_set_property(css, "text-transform", nullptr);
+ sp_repr_css_set_property(css, "direction", nullptr);
+ sp_repr_css_set_property(css, "writing-mode", nullptr);
+ sp_repr_css_set_property(css, "text-orientation", nullptr);
+ sp_repr_css_set_property(css, "text-anchor", nullptr);
+ sp_repr_css_set_property(css, "white-space", nullptr);
+ sp_repr_css_set_property(css, "shape-inside", nullptr);
+ sp_repr_css_set_property(css, "shape-subtract", nullptr);
+ sp_repr_css_set_property(css, "shape-padding", nullptr);
+ sp_repr_css_set_property(css, "shape-margin", nullptr);
+ sp_repr_css_set_property(css, "inline-size", nullptr);
+ sp_repr_css_set_property(css, "kerning", nullptr); // not implemented yet
+ sp_repr_css_set_property(css, "dominant-baseline", nullptr); // not implemented yet
+ sp_repr_css_set_property(css, "alignment-baseline", nullptr); // not implemented yet
+ sp_repr_css_set_property(css, "baseline-shift", nullptr);
+
+ sp_repr_css_set_property(css, "text-decoration", nullptr);
+ sp_repr_css_set_property(css, "text-decoration-line", nullptr);
+ sp_repr_css_set_property(css, "text-decoration-color", nullptr);
+ sp_repr_css_set_property(css, "text-decoration-style", nullptr);
+
+ sp_repr_css_set_property(css, "font-variant-ligatures", nullptr);
+ sp_repr_css_set_property(css, "font-variant-position", nullptr);
+ sp_repr_css_set_property(css, "font-variant-caps", nullptr);
+ sp_repr_css_set_property(css, "font-variant-numeric", nullptr);
+ sp_repr_css_set_property(css, "font-variant-alternates", nullptr);
+ sp_repr_css_set_property(css, "font-variant-east-asian", nullptr);
+ sp_repr_css_set_property(css, "font-feature-settings", nullptr);
+
+ return css;
+}
+
+// ui/dialog/inkscape-preferences.cpp
+/**
+ * Unset properties that should not be set for default tool style.
+ * This list needs to be reviewed.
+ */
+SPCSSAttr *
+sp_css_attr_unset_blacklist(SPCSSAttr *css)
+{
+ sp_repr_css_set_property(css, "color", nullptr);
+ sp_repr_css_set_property(css, "clip-rule", nullptr);
+ sp_repr_css_set_property(css, "d", nullptr);
+ sp_repr_css_set_property(css, "display", nullptr);
+ sp_repr_css_set_property(css, "overflow", nullptr);
+ sp_repr_css_set_property(css, "visibility", nullptr);
+ sp_repr_css_set_property(css, "isolation", nullptr);
+ sp_repr_css_set_property(css, "mix-blend-mode", nullptr);
+ sp_repr_css_set_property(css, "color-interpolation", nullptr);
+ sp_repr_css_set_property(css, "color-interpolation-filters", nullptr);
+ sp_repr_css_set_property(css, "solid-color", nullptr);
+ sp_repr_css_set_property(css, "solid-opacity", nullptr);
+ sp_repr_css_set_property(css, "fill-rule", nullptr);
+ sp_repr_css_set_property(css, "color-rendering", nullptr);
+ sp_repr_css_set_property(css, "image-rendering", nullptr);
+ sp_repr_css_set_property(css, "shape-rendering", nullptr);
+ sp_repr_css_set_property(css, "text-rendering", nullptr);
+ sp_repr_css_set_property(css, "enable-background", nullptr);
+
+ return css;
+}
+
+// Called in style.cpp
+static bool
+is_url(char const *p)
+{
+ if (p == nullptr)
+ return false;
+/** \todo
+ * FIXME: I'm not sure if this applies to SVG as well, but CSS2 says any URIs
+ * in property values must start with 'url('.
+ */
+ return (g_ascii_strncasecmp(p, "url(", 4) == 0);
+}
+
+// Called in: ui/dialog/inkscape-preferences.cpp, ui/tools/tweek-tool.cpp
+/**
+ * Unset any properties that contain URI values.
+ *
+ * Used for storing style that will be reused across documents when carrying
+ * the referenced defs is impractical.
+ */
+SPCSSAttr *
+sp_css_attr_unset_uris(SPCSSAttr *css)
+{
+// All properties that may hold <uri> or <paint> according to SVG 1.1
+ if (is_url(sp_repr_css_property(css, "clip-path", nullptr))) sp_repr_css_set_property(css, "clip-path", nullptr);
+ if (is_url(sp_repr_css_property(css, "color-profile", nullptr))) sp_repr_css_set_property(css, "color-profile", nullptr);
+ if (is_url(sp_repr_css_property(css, "cursor", nullptr))) sp_repr_css_set_property(css, "cursor", nullptr);
+ if (is_url(sp_repr_css_property(css, "filter", nullptr))) sp_repr_css_set_property(css, "filter", nullptr);
+ if (is_url(sp_repr_css_property(css, "marker", nullptr))) sp_repr_css_set_property(css, "marker", nullptr);
+ if (is_url(sp_repr_css_property(css, "marker-start", nullptr))) sp_repr_css_set_property(css, "marker-start", nullptr);
+ if (is_url(sp_repr_css_property(css, "marker-mid", nullptr))) sp_repr_css_set_property(css, "marker-mid", nullptr);
+ if (is_url(sp_repr_css_property(css, "marker-end", nullptr))) sp_repr_css_set_property(css, "marker-end", nullptr);
+ if (is_url(sp_repr_css_property(css, "mask", nullptr))) sp_repr_css_set_property(css, "mask", nullptr);
+ if (is_url(sp_repr_css_property(css, "fill", nullptr))) sp_repr_css_set_property(css, "fill", nullptr);
+ if (is_url(sp_repr_css_property(css, "stroke", nullptr))) sp_repr_css_set_property(css, "stroke", nullptr);
+
+ return css;
+}
+
+// Called in style.cpp
+/**
+ * Scale a single-value property.
+ */
+static void
+sp_css_attr_scale_property_single(SPCSSAttr *css, gchar const *property,
+ double ex, bool only_with_units = false)
+{
+ gchar const *w = sp_repr_css_property(css, property, nullptr);
+ if (w) {
+ gchar *units = nullptr;
+ double wd = g_ascii_strtod(w, &units) * ex;
+ if (w == units) {// nothing converted, non-numeric value
+ return;
+ }
+ if (only_with_units && (units == nullptr || *units == '\0' || *units == '%' || *units == 'e')) {
+ // only_with_units, but no units found, so do nothing.
+ // 'e' matches 'em' or 'ex'
+ return;
+ }
+ Inkscape::CSSOStringStream os;
+ os << wd << units; // reattach units
+ sp_repr_css_set_property(css, property, os.str().c_str());
+ }
+}
+
+// Called in style.cpp for stroke-dasharray
+/**
+ * Scale a list-of-values property.
+ */
+static void
+sp_css_attr_scale_property_list(SPCSSAttr *css, gchar const *property, double ex)
+{
+ gchar const *string = sp_repr_css_property(css, property, nullptr);
+ if (string) {
+ Inkscape::CSSOStringStream os;
+ gchar **a = g_strsplit(string, ",", 10000);
+ bool first = true;
+ for (gchar **i = a; i != nullptr; i++) {
+ gchar *w = *i;
+ if (w == nullptr)
+ break;
+ gchar *units = nullptr;
+ double wd = g_ascii_strtod(w, &units) * ex;
+ if (w == units) {// nothing converted, non-numeric value ("none" or "inherit"); do nothing
+ g_strfreev(a);
+ return;
+ }
+ if (!first) {
+ os << ",";
+ }
+ os << wd << units; // reattach units
+ first = false;
+ }
+ sp_repr_css_set_property(css, property, os.str().c_str());
+ g_strfreev(a);
+ }
+}
+
+// Called in: text-editing.cpp,
+/**
+ * Scale any properties that may hold <length> by ex.
+ */
+SPCSSAttr *
+sp_css_attr_scale(SPCSSAttr *css, double ex)
+{
+ sp_css_attr_scale_property_single(css, "baseline-shift", ex);
+ sp_css_attr_scale_property_single(css, "stroke-width", ex);
+ sp_css_attr_scale_property_list (css, "stroke-dasharray", ex);
+ sp_css_attr_scale_property_single(css, "stroke-dashoffset", ex);
+ sp_css_attr_scale_property_single(css, "font-size", ex, true);
+ sp_css_attr_scale_property_single(css, "kerning", ex);
+ sp_css_attr_scale_property_single(css, "letter-spacing", ex);
+ sp_css_attr_scale_property_single(css, "word-spacing", ex);
+ sp_css_attr_scale_property_single(css, "line-height", ex, true);
+
+ return css;
+}
+
+
+/**
+ * Quote and/or escape string for writing to CSS, changing strings in place.
+ * See: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
+ */
+void
+css_quote(Glib::ustring &val)
+{
+ Glib::ustring out;
+ bool quote = false;
+
+ // Can't wait for C++11!
+ for( Glib::ustring::iterator it = val.begin(); it != val.end(); ++it) {
+ if(g_ascii_isalnum(*it) || *it=='-' || *it=='_' || *it > 0xA0) {
+ out += *it;
+ } else if (*it == '\'') {
+ // Single quotes require escaping and quotes.
+ out += '\\';
+ out += *it;
+ quote = true;
+ } else {
+ // Quote everything else including spaces.
+ // (CSS Fonts Level 3 recommends quoting with spaces.)
+ out += *it;
+ quote = true;
+ }
+ if( it == val.begin() && !g_ascii_isalpha(*it) ) {
+ // A non-ASCII/non-alpha initial value on any identifier needs quotes.
+ // (Actually it's a bit more complicated but as it never hurts to quote...)
+ quote = true;
+ }
+ }
+ if( quote ) {
+ out.insert( out.begin(), '\'' );
+ out += '\'';
+ }
+ val = out;
+}
+
+
+/**
+ * Quote font names in font-family lists, changing string in place.
+ * We use unquoted names internally but some need to be quoted in CSS.
+ */
+void
+css_font_family_quote(Glib::ustring &val)
+{
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("\\s*,\\s*", val );
+
+ val.erase();
+ for(auto & token : tokens) {
+ css_quote( token );
+ val += token + ", ";
+ }
+ if( val.size() > 1 )
+ val.erase( val.size() - 2 ); // Remove trailing ", "
+}
+
+
+// Called in style-internal.cpp, xml/repr-css.cpp
+/**
+ * Remove paired single and double quotes from a string, changing string in place.
+ */
+void
+css_unquote(Glib::ustring &val)
+{
+ if( val.size() > 1 &&
+ ( (val[0] == '"' && val[val.size()-1] == '"' ) ||
+ (val[0] == '\'' && val[val.size()-1] == '\'' ) ) ) {
+
+ val.erase( 0, 1 );
+ val.erase( val.size()-1 );
+ }
+}
+
+// Called in style-internal.cpp, text-toolbar.cpp
+/**
+ * Remove paired single and double quotes from font names in font-family lists,
+ * changing string in place.
+ * We use unquoted family names internally but CSS sometimes uses quoted names.
+ */
+void
+css_font_family_unquote(Glib::ustring &val)
+{
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("\\s*,\\s*", val );
+
+ val.erase();
+ for(auto & token : tokens) {
+ css_unquote( token );
+ val += token + ", ";
+ }
+ if( val.size() > 1 )
+ val.erase( val.size() - 2 ); // Remove trailing ", "
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :