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