1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Desktop style management
4  *
5  * Authors:
6  *   bulia byak
7  *   verbalshadow
8  *   Jon A. Cruz <jon@joncruz.org>
9  *   Abhishek Sharma
10  *
11  * Copyright (C) 2004, 2006 authors
12  *
13  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
14  */
15 
16 #include <string>
17 #include <cstring>
18 
19 #include <glibmm.h>
20 
21 #include "desktop-style.h"
22 
23 #include "color-rgba.h"
24 #include "desktop.h"
25 #include "filter-chemistry.h"
26 #include "inkscape.h"
27 #include "selection.h"
28 
29 #include "object/box3d-side.h"
30 #include "object/filters/blend.h"
31 #include "object/filters/gaussian-blur.h"
32 #include "object/sp-flowdiv.h"
33 #include "object/sp-flowregion.h"
34 #include "object/sp-flowtext.h"
35 #include "object/sp-hatch.h"
36 #include "object/sp-linear-gradient.h"
37 #include "object/sp-path.h"
38 #include "object/sp-pattern.h"
39 #include "object/sp-radial-gradient.h"
40 #include "object/sp-textpath.h"
41 #include "object/sp-tref.h"
42 #include "object/sp-tspan.h"
43 #include "object/sp-use.h"
44 #include "style.h"
45 
46 #include "svg/css-ostringstream.h"
47 #include "svg/svg-color.h"
48 #include "svg/svg.h"
49 
50 #include "ui/tools/tool-base.h"
51 
52 #include "xml/sp-css-attr.h"
53 #include "xml/attribute-record.h"
54 
55 namespace {
56 
isTextualItem(SPObject const * obj)57 bool isTextualItem(SPObject const *obj)
58 {
59     bool isTextual = dynamic_cast<SPText const *>(obj) //
60         || dynamic_cast<SPFlowtext const *>(obj) //
61         || dynamic_cast<SPTSpan const *>(obj) //
62         || dynamic_cast<SPTRef const *>(obj) //
63         || dynamic_cast<SPTextPath const *>(obj) //
64         || dynamic_cast<SPFlowdiv const *>(obj) //
65         || dynamic_cast<SPFlowpara const *>(obj) //
66         || dynamic_cast<SPFlowtspan const *>(obj);
67 
68     return isTextual;
69 }
70 
71 } // namespace
72 
73 /**
74  * Set color on selection on desktop.
75  */
76 void
sp_desktop_set_color(SPDesktop * desktop,ColorRGBA const & color,bool is_relative,bool fill)77 sp_desktop_set_color(SPDesktop *desktop, ColorRGBA const &color, bool is_relative, bool fill)
78 {
79     /// \todo relative color setting
80     if (is_relative) {
81         g_warning("FIXME: relative color setting not yet implemented");
82         return;
83     }
84 
85     guint32 rgba = SP_RGBA32_F_COMPOSE(color[0], color[1], color[2], color[3]);
86     gchar b[64];
87     sp_svg_write_color(b, sizeof(b), rgba);
88     SPCSSAttr *css = sp_repr_css_attr_new();
89     if (fill) {
90         sp_repr_css_set_property(css, "fill", b);
91         Inkscape::CSSOStringStream osalpha;
92         osalpha << color[3];
93         sp_repr_css_set_property(css, "fill-opacity", osalpha.str().c_str());
94     } else {
95         sp_repr_css_set_property(css, "stroke", b);
96         Inkscape::CSSOStringStream osalpha;
97         osalpha << color[3];
98         sp_repr_css_set_property(css, "stroke-opacity", osalpha.str().c_str());
99     }
100 
101     sp_desktop_set_style(desktop, css);
102 
103     sp_repr_css_attr_unref(css);
104 }
105 
106 /**
107  * Apply style on object and children, recursively.
108  */
109 void
sp_desktop_apply_css_recursive(SPObject * o,SPCSSAttr * css,bool skip_lines)110 sp_desktop_apply_css_recursive(SPObject *o, SPCSSAttr *css, bool skip_lines)
111 {
112     // non-items should not have style
113     SPItem *item = dynamic_cast<SPItem *>(o);
114     if (!item) {
115         return;
116     }
117 
118     // 1. tspans with role=line are not regular objects in that they are not supposed to have style of their own,
119     // but must always inherit from the parent text. Same for textPath.
120     // However, if the line tspan or textPath contains some style (old file?), we reluctantly set our style to it too.
121 
122     // 2. Generally we allow setting style on clones, but when it's inside flowRegion, do not touch
123     // it, be it clone or not; it's just styleless shape (because that's how Inkscape does
124     // flowtext).
125 
126     SPTSpan *tspan = dynamic_cast<SPTSpan *>(o);
127 
128     if (!(skip_lines
129           && ((tspan && tspan->role == SP_TSPAN_ROLE_LINE)
130               || dynamic_cast<SPFlowdiv *>(o)
131               || dynamic_cast<SPFlowpara *>(o)
132               || dynamic_cast<SPTextPath *>(o))
133           &&  !o->getAttribute("style"))
134         &&
135         !(dynamic_cast<SPFlowregionbreak *>(o) ||
136           dynamic_cast<SPFlowregionExclude *>(o) ||
137           (dynamic_cast<SPUse *>(o) &&
138            o->parent &&
139            (dynamic_cast<SPFlowregion *>(o->parent) ||
140             dynamic_cast<SPFlowregionExclude *>(o->parent)
141                )
142               )
143             )
144         ) {
145 
146         SPCSSAttr *css_set = sp_repr_css_attr_new();
147         sp_repr_css_merge(css_set, css);
148 
149         // Scale the style by the inverse of the accumulated parent transform in the paste context.
150         {
151             Geom::Affine const local(item->i2doc_affine());
152             double const ex(local.descrim());
153             if ( ( ex != 0. )
154                  && ( ex != 1. ) ) {
155                 sp_css_attr_scale(css_set, 1/ex);
156             }
157         }
158 
159         o->changeCSS(css_set,"style");
160 
161         sp_repr_css_attr_unref(css_set);
162     }
163 
164     // setting style on child of clone spills into the clone original (via shared repr), don't do it!
165     if (dynamic_cast<SPUse *>(o)) {
166         return;
167     }
168 
169     for (auto& child: o->children) {
170         if (sp_repr_css_property(css, "opacity", nullptr) != nullptr) {
171             // Unset properties which are accumulating and thus should not be set recursively.
172             // For example, setting opacity 0.5 on a group recursively would result in the visible opacity of 0.25 for an item in the group.
173             SPCSSAttr *css_recurse = sp_repr_css_attr_new();
174             sp_repr_css_merge(css_recurse, css);
175             sp_repr_css_set_property(css_recurse, "opacity", nullptr);
176             sp_desktop_apply_css_recursive(&child, css_recurse, skip_lines);
177             sp_repr_css_attr_unref(css_recurse);
178         } else {
179             sp_desktop_apply_css_recursive(&child, css, skip_lines);
180         }
181     }
182 }
183 
184 /**
185  * Apply style on selection on desktop.
186  */
187 
sp_desktop_set_style(SPDesktop * desktop,SPCSSAttr * css,bool change,bool write_current)188 void sp_desktop_set_style(SPDesktop *desktop, SPCSSAttr *css, bool change, bool write_current)
189 {
190     return sp_desktop_set_style(desktop->getSelection(), desktop, css, change, write_current);
191 }
192 
193 void
sp_desktop_set_style(Inkscape::ObjectSet * set,SPDesktop * desktop,SPCSSAttr * css,bool change,bool write_current)194 sp_desktop_set_style(Inkscape::ObjectSet *set, SPDesktop *desktop, SPCSSAttr *css, bool change, bool write_current)
195 {
196     if (write_current) {
197         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
198         // 1. Set internal value
199         sp_repr_css_merge(desktop->current, css);
200 
201         // 1a. Write to prefs; make a copy and unset any URIs first
202         SPCSSAttr *css_write = sp_repr_css_attr_new();
203         sp_repr_css_merge(css_write, css);
204         sp_css_attr_unset_uris(css_write);
205         prefs->mergeStyle("/desktop/style", css_write);
206         auto itemlist = set->items();
207         for (auto i = itemlist.begin(); i!= itemlist.end(); ++i) {
208             /* last used styles for 3D box faces are stored separately */
209             SPObject *obj = *i;
210             Box3DSide *side = dynamic_cast<Box3DSide *>(obj);
211             if (side) {
212                 prefs->mergeStyle(
213                         Glib::ustring("/desktop/") + side->axes_string() + "/style", css_write);
214             }
215         }
216         sp_repr_css_attr_unref(css_write);
217     }
218 
219     if (!change)
220         return;
221 
222 // 2. Emit signal... See desktop->connectSetStyle in text-tool, tweak-tool, and gradient-drag.
223     bool intercepted = desktop->_set_style_signal.emit(css);
224 
225 /** \todo
226  * FIXME: in set_style, compensate pattern and gradient fills, stroke width,
227  * rect corners, font size for the object's own transform so that pasting
228  * fills does not depend on preserve/optimize.
229  */
230 
231 // 3. If nobody has intercepted the signal, apply the style to the selection
232     if (!intercepted) {
233         // If we have an event context, update its cursor (TODO: it could be neater to do this with the signal sent above, but what if the signal gets intercepted?)
234         if (desktop->event_context) {
235             desktop->event_context->sp_event_context_update_cursor();
236         }
237 
238         // Remove text attributes if not text...
239         // Do this once in case a zillion objects are selected.
240         SPCSSAttr *css_no_text = sp_repr_css_attr_new();
241         sp_repr_css_merge(css_no_text, css);
242         css_no_text = sp_css_attr_unset_text(css_no_text);
243 
244         auto itemlist = set->items();
245         for (auto i = itemlist.begin(); i!= itemlist.end(); ++i) {
246             SPItem *item = *i;
247 
248             // If not text, don't apply text attributes (can a group have text attributes? Yes! FIXME)
249             if (isTextualItem(item)) {
250 
251                 // If any font property has changed, then we have written out the font
252                 // properties in longhand and we need to remove the 'font' shorthand.
253                 if( !sp_repr_css_property_is_unset(css, "font-family") ) {
254                     sp_repr_css_unset_property(css, "font");
255                 }
256 
257                 sp_desktop_apply_css_recursive(item, css, true);
258 
259             } else {
260 
261                 sp_desktop_apply_css_recursive(item, css_no_text, true);
262 
263             }
264         }
265         sp_repr_css_attr_unref(css_no_text);
266     }
267 }
268 
269 /**
270  * Return the desktop's current style.
271  */
272 SPCSSAttr *
sp_desktop_get_style(SPDesktop * desktop,bool with_text)273 sp_desktop_get_style(SPDesktop *desktop, bool with_text)
274 {
275     SPCSSAttr *css = sp_repr_css_attr_new();
276     sp_repr_css_merge(css, desktop->current);
277     const auto & l = css->attributeList();
278     if (l.empty()) {
279         sp_repr_css_attr_unref(css);
280         return nullptr;
281     } else {
282         if (!with_text) {
283             css = sp_css_attr_unset_text(css);
284         }
285         return css;
286     }
287 }
288 
289 /**
290  * Return the desktop's current color.
291  */
292 guint32
sp_desktop_get_color(SPDesktop * desktop,bool is_fill)293 sp_desktop_get_color(SPDesktop *desktop, bool is_fill)
294 {
295     guint32 r = 0; // if there's no color, return black
296     gchar const *property = sp_repr_css_property(desktop->current,
297                                                  is_fill ? "fill" : "stroke",
298                                                  "#000");
299 
300     if (desktop->current && property) { // if there is style and the property in it,
301         if (strncmp(property, "url", 3)) { // and if it's not url,
302             // read it
303             r = sp_svg_read_color(property, r);
304         }
305     }
306 
307     return r;
308 }
309 
310 double
sp_desktop_get_master_opacity_tool(SPDesktop * desktop,Glib::ustring const & tool,bool * has_opacity)311 sp_desktop_get_master_opacity_tool(SPDesktop *desktop, Glib::ustring const &tool, bool *has_opacity)
312 {
313     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
314     SPCSSAttr *css = nullptr;
315     gfloat value = 1.0; // default if nothing else found
316     if (has_opacity)
317         *has_opacity = false;
318     if (prefs->getBool(tool + "/usecurrent")) {
319         css = sp_desktop_get_style(desktop, true);
320     } else {
321         css = prefs->getStyle(tool + "/style");
322     }
323 
324     if (css) {
325         gchar const *property = css ? sp_repr_css_property(css, "opacity", "1.000") : nullptr;
326 
327         if (desktop->current && property) { // if there is style and the property in it,
328             if ( !sp_svg_number_read_f(property, &value) ) {
329                 value = 1.0; // things failed. set back to the default
330             } else {
331                 if (has_opacity)
332                    *has_opacity = true;
333             }
334         }
335 
336         sp_repr_css_attr_unref(css);
337     }
338 
339     return value;
340 }
341 double
sp_desktop_get_opacity_tool(SPDesktop * desktop,Glib::ustring const & tool,bool is_fill)342 sp_desktop_get_opacity_tool(SPDesktop *desktop, Glib::ustring const &tool, bool is_fill)
343 {
344     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
345     SPCSSAttr *css = nullptr;
346     gfloat value = 1.0; // default if nothing else found
347     if (prefs->getBool(tool + "/usecurrent")) {
348         css = sp_desktop_get_style(desktop, true);
349     } else {
350         css = prefs->getStyle(tool + "/style");
351     }
352 
353     if (css) {
354         gchar const *property = css ? sp_repr_css_property(css, is_fill ? "fill-opacity": "stroke-opacity", "1.000") : nullptr;
355 
356         if (desktop->current && property) { // if there is style and the property in it,
357             if ( !sp_svg_number_read_f(property, &value) ) {
358                 value = 1.0; // things failed. set back to the default
359             }
360         }
361 
362         sp_repr_css_attr_unref(css);
363     }
364 
365     return value;
366 }
367 
368 guint32
sp_desktop_get_color_tool(SPDesktop * desktop,Glib::ustring const & tool,bool is_fill,bool * has_color)369 sp_desktop_get_color_tool(SPDesktop *desktop, Glib::ustring const &tool, bool is_fill, bool *has_color)
370 {
371     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
372     SPCSSAttr *css = nullptr;
373     guint32 r = 0; // if there's no color, return black
374     if (has_color)
375         *has_color = false;
376     bool styleFromCurrent = prefs->getBool(tool + "/usecurrent");
377     if (styleFromCurrent) {
378         css = sp_desktop_get_style(desktop, true);
379     } else {
380         css = prefs->getStyle(tool + "/style");
381         Inkscape::GC::anchor(css);
382     }
383 
384     if (css) {
385         gchar const *property = sp_repr_css_property(css, is_fill ? "fill" : "stroke", "#000");
386 
387         if (desktop->current && property) { // if there is style and the property in it,
388             if (strncmp(property, "url", 3) && strncmp(property, "none", 4)) { // and if it's not url or none,
389                 // read it
390                 r = sp_svg_read_color(property, r);
391                 if (has_color)
392                     *has_color = true;
393             }
394         }
395 
396         sp_repr_css_attr_unref(css);
397     }
398 
399     return r | 0xff;
400 }
401 
402 /**
403  * Apply the desktop's current style or the tool style to repr.
404  */
405 void
sp_desktop_apply_style_tool(SPDesktop * desktop,Inkscape::XML::Node * repr,Glib::ustring const & tool_path,bool with_text)406 sp_desktop_apply_style_tool(SPDesktop *desktop, Inkscape::XML::Node *repr, Glib::ustring const &tool_path, bool with_text)
407 {
408     SPCSSAttr *css_current = sp_desktop_get_style(desktop, with_text);
409     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
410 
411     if (prefs->getBool(tool_path + "/usecurrent") && css_current) {
412         sp_repr_css_unset_property(css_current, "mix-blend-mode");
413         sp_repr_css_unset_property(css_current, "filter");
414         sp_repr_css_set(repr, css_current, "style");
415     } else {
416         SPCSSAttr *css = prefs->getInheritedStyle(tool_path + "/style");
417         sp_repr_css_set(repr, css, "style");
418         sp_repr_css_attr_unref(css);
419     }
420     if (css_current) {
421         sp_repr_css_attr_unref(css_current);
422     }
423 }
424 
425 /**
426  * Returns the font size (in SVG pixels) of the text tool style (if text
427  * tool uses its own style) or desktop style (otherwise).
428 */
429 double
sp_desktop_get_font_size_tool(SPDesktop * desktop)430 sp_desktop_get_font_size_tool(SPDesktop *desktop)
431 {
432     (void)desktop; // TODO cleanup
433     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
434     Glib::ustring desktop_style = prefs->getString("/desktop/style");
435     Glib::ustring style_str;
436     if ((prefs->getBool("/tools/text/usecurrent")) && !desktop_style.empty()) {
437         style_str = desktop_style;
438     } else {
439         style_str = prefs->getString("/tools/text/style");
440     }
441 
442     double ret = 12;
443     if (!style_str.empty()) {
444         SPStyle style(SP_ACTIVE_DOCUMENT);
445         style.mergeString(style_str.data());
446         ret = style.font_size.computed;
447     }
448     return ret;
449 }
450 
451 /** Determine average stroke width, simple method */
452 // see TODO in dialogs/stroke-style.cpp on how to get rid of this eventually
453 gdouble
stroke_average_width(const std::vector<SPItem * > & objects)454 stroke_average_width (const std::vector<SPItem*> &objects)
455 {
456     if (objects.empty())
457         return Geom::infinity();
458 
459     gdouble avgwidth = 0.0;
460     bool notstroked = true;
461     int n_notstroked = 0;
462     for (auto item : objects) {
463         if (!item) {
464             continue;
465         }
466 
467         Geom::Affine i2dt = item->i2dt_affine();
468 
469         double width = item->style->stroke_width.computed * i2dt.descrim();
470 
471         // Width becomes NaN when scaling a diagonal line to a horizontal line (lp:825840)
472         if ( std::isnan(width)) {
473             ++n_notstroked;   // do not count nonstroked objects
474             continue;
475         } else {
476             notstroked = false;
477         }
478 
479         avgwidth += width;
480     }
481 
482     if (notstroked)
483         return Geom::infinity();
484 
485     return avgwidth / (objects.size() - n_notstroked);
486 }
487 
vectorsClose(std::vector<double> const & lhs,std::vector<double> const & rhs)488 static bool vectorsClose( std::vector<double> const &lhs, std::vector<double> const &rhs )
489 {
490     bool isClose = false;
491     if ( lhs.size() == rhs.size() ) {
492         static double epsilon = 1e-6;
493         isClose = true;
494         for ( size_t i = 0; (i < lhs.size()) && isClose; ++i ) {
495             isClose = fabs(lhs[i] - rhs[i]) < epsilon;
496         }
497     }
498     return isClose;
499 }
500 
501 
502 /**
503  * Write to style_res the average fill or stroke of list of objects, if applicable.
504  */
505 int
objects_query_fillstroke(const std::vector<SPItem * > & objects,SPStyle * style_res,bool const isfill)506 objects_query_fillstroke (const std::vector<SPItem*> &objects, SPStyle *style_res, bool const isfill)
507 {
508     if (objects.empty()) {
509         /* No objects, set empty */
510         return QUERY_STYLE_NOTHING;
511     }
512 
513     SPIPaint *paint_res = style_res->getFillOrStroke(isfill);
514     bool paintImpossible = true;
515     paint_res->set = true;
516 
517     SVGICCColor* iccColor = nullptr;
518 
519     bool iccSeen = false;
520     gfloat c[4];
521     c[0] = c[1] = c[2] = c[3] = 0.0;
522     gint num = 0;
523 
524     gfloat prev[3];
525     prev[0] = prev[1] = prev[2] = 0.0;
526     bool same_color = true;
527 
528     for (auto obj : objects) {
529         if (!obj) {
530             continue;
531         }
532         SPStyle *style = obj->style;
533         if (!style) {
534             continue;
535         }
536 
537         SPIPaint *paint = style->getFillOrStroke(isfill);
538         if (!paint) {
539             continue;
540         }
541 
542         // We consider paint "effectively set" for anything within text hierarchy
543         SPObject *parent = obj->parent;
544         bool paint_effectively_set =
545             paint->set || (dynamic_cast<SPText *>(parent) || dynamic_cast<SPTextPath *>(parent) || dynamic_cast<SPTSpan *>(parent)
546             || dynamic_cast<SPFlowtext *>(parent) || dynamic_cast<SPFlowdiv *>(parent) || dynamic_cast<SPFlowpara *>(parent)
547             || dynamic_cast<SPFlowtspan *>(parent) || dynamic_cast<SPFlowline*>(parent));
548 
549         // 1. Bail out with QUERY_STYLE_MULTIPLE_DIFFERENT if necessary
550 
551         // cppcheck-suppress comparisonOfBoolWithInt
552         if ((!paintImpossible) && (!paint->isSameType(*paint_res) || (paint_res->set != paint_effectively_set))) {
553             return QUERY_STYLE_MULTIPLE_DIFFERENT;  // different types of paint
554         }
555 
556         if (paint_res->set && paint->set && paint_res->isPaintserver()) {
557             // both previous paint and this paint were a server, see if the servers are compatible
558 
559             SPPaintServer *server_res = isfill ? style_res->getFillPaintServer() : style_res->getStrokePaintServer();
560             SPPaintServer *server = isfill ? style->getFillPaintServer() : style->getStrokePaintServer();
561 
562             SPLinearGradient *linear_res = dynamic_cast<SPLinearGradient *>(server_res);
563             SPRadialGradient *radial_res = linear_res ? nullptr : dynamic_cast<SPRadialGradient *>(server_res);
564             SPPattern *pattern_res = (linear_res || radial_res) ? nullptr : dynamic_cast<SPPattern *>(server_res);
565             SPHatch *hatch_res =
566                 (linear_res || radial_res || pattern_res) ? nullptr : dynamic_cast<SPHatch *>(server_res);
567 
568             if (linear_res) {
569                 SPLinearGradient *linear = dynamic_cast<SPLinearGradient *>(server);
570                 if (!linear) {
571                    return QUERY_STYLE_MULTIPLE_DIFFERENT;  // different kind of server
572                 }
573 
574                 SPGradient *vector = linear->getVector();
575                 SPGradient *vector_res = linear_res->getVector();
576                 if (vector_res != vector) {
577                    return QUERY_STYLE_MULTIPLE_DIFFERENT;  // different gradient vectors
578                 }
579             } else if (radial_res) {
580                 SPRadialGradient *radial = dynamic_cast<SPRadialGradient *>(server);
581                 if (!radial) {
582                    return QUERY_STYLE_MULTIPLE_DIFFERENT;  // different kind of server
583                 }
584 
585                 SPGradient *vector = radial->getVector();
586                 SPGradient *vector_res = radial_res->getVector();
587                 if (vector_res != vector) {
588                    return QUERY_STYLE_MULTIPLE_DIFFERENT;  // different gradient vectors
589                 }
590             } else if (pattern_res) {
591                 SPPattern *pattern = dynamic_cast<SPPattern *>(server);
592                 if (!pattern) {
593                    return QUERY_STYLE_MULTIPLE_DIFFERENT;  // different kind of server
594                 }
595 
596                 SPPattern *pat = SP_PATTERN (server)->rootPattern();
597                 SPPattern *pat_res = SP_PATTERN (server_res)->rootPattern();
598                 if (pat_res != pat) {
599                    return QUERY_STYLE_MULTIPLE_DIFFERENT;  // different pattern roots
600                 }
601             } else if (hatch_res) {
602                 SPHatch *hatch = dynamic_cast<SPHatch *>(server);
603                 if (!hatch) {
604                     return QUERY_STYLE_MULTIPLE_DIFFERENT; // different kind of server
605                 }
606 
607                 SPHatch *hat = SP_HATCH(server)->rootHatch();
608                 SPHatch *hat_res = SP_HATCH(server_res)->rootHatch();
609                 if (hat_res != hat) {
610                     return QUERY_STYLE_MULTIPLE_DIFFERENT; // different hatch roots
611                 }
612             }
613         }
614 
615         // 2. Sum color, copy server from paint to paint_res
616 
617         if (paint_res->set && paint_effectively_set && paint->isColor()) {
618             gfloat d[3];
619             paint->value.color.get_rgb_floatv(d);
620 
621             // Check if this color is the same as previous
622             if (paintImpossible) {
623                 prev[0] = d[0];
624                 prev[1] = d[1];
625                 prev[2] = d[2];
626                 paint_res->setColor(d[0], d[1], d[2]);
627                 iccColor = paint->value.color.icc;
628                 iccSeen = true;
629             } else {
630                 if (same_color && (prev[0] != d[0] || prev[1] != d[1] || prev[2] != d[2])) {
631                     same_color = false;
632                     iccColor = nullptr;
633                 }
634                 if ( iccSeen && iccColor ) {
635                     if ( !paint->value.color.icc
636                          || (iccColor->colorProfile != paint->value.color.icc->colorProfile)
637                          || !vectorsClose(iccColor->colors, paint->value.color.icc->colors) ) {
638                         same_color = false;
639                         iccColor = nullptr;
640                     }
641                 }
642             }
643 
644             // average color
645             c[0] += d[0];
646             c[1] += d[1];
647             c[2] += d[2];
648             c[3] += SP_SCALE24_TO_FLOAT (isfill? style->fill_opacity.value : style->stroke_opacity.value);
649 
650             num ++;
651         }
652 
653        paintImpossible = false;
654        paint_res->colorSet = paint->colorSet;
655        paint_res->paintOrigin = paint->paintOrigin;
656        if (paint_res->set && paint_effectively_set && paint->isPaintserver()) { // copy the server
657            if (isfill) {
658                sp_style_set_to_uri(style_res, true, style->getFillURI());
659            } else {
660                sp_style_set_to_uri(style_res, false, style->getStrokeURI());
661            }
662        }
663        paint_res->set = paint_effectively_set;
664        style_res->fill_rule.computed = style->fill_rule.computed; // no averaging on this, just use the last one
665     }
666 
667     // After all objects processed, divide the color if necessary and return
668     if (paint_res->set && paint_res->isColor()) { // set the color
669         g_assert (num >= 1);
670 
671         c[0] /= num;
672         c[1] /= num;
673         c[2] /= num;
674         c[3] /= num;
675         paint_res->setColor(c[0], c[1], c[2]);
676         if (isfill) {
677             style_res->fill_opacity.value = SP_SCALE24_FROM_FLOAT (c[3]);
678         } else {
679             style_res->stroke_opacity.value = SP_SCALE24_FROM_FLOAT (c[3]);
680         }
681 
682 
683         if ( iccSeen && iccColor ) {
684             // TODO check for existing
685             SVGICCColor* tmp = new SVGICCColor(*iccColor);
686             paint_res->value.color.icc = tmp;
687         }
688 
689         if (num > 1) {
690             if (same_color)
691                 return QUERY_STYLE_MULTIPLE_SAME;
692             else
693                 return QUERY_STYLE_MULTIPLE_AVERAGED;
694         } else {
695             return QUERY_STYLE_SINGLE;
696         }
697     }
698 
699     // Not color
700     if (objects.size() > 1) {
701         return QUERY_STYLE_MULTIPLE_SAME;
702     } else {
703         return QUERY_STYLE_SINGLE;
704     }
705 }
706 
707 /**
708  * Write to style_res the average opacity of a list of objects.
709  */
710 int
objects_query_opacity(const std::vector<SPItem * > & objects,SPStyle * style_res)711 objects_query_opacity (const std::vector<SPItem*> &objects, SPStyle *style_res)
712 {
713     if (objects.empty()) {
714         /* No objects, set empty */
715         return QUERY_STYLE_NOTHING;
716     }
717 
718     gdouble opacity_sum = 0;
719     gdouble opacity_prev = -1;
720     bool same_opacity = true;
721     guint opacity_items = 0;
722 
723 
724     for (auto obj : objects) {
725         if (!obj) {
726             continue;
727         }
728         SPStyle *style = obj->style;
729         if (!style) {
730             continue;
731         }
732 
733         double opacity = SP_SCALE24_TO_FLOAT(style->opacity.value);
734         opacity_sum += opacity;
735         if (opacity_prev != -1 && opacity != opacity_prev) {
736             same_opacity = false;
737         }
738         opacity_prev = opacity;
739         opacity_items ++;
740     }
741     if (opacity_items > 1) {
742         opacity_sum /= opacity_items;
743     }
744 
745     style_res->opacity.value = SP_SCALE24_FROM_FLOAT(opacity_sum);
746 
747     if (opacity_items == 0) {
748         return QUERY_STYLE_NOTHING;
749     } else if (opacity_items == 1) {
750         return QUERY_STYLE_SINGLE;
751     } else {
752         if (same_opacity) {
753             return QUERY_STYLE_MULTIPLE_SAME;
754         } else {
755             return QUERY_STYLE_MULTIPLE_AVERAGED;
756         }
757     }
758 }
759 
760 /**
761  * Write to style_res the average stroke width of a list of objects.
762  */
763 int
objects_query_strokewidth(const std::vector<SPItem * > & objects,SPStyle * style_res)764 objects_query_strokewidth (const std::vector<SPItem*> &objects, SPStyle *style_res)
765 {
766     if (objects.empty()) {
767         /* No objects, set empty */
768         return QUERY_STYLE_NOTHING;
769     }
770 
771     gdouble avgwidth = 0.0;
772 
773     gdouble prev_sw = -1;
774     bool same_sw = true;
775     bool noneSet = true; // is stroke set to none?
776     bool prev_hairline;
777 
778     int n_stroked = 0;
779 
780     for (auto obj : objects) {
781         if (!obj) {
782             continue;
783         }
784         SPItem *item = dynamic_cast<SPItem *>(obj);
785         if (!item) {
786             continue;
787         }
788         SPStyle *style = obj->style;
789         if (!style) {
790             continue;
791         }
792 
793         noneSet &= style->stroke.isNone();
794 
795         if (style->stroke_extensions.hairline) {
796             // Can't average a bool. It's true if there's any hairlines in the selection.
797             style_res->stroke_extensions.hairline = true;
798         }
799 
800         if (n_stroked > 0 && prev_hairline != style->stroke_extensions.hairline) {
801             same_sw = false;
802         }
803         prev_hairline = style->stroke_extensions.hairline;
804 
805         Geom::Affine i2d = item->i2dt_affine();
806         double sw = style->stroke_width.computed * i2d.descrim();
807 
808         if (!std::isnan(sw)) {
809             if (prev_sw != -1 && fabs(sw - prev_sw) > 1e-3)
810                 same_sw = false;
811             prev_sw = sw;
812 
813             avgwidth += sw;
814             n_stroked ++;
815         } else if (style->stroke_extensions.hairline) {
816             n_stroked ++;
817         }
818     }
819 
820     if (n_stroked > 1)
821         avgwidth /= (n_stroked);
822 
823     style_res->stroke_width.computed = avgwidth;
824     style_res->stroke_width.set = true;
825     style_res->stroke.noneSet = noneSet; // Will only be true if none of the selected objects has it's stroke set.
826 
827     if (n_stroked == 0) {
828         return QUERY_STYLE_NOTHING;
829     } else if (n_stroked == 1) {
830         return QUERY_STYLE_SINGLE;
831     } else {
832         if (same_sw)
833             return QUERY_STYLE_MULTIPLE_SAME;
834         else
835             return QUERY_STYLE_MULTIPLE_AVERAGED;
836     }
837 }
838 
839 /**
840  * Write to style_res the average miter limit of a list of objects.
841  */
842 int
objects_query_miterlimit(const std::vector<SPItem * > & objects,SPStyle * style_res)843 objects_query_miterlimit (const std::vector<SPItem*> &objects, SPStyle *style_res)
844 {
845     if (objects.empty()) {
846         /* No objects, set empty */
847         return QUERY_STYLE_NOTHING;
848     }
849 
850     gdouble avgml = 0.0;
851     int n_stroked = 0;
852 
853     gdouble prev_ml = -1;
854     bool same_ml = true;
855 
856     for (auto obj : objects) {
857         if (!dynamic_cast<SPItem *>(obj)) {
858             continue;
859         }
860         SPStyle *style = obj->style;
861         if (!style) {
862             continue;
863         }
864 
865         if ( style->stroke.isNone() ) {
866             continue;
867         }
868 
869         n_stroked ++;
870 
871         if (prev_ml != -1 && fabs(style->stroke_miterlimit.value - prev_ml) > 1e-3) {
872             same_ml = false;
873         }
874         prev_ml = style->stroke_miterlimit.value;
875 
876         avgml += style->stroke_miterlimit.value;
877     }
878 
879     if (n_stroked > 1) {
880         avgml /= (n_stroked);
881     }
882 
883     style_res->stroke_miterlimit.value = avgml;
884     style_res->stroke_miterlimit.set = true;
885 
886     if (n_stroked == 0) {
887         return QUERY_STYLE_NOTHING;
888     } else if (n_stroked == 1) {
889         return QUERY_STYLE_SINGLE;
890     } else {
891         if (same_ml)
892             return QUERY_STYLE_MULTIPLE_SAME;
893         else
894             return QUERY_STYLE_MULTIPLE_AVERAGED;
895     }
896 }
897 
898 /**
899  * Write to style_res the stroke cap of a list of objects.
900  */
901 int
objects_query_strokecap(const std::vector<SPItem * > & objects,SPStyle * style_res)902 objects_query_strokecap (const std::vector<SPItem*> &objects, SPStyle *style_res)
903 {
904     if (objects.empty()) {
905         /* No objects, set empty */
906         return QUERY_STYLE_NOTHING;
907     }
908 
909     auto prev_cap = SP_STROKE_LINECAP_BUTT;
910     bool same_cap = true;
911     int n_stroked = 0;
912 
913     for (auto obj : objects) {
914         if (!dynamic_cast<SPItem *>(obj)) {
915             continue;
916         }
917         SPStyle *style = obj->style;
918         if (!style) {
919             continue;
920         }
921 
922         if ( style->stroke.isNone() ) {
923             continue;
924         }
925 
926         n_stroked ++;
927 
928         if (n_stroked > 1 && style->stroke_linecap.value != prev_cap)
929             same_cap = false;
930         prev_cap = style->stroke_linecap.value;
931     }
932 
933     style_res->stroke_linecap.value = prev_cap;
934     style_res->stroke_linecap.set = true;
935 
936     if (n_stroked == 0) {
937         return QUERY_STYLE_NOTHING;
938     } else if (n_stroked == 1) {
939         return QUERY_STYLE_SINGLE;
940     } else {
941         if (same_cap)
942             return QUERY_STYLE_MULTIPLE_SAME;
943         else
944             return QUERY_STYLE_MULTIPLE_DIFFERENT;
945     }
946 }
947 
948 /**
949  * Write to style_res the stroke join of a list of objects.
950  */
951 int
objects_query_strokejoin(const std::vector<SPItem * > & objects,SPStyle * style_res)952 objects_query_strokejoin (const std::vector<SPItem*> &objects, SPStyle *style_res)
953 {
954     if (objects.empty()) {
955         /* No objects, set empty */
956         return QUERY_STYLE_NOTHING;
957     }
958 
959     auto prev_join = SP_STROKE_LINEJOIN_MITER;
960     bool same_join = true;
961     int n_stroked = 0;
962 
963     for (auto obj : objects) {
964         if (!dynamic_cast<SPItem *>(obj)) {
965             continue;
966         }
967         SPStyle *style = obj->style;
968         if (!style) {
969             continue;
970         }
971 
972         if ( style->stroke.isNone() ) {
973             continue;
974         }
975 
976         n_stroked ++;
977 
978         if (n_stroked > 1 && style->stroke_linejoin.value != prev_join) {
979             same_join = false;
980         }
981         prev_join = style->stroke_linejoin.value;
982     }
983 
984     style_res->stroke_linejoin.value = prev_join;
985     style_res->stroke_linejoin.set = true;
986 
987     if (n_stroked == 0) {
988         return QUERY_STYLE_NOTHING;
989     } else if (n_stroked == 1) {
990         return QUERY_STYLE_SINGLE;
991     } else {
992         if (same_join)
993             return QUERY_STYLE_MULTIPLE_SAME;
994         else
995             return QUERY_STYLE_MULTIPLE_DIFFERENT;
996     }
997 }
998 
999 /**
1000  * Write to style_res the paint order of a list of objects.
1001  */
1002 int
objects_query_paintorder(const std::vector<SPItem * > & objects,SPStyle * style_res)1003 objects_query_paintorder (const std::vector<SPItem*> &objects, SPStyle *style_res)
1004 {
1005     if (objects.empty()) {
1006         /* No objects, set empty */
1007         return QUERY_STYLE_NOTHING;
1008     }
1009 
1010     std::string prev_order;
1011     bool same_order = true;
1012     int n_order = 0;
1013 
1014     for (auto obj : objects) {
1015         if (!dynamic_cast<SPItem *>(obj)) {
1016             continue;
1017         }
1018         SPStyle *style = obj->style;
1019         if (!style) {
1020             continue;
1021         }
1022 
1023         if ( style->stroke.isNone() ) {
1024             continue;
1025         }
1026 
1027         n_order ++;
1028 
1029         if (style->paint_order.set) {
1030             if (!prev_order.empty() && prev_order.compare( style->paint_order.value ) != 0) {
1031                 same_order = false;
1032             }
1033             prev_order = style->paint_order.value;
1034         }
1035     }
1036 
1037 
1038     g_free( style_res->paint_order.value );
1039     style_res->paint_order.value= g_strdup( prev_order.c_str() );
1040     style_res->paint_order.set = true;
1041 
1042     if (n_order == 0) {
1043         return QUERY_STYLE_NOTHING;
1044     } else if (n_order == 1) {
1045         return QUERY_STYLE_SINGLE;
1046     } else {
1047         if (same_order)
1048             return QUERY_STYLE_MULTIPLE_SAME;
1049         else
1050             return QUERY_STYLE_MULTIPLE_DIFFERENT;
1051     }
1052 }
1053 
1054 /**
1055  * Write to style_res the average font size and spacing of objects.
1056  */
1057 int
objects_query_fontnumbers(const std::vector<SPItem * > & objects,SPStyle * style_res)1058 objects_query_fontnumbers (const std::vector<SPItem*> &objects, SPStyle *style_res)
1059 {
1060     bool different = false;
1061     bool different_lineheight = false;
1062     bool different_lineheight_unit = false;
1063 
1064     double size = 0;
1065     double letterspacing = 0;
1066     double wordspacing = 0;
1067     double lineheight = 0;
1068     bool letterspacing_normal = false;
1069     bool wordspacing_normal = false;
1070     bool lineheight_normal = false;
1071     bool lineheight_unit_proportional = false;
1072     bool lineheight_unit_absolute = false;
1073     bool lineheight_set = false; // Set true if any object has lineheight set.
1074 
1075     double size_prev = 0;
1076     double letterspacing_prev = 0;
1077     double wordspacing_prev = 0;
1078     double lineheight_prev = 0;
1079     int  lineheight_unit_prev = -1;
1080 
1081     int texts = 0;
1082     int no_size = 0;
1083 
1084     for (auto obj : objects) {
1085         if (!isTextualItem(obj)) {
1086             continue;
1087         }
1088 
1089         SPStyle *style = obj->style;
1090         if (!style) {
1091             continue;
1092         }
1093 
1094         texts ++;
1095         SPItem *item = dynamic_cast<SPItem *>(obj);
1096         g_assert(item != nullptr);
1097 
1098         // Quick way of getting document scale. Should be same as:
1099         // item->document->getDocumentScale().Affine().descrim()
1100         double doc_scale = Geom::Affine(item->i2dt_affine()).descrim();
1101 
1102         double dummy = style->font_size.computed * doc_scale;
1103         if (!std::isnan(dummy)) {
1104             size += dummy; /// \todo FIXME: we assume non-% units here
1105         } else {
1106             no_size++;
1107         }
1108 
1109         if (style->letter_spacing.normal) {
1110             if (!different && (letterspacing_prev == 0 || letterspacing_prev == letterspacing)) {
1111                 letterspacing_normal = true;
1112             }
1113         } else {
1114             letterspacing += style->letter_spacing.computed * doc_scale;; /// \todo FIXME: we assume non-% units here
1115             letterspacing_normal = false;
1116         }
1117 
1118         if (style->word_spacing.normal) {
1119             if (!different && (wordspacing_prev == 0 || wordspacing_prev == wordspacing)) {
1120                 wordspacing_normal = true;
1121             }
1122         } else {
1123             wordspacing += style->word_spacing.computed * doc_scale; /// \todo FIXME: we assume non-% units here
1124             wordspacing_normal = false;
1125         }
1126 
1127         // If all line spacing units the same, use that (average line spacing).
1128         // Else if all line spacings absolute, use 'px' (average line spacing).
1129         // Else if all line spacings proportional, use % (average line spacing).
1130         // Else use default.
1131         double lineheight_current;
1132         int    lineheight_unit_current;
1133         if (style->line_height.normal) {
1134             lineheight_current = Inkscape::Text::Layout::LINE_HEIGHT_NORMAL;
1135             lineheight_unit_current = SP_CSS_UNIT_NONE;
1136             if (!different_lineheight &&
1137                 (lineheight_prev == 0 || lineheight_prev == lineheight_current))
1138                 lineheight_normal = true;
1139         } else if (style->line_height.unit == SP_CSS_UNIT_NONE ||
1140                    style->line_height.unit == SP_CSS_UNIT_PERCENT ||
1141                    style->line_height.unit == SP_CSS_UNIT_EM ||
1142                    style->line_height.unit == SP_CSS_UNIT_EX ||
1143                    style->font_size.computed == 0) {
1144             lineheight_current = style->line_height.value;
1145             lineheight_unit_current = style->line_height.unit;
1146             lineheight_unit_proportional = true;
1147             lineheight_normal = false;
1148             lineheight += lineheight_current;
1149         } else {
1150             // Always 'px' internally
1151             lineheight_current = style->line_height.computed;
1152             lineheight_unit_current = style->line_height.unit;
1153             lineheight_unit_absolute = true;
1154             lineheight_normal = false;
1155             lineheight += lineheight_current * doc_scale;
1156         }
1157         if (style->line_height.set) {
1158             lineheight_set = true;
1159         }
1160 
1161         if ((size_prev != 0 && style->font_size.computed != size_prev) ||
1162             (letterspacing_prev != 0 && style->letter_spacing.computed != letterspacing_prev) ||
1163             (wordspacing_prev != 0 && style->word_spacing.computed != wordspacing_prev)) {
1164             different = true;
1165         }
1166 
1167         if (lineheight_prev != 0 && lineheight_current != lineheight_prev) {
1168             different_lineheight = true;
1169         }
1170 
1171         if (lineheight_unit_prev != -1 && lineheight_unit_current != lineheight_unit_prev) {
1172             different_lineheight_unit = true;
1173         }
1174 
1175         size_prev = style->font_size.computed;
1176         letterspacing_prev = style->letter_spacing.computed;
1177         wordspacing_prev = style->word_spacing.computed;
1178         lineheight_prev = lineheight_current;
1179         lineheight_unit_prev = lineheight_unit_current;
1180 
1181         // FIXME: we must detect MULTIPLE_DIFFERENT for these too
1182         style_res->text_anchor.computed = style->text_anchor.computed;
1183     }
1184 
1185     if (texts == 0)
1186         return QUERY_STYLE_NOTHING;
1187 
1188     if (texts > 1) {
1189         if (texts - no_size > 0) {
1190             size /= (texts - no_size);
1191         }
1192         letterspacing /= texts;
1193         wordspacing /= texts;
1194         lineheight /= texts;
1195     }
1196 
1197     style_res->font_size.computed = size;
1198     style_res->font_size.type = SP_FONT_SIZE_LENGTH;
1199 
1200     style_res->letter_spacing.normal = letterspacing_normal;
1201     style_res->letter_spacing.computed = letterspacing;
1202 
1203     style_res->word_spacing.normal = wordspacing_normal;
1204     style_res->word_spacing.computed = wordspacing;
1205 
1206     style_res->line_height.normal = lineheight_normal;
1207     style_res->line_height.computed = lineheight;
1208     style_res->line_height.value = lineheight;
1209     if (different_lineheight_unit) {
1210         if (lineheight_unit_absolute && !lineheight_unit_proportional) {
1211             // Mixture of absolute units
1212             style_res->line_height.unit = SP_CSS_UNIT_PX;
1213         } else {
1214             // Mixture of relative units
1215             style_res->line_height.unit = SP_CSS_UNIT_PERCENT;
1216         }
1217         if (lineheight_unit_absolute && lineheight_unit_proportional) {
1218             // Mixed types of units, fallback to default
1219             style_res->line_height.computed = Inkscape::Text::Layout::LINE_HEIGHT_NORMAL * 100.0;
1220             style_res->line_height.value    = Inkscape::Text::Layout::LINE_HEIGHT_NORMAL * 100.0;
1221         }
1222     } else {
1223         // Same units.
1224         if (lineheight_unit_prev != -1) {
1225             style_res->line_height.unit = lineheight_unit_prev;
1226         } else {
1227             // No text object... use default.
1228             style_res->line_height.unit = SP_CSS_UNIT_NONE;
1229             style_res->line_height.computed = Inkscape::Text::Layout::LINE_HEIGHT_NORMAL;
1230             style_res->line_height.value    = Inkscape::Text::Layout::LINE_HEIGHT_NORMAL;
1231         }
1232     }
1233 
1234     // Used by text toolbar unset 'line-height'
1235     style_res->line_height.set = lineheight_set;
1236 
1237     if (texts > 1) {
1238         if (different || different_lineheight) {
1239             return QUERY_STYLE_MULTIPLE_AVERAGED;
1240         } else {
1241             return QUERY_STYLE_MULTIPLE_SAME;
1242         }
1243     } else {
1244         return QUERY_STYLE_SINGLE;
1245     }
1246 }
1247 
1248 /**
1249  * Write to style_res the average font style of objects.
1250  */
1251 int
objects_query_fontstyle(const std::vector<SPItem * > & objects,SPStyle * style_res)1252 objects_query_fontstyle (const std::vector<SPItem*> &objects, SPStyle *style_res)
1253 {
1254     bool different = false;
1255     bool set = false;
1256 
1257     int texts = 0;
1258 
1259     for (auto obj : objects) {
1260         if (!isTextualItem(obj)) {
1261             continue;
1262         }
1263 
1264         SPStyle *style = obj->style;
1265         if (!style) {
1266             continue;
1267         }
1268 
1269         texts ++;
1270 
1271         if (set &&
1272             ( ( style_res->font_weight.computed    != style->font_weight.computed  ) ||
1273               ( style_res->font_style.computed     != style->font_style.computed   ) ||
1274               ( style_res->font_stretch.computed   != style->font_stretch.computed ) ||
1275               ( style_res->font_variant.computed   != style->font_variant.computed ) ||
1276               ( style_res->font_variation_settings != style->font_variation_settings ) ) ) {
1277             different = true;  // different styles
1278         }
1279 
1280         set = true;
1281         style_res->font_weight.value = style_res->font_weight.computed = style->font_weight.computed;
1282         style_res->font_style.value = style_res->font_style.computed = style->font_style.computed;
1283         style_res->font_stretch.value = style_res->font_stretch.computed = style->font_stretch.computed;
1284         style_res->font_variant.value = style_res->font_variant.computed = style->font_variant.computed;
1285         style_res->font_variation_settings = style->font_variation_settings;
1286         style_res->text_align.value = style_res->text_align.computed = style->text_align.computed;
1287         style_res->font_size.value = style->font_size.value;
1288         style_res->font_size.unit = style->font_size.unit;
1289     }
1290 
1291     if (texts == 0 || !set)
1292         return QUERY_STYLE_NOTHING;
1293 
1294     if (texts > 1) {
1295         if (different) {
1296             return QUERY_STYLE_MULTIPLE_DIFFERENT;
1297         } else {
1298             return QUERY_STYLE_MULTIPLE_SAME;
1299         }
1300     } else {
1301         return QUERY_STYLE_SINGLE;
1302     }
1303 }
1304 
1305 int
objects_query_fontvariants(const std::vector<SPItem * > & objects,SPStyle * style_res)1306 objects_query_fontvariants (const std::vector<SPItem*> &objects, SPStyle *style_res)
1307 {
1308     bool set = false;
1309 
1310     int texts = 0;
1311 
1312     SPILigatures* ligatures_res = &(style_res->font_variant_ligatures);
1313     SPINumeric* numeric_res     = &(style_res->font_variant_numeric);
1314     SPIEastAsian* asian_res     = &(style_res->font_variant_east_asian);
1315 
1316     // Stores 'and' of all values
1317     ligatures_res->computed = SP_CSS_FONT_VARIANT_LIGATURES_NORMAL;
1318     int position_computed   = SP_CSS_FONT_VARIANT_POSITION_NORMAL;
1319     int caps_computed       = SP_CSS_FONT_VARIANT_CAPS_NORMAL;
1320     numeric_res->computed   = SP_CSS_FONT_VARIANT_NUMERIC_NORMAL;
1321     asian_res->computed     = SP_CSS_FONT_VARIANT_EAST_ASIAN_NORMAL;
1322 
1323     // Stores only differences
1324     ligatures_res->value = 0;
1325     int position_value   = 0;
1326     int caps_value       = 0;
1327     numeric_res->value   = 0;
1328     asian_res->value     = 0;
1329 
1330     for (auto obj : objects) {
1331         if (!isTextualItem(obj)) {
1332             continue;
1333         }
1334 
1335         SPStyle *style = obj->style;
1336         if (!style) {
1337             continue;
1338         }
1339 
1340         texts ++;
1341 
1342         SPILigatures* ligatures_in = &(style->font_variant_ligatures);
1343         auto*         position_in  = &(style->font_variant_position);
1344         auto*         caps_in      = &(style->font_variant_caps);
1345         SPINumeric*   numeric_in   = &(style->font_variant_numeric);
1346         SPIEastAsian* asian_in     = &(style->font_variant_east_asian);
1347 
1348         // computed stores which bits are on/off, only valid if same between all selected objects.
1349         // value stores which bits are different between objects. This is a bit of an abuse of
1350         // the values but then we don't need to add new variables to class.
1351         if (set) {
1352             ligatures_res->value  |= (ligatures_res->computed ^ ligatures_in->computed );
1353             ligatures_res->computed &= ligatures_in->computed;
1354 
1355             position_value |= (position_computed ^ position_in->computed);
1356             position_computed &= position_in->computed;
1357 
1358             caps_value |= (caps_computed ^ caps_in->computed);
1359             caps_computed &= caps_in->computed;
1360 
1361             numeric_res->value  |= (numeric_res->computed ^ numeric_in->computed );
1362             numeric_res->computed &= numeric_in->computed;
1363 
1364             asian_res->value  |= (asian_res->computed ^ asian_in->computed );
1365             asian_res->computed &= asian_in->computed;
1366 
1367         } else {
1368             ligatures_res->computed  = ligatures_in->computed;
1369             position_computed        = position_in->computed;
1370             caps_computed            = caps_in->computed;
1371             numeric_res->computed    = numeric_in->computed;
1372             asian_res->computed      = asian_in->computed;
1373         }
1374 
1375         set = true;
1376     }
1377 
1378     // violates enum type safety!
1379     style_res->font_variant_position.value = static_cast<SPCSSFontVariantPosition>(position_value);
1380     style_res->font_variant_position.computed = static_cast<SPCSSFontVariantPosition>(position_computed);
1381     style_res->font_variant_caps.value = static_cast<SPCSSFontVariantCaps>(caps_value);
1382     style_res->font_variant_caps.computed = static_cast<SPCSSFontVariantCaps>(caps_computed);
1383 
1384     bool different = (style_res->font_variant_ligatures.value  != 0 ||
1385                       position_value                           != 0 ||
1386                       caps_value                               != 0 ||
1387                       style_res->font_variant_numeric.value    != 0 ||
1388                       style_res->font_variant_east_asian.value != 0);
1389 
1390     if (texts == 0 || !set)
1391         return QUERY_STYLE_NOTHING;
1392 
1393     if (texts > 1) {
1394         if (different) {
1395             return QUERY_STYLE_MULTIPLE_DIFFERENT;
1396         } else {
1397             return QUERY_STYLE_MULTIPLE_SAME;
1398         }
1399     } else {
1400         return QUERY_STYLE_SINGLE;
1401     }
1402 }
1403 
1404 
1405 /**
1406  * Write to style_res the average writing modes style of objects.
1407  */
1408 int
objects_query_writing_modes(const std::vector<SPItem * > & objects,SPStyle * style_res)1409 objects_query_writing_modes (const std::vector<SPItem*> &objects, SPStyle *style_res)
1410 {
1411     bool different = false;
1412     bool set = false;
1413 
1414     int texts = 0;
1415 
1416     for (auto obj : objects) {
1417         if (!isTextualItem(obj)) {
1418             continue;
1419         }
1420 
1421         SPStyle *style = obj->style;
1422         if (!style) {
1423             continue;
1424         }
1425 
1426         texts ++;
1427 
1428         if (set &&
1429             ( ( style_res->writing_mode.computed     != style->writing_mode.computed     ) ||
1430               ( style_res->direction.computed        != style->direction.computed        ) ||
1431               ( style_res->text_orientation.computed != style->text_orientation.computed ) ) ) {
1432             different = true;  // different styles
1433         }
1434 
1435         set = true;
1436         style_res->writing_mode.computed = style->writing_mode.computed;
1437         style_res->direction.computed = style->direction.computed;
1438         style_res->text_orientation.computed = style->text_orientation.computed;
1439     }
1440 
1441     if (texts == 0 || !set)
1442         return QUERY_STYLE_NOTHING;
1443 
1444     if (texts > 1) {
1445         if (different) {
1446             return QUERY_STYLE_MULTIPLE_DIFFERENT;
1447         } else {
1448             return QUERY_STYLE_MULTIPLE_SAME;
1449         }
1450     } else {
1451         return QUERY_STYLE_SINGLE;
1452     }
1453 }
1454 
1455 int
objects_query_fontfeaturesettings(const std::vector<SPItem * > & objects,SPStyle * style_res)1456 objects_query_fontfeaturesettings (const std::vector<SPItem*> &objects, SPStyle *style_res)
1457 {
1458     bool different = false;
1459     int texts = 0;
1460 
1461     style_res->font_feature_settings.clear();
1462 
1463     for (auto obj : objects) {
1464         // std::cout << "  " << reinterpret_cast<SPObject*>(i->data)->getId() << std::endl;
1465         if (!isTextualItem(obj)) {
1466             continue;
1467         }
1468 
1469         SPStyle *style = obj->style;
1470         if (!style) {
1471             continue;
1472         }
1473 
1474         texts ++;
1475 
1476         if (style_res->font_feature_settings.set && //
1477             strcmp(style_res->font_feature_settings.value(),
1478                    style->font_feature_settings.value())) {
1479             different = true;  // different fonts
1480         }
1481 
1482         style_res->font_feature_settings = style->font_feature_settings;
1483         style_res->font_feature_settings.set = true;
1484     }
1485 
1486     if (texts == 0 || !style_res->font_feature_settings.set) {
1487         return QUERY_STYLE_NOTHING;
1488     }
1489 
1490     if (texts > 1) {
1491         if (different) {
1492             return QUERY_STYLE_MULTIPLE_DIFFERENT;
1493         } else {
1494             return QUERY_STYLE_MULTIPLE_SAME;
1495         }
1496     } else {
1497         return QUERY_STYLE_SINGLE;
1498     }
1499 }
1500 
1501 
1502 /**
1503  * Write to style_res the baseline numbers.
1504  */
1505 static int
objects_query_baselines(const std::vector<SPItem * > & objects,SPStyle * style_res)1506 objects_query_baselines (const std::vector<SPItem*> &objects, SPStyle *style_res)
1507 {
1508     bool different = false;
1509 
1510     // Only baseline-shift at the moment
1511     // We will return:
1512     //   If baseline-shift is same for all objects:
1513     //     The full baseline-shift data (used for subscripts and superscripts)
1514     //   If baseline-shift is different:
1515     //     The average baseline-shift (not implemented at the moment as this is complicated June 2010)
1516     SPIBaselineShift old;
1517     old.value = 0.0;
1518     old.computed = 0.0;
1519 
1520     // double baselineshift = 0.0;
1521     bool set = false;
1522 
1523     int texts = 0;
1524 
1525     for (auto obj : objects) {
1526         if (!isTextualItem(obj)) {
1527             continue;
1528         }
1529 
1530         SPStyle *style = obj->style;
1531         if (!style) {
1532             continue;
1533         }
1534 
1535         texts ++;
1536 
1537         SPIBaselineShift current;
1538         if(style->baseline_shift.set) {
1539 
1540             current.set      = style->baseline_shift.set;
1541             current.inherit  = style->baseline_shift.inherit;
1542             current.type     = style->baseline_shift.type;
1543             current.literal  = style->baseline_shift.literal;
1544             current.value    = style->baseline_shift.value;
1545             current.computed = style->baseline_shift.computed;
1546 
1547             if( set ) {
1548                 if( current.set      != old.set ||
1549                     current.inherit  != old.inherit ||
1550                     current.type     != old.type ||
1551                     current.literal  != old.literal ||
1552                     current.value    != old.value ||
1553                     current.computed != old.computed ) {
1554                     // Maybe this needs to be better thought out.
1555                     different = true;
1556                 }
1557             }
1558 
1559             set = true;
1560 
1561             old.set      = current.set;
1562             old.inherit  = current.inherit;
1563             old.type     = current.type;
1564             old.literal  = current.literal;
1565             old.value    = current.value;
1566             old.computed = current.computed;
1567         }
1568     }
1569 
1570     if (different || !set ) {
1571         style_res->baseline_shift.set = false;
1572         style_res->baseline_shift.computed = 0.0;
1573     } else {
1574         style_res->baseline_shift.set      = old.set;
1575         style_res->baseline_shift.inherit  = old.inherit;
1576         style_res->baseline_shift.type     = old.type;
1577         style_res->baseline_shift.literal  = old.literal;
1578         style_res->baseline_shift.value    = old.value;
1579         style_res->baseline_shift.computed = old.computed;
1580     }
1581 
1582     if (texts == 0 || !set)
1583         return QUERY_STYLE_NOTHING;
1584 
1585     if (texts > 1) {
1586         if (different) {
1587             return QUERY_STYLE_MULTIPLE_DIFFERENT;
1588         } else {
1589             return QUERY_STYLE_MULTIPLE_SAME;
1590         }
1591     } else {
1592         return QUERY_STYLE_SINGLE;
1593     }
1594 }
1595 
1596 /**
1597  * Write to style_res the average font family of objects.
1598  */
1599 int
objects_query_fontfamily(const std::vector<SPItem * > & objects,SPStyle * style_res)1600 objects_query_fontfamily (const std::vector<SPItem*> &objects, SPStyle *style_res)
1601 {
1602     bool different = false;
1603     int texts = 0;
1604 
1605     style_res->font_family.clear();
1606 
1607     for (auto obj : objects) {
1608         // std::cout << "  " << reinterpret_cast<SPObject*>(i->data)->getId() << std::endl;
1609         if (!isTextualItem(obj)) {
1610             continue;
1611         }
1612 
1613         SPStyle *style = obj->style;
1614         if (!style) {
1615             continue;
1616         }
1617 
1618         texts ++;
1619 
1620         if (style_res->font_family.set && //
1621             strcmp(style_res->font_family.value(), style->font_family.value())) {
1622             different = true;  // different fonts
1623         }
1624 
1625         style_res->font_family = style->font_family;
1626         style_res->font_family.set = true;
1627     }
1628 
1629     if (texts == 0 || !style_res->font_family.set) {
1630         return QUERY_STYLE_NOTHING;
1631     }
1632 
1633     if (texts > 1) {
1634         if (different) {
1635             return QUERY_STYLE_MULTIPLE_DIFFERENT;
1636         } else {
1637             return QUERY_STYLE_MULTIPLE_SAME;
1638         }
1639     } else {
1640         return QUERY_STYLE_SINGLE;
1641     }
1642 }
1643 
1644 static int
objects_query_fontspecification(const std::vector<SPItem * > & objects,SPStyle * style_res)1645 objects_query_fontspecification (const std::vector<SPItem*> &objects, SPStyle *style_res)
1646 {
1647     bool different = false;
1648     int texts = 0;
1649 
1650     style_res->font_specification.clear();
1651 
1652     for (auto obj : objects) {
1653         // std::cout << "  " << reinterpret_cast<SPObject*>(i->data)->getId() << std::endl;
1654         if (!isTextualItem(obj)) {
1655             continue;
1656         }
1657 
1658         SPStyle *style = obj->style;
1659         if (!style) {
1660             continue;
1661         }
1662 
1663         texts ++;
1664 
1665         if (style_res->font_specification.set &&
1666             g_strcmp0(style_res->font_specification.value(), style->font_specification.value())) {
1667             different = true;  // different fonts
1668         }
1669 
1670         if (style->font_specification.set) {
1671             style_res->font_specification = style->font_specification;
1672             style_res->font_specification.set = true;
1673         }
1674     }
1675 
1676     if (texts == 0) {
1677         return QUERY_STYLE_NOTHING;
1678     }
1679 
1680     if (texts > 1) {
1681         if (different) {
1682             return QUERY_STYLE_MULTIPLE_DIFFERENT;
1683         } else {
1684             return QUERY_STYLE_MULTIPLE_SAME;
1685         }
1686     } else {
1687         return QUERY_STYLE_SINGLE;
1688     }
1689 }
1690 
1691 int
objects_query_blend(const std::vector<SPItem * > & objects,SPStyle * style_res)1692 objects_query_blend (const std::vector<SPItem*> &objects, SPStyle *style_res)
1693 {
1694     auto blend = SP_CSS_BLEND_NORMAL;
1695     auto blend_prev = blend;
1696     bool same_blend = true;
1697     guint items = 0;
1698 
1699     for (auto obj : objects) {
1700         if (!obj) {
1701             continue;
1702         }
1703         SPStyle *style = obj->style;
1704         if (!style || !dynamic_cast<SPItem *>(obj)) {
1705             continue;
1706         }
1707 
1708         items++;
1709 
1710         if (style->mix_blend_mode.set) {
1711             blend = style->mix_blend_mode.value;
1712         }
1713         // defaults to blend mode = "normal"
1714         else {
1715             if (style->filter.set && style->getFilter()) {
1716                 blend = filter_get_legacy_blend(obj);
1717             } else {
1718                 blend = SP_CSS_BLEND_NORMAL;
1719             }
1720         }
1721 
1722         if (items > 1 && blend_prev != blend)
1723             same_blend = false;
1724         blend_prev = blend;
1725     }
1726 
1727     if (items > 0) {
1728         style_res->mix_blend_mode.value = blend;
1729     }
1730 
1731     if (items == 0) {
1732         return QUERY_STYLE_NOTHING;
1733     } else if (items == 1) {
1734         return QUERY_STYLE_SINGLE;
1735     } else {
1736         if(same_blend)
1737             return QUERY_STYLE_MULTIPLE_SAME;
1738         else
1739             return QUERY_STYLE_MULTIPLE_DIFFERENT;
1740     }
1741 }
1742 
objects_query_isolation(const std::vector<SPItem * > & objects,SPStyle * style_res)1743 int objects_query_isolation(const std::vector<SPItem *> &objects, SPStyle *style_res)
1744 {
1745     auto isolation = SP_CSS_ISOLATION_AUTO;
1746     auto isolation_prev = isolation;
1747     bool same_isolation = true;
1748     guint items = 0;
1749 
1750     for (auto obj : objects) {
1751         if (!obj) {
1752             continue;
1753         }
1754         SPStyle *style = obj->style;
1755         if (!style || !dynamic_cast<SPItem *>(obj)) {
1756             continue;
1757         }
1758 
1759         items++;
1760 
1761         if (style->isolation.set) {
1762             isolation = style->isolation.value;
1763         }
1764         else {
1765             isolation = SP_CSS_ISOLATION_AUTO;
1766         }
1767 
1768         if (items > 1 && isolation_prev != isolation)
1769             same_isolation = false;
1770         isolation_prev = isolation;
1771     }
1772 
1773     if (items > 0) {
1774         style_res->isolation.value = isolation;
1775     }
1776 
1777     if (items == 0) {
1778         return QUERY_STYLE_NOTHING;
1779     } else if (items == 1) {
1780         return QUERY_STYLE_SINGLE;
1781     } else {
1782         if (same_isolation)
1783             return QUERY_STYLE_MULTIPLE_SAME;
1784         else
1785             return QUERY_STYLE_MULTIPLE_DIFFERENT;
1786     }
1787 }
1788 
1789 /**
1790  * Write to style_res the average blurring of a list of objects.
1791  */
1792 int
objects_query_blur(const std::vector<SPItem * > & objects,SPStyle * style_res)1793 objects_query_blur (const std::vector<SPItem*> &objects, SPStyle *style_res)
1794 {
1795    if (objects.empty()) {
1796         /* No objects, set empty */
1797         return QUERY_STYLE_NOTHING;
1798     }
1799 
1800     float blur_sum = 0;
1801     float blur_prev = -1;
1802     bool same_blur = true;
1803     guint blur_items = 0;
1804     guint items = 0;
1805 
1806     for (auto obj : objects) {
1807         if (!obj) {
1808             continue;
1809         }
1810         SPStyle *style = obj->style;
1811         if (!style) {
1812             continue;
1813         }
1814         SPItem *item = dynamic_cast<SPItem *>(obj);
1815         if (!item) {
1816             continue;
1817         }
1818 
1819         Geom::Affine i2d = item->i2dt_affine();
1820 
1821         items ++;
1822 
1823         //if object has a filter
1824         if (style->filter.set && style->getFilter()) {
1825             //cycle through filter primitives
1826             for(auto& primitive_obj: style->getFilter()->children) {
1827                 SPFilterPrimitive *primitive = dynamic_cast<SPFilterPrimitive *>(&primitive_obj);
1828                 if (primitive) {
1829 
1830                     //if primitive is gaussianblur
1831                     SPGaussianBlur * spblur = dynamic_cast<SPGaussianBlur *>(primitive);
1832                     if (spblur) {
1833                         float num = spblur->stdDeviation.getNumber();
1834                         float dummy = num * i2d.descrim();
1835                         if (!std::isnan(dummy)) {
1836                             blur_sum += dummy;
1837                             if (blur_prev != -1 && fabs (num - blur_prev) > 1e-2) // rather low tolerance because difference in blur radii is much harder to notice than e.g. difference in sizes
1838                                 same_blur = false;
1839                             blur_prev = num;
1840                             //TODO: deal with opt number, for the moment it's not necessary to the ui.
1841                             blur_items ++;
1842                         }
1843                     }
1844                 }
1845             }
1846         }
1847     }
1848 
1849     if (items > 0) {
1850         if (blur_items > 0)
1851             blur_sum /= blur_items;
1852         style_res->filter_gaussianBlur_deviation.value = blur_sum;
1853     }
1854 
1855     if (items == 0) {
1856         return QUERY_STYLE_NOTHING;
1857     } else if (items == 1) {
1858         return QUERY_STYLE_SINGLE;
1859     } else {
1860         if (same_blur)
1861             return QUERY_STYLE_MULTIPLE_SAME;
1862         else
1863             return QUERY_STYLE_MULTIPLE_AVERAGED;
1864     }
1865 }
1866 
1867 /**
1868  * Query the given list of objects for the given property, write
1869  * the result to style, return appropriate flag.
1870  */
1871 int
sp_desktop_query_style_from_list(const std::vector<SPItem * > & list,SPStyle * style,int property)1872 sp_desktop_query_style_from_list (const std::vector<SPItem*> &list, SPStyle *style, int property)
1873 {
1874     if (property == QUERY_STYLE_PROPERTY_FILL) {
1875         return objects_query_fillstroke (list, style, true);
1876     } else if (property == QUERY_STYLE_PROPERTY_STROKE) {
1877         return objects_query_fillstroke (list, style, false);
1878 
1879     } else if (property == QUERY_STYLE_PROPERTY_STROKEWIDTH) {
1880         return objects_query_strokewidth (list, style);
1881     } else if (property == QUERY_STYLE_PROPERTY_STROKEMITERLIMIT) {
1882         return objects_query_miterlimit (list, style);
1883     } else if (property == QUERY_STYLE_PROPERTY_STROKECAP) {
1884         return objects_query_strokecap (list, style);
1885     } else if (property == QUERY_STYLE_PROPERTY_STROKEJOIN) {
1886         return objects_query_strokejoin (list, style);
1887 
1888     } else if (property == QUERY_STYLE_PROPERTY_PAINTORDER) {
1889         return objects_query_paintorder (list, style);
1890     } else if (property == QUERY_STYLE_PROPERTY_MASTEROPACITY) {
1891         return objects_query_opacity (list, style);
1892 
1893     } else if (property == QUERY_STYLE_PROPERTY_FONT_SPECIFICATION) {
1894         return objects_query_fontspecification (list, style);
1895     } else if (property == QUERY_STYLE_PROPERTY_FONTFAMILY) {
1896         return objects_query_fontfamily (list, style);
1897     } else if (property == QUERY_STYLE_PROPERTY_FONTSTYLE) {
1898         return objects_query_fontstyle (list, style);
1899     } else if (property == QUERY_STYLE_PROPERTY_FONTVARIANTS) {
1900         return objects_query_fontvariants (list, style);
1901     } else if (property == QUERY_STYLE_PROPERTY_FONTFEATURESETTINGS) {
1902         return objects_query_fontfeaturesettings (list, style);
1903     } else if (property == QUERY_STYLE_PROPERTY_FONTNUMBERS) {
1904         return objects_query_fontnumbers (list, style);
1905     } else if (property == QUERY_STYLE_PROPERTY_WRITINGMODES) {
1906         return objects_query_writing_modes (list, style);
1907     } else if (property == QUERY_STYLE_PROPERTY_BASELINES) {
1908         return objects_query_baselines (list, style);
1909 
1910     } else if (property == QUERY_STYLE_PROPERTY_BLEND) {
1911         return objects_query_blend (list, style);
1912     } else if (property == QUERY_STYLE_PROPERTY_ISOLATION) {
1913         return objects_query_isolation(list, style);
1914     } else if (property == QUERY_STYLE_PROPERTY_BLUR) {
1915         return objects_query_blur (list, style);
1916     }
1917     return QUERY_STYLE_NOTHING;
1918 }
1919 
1920 
1921 /**
1922  * Query the subselection (if any) or selection on the given desktop for the given property, write
1923  * the result to style, return appropriate flag.
1924  */
1925 int
sp_desktop_query_style(SPDesktop * desktop,SPStyle * style,int property)1926 sp_desktop_query_style(SPDesktop *desktop, SPStyle *style, int property)
1927 {
1928     // Used by text tool and in gradient dragging. See connectQueryStyle.
1929     int ret = desktop->_query_style_signal.emit(style, property);
1930 
1931     if (ret != QUERY_STYLE_NOTHING)
1932         return ret; // subselection returned a style, pass it on
1933 
1934     // otherwise, do querying and averaging over selection
1935     if (desktop->selection != nullptr) {
1936         std::vector<SPItem *> vec(desktop->selection->items().begin(), desktop->selection->items().end());
1937         return sp_desktop_query_style_from_list (vec, style, property);
1938     }
1939 
1940     return QUERY_STYLE_NOTHING;
1941 }
1942 
1943 /**
1944  * Do the same as sp_desktop_query_style for all (defined) style properties, return true if at
1945  * least one of the properties did not return QUERY_STYLE_NOTHING.
1946  */
1947 bool
sp_desktop_query_style_all(SPDesktop * desktop,SPStyle * query)1948 sp_desktop_query_style_all (SPDesktop *desktop, SPStyle *query)
1949 {
1950         int result_family = sp_desktop_query_style (desktop, query, QUERY_STYLE_PROPERTY_FONTFAMILY);
1951         int result_fstyle = sp_desktop_query_style (desktop, query, QUERY_STYLE_PROPERTY_FONTSTYLE);
1952         int result_fnumbers = sp_desktop_query_style (desktop, query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
1953         int result_fill = sp_desktop_query_style (desktop, query, QUERY_STYLE_PROPERTY_FILL);
1954         int result_stroke = sp_desktop_query_style (desktop, query, QUERY_STYLE_PROPERTY_STROKE);
1955         int result_strokewidth = sp_desktop_query_style (desktop, query, QUERY_STYLE_PROPERTY_STROKEWIDTH);
1956         int result_strokemiterlimit = sp_desktop_query_style (desktop, query, QUERY_STYLE_PROPERTY_STROKEMITERLIMIT);
1957         int result_strokecap = sp_desktop_query_style (desktop, query, QUERY_STYLE_PROPERTY_STROKECAP);
1958         int result_strokejoin = sp_desktop_query_style (desktop, query, QUERY_STYLE_PROPERTY_STROKEJOIN);
1959 
1960         int result_paintorder = sp_desktop_query_style (desktop, query, QUERY_STYLE_PROPERTY_PAINTORDER);
1961 
1962         int result_opacity = sp_desktop_query_style (desktop, query, QUERY_STYLE_PROPERTY_MASTEROPACITY);
1963         int result_blur = sp_desktop_query_style (desktop, query, QUERY_STYLE_PROPERTY_BLUR);
1964         int result_blend = sp_desktop_query_style (desktop, query, QUERY_STYLE_PROPERTY_BLEND);
1965         int result_isolation = sp_desktop_query_style (desktop, query, QUERY_STYLE_PROPERTY_ISOLATION);
1966 
1967         return (result_family != QUERY_STYLE_NOTHING ||
1968                 result_fstyle != QUERY_STYLE_NOTHING ||
1969                 result_fnumbers != QUERY_STYLE_NOTHING ||
1970                 result_fill != QUERY_STYLE_NOTHING ||
1971                 result_stroke != QUERY_STYLE_NOTHING ||
1972                 result_opacity != QUERY_STYLE_NOTHING ||
1973                 result_strokewidth != QUERY_STYLE_NOTHING ||
1974                 result_strokemiterlimit != QUERY_STYLE_NOTHING ||
1975                 result_strokecap != QUERY_STYLE_NOTHING ||
1976                 result_strokejoin != QUERY_STYLE_NOTHING ||
1977                 result_paintorder != QUERY_STYLE_NOTHING ||
1978                 result_blur != QUERY_STYLE_NOTHING ||
1979                 result_isolation != QUERY_STYLE_NOTHING ||
1980                 result_blend != QUERY_STYLE_NOTHING);
1981 }
1982 
1983 
1984 /*
1985   Local Variables:
1986   mode:c++
1987   c-file-style:"stroustrup"
1988   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1989   indent-tabs-mode:nil
1990   fill-column:99
1991   End:
1992 */
1993 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
1994