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