1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /** @file
3 * TODO: insert short description here
4 *//*
5 * Authors: see git history
6 *
7 * Copyright (C) 2018 Authors
8 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
9 */
10 /*
11 * bulia byak <buliabyak@users.sf.net>
12 * Tavmjong Bah <tavmjong@free.fr> (Documentation)
13 *
14 * Functions to manipulate SPCSSAttr which is a class derived from Inkscape::XML::Node See
15 * sp-css-attr.h and node.h
16 *
17 * SPCSSAttr is a special node type where the "attributes" are the properties in an element's style
18 * attribute. For example, style="fill:blue;stroke:none" is stored in a List (Inkscape::Util:List)
19 * where the key is the property (e.g. "fill" or "stroke") and the value is the property's value
20 * (e.g. "blue" or "none"). An element's properties are manipulated by adding, removing, or
21 * changing an item in the List. Utility functions are provided to go back and forth between the
22 * two ways of representing properties (by a string or by a list).
23 *
24 * Use sp_repr_css_write_string to go from a property list to a style string.
25 *
26 */
27
28 #define SP_REPR_CSS_C
29
30 #include <cstring>
31 #include <string>
32 #include <sstream>
33
34 #include <glibmm/ustring.h>
35
36 #include "3rdparty/libcroco/cr-declaration.h"
37
38 #include "svg/css-ostringstream.h"
39
40 #include "xml/repr.h"
41 #include "xml/simple-document.h"
42 #include "xml/sp-css-attr.h"
43
44 using Inkscape::XML::SimpleNode;
45 using Inkscape::XML::Node;
46 using Inkscape::XML::NodeType;
47 using Inkscape::XML::Document;
48
49 struct SPCSSAttrImpl : public SimpleNode, public SPCSSAttr {
50 public:
SPCSSAttrImplSPCSSAttrImpl51 SPCSSAttrImpl(Document *doc)
52 : SimpleNode(g_quark_from_static_string("css"), doc) {}
SPCSSAttrImplSPCSSAttrImpl53 SPCSSAttrImpl(SPCSSAttrImpl const &other, Document *doc)
54 : SimpleNode(other, doc) {}
55
typeSPCSSAttrImpl56 NodeType type() const override { return Inkscape::XML::NodeType::ELEMENT_NODE; }
57
58 protected:
_duplicateSPCSSAttrImpl59 SimpleNode *_duplicate(Document* doc) const override { return new SPCSSAttrImpl(*this, doc); }
60 };
61
62 static void sp_repr_css_add_components(SPCSSAttr *css, Node const *repr, gchar const *attr);
63
64 /**
65 * Creates an empty SPCSSAttr (a class for manipulating CSS style properties).
66 */
sp_repr_css_attr_new()67 SPCSSAttr *sp_repr_css_attr_new()
68 {
69 static Inkscape::XML::Document *attr_doc=nullptr;
70 if (!attr_doc) {
71 attr_doc = new Inkscape::XML::SimpleDocument();
72 }
73 return new SPCSSAttrImpl(attr_doc);
74 }
75
76 /**
77 * Unreferences an SPCSSAttr (will be garbage collected if no references remain).
78 */
sp_repr_css_attr_unref(SPCSSAttr * css)79 void sp_repr_css_attr_unref(SPCSSAttr *css)
80 {
81 g_assert(css != nullptr);
82 Inkscape::GC::release((Node *) css);
83 }
84
85 /**
86 * Creates a new SPCSSAttr with one attribute (i.e. style) copied from an existing repr (node). The
87 * repr attribute data is in the form of a char const * string (e.g. fill:#00ff00;stroke:none). The
88 * string is parsed by libcroco which returns a CRDeclaration list (a typical C linked list) of
89 * properties and values. This list is then used to fill the attributes of the new SPCSSAttr.
90 */
sp_repr_css_attr(Node const * repr,gchar const * attr)91 SPCSSAttr *sp_repr_css_attr(Node const *repr, gchar const *attr)
92 {
93 g_assert(repr != nullptr);
94 g_assert(attr != nullptr);
95
96 SPCSSAttr *css = sp_repr_css_attr_new();
97 sp_repr_css_add_components(css, repr, attr);
98 return css;
99 }
100
101
102 /**
103 * Adds an attribute to an existing SPCSAttr with the cascaded value including all parents.
104 */
sp_repr_css_attr_inherited_recursive(SPCSSAttr * css,Node const * repr,gchar const * attr)105 static void sp_repr_css_attr_inherited_recursive(SPCSSAttr *css, Node const *repr, gchar const *attr)
106 {
107 const Node *parent = repr->parent();
108
109 // read the ancestors from root down, using head recursion, so that children override parents
110 if (parent) {
111 sp_repr_css_attr_inherited_recursive(css, parent, attr);
112 }
113 sp_repr_css_add_components(css, repr, attr);
114 }
115
116 /**
117 * Creates a new SPCSSAttr with one attribute whose value is determined by cascading.
118 */
sp_repr_css_attr_inherited(Node const * repr,gchar const * attr)119 SPCSSAttr *sp_repr_css_attr_inherited(Node const *repr, gchar const *attr)
120 {
121 g_assert(repr != nullptr);
122 g_assert(attr != nullptr);
123
124 SPCSSAttr *css = sp_repr_css_attr_new();
125
126 sp_repr_css_attr_inherited_recursive(css, repr, attr);
127
128 return css;
129 }
130
131 /**
132 * Adds components (style properties) to an existing SPCSAttr from the specified attribute's data
133 * (nominally a style attribute).
134 *
135 */
sp_repr_css_add_components(SPCSSAttr * css,Node const * repr,gchar const * attr)136 static void sp_repr_css_add_components(SPCSSAttr *css, Node const *repr, gchar const *attr)
137 {
138 g_assert(css != nullptr);
139 g_assert(repr != nullptr);
140 g_assert(attr != nullptr);
141
142 char const *data = repr->attribute(attr);
143 sp_repr_css_attr_add_from_string(css, data);
144 }
145
146 /**
147 * Returns a character string of the value of a given style property or a default value if the
148 * attribute is not found.
149 */
sp_repr_css_property(SPCSSAttr * css,gchar const * name,gchar const * defval)150 char const *sp_repr_css_property(SPCSSAttr *css, gchar const *name, gchar const *defval)
151 {
152 g_assert(css != nullptr);
153 g_assert(name != nullptr);
154
155 char const *attr = ((Node *)css)->attribute(name);
156 return ( attr == nullptr
157 ? defval
158 : attr );
159 }
160
161 /**
162 * Returns a character string of the value of a given style property or a default value if the
163 * attribute is not found.
164 */
sp_repr_css_property(SPCSSAttr * css,Glib::ustring const & name,Glib::ustring const & defval)165 Glib::ustring sp_repr_css_property(SPCSSAttr *css, Glib::ustring const &name, Glib::ustring const &defval)
166 {
167 g_assert(css != nullptr);
168
169 Glib::ustring retval = defval;
170 char const *attr = ((Node *)css)->attribute(name.c_str());
171 if (attr) {
172 retval = attr;
173 }
174
175 return retval;
176 }
177
178 /**
179 * Returns true if a style property is present and its value is unset.
180 */
sp_repr_css_property_is_unset(SPCSSAttr * css,gchar const * name)181 bool sp_repr_css_property_is_unset(SPCSSAttr *css, gchar const *name)
182 {
183 g_assert(css != nullptr);
184 g_assert(name != nullptr);
185
186 char const *attr = ((Node *)css)->attribute(name);
187 return (attr && !strcmp(attr, "inkscape:unset"));
188 }
189
190
191 /**
192 * Set a style property to a new value (e.g. fill to #ffff00).
193 */
sp_repr_css_set_property(SPCSSAttr * css,gchar const * name,gchar const * value)194 void sp_repr_css_set_property(SPCSSAttr *css, gchar const *name, gchar const *value)
195 {
196 g_assert(css != nullptr);
197 g_assert(name != nullptr);
198
199 ((Node *) css)->setAttribute(name, value);
200 }
201
202 /**
203 * Set a style property to "inkscape:unset".
204 */
sp_repr_css_unset_property(SPCSSAttr * css,gchar const * name)205 void sp_repr_css_unset_property(SPCSSAttr *css, gchar const *name)
206 {
207 g_assert(css != nullptr);
208 g_assert(name != nullptr);
209
210 ((Node *) css)->setAttribute(name, "inkscape:unset");
211 }
212
213 /**
214 * Return the value of a style property if property define, or a default value if not.
215 */
sp_repr_css_double_property(SPCSSAttr * css,gchar const * name,double defval)216 double sp_repr_css_double_property(SPCSSAttr *css, gchar const *name, double defval)
217 {
218 g_assert(css != nullptr);
219 g_assert(name != nullptr);
220
221 double val = defval;
222 sp_repr_get_double((Node *) css, name, &val);
223 return val;
224 }
225
226 /**
227 * Write a style attribute string from a list of properties stored in an SPCSAttr object.
228 */
sp_repr_css_write_string(SPCSSAttr * css,Glib::ustring & str)229 void sp_repr_css_write_string(SPCSSAttr *css, Glib::ustring &str)
230 {
231 str.clear();
232 for (const auto & iter : css->attributeList())
233 {
234 if (iter.value && !strcmp(iter.value, "inkscape:unset")) {
235 continue;
236 }
237
238 if (!str.empty()) {
239 str.push_back(';');
240 }
241
242 str.append(g_quark_to_string(iter.key));
243 str.push_back(':');
244 str.append(iter.value); // Any necessary quoting to be done by calling routine.
245 }
246 }
247
248 /**
249 * Sets an attribute (e.g. style) to a string created from a list of style properties.
250 */
sp_repr_css_set(Node * repr,SPCSSAttr * css,gchar const * attr)251 void sp_repr_css_set(Node *repr, SPCSSAttr *css, gchar const *attr)
252 {
253 g_assert(repr != nullptr);
254 g_assert(css != nullptr);
255 g_assert(attr != nullptr);
256
257 Glib::ustring value;
258 sp_repr_css_write_string(css, value);
259
260 /*
261 * If the new value is different from the old value, this will sometimes send a signal via
262 * CompositeNodeObserver::notiftyAttributeChanged() which results in calling
263 * SPObject::repr_attr_changed and thus updates the object's SPStyle. This update
264 * results in another call to repr->setAttribute().
265 */
266 repr->setAttributeOrRemoveIfEmpty(attr, value);
267 }
268
269 /**
270 * Loops through a List of style properties, printing key/value pairs.
271 */
sp_repr_css_print(SPCSSAttr * css)272 void sp_repr_css_print(SPCSSAttr *css)
273 {
274 for ( const auto & attr: css->attributeList() )
275 {
276 gchar const * key = g_quark_to_string(attr.key);
277 gchar const * val = attr.value;
278 g_print("%s:\t%s\n",key,val);
279 }
280 }
281
282 /**
283 * Merges two SPCSSAttr's. Properties in src overwrite properties in dst if present in both.
284 */
sp_repr_css_merge(SPCSSAttr * dst,SPCSSAttr * src)285 void sp_repr_css_merge(SPCSSAttr *dst, SPCSSAttr *src)
286 {
287 g_assert(dst != nullptr);
288 g_assert(src != nullptr);
289
290 dst->mergeFrom(src, "");
291 }
292
293 /**
294 * Merges style properties as parsed by libcroco into an existing SPCSSAttr.
295 * libcroco converts all single quotes to double quotes, which needs to be
296 * undone as we always use single quotes inside our 'style' strings since
297 * double quotes are used outside: e.g.:
298 * style="font-family:'DejaVu Sans'"
299 */
sp_repr_css_merge_from_decl(SPCSSAttr * css,CRDeclaration const * const decl)300 static void sp_repr_css_merge_from_decl(SPCSSAttr *css, CRDeclaration const *const decl)
301 {
302 guchar *const str_value_unsigned = cr_term_to_string(decl->value);
303 css->setAttribute(decl->property->stryng->str, reinterpret_cast<char const *>(str_value_unsigned));
304 g_free(str_value_unsigned);
305 }
306
307 /**
308 * Merges style properties as parsed by libcroco into an existing SPCSSAttr.
309 *
310 * \pre decl_list != NULL
311 */
sp_repr_css_merge_from_decl_list(SPCSSAttr * css,CRDeclaration const * const decl_list)312 static void sp_repr_css_merge_from_decl_list(SPCSSAttr *css, CRDeclaration const *const decl_list)
313 {
314 // read the decls from start to end, using tail recursion, so that latter declarations override
315 // (Ref: http://www.w3.org/TR/REC-CSS2/cascade.html#cascading-order point 4.)
316 // because sp_repr_css_merge_from_decl sets properties unconditionally
317 sp_repr_css_merge_from_decl(css, decl_list);
318 if (decl_list->next) {
319 sp_repr_css_merge_from_decl_list(css, decl_list->next);
320 }
321 }
322
323 /**
324 * Use libcroco to parse a string for CSS properties and then merge
325 * them into an existing SPCSSAttr.
326 */
sp_repr_css_attr_add_from_string(SPCSSAttr * css,gchar const * p)327 void sp_repr_css_attr_add_from_string(SPCSSAttr *css, gchar const *p)
328 {
329 if (p != nullptr) {
330 CRDeclaration *const decl_list
331 = cr_declaration_parse_list_from_buf(reinterpret_cast<guchar const *>(p), CR_UTF_8);
332 if (decl_list) {
333 sp_repr_css_merge_from_decl_list(css, decl_list);
334 cr_declaration_destroy(decl_list);
335 }
336 }
337 }
338
339 /**
340 * Creates a new SPCSAttr with the values filled from a repr, merges in properties from the given
341 * SPCSAttr, and then replaces that SPCSAttr with the new one. This is called, for example, for
342 * each object in turn when a selection's style is updated via sp_desktop_set_style().
343 */
sp_repr_css_change(Node * repr,SPCSSAttr * css,gchar const * attr)344 void sp_repr_css_change(Node *repr, SPCSSAttr *css, gchar const *attr)
345 {
346 g_assert(repr != nullptr);
347 g_assert(css != nullptr);
348 g_assert(attr != nullptr);
349
350 SPCSSAttr *current = sp_repr_css_attr(repr, attr);
351 sp_repr_css_merge(current, css);
352 sp_repr_css_set(repr, current, attr);
353
354 sp_repr_css_attr_unref(current);
355 }
356
sp_repr_css_change_recursive(Node * repr,SPCSSAttr * css,gchar const * attr)357 void sp_repr_css_change_recursive(Node *repr, SPCSSAttr *css, gchar const *attr)
358 {
359 g_assert(repr != nullptr);
360 g_assert(css != nullptr);
361 g_assert(attr != nullptr);
362
363 sp_repr_css_change(repr, css, attr);
364
365 for (Node *child = repr->firstChild(); child != nullptr; child = child->next()) {
366 sp_repr_css_change_recursive(child, css, attr);
367 }
368 }
369
370 /**
371 * Return a new SPCSSAttr with all the properties found in the input SPCSSAttr unset.
372 */
sp_repr_css_attr_unset_all(SPCSSAttr * css)373 SPCSSAttr* sp_repr_css_attr_unset_all(SPCSSAttr *css)
374 {
375 SPCSSAttr* css_unset = sp_repr_css_attr_new();
376 for ( const auto & iter : css->attributeList() ) {
377 sp_repr_css_set_property (css_unset, g_quark_to_string(iter.key), "inkscape:unset");
378 }
379 return css_unset;
380 }
381
382 /*
383 Local Variables:
384 mode:c++
385 c-file-style:"stroustrup"
386 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
387 indent-tabs-mode:nil
388 fill-column:99
389 End:
390 */
391 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
392