1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Various utility methods for gradients
4  *
5  * Authors:
6  *   Lauris Kaplinski <lauris@kaplinski.com>
7  *   bulia byak
8  *   Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
9  *   Jon A. Cruz <jon@joncruz.org>
10  *   Abhishek Sharma
11  *   Tavmjong Bah <tavmjong@free.fr>
12  *
13  * Copyright (C) 2012 Tavmjong Bah
14  * Copyright (C) 2010 Authors
15  * Copyright (C) 2007 Johan Engelen
16  * Copyright (C) 2001-2005 authors
17  * Copyright (C) 2001 Ximian, Inc.
18  *
19  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
20  */
21 
22 #include <glibmm/i18n.h>
23 
24 #include <2geom/transforms.h>
25 #include <2geom/bezier-curve.h>
26 #include <2geom/crossing.h>
27 #include <2geom/line.h>
28 
29 #include "desktop-style.h"
30 #include "desktop.h"
31 #include "document-undo.h"
32 #include "gradient-chemistry.h"
33 #include "gradient-drag.h"
34 #include "selection.h"
35 #include "verbs.h"
36 
37 #include "object/sp-defs.h"
38 #include "object/sp-gradient-reference.h"
39 #include "object/sp-linear-gradient.h"
40 #include "object/sp-mesh-gradient.h"
41 #include "object/sp-radial-gradient.h"
42 #include "object/sp-stop.h"
43 #include "object/sp-text.h"
44 #include "object/sp-tspan.h"
45 #include "style.h"
46 
47 #include "svg/svg.h"
48 #include "svg/svg-color.h"
49 #include "svg/css-ostringstream.h"
50 
51 #include "ui/tools/tool-base.h"
52 
53 #include "ui/widget/gradient-vector-selector.h"
54 
55 #define noSP_GR_VERBOSE
56 
57 using Inkscape::DocumentUndo;
58 
59 namespace {
60 
61 Inkscape::PaintTarget paintTargetItems[] = {Inkscape::FOR_FILL, Inkscape::FOR_STROKE};
62 
63 std::vector<Inkscape::PaintTarget> vectorOfPaintTargets(paintTargetItems, paintTargetItems + (sizeof(paintTargetItems) / sizeof(paintTargetItems[0])));
64 
65 } // namespace
66 
67 namespace Inkscape {
68 
allPaintTargets()69 std::vector<PaintTarget> const &allPaintTargets()
70 {
71     return vectorOfPaintTargets;
72 }
73 
74 } // namespace Inkscape
75 
76 // Terminology:
77 //
78 // "vector" is a gradient that has stops but not position coords. It can be referenced by one or
79 // more privates. Objects should not refer to it directly. It has no radial/linear distinction.
80 //
81 // "array" is a gradient that has mesh rows and patches. It may or may not have "x" and "y" attributes.
82 // An array does have spacial information so it cannot be normalized like a "vector".
83 //
84 // "shared" is either a "vector" or "array" that is shared between multiple objects.
85 //
86 // "private" is a gradient that is not shared. A private linear or radial gradient has no stops but
87 // has position coords (e.g. center, radius etc for a radial); it references a "vector" for the
88 // actual colors. A mesh may or may not reference an array. Each private is only used by one object.
89 
90 static void sp_gradient_repr_set_link(Inkscape::XML::Node *repr, SPGradient *gr);
91 
sp_gradient_ensure_vector_normalized(SPGradient * gr)92 SPGradient *sp_gradient_ensure_vector_normalized(SPGradient *gr)
93 {
94 #ifdef SP_GR_VERBOSE
95     g_message("sp_gradient_ensure_vector_normalized(%p)", gr);
96 #endif
97     g_return_val_if_fail(gr != nullptr, NULL);
98     g_return_val_if_fail(SP_IS_GRADIENT(gr), NULL);
99     g_return_val_if_fail(!SP_IS_MESHGRADIENT(gr), NULL);
100 
101     /* If we are already normalized vector, just return */
102     if (gr->state == SP_GRADIENT_STATE_VECTOR) return gr;
103     /* Fail, if we have wrong state set */
104     if (gr->state != SP_GRADIENT_STATE_UNKNOWN) {
105         g_warning("file %s: line %d: Cannot normalize private gradient to vector (%s)", __FILE__, __LINE__, gr->getId());
106         return nullptr;
107     }
108 
109     /* First make sure we have vector directly defined (i.e. gr has its own stops) */
110     if ( !gr->hasStops() ) {
111         /* We do not have stops ourselves, so flatten stops as well */
112         gr->ensureVector();
113         g_assert(gr->vector.built);
114         // this adds stops from gr->vector as children to gr
115         gr->repr_write_vector ();
116     }
117 
118     /* If gr hrefs some other gradient, remove the href */
119     if (gr->ref){
120         if (gr->ref->getObject()) {
121             // We are hrefing someone, so require flattening
122             gr->updateRepr(SP_OBJECT_WRITE_EXT | SP_OBJECT_WRITE_ALL);
123             sp_gradient_repr_set_link(gr->getRepr(), nullptr);
124         }
125     }
126 
127     /* Everything is OK, set state flag */
128     gr->state = SP_GRADIENT_STATE_VECTOR;
129     return gr;
130 }
131 
132 /**
133  * Creates new private gradient for the given shared gradient.
134  */
135 
sp_gradient_get_private_normalized(SPDocument * document,SPGradient * shared,SPGradientType type)136 static SPGradient *sp_gradient_get_private_normalized(SPDocument *document, SPGradient *shared, SPGradientType type)
137 {
138 #ifdef SP_GR_VERBOSE
139     g_message("sp_gradient_get_private_normalized(%p, %p, %d)", document, shared, type);
140 #endif
141 
142     g_return_val_if_fail(document != nullptr, NULL);
143     g_return_val_if_fail(shared != nullptr, NULL);
144     g_return_val_if_fail(SP_IS_GRADIENT(shared), NULL);
145     g_return_val_if_fail(shared->hasStops() || shared->hasPatches(), NULL);
146 
147     SPDefs *defs = document->getDefs();
148 
149     Inkscape::XML::Document *xml_doc = document->getReprDoc();
150     // create a new private gradient of the requested type
151     Inkscape::XML::Node *repr;
152     if (type == SP_GRADIENT_TYPE_LINEAR) {
153         repr = xml_doc->createElement("svg:linearGradient");
154     } else if(type == SP_GRADIENT_TYPE_RADIAL) {
155         repr = xml_doc->createElement("svg:radialGradient");
156     } else {
157         repr = xml_doc->createElement("svg:meshgradient");
158     }
159 
160     // privates are garbage-collectable
161     repr->setAttribute("inkscape:collect", "always");
162 
163     // link to shared
164     sp_gradient_repr_set_link(repr, shared);
165 
166     /* Append the new private gradient to defs */
167     defs->getRepr()->appendChild(repr);
168     Inkscape::GC::release(repr);
169 
170     // get corresponding object
171     SPGradient *gr = static_cast<SPGradient *>(document->getObjectByRepr(repr));
172     g_assert(gr != nullptr);
173     g_assert(SP_IS_GRADIENT(gr));
174 
175     return gr;
176 }
177 
178 /**
179 Count how many times gr is used by the styles of o and its descendants
180 */
count_gradient_hrefs(SPObject * o,SPGradient * gr)181 static guint count_gradient_hrefs(SPObject *o, SPGradient *gr)
182 {
183     if (!o)
184         return 1;
185 
186     guint i = 0;
187 
188     SPStyle *style = o->style;
189     if (style
190         && style->fill.isPaintserver()
191         && SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style))
192         && SP_GRADIENT(SP_STYLE_FILL_SERVER(style)) == gr)
193     {
194         i ++;
195     }
196     if (style
197         && style->stroke.isPaintserver()
198         && SP_IS_GRADIENT(SP_STYLE_STROKE_SERVER(style))
199         && SP_GRADIENT(SP_STYLE_STROKE_SERVER(style)) == gr)
200     {
201         i ++;
202     }
203 
204     for (auto& child: o->children) {
205         i += count_gradient_hrefs(&child, gr);
206     }
207 
208     return i;
209 }
210 
211 
212 /**
213  * If gr has other users, create a new shared; also check if gr links to shared, relink if not
214  */
sp_gradient_fork_private_if_necessary(SPGradient * gr,SPGradient * shared,SPGradientType type,SPObject * o)215 static SPGradient *sp_gradient_fork_private_if_necessary(SPGradient *gr, SPGradient *shared,
216                                                          SPGradientType type, SPObject *o)
217 {
218 #ifdef SP_GR_VERBOSE
219     g_message("sp_gradient_fork_private_if_necessary(%p, %p, %d, %p)", gr, shared, type, o);
220 #endif
221     g_return_val_if_fail(gr != nullptr, NULL);
222     g_return_val_if_fail(SP_IS_GRADIENT(gr), NULL);
223 
224     // Orphaned gradient, no shared with stops or patches at the end of the line; this used to be
225     // an assert
226     if ( !shared || !(shared->hasStops() || shared->hasPatches()) ) {
227         std::cerr << "sp_gradient_fork_private_if_necessary: Orphaned gradient" << std::endl;
228         return (gr);
229     }
230 
231     // user is the object that uses this gradient; normally it's item but for tspans, we
232     // check its ancestor text so that tspans don't get different gradients from their
233     // texts.
234     SPObject *user = o;
235     while (SP_IS_TSPAN(user)) {
236         user = user->parent;
237     }
238 
239     // Check the number of uses of the gradient within this object;
240     // if we are private and there are no other users,
241     if (!shared->isSwatch() && (gr->hrefcount <= count_gradient_hrefs(user, gr))) {
242         // check shared
243         if ( gr != shared && gr->ref->getObject() != shared ) {
244             /* our href is not the shared, and shared is different from gr; relink */
245             sp_gradient_repr_set_link(gr->getRepr(), shared);
246         }
247         return gr;
248     }
249 
250     SPDocument *doc = gr->document;
251     SPObject *defs = doc->getDefs();
252 
253     if ((gr->hasStops()) ||
254         (gr->hasPatches()) ||
255         (gr->state != SP_GRADIENT_STATE_UNKNOWN) ||
256         (gr->parent != SP_OBJECT(defs)) ||
257         (gr->hrefcount > 1)) {
258 
259         // we have to clone a fresh new private gradient for the given shared
260 
261         // create an empty one
262         SPGradient *gr_new = sp_gradient_get_private_normalized(doc, shared, type);
263 
264         // copy all the attributes to it
265         Inkscape::XML::Node *repr_new = gr_new->getRepr();
266         Inkscape::XML::Node *repr = gr->getRepr();
267         repr_new->setAttribute("gradientUnits", repr->attribute("gradientUnits"));
268         repr_new->setAttribute("gradientTransform", repr->attribute("gradientTransform"));
269         if (SP_IS_RADIALGRADIENT(gr)) {
270             repr_new->setAttribute("cx", repr->attribute("cx"));
271             repr_new->setAttribute("cy", repr->attribute("cy"));
272             repr_new->setAttribute("fx", repr->attribute("fx"));
273             repr_new->setAttribute("fy", repr->attribute("fy"));
274             repr_new->setAttribute("r",  repr->attribute("r" ));
275             repr_new->setAttribute("fr", repr->attribute("fr"));
276             repr_new->setAttribute("spreadMethod", repr->attribute("spreadMethod"));
277         } else if (SP_IS_LINEARGRADIENT(gr)) {
278             repr_new->setAttribute("x1", repr->attribute("x1"));
279             repr_new->setAttribute("y1", repr->attribute("y1"));
280             repr_new->setAttribute("x2", repr->attribute("x2"));
281             repr_new->setAttribute("y2", repr->attribute("y2"));
282             repr_new->setAttribute("spreadMethod", repr->attribute("spreadMethod"));
283         } else { // Mesh
284             repr_new->setAttribute("x", repr->attribute("x"));
285             repr_new->setAttribute("y", repr->attribute("y"));
286             repr_new->setAttribute("type", repr->attribute("type"));
287 
288             // We probably want a completely separate mesh gradient so
289             // copy the children and unset the link to the shared.
290             for ( Inkscape::XML::Node *child = repr->firstChild() ; child ; child = child->next() ) {
291                 Inkscape::XML::Node *copy = child->duplicate(doc->getReprDoc());
292                 repr_new->appendChild( copy );
293                 Inkscape::GC::release( copy );
294             }
295             sp_gradient_repr_set_link(repr_new, nullptr);
296         }
297         return gr_new;
298     } else {
299         return gr;
300     }
301 }
302 
sp_gradient_fork_vector_if_necessary(SPGradient * gr)303 SPGradient *sp_gradient_fork_vector_if_necessary(SPGradient *gr)
304 {
305 #ifdef SP_GR_VERBOSE
306     g_message("sp_gradient_fork_vector_if_necessary(%p)", gr);
307 #endif
308     // Some people actually prefer their gradient vectors to be shared...
309     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
310     if (!prefs->getBool("/options/forkgradientvectors/value", true))
311         return gr;
312 
313     if (gr->hrefcount > 1) {
314         SPDocument *doc = gr->document;
315         Inkscape::XML::Document *xml_doc = doc->getReprDoc();
316 
317         Inkscape::XML::Node *repr = gr->getRepr()->duplicate(xml_doc);
318         doc->getDefs()->getRepr()->addChild(repr, nullptr);
319         SPGradient *gr_new = static_cast<SPGradient *>(doc->getObjectByRepr(repr));
320         gr_new = sp_gradient_ensure_vector_normalized (gr_new);
321         Inkscape::GC::release(repr);
322         return gr_new;
323     }
324     return gr;
325 }
326 
327 /**
328  *  Obtain the vector from the gradient. A forked vector will be created and linked to this gradient if another gradient uses it.
329  */
sp_gradient_get_forked_vector_if_necessary(SPGradient * gradient,bool force_vector)330 SPGradient *sp_gradient_get_forked_vector_if_necessary(SPGradient *gradient, bool force_vector)
331 {
332 #ifdef SP_GR_VERBOSE
333     g_message("sp_gradient_get_forked_vector_if_necessary(%p, %d)", gradient, force_vector);
334 #endif
335     SPGradient *vector = gradient->getVector(force_vector);
336     vector = sp_gradient_fork_vector_if_necessary (vector);
337     if ( gradient != vector && gradient->ref->getObject() != vector ) {
338         sp_gradient_repr_set_link(gradient->getRepr(), vector);
339     }
340     return vector;
341 }
342 
343 
344 /**
345  * Convert an item's gradient to userspace _without_ preserving coords, setting them to defaults
346  * instead. No forking or reapplying is done because this is only called for newly created privates.
347  * @return The new gradient.
348  */
sp_gradient_reset_to_userspace(SPGradient * gr,SPItem * item)349 SPGradient *sp_gradient_reset_to_userspace(SPGradient *gr, SPItem *item)
350 {
351 #ifdef SP_GR_VERBOSE
352     g_message("sp_gradient_reset_to_userspace(%p, %p)", gr, item);
353 #endif
354     Inkscape::XML::Node *repr = gr->getRepr();
355 
356     // calculate the bbox of the item
357     item->document->ensureUpToDate();
358     Geom::OptRect bbox = item->visualBounds(); // we need "true" bbox without item_i2d_affine
359 
360     if (!bbox)
361         return gr;
362 
363     Geom::Coord const width = bbox->dimensions()[Geom::X];
364     Geom::Coord const height = bbox->dimensions()[Geom::Y];
365 
366     Geom::Point const center = bbox->midpoint();
367 
368     if (SP_IS_RADIALGRADIENT(gr)) {
369         sp_repr_set_svg_double(repr, "cx", center[Geom::X]);
370         sp_repr_set_svg_double(repr, "cy", center[Geom::Y]);
371         sp_repr_set_svg_double(repr, "fx", center[Geom::X]);
372         sp_repr_set_svg_double(repr, "fy", center[Geom::Y]);
373         sp_repr_set_svg_double(repr, "r", width/2);
374 
375         // we want it to be elliptic, not circular
376         Geom::Affine squeeze = Geom::Translate (-center) *
377             Geom::Scale(1, height/width) *
378             Geom::Translate (center);
379 
380         gr->gradientTransform = squeeze;
381         gr->setAttributeOrRemoveIfEmpty("gradientTransform", sp_svg_transform_write(gr->gradientTransform));
382     } else if (SP_IS_LINEARGRADIENT(gr)) {
383 
384         // Assume horizontal gradient by default (as per SVG 1.1)
385         Geom::Point pStart = center - Geom::Point(width/2, 0);
386         Geom::Point pEnd = center + Geom::Point(width/2, 0);
387 
388         // Get the preferred gradient angle from prefs
389         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
390         double angle = prefs->getDouble("/dialogs/gradienteditor/angle", 0.0);
391 
392         if (angle != 0.0) {
393 
394             Geom::Line grl(center, Geom::rad_from_deg(angle));
395             Geom::LineSegment bbl1(bbox->corner(0), bbox->corner(1));
396             Geom::LineSegment bbl2(bbox->corner(1), bbox->corner(2));
397             Geom::LineSegment bbl3(bbox->corner(2), bbox->corner(3));
398             Geom::LineSegment bbl4(bbox->corner(3), bbox->corner(0));
399 
400             // Find where our gradient line intersects the bounding box.
401             if (!bbl1.isDegenerate() && intersection(bbl1, grl)) {
402                 pStart = bbl1.pointAt((*intersection(bbl1, grl)).ta);
403                 pEnd = bbl3.pointAt((*intersection(bbl3, grl)).ta);
404                 if (intersection(bbl1, grl.ray(grl.angle()))) {
405                     std::swap(pStart, pEnd);
406                 }
407             } else if (!bbl2.isDegenerate() && intersection(bbl2, grl)) {
408                 pStart = bbl2.pointAt((*intersection(bbl2, grl)).ta);
409                 pEnd = bbl4.pointAt((*intersection(bbl4, grl)).ta);
410                 if (intersection(bbl2, grl.ray(grl.angle()))) {
411                     std::swap(pStart, pEnd);
412                 }
413             }
414 
415         }
416 
417         sp_repr_set_svg_double(repr, "x1", pStart[Geom::X]);
418         sp_repr_set_svg_double(repr, "y1", pStart[Geom::Y]);
419         sp_repr_set_svg_double(repr, "x2", pEnd[Geom::X]);
420         sp_repr_set_svg_double(repr, "y2", pEnd[Geom::Y]);
421 
422     } else {
423         // Mesh
424         // THIS IS BEING CALLED TWICE WHENEVER A NEW GRADIENT IS CREATED, WRITING HERE CAUSES PROBLEMS
425         // IN SPMeshNodeArray::create()
426         //sp_repr_set_svg_double(repr, "x", bbox->min()[Geom::X]);
427         //sp_repr_set_svg_double(repr, "y", bbox->min()[Geom::Y]);
428 
429         // We don't create a shared array gradient.
430         SPMeshGradient* mg = SP_MESHGRADIENT( gr );
431         mg->array.create( mg, item, bbox );
432     }
433 
434     // set the gradientUnits
435     repr->setAttribute("gradientUnits", "userSpaceOnUse");
436 
437     return gr;
438 }
439 
440 /**
441  * Convert an item's gradient to userspace if necessary, also fork it if necessary.
442  * @return The new gradient.
443  */
sp_gradient_convert_to_userspace(SPGradient * gr,SPItem * item,gchar const * property)444 SPGradient *sp_gradient_convert_to_userspace(SPGradient *gr, SPItem *item, gchar const *property)
445 {
446 #ifdef SP_GR_VERBOSE
447     g_message("sp_gradient_convert_to_userspace(%p, %p, \"%s\")", gr, item, property);
448 #endif
449     g_return_val_if_fail(SP_IS_GRADIENT(gr), NULL);
450 
451     if ( gr && gr->isSolid() ) {
452         return gr;
453     }
454 
455     // First, fork it if it is shared
456     if (SP_IS_LINEARGRADIENT(gr)) {
457         gr = sp_gradient_fork_private_if_necessary(gr, gr->getVector(), SP_GRADIENT_TYPE_LINEAR, item);
458     } else if (SP_IS_RADIALGRADIENT(gr)) {
459         gr = sp_gradient_fork_private_if_necessary(gr, gr->getVector(), SP_GRADIENT_TYPE_RADIAL, item);
460     } else {
461         gr = sp_gradient_fork_private_if_necessary(gr, gr->getArray(),  SP_GRADIENT_TYPE_MESH,   item);
462     }
463 
464     if (gr->getUnits() == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) {
465 
466         Inkscape::XML::Node *repr = gr->getRepr();
467 
468         // calculate the bbox of the item
469         item->document->ensureUpToDate();
470         Geom::Affine bbox2user;
471         Geom::OptRect bbox = item->visualBounds(); // we need "true" bbox without item_i2d_affine
472         if ( bbox ) {
473             bbox2user = Geom::Affine(bbox->dimensions()[Geom::X], 0,
474                                    0, bbox->dimensions()[Geom::Y],
475                                    bbox->min()[Geom::X], bbox->min()[Geom::Y]);
476         } else {
477             // would be degenerate otherwise
478             bbox2user = Geom::identity();
479         }
480 
481         /* skew is the additional transform, defined by the proportions of the item, that we need
482          * to apply to the gradient in order to work around this weird bit from SVG 1.1
483          * (http://www.w3.org/TR/SVG11/pservers.html#LinearGradients):
484          *
485          *   When gradientUnits="objectBoundingBox" and gradientTransform is the identity
486          *   matrix, the stripes of the linear gradient are perpendicular to the gradient
487          *   vector in object bounding box space (i.e., the abstract coordinate system where
488          *   (0,0) is at the top/left of the object bounding box and (1,1) is at the
489          *   bottom/right of the object bounding box). When the object's bounding box is not
490          *   square, the stripes that are conceptually perpendicular to the gradient vector
491          *   within object bounding box space will render non-perpendicular relative to the
492          *   gradient vector in user space due to application of the non-uniform scaling
493          *   transformation from bounding box space to user space.
494          */
495         Geom::Affine skew = bbox2user;
496         double exp = skew.descrim();
497         skew[0] /= exp;
498         skew[1] /= exp;
499         skew[2] /= exp;
500         skew[3] /= exp;
501         skew[4] = 0;
502         skew[5] = 0;
503 
504         // apply skew to the gradient
505         gr->gradientTransform = skew;
506         gr->setAttributeOrRemoveIfEmpty("gradientTransform", sp_svg_transform_write(gr->gradientTransform));
507 
508         // Matrix to convert points to userspace coords; postmultiply by inverse of skew so
509         // as to cancel it out when it's applied to the gradient during rendering
510         Geom::Affine point_convert = bbox2user * skew.inverse();
511 
512         if (SP_IS_LINEARGRADIENT(gr)) {
513             SPLinearGradient *lg = SP_LINEARGRADIENT(gr);
514 
515             Geom::Point p1_b = Geom::Point(lg->x1.computed, lg->y1.computed);
516             Geom::Point p2_b = Geom::Point(lg->x2.computed, lg->y2.computed);
517 
518             Geom::Point p1_u = p1_b * point_convert;
519             Geom::Point p2_u = p2_b * point_convert;
520 
521             sp_repr_set_svg_double(repr, "x1", p1_u[Geom::X]);
522             sp_repr_set_svg_double(repr, "y1", p1_u[Geom::Y]);
523             sp_repr_set_svg_double(repr, "x2", p2_u[Geom::X]);
524             sp_repr_set_svg_double(repr, "y2", p2_u[Geom::Y]);
525 
526             // set the gradientUnits
527             repr->setAttribute("gradientUnits", "userSpaceOnUse");
528 
529         } else if (SP_IS_RADIALGRADIENT(gr)) {
530             SPRadialGradient *rg = SP_RADIALGRADIENT(gr);
531 
532             // original points in the bbox coords
533             Geom::Point c_b = Geom::Point(rg->cx.computed, rg->cy.computed);
534             Geom::Point f_b = Geom::Point(rg->fx.computed, rg->fy.computed);
535             double r_b = rg->r.computed;
536 
537             // converted points in userspace coords
538             Geom::Point c_u = c_b * point_convert;
539             Geom::Point f_u = f_b * point_convert;
540             double r_u = r_b * point_convert.descrim();
541 
542             sp_repr_set_svg_double(repr, "cx", c_u[Geom::X]);
543             sp_repr_set_svg_double(repr, "cy", c_u[Geom::Y]);
544             sp_repr_set_svg_double(repr, "fx", f_u[Geom::X]);
545             sp_repr_set_svg_double(repr, "fy", f_u[Geom::Y]);
546             sp_repr_set_svg_double(repr, "r", r_u);
547 
548             // set the gradientUnits
549             repr->setAttribute("gradientUnits", "userSpaceOnUse");
550 
551         } else {
552             std::cerr << "sp_gradient_convert_to_userspace: Conversion of mesh to userspace not implemented" << std::endl;
553         }
554     }
555 
556     // apply the gradient to the item (may be necessary if we forked it); not recursive
557     // generally because grouped items will be taken care of later (we're being called
558     // from sp_item_adjust_paint_recursive); however text and all its children should all
559     // refer to one gradient, hence the recursive call for text (because we can't/don't
560     // want to access tspans and set gradients on them separately)
561     if (SP_IS_TEXT(item)) {
562         sp_style_set_property_url(item, property, gr, true);
563     } else {
564         sp_style_set_property_url(item, property, gr, false);
565     }
566 
567     return gr;
568 }
569 
sp_gradient_transform_multiply(SPGradient * gradient,Geom::Affine postmul,bool set)570 void sp_gradient_transform_multiply(SPGradient *gradient, Geom::Affine postmul, bool set)
571 {
572 #ifdef SP_GR_VERBOSE
573     g_message("sp_gradient_transform_multiply(%p, , %d)", gradient, set);
574 #endif
575     if (set) {
576         gradient->gradientTransform = postmul;
577     } else {
578         gradient->gradientTransform *= postmul; // fixme: get gradient transform by climbing to hrefs?
579     }
580     gradient->gradientTransform_set = TRUE;
581 
582     auto c = sp_svg_transform_write(gradient->gradientTransform);
583     gradient->setAttributeOrRemoveIfEmpty("gradientTransform", c);
584 }
585 
getGradient(SPItem * item,Inkscape::PaintTarget fill_or_stroke)586 SPGradient *getGradient(SPItem *item, Inkscape::PaintTarget fill_or_stroke)
587 {
588     SPStyle *style = item->style;
589     SPGradient *gradient = nullptr;
590 
591     switch (fill_or_stroke)
592     {
593         case Inkscape::FOR_FILL:
594             if (style && (style->fill.isPaintserver())) {
595                 SPPaintServer *server = item->style->getFillPaintServer();
596                 if ( SP_IS_GRADIENT(server) ) {
597                     gradient = SP_GRADIENT(server);
598                 }
599             }
600             break;
601         case Inkscape::FOR_STROKE:
602             if (style && (style->stroke.isPaintserver())) {
603                 SPPaintServer *server = item->style->getStrokePaintServer();
604                 if ( SP_IS_GRADIENT(server) ) {
605                     gradient = SP_GRADIENT(server);
606                 }
607             }
608             break;
609     }
610 
611    return gradient;
612 }
613 
sp_last_stop(SPGradient * gradient)614 SPStop *sp_last_stop(SPGradient *gradient)
615 {
616     for (SPStop *stop = gradient->getFirstStop(); stop != nullptr; stop = stop->getNextStop()) {
617         if (stop->getNextStop() == nullptr)
618             return stop;
619     }
620     return nullptr;
621 }
622 
sp_get_stop_i(SPGradient * gradient,guint stop_i)623 SPStop *sp_get_stop_i(SPGradient *gradient, guint stop_i)
624 {
625     SPStop *stop = gradient->getFirstStop();
626     if (!stop) {
627         return nullptr;
628     }
629 
630     // if this is valid but weird gradient without an offset-zero stop element,
631     // inkscape has created a handle for the start of gradient anyway,
632     // so when it asks for stop N that corresponds to stop element N-1
633     if (stop->offset != 0)
634     {
635         stop_i--;
636     }
637 
638     for (guint i = 0; i < stop_i; i++) {
639         if (!stop) {
640             return nullptr;
641         }
642         stop = stop->getNextStop();
643     }
644 
645     return stop;
646 }
647 
average_color(guint32 c1,guint32 c2,gdouble p)648 guint32 average_color(guint32 c1, guint32 c2, gdouble p)
649 {
650     guint32 r = static_cast<guint32>(SP_RGBA32_R_U (c1) * (1 - p) + SP_RGBA32_R_U (c2) * p);
651     guint32 g = static_cast<guint32>(SP_RGBA32_G_U (c1) * (1 - p) + SP_RGBA32_G_U (c2) * p);
652     guint32 b = static_cast<guint32>(SP_RGBA32_B_U (c1) * (1 - p) + SP_RGBA32_B_U (c2) * p);
653     guint32 a = static_cast<guint32>(SP_RGBA32_A_U (c1) * (1 - p) + SP_RGBA32_A_U (c2) * p);
654 
655     return SP_RGBA32_U_COMPOSE(r, g, b, a);
656 }
657 
sp_vector_add_stop(SPGradient * vector,SPStop * prev_stop,SPStop * next_stop,gfloat offset)658 SPStop *sp_vector_add_stop(SPGradient *vector, SPStop* prev_stop, SPStop* next_stop, gfloat offset)
659 {
660 #ifdef SP_GR_VERBOSE
661     g_message("sp_vector_add_stop(%p, %p, %p, %f)", vector, prev_stop, next_stop, offset);
662 #endif
663 
664     Inkscape::XML::Node *new_stop_repr = nullptr;
665     new_stop_repr = prev_stop->getRepr()->duplicate(vector->getRepr()->document());
666     vector->getRepr()->addChild(new_stop_repr, prev_stop->getRepr());
667 
668     SPStop *newstop = reinterpret_cast<SPStop *>(vector->document->getObjectByRepr(new_stop_repr));
669     newstop->offset = offset;
670     sp_repr_set_css_double( newstop->getRepr(), "offset", (double)offset);
671     guint32 const c1 = prev_stop->get_rgba32();
672     guint32 const c2 = next_stop->get_rgba32();
673     guint32 cnew = average_color (c1, c2, (offset - prev_stop->offset) / (next_stop->offset - prev_stop->offset));
674     Inkscape::CSSOStringStream os;
675     gchar c[64];
676     sp_svg_write_color (c, sizeof(c), cnew);
677     gdouble opacity = (gdouble) SP_RGBA32_A_F (cnew);
678     os << "stop-color:" << c << ";stop-opacity:" << opacity <<";";
679     newstop->setAttributeOrRemoveIfEmpty("style", os.str());
680     Inkscape::GC::release(new_stop_repr);
681 
682     return newstop;
683 }
684 
sp_item_gradient_stop_query_style(SPItem * item,GrPointType point_type,guint point_i,Inkscape::PaintTarget fill_or_stroke)685 guint32 sp_item_gradient_stop_query_style(SPItem *item, GrPointType point_type, guint point_i, Inkscape::PaintTarget fill_or_stroke)
686 {
687     SPGradient *gradient = getGradient(item, fill_or_stroke);
688 
689     if (!gradient || !SP_IS_GRADIENT(gradient)) {
690         return 0;
691     }
692 
693     if (SP_IS_LINEARGRADIENT(gradient) || SP_IS_RADIALGRADIENT(gradient) ) {
694 
695         SPGradient *vector = gradient->getVector();
696 
697         if (!vector) // orphan!
698             return 0; // what else to do?
699 
700         switch (point_type) {
701             case POINT_LG_BEGIN:
702             case POINT_RG_CENTER:
703             case POINT_RG_FOCUS:
704             {
705                 SPStop *first = vector->getFirstStop();
706                 if (first) {
707                     return first->get_rgba32();
708                 }
709             }
710             break;
711 
712             case POINT_LG_END:
713             case POINT_RG_R1:
714             case POINT_RG_R2:
715             {
716                 SPStop *last = sp_last_stop (vector);
717                 if (last) {
718                     return last->get_rgba32();
719                 }
720             }
721             break;
722 
723             case POINT_LG_MID:
724             case POINT_RG_MID1:
725             case POINT_RG_MID2:
726             {
727                 SPStop *stopi = sp_get_stop_i (vector, point_i);
728                 if (stopi) {
729                     return stopi->get_rgba32();
730                 }
731             }
732             break;
733 
734             default:
735                 g_warning( "Bad linear/radial gradient handle type" );
736                 break;
737         }
738         return 0;
739     } else if (SP_IS_MESHGRADIENT(gradient)) {
740 
741         // Mesh gradient
742         SPMeshGradient *mg = SP_MESHGRADIENT(gradient);
743 
744         switch (point_type) {
745             case POINT_MG_CORNER: {
746                 if (point_i >= mg->array.corners.size()) {
747                     return 0;
748                 }
749                 SPMeshNode const* cornerpoint = mg->array.corners[ point_i ];
750 
751                 if (cornerpoint) {
752                     SPColor color  = cornerpoint->color;
753                     double opacity = cornerpoint->opacity;
754                     return  color.toRGBA32( opacity );
755                 } else {
756                     return 0;
757                 }
758                 break;
759             }
760 
761             case POINT_MG_HANDLE:
762             case POINT_MG_TENSOR:
763             {
764                 // Do nothing. Handles and tensors don't have color
765                 break;
766             }
767 
768             default:
769                 g_warning( "Bad mesh handle type" );
770         }
771         return 0;
772     }
773 
774     return 0;
775 }
776 
sp_item_gradient_stop_set_style(SPItem * item,GrPointType point_type,guint point_i,Inkscape::PaintTarget fill_or_stroke,SPCSSAttr * stop)777 void sp_item_gradient_stop_set_style(SPItem *item, GrPointType point_type, guint point_i, Inkscape::PaintTarget fill_or_stroke, SPCSSAttr *stop)
778 {
779 #ifdef SP_GR_VERBOSE
780     g_message("sp_item_gradient_stop_set_style(%p, %d, %d, %d, %p)", item, point_type, point_i, fill_or_stroke, stop);
781 #endif
782     SPGradient *gradient = getGradient(item, fill_or_stroke);
783 
784     if (!gradient || !SP_IS_GRADIENT(gradient))
785         return;
786 
787     if (SP_IS_LINEARGRADIENT(gradient) || SP_IS_RADIALGRADIENT(gradient) ) {
788 
789         SPGradient *vector = gradient->getVector();
790 
791         if (!vector) // orphan!
792             return;
793 
794         vector = sp_gradient_fork_vector_if_necessary (vector);
795         if ( gradient != vector && gradient->ref->getObject() != vector ) {
796             sp_gradient_repr_set_link(gradient->getRepr(), vector);
797         }
798 
799         switch (point_type) {
800             case POINT_LG_BEGIN:
801             case POINT_RG_CENTER:
802             case POINT_RG_FOCUS:
803             {
804                 SPStop *first = vector->getFirstStop();
805                 if (first) {
806                     sp_repr_css_change(first->getRepr(), stop, "style");
807                 }
808             }
809             break;
810 
811             case POINT_LG_END:
812             case POINT_RG_R1:
813             case POINT_RG_R2:
814             {
815                 SPStop *last = sp_last_stop (vector);
816                 if (last) {
817                     sp_repr_css_change(last->getRepr(), stop, "style");
818                 }
819             }
820             break;
821 
822             case POINT_LG_MID:
823             case POINT_RG_MID1:
824             case POINT_RG_MID2:
825             {
826                 SPStop *stopi = sp_get_stop_i (vector, point_i);
827                 if (stopi) {
828                     sp_repr_css_change(stopi->getRepr(), stop, "style");
829                 }
830             }
831             break;
832 
833             default:
834                 g_warning( "Bad linear/radial gradient handle type" );
835                 break;
836         }
837     } else {
838 
839         // Mesh gradient
840         SPMeshGradient *mg = SP_MESHGRADIENT(gradient);
841 
842         bool changed = false;
843         switch (point_type) {
844             case POINT_MG_CORNER: {
845 
846                 // Update mesh array (which is not updated automatically when stop is changed?)
847                 gchar const* color_str = sp_repr_css_property( stop, "stop-color", nullptr );
848                 if( color_str ) {
849                     SPColor color( 0 );
850                     SPIPaint paint;
851                     paint.read( color_str );
852                     if( paint.isColor() ) {
853                         color = paint.value.color;
854                     }
855                     mg->array.corners[ point_i ]->color = color;
856                     changed = true;
857                 }
858                 gchar const* opacity_str = sp_repr_css_property( stop, "stop-opacity", nullptr );
859                 if( opacity_str ) {
860                     std::stringstream os( opacity_str );
861                     double opacity = 1.0;
862                     os >> opacity;
863                     mg->array.corners[ point_i ]->opacity = opacity;
864                     changed = true;
865                 }
866                 // Update stop
867                 if( changed ) {
868                     SPStop *stopi = mg->array.corners[ point_i ]->stop;
869                     if (stopi) {
870                         sp_repr_css_change(stopi->getRepr(), stop, "style");
871                     } else {
872                         std::cerr << "sp_item_gradient_stop_set_style: null stopi" << std::endl;
873                     }
874                 }
875                 break;
876             }
877 
878             case POINT_MG_HANDLE:
879             case POINT_MG_TENSOR:
880             {
881                 // Do nothing. Handles and tensors don't have colors.
882                 break;
883             }
884 
885             default:
886                 g_warning( "Bad mesh handle type" );
887         }
888     }
889 }
890 
sp_item_gradient_reverse_vector(SPItem * item,Inkscape::PaintTarget fill_or_stroke)891 void sp_item_gradient_reverse_vector(SPItem *item, Inkscape::PaintTarget fill_or_stroke)
892 {
893 #ifdef SP_GR_VERBOSE
894     g_message("sp_item_gradient_reverse_vector(%p, %d)", item, fill_or_stroke);
895 #endif
896     SPGradient *gradient = getGradient(item, fill_or_stroke);
897     if (!gradient || !SP_IS_GRADIENT(gradient))
898         return;
899 
900     SPGradient *vector = gradient->getVector();
901     if (!vector) // orphan!
902         return;
903 
904     vector = sp_gradient_fork_vector_if_necessary (vector);
905     if ( gradient != vector && gradient->ref->getObject() != vector ) {
906         sp_gradient_repr_set_link(gradient->getRepr(), vector);
907     }
908 
909     std::vector<SPObject *> child_objects;
910     std::vector<Inkscape::XML::Node *>child_reprs;
911     std::vector<double> offsets;
912     double offset;
913     for (auto& child: vector->children) {
914         child_reprs.push_back(child.getRepr());
915         child_objects.push_back(&child);
916         offset=0;
917         sp_repr_get_double(child.getRepr(), "offset", &offset);
918         offsets.push_back(offset);
919     }
920 
921     std::vector<Inkscape::XML::Node *> child_copies;
922     for (auto repr:child_reprs) {
923         Inkscape::XML::Document *xml_doc = vector->getRepr()->document();
924         child_copies.push_back(repr->duplicate(xml_doc));
925     }
926 
927 
928     for (auto i:child_objects) {
929         i->deleteObject();
930     }
931 
932     std::vector<double>::reverse_iterator o_it = offsets.rbegin();
933     for (auto c_it = child_copies.rbegin(); c_it != child_copies.rend(); ++c_it, ++o_it) {
934         vector->appendChildRepr(*c_it);
935         sp_repr_set_svg_double (*c_it, "offset", 1 - *o_it);
936         Inkscape::GC::release(*c_it);
937     }
938 }
939 
sp_item_gradient_invert_vector_color(SPItem * item,Inkscape::PaintTarget fill_or_stroke)940 void sp_item_gradient_invert_vector_color(SPItem *item, Inkscape::PaintTarget fill_or_stroke)
941 {
942 #ifdef SP_GR_VERBOSE
943     g_message("sp_item_gradient_invert_vector_color(%p, %d)", item, fill_or_stroke);
944 #endif
945     SPGradient *gradient = getGradient(item, fill_or_stroke);
946     if (!gradient || !SP_IS_GRADIENT(gradient))
947         return;
948 
949     SPGradient *vector = gradient->getVector();
950     if (!vector) // orphan!
951         return;
952 
953     vector = sp_gradient_fork_vector_if_necessary (vector);
954     if ( gradient != vector && gradient->ref->getObject() != vector ) {
955         sp_gradient_repr_set_link(gradient->getRepr(), vector);
956     }
957 
958     for (auto& child: vector->children) {
959         if (SP_IS_STOP(&child)) {
960             guint32 color =  SP_STOP(&child)->get_rgba32();
961             //g_message("Stop color %d", color);
962             gchar c[64];
963             sp_svg_write_color (c, sizeof(c),
964                 SP_RGBA32_U_COMPOSE(
965                         (255 - SP_RGBA32_R_U(color)),
966                         (255 - SP_RGBA32_G_U(color)),
967                         (255 - SP_RGBA32_B_U(color)),
968                         SP_RGBA32_A_U(color)
969                 )
970             );
971             SPCSSAttr *css = sp_repr_css_attr_new ();
972             sp_repr_css_set_property (css, "stop-color", c);
973             sp_repr_css_change(child.getRepr(), css, "style");
974             sp_repr_css_attr_unref (css);
975         }
976     }
977 }
978 
979 /**
980 Set the position of point point_type of the gradient applied to item (either fill_or_stroke) to
981 p_w (in desktop coordinates). Write_repr if you want the change to become permanent.
982 */
sp_item_gradient_set_coords(SPItem * item,GrPointType point_type,guint point_i,Geom::Point p_w,Inkscape::PaintTarget fill_or_stroke,bool write_repr,bool scale)983 void sp_item_gradient_set_coords(SPItem *item, GrPointType point_type, guint point_i, Geom::Point p_w, Inkscape::PaintTarget fill_or_stroke, bool write_repr, bool scale)
984 {
985 #ifdef SP_GR_VERBOSE
986     g_message("sp_item_gradient_set_coords(%p, %d, %d, (%f, %f), ...)", item, point_type, point_i, p_w[Geom::X], p_w[Geom::Y] );
987 #endif
988     SPGradient *gradient = getGradient(item, fill_or_stroke);
989 
990     if (!gradient || !SP_IS_GRADIENT(gradient))
991         return;
992 
993     // Needed only if units are set to SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX
994     gradient = sp_gradient_convert_to_userspace(gradient, item, (fill_or_stroke == Inkscape::FOR_FILL) ? "fill" : "stroke");
995 
996     Geom::Affine i2d (item->i2dt_affine ());
997     Geom::Point p = p_w * i2d.inverse();
998     p *= (gradient->gradientTransform).inverse();
999     // now p is in gradient's original coordinates
1000 
1001     Inkscape::XML::Node *repr = gradient->getRepr();
1002 
1003     if (SP_IS_LINEARGRADIENT(gradient)) {
1004         SPLinearGradient *lg = SP_LINEARGRADIENT(gradient);
1005         switch (point_type) {
1006             case POINT_LG_BEGIN:
1007                 if (scale) {
1008                     lg->x2.computed += (lg->x1.computed - p[Geom::X]);
1009                     lg->y2.computed += (lg->y1.computed - p[Geom::Y]);
1010                 }
1011                 lg->x1.computed = p[Geom::X];
1012                 lg->y1.computed = p[Geom::Y];
1013                 if (write_repr) {
1014                     if (scale) {
1015                         sp_repr_set_svg_double(repr, "x2", lg->x2.computed);
1016                         sp_repr_set_svg_double(repr, "y2", lg->y2.computed);
1017                     }
1018                     sp_repr_set_svg_double(repr, "x1", lg->x1.computed);
1019                     sp_repr_set_svg_double(repr, "y1", lg->y1.computed);
1020                 } else {
1021                     gradient->requestModified(SP_OBJECT_MODIFIED_FLAG);
1022                 }
1023                 break;
1024             case POINT_LG_END:
1025                 if (scale) {
1026                     lg->x1.computed += (lg->x2.computed - p[Geom::X]);
1027                     lg->y1.computed += (lg->y2.computed - p[Geom::Y]);
1028                 }
1029                 lg->x2.computed = p[Geom::X];
1030                 lg->y2.computed = p[Geom::Y];
1031                 if (write_repr) {
1032                     if (scale) {
1033                         sp_repr_set_svg_double(repr, "x1", lg->x1.computed);
1034                         sp_repr_set_svg_double(repr, "y1", lg->y1.computed);
1035                     }
1036                     sp_repr_set_svg_double(repr, "x2", lg->x2.computed);
1037                     sp_repr_set_svg_double(repr, "y2", lg->y2.computed);
1038                 } else {
1039                     gradient->requestModified(SP_OBJECT_MODIFIED_FLAG);
1040                 }
1041                 break;
1042             case POINT_LG_MID:
1043             {
1044                 // using X-coordinates only to determine the offset, assuming p has been snapped to the vector from begin to end.
1045                 Geom::Point begin(lg->x1.computed, lg->y1.computed);
1046                 Geom::Point end(lg->x2.computed, lg->y2.computed);
1047                 double offset = Geom::LineSegment(begin, end).nearestTime(p);
1048                 SPGradient *vector = sp_gradient_get_forked_vector_if_necessary (lg, false);
1049                 lg->ensureVector();
1050                 lg->vector.stops.at(point_i).offset = offset;
1051                 SPStop* stopi = sp_get_stop_i(vector, point_i);
1052                 stopi->offset = offset;
1053                 if (write_repr) {
1054                     sp_repr_set_css_double(stopi->getRepr(), "offset", stopi->offset);
1055                 } else {
1056                     stopi->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1057                 }
1058             }
1059             break;
1060             default:
1061                 g_warning( "Bad linear gradient handle type" );
1062                 break;
1063         }
1064     } else if (SP_IS_RADIALGRADIENT(gradient)) {
1065         SPRadialGradient *rg = SP_RADIALGRADIENT(gradient);
1066         Geom::Point c (rg->cx.computed, rg->cy.computed);
1067         Geom::Point c_w = c * gradient->gradientTransform * i2d; // now in desktop coords
1068         if ((point_type == POINT_RG_R1 || point_type == POINT_RG_R2) && Geom::L2 (p_w - c_w) < 1e-3) {
1069             // prevent setting a radius too close to the center
1070             return;
1071         }
1072         Geom::Affine new_transform;
1073         bool transform_set = false;
1074 
1075         switch (point_type) {
1076             case POINT_RG_CENTER:
1077                 rg->fx.computed = p[Geom::X] + (rg->fx.computed - rg->cx.computed);
1078                 rg->fy.computed = p[Geom::Y] + (rg->fy.computed - rg->cy.computed);
1079                 rg->cx.computed = p[Geom::X];
1080                 rg->cy.computed = p[Geom::Y];
1081                 if (write_repr) {
1082                     sp_repr_set_svg_double(repr, "fx", rg->fx.computed);
1083                     sp_repr_set_svg_double(repr, "fy", rg->fy.computed);
1084                     sp_repr_set_svg_double(repr, "cx", rg->cx.computed);
1085                     sp_repr_set_svg_double(repr, "cy", rg->cy.computed);
1086                 } else {
1087                     gradient->requestModified(SP_OBJECT_MODIFIED_FLAG);
1088                 }
1089                 break;
1090             case POINT_RG_FOCUS:
1091                 rg->fx.computed = p[Geom::X];
1092                 rg->fy.computed = p[Geom::Y];
1093                 if (write_repr) {
1094                     sp_repr_set_svg_double(repr, "fx", rg->fx.computed);
1095                     sp_repr_set_svg_double(repr, "fy", rg->fy.computed);
1096                 } else {
1097                     gradient->requestModified(SP_OBJECT_MODIFIED_FLAG);
1098                 }
1099                 break;
1100             case POINT_RG_R1:
1101             {
1102                 Geom::Point r1_w = (c + Geom::Point(rg->r.computed, 0)) * gradient->gradientTransform * i2d;
1103                 double r1_angle = Geom::atan2(r1_w - c_w);
1104                 double move_angle = Geom::atan2(p_w - c_w) - r1_angle;
1105                 double move_stretch = Geom::L2(p_w - c_w) / Geom::L2(r1_w - c_w);
1106 
1107                 Geom::Affine move = Geom::Affine (Geom::Translate (-c_w)) *
1108                     Geom::Affine (Geom::Rotate(-r1_angle)) *
1109                     Geom::Affine (Geom::Scale(move_stretch, scale? move_stretch : 1)) *
1110                     Geom::Affine (Geom::Rotate(r1_angle)) *
1111                     Geom::Affine (Geom::Rotate(move_angle)) *
1112                     Geom::Affine (Geom::Translate (c_w));
1113 
1114                 new_transform = gradient->gradientTransform * i2d * move * i2d.inverse();
1115                 transform_set = true;
1116 
1117                 break;
1118             }
1119             case POINT_RG_R2:
1120             {
1121                 Geom::Point r2_w = (c + Geom::Point(0, -rg->r.computed)) * gradient->gradientTransform * i2d;
1122                 double r2_angle = Geom::atan2(r2_w - c_w);
1123                 double move_angle = Geom::atan2(p_w - c_w) - r2_angle;
1124                 double move_stretch = Geom::L2(p_w - c_w) / Geom::L2(r2_w - c_w);
1125 
1126                 Geom::Affine move = Geom::Affine (Geom::Translate (-c_w)) *
1127                     Geom::Affine (Geom::Rotate(-r2_angle)) *
1128                     Geom::Affine (Geom::Scale(move_stretch, scale? move_stretch : 1)) *
1129                     Geom::Affine (Geom::Rotate(r2_angle)) *
1130                     Geom::Affine (Geom::Rotate(move_angle)) *
1131                     Geom::Affine (Geom::Translate (c_w));
1132 
1133                 new_transform = gradient->gradientTransform * i2d * move * i2d.inverse();
1134                 transform_set = true;
1135 
1136                 break;
1137             }
1138             case POINT_RG_MID1:
1139             {
1140                 Geom::Point start = Geom::Point (rg->cx.computed, rg->cy.computed);
1141                 Geom::Point end   = Geom::Point (rg->cx.computed + rg->r.computed, rg->cy.computed);
1142                 double offset = Geom::LineSegment(start, end).nearestTime(p);
1143                 SPGradient *vector = sp_gradient_get_forked_vector_if_necessary (rg, false);
1144                 rg->ensureVector();
1145                 rg->vector.stops.at(point_i).offset = offset;
1146                 SPStop* stopi = sp_get_stop_i(vector, point_i);
1147                 stopi->offset = offset;
1148                 if (write_repr) {
1149                     sp_repr_set_css_double(stopi->getRepr(), "offset", stopi->offset);
1150                 } else {
1151                     stopi->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1152                 }
1153                 break;
1154             }
1155             case POINT_RG_MID2:
1156             {
1157                 Geom::Point start = Geom::Point (rg->cx.computed, rg->cy.computed);
1158                 Geom::Point end   = Geom::Point (rg->cx.computed, rg->cy.computed - rg->r.computed);
1159                 double offset = Geom::LineSegment(start, end).nearestTime(p);
1160                 SPGradient *vector = sp_gradient_get_forked_vector_if_necessary(rg, false);
1161                 rg->ensureVector();
1162                 rg->vector.stops.at(point_i).offset = offset;
1163                 SPStop* stopi = sp_get_stop_i(vector, point_i);
1164                 stopi->offset = offset;
1165                 if (write_repr) {
1166                     sp_repr_set_css_double(stopi->getRepr(), "offset", stopi->offset);
1167                 } else {
1168                     stopi->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1169                 }
1170                 break;
1171             }
1172             default:
1173                 g_warning( "Bad radial gradient handle type" );
1174                 break;
1175         }
1176 
1177         if (transform_set) {
1178             gradient->gradientTransform = new_transform;
1179             gradient->gradientTransform_set = TRUE;
1180             if (write_repr) {
1181                 auto s = sp_svg_transform_write(gradient->gradientTransform);
1182                 gradient->setAttributeOrRemoveIfEmpty("gradientTransform", s);
1183             } else {
1184                 gradient->requestModified(SP_OBJECT_MODIFIED_FLAG);
1185             }
1186         }
1187     } else if (SP_IS_MESHGRADIENT(gradient)) {
1188         SPMeshGradient *mg = SP_MESHGRADIENT(gradient);
1189         //Geom::Affine new_transform;
1190         //bool transform_set = false;
1191 
1192         switch (point_type) {
1193             case POINT_MG_CORNER:
1194             {
1195                 mg->array.corners[ point_i ]->p = p;
1196                 // Handles are moved in gradient-drag.cpp
1197                 gradient->requestModified(SP_OBJECT_MODIFIED_FLAG);
1198                 break;
1199             }
1200 
1201             case POINT_MG_HANDLE: {
1202                 mg->array.handles[ point_i ]->p = p;
1203                 gradient->requestModified(SP_OBJECT_MODIFIED_FLAG);
1204                 break;
1205             }
1206 
1207             case POINT_MG_TENSOR: {
1208                 mg->array.tensors[ point_i ]->p = p;
1209                 gradient->requestModified(SP_OBJECT_MODIFIED_FLAG);
1210                 break;
1211             }
1212 
1213             default:
1214                 g_warning( "Bad mesh handle type" );
1215         }
1216         if( write_repr ) {
1217             //std::cout << "Write mesh repr" << std::endl;
1218             mg->array.write( mg );
1219         }
1220     }
1221 
1222 }
1223 
sp_item_gradient_get_vector(SPItem * item,Inkscape::PaintTarget fill_or_stroke)1224 SPGradient *sp_item_gradient_get_vector(SPItem *item, Inkscape::PaintTarget fill_or_stroke)
1225 {
1226     SPGradient *gradient = getGradient(item, fill_or_stroke);
1227 
1228     if (gradient) {
1229         return gradient->getVector();
1230     }
1231     return nullptr;
1232 }
1233 
sp_item_gradient_get_spread(SPItem * item,Inkscape::PaintTarget fill_or_stroke)1234 SPGradientSpread sp_item_gradient_get_spread(SPItem *item, Inkscape::PaintTarget fill_or_stroke)
1235 {
1236     SPGradientSpread spread = SP_GRADIENT_SPREAD_PAD;
1237     SPGradient *gradient = getGradient(item, fill_or_stroke);
1238 
1239     if (gradient) {
1240         spread = gradient->fetchSpread();
1241     }
1242     return spread;
1243 }
1244 
1245 
1246 /**
1247 Returns the position of point point_type of the gradient applied to item (either fill_or_stroke),
1248 in desktop coordinates.
1249 */
getGradientCoords(SPItem * item,GrPointType point_type,guint point_i,Inkscape::PaintTarget fill_or_stroke)1250 Geom::Point getGradientCoords(SPItem *item, GrPointType point_type, guint point_i, Inkscape::PaintTarget fill_or_stroke)
1251 {
1252     SPGradient *gradient = getGradient(item, fill_or_stroke);
1253 #ifdef SP_GR_VERBOSE
1254     g_message("getGradientCoords(%p, %d, %d, %d, %p)", item, point_type, point_i, fill_or_stroke, gradient);
1255 #endif
1256 
1257     Geom::Point p (0, 0);
1258 
1259     if (!gradient)
1260         return p;
1261 
1262     if (SP_IS_LINEARGRADIENT(gradient)) {
1263         SPLinearGradient *lg = SP_LINEARGRADIENT(gradient);
1264         switch (point_type) {
1265             case POINT_LG_BEGIN:
1266                 p = Geom::Point (lg->x1.computed, lg->y1.computed);
1267                 break;
1268             case POINT_LG_END:
1269                 p = Geom::Point (lg->x2.computed, lg->y2.computed);
1270                 break;
1271             case POINT_LG_MID:
1272                 {
1273                     if (lg->vector.stops.size() < point_i) {
1274                         g_message("POINT_LG_MID bug trigger, see LP bug #453067");
1275                         break;
1276                     }
1277                     gdouble offset = lg->vector.stops.at(point_i).offset;
1278                     p = (1-offset) * Geom::Point(lg->x1.computed, lg->y1.computed) + offset * Geom::Point(lg->x2.computed, lg->y2.computed);
1279                 }
1280                 break;
1281             default:
1282                 g_warning( "Bad linear gradient handle type" );
1283                 break;
1284         }
1285     } else     if (SP_IS_RADIALGRADIENT(gradient)) {
1286         SPRadialGradient *rg = SP_RADIALGRADIENT(gradient);
1287         switch (point_type) {
1288             case POINT_RG_CENTER:
1289                 p = Geom::Point (rg->cx.computed, rg->cy.computed);
1290                 break;
1291             case POINT_RG_FOCUS:
1292                 p = Geom::Point (rg->fx.computed, rg->fy.computed);
1293                 break;
1294             case POINT_RG_R1:
1295                 p = Geom::Point (rg->cx.computed + rg->r.computed, rg->cy.computed);
1296                 break;
1297             case POINT_RG_R2:
1298                 p = Geom::Point (rg->cx.computed, rg->cy.computed - rg->r.computed);
1299                 break;
1300             case POINT_RG_MID1:
1301                 {
1302                     if (rg->vector.stops.size() < point_i) {
1303                         g_message("POINT_RG_MID1 bug trigger, see LP bug #453067");
1304                         break;
1305                     }
1306                     gdouble offset = rg->vector.stops.at(point_i).offset;
1307                     p = (1-offset) * Geom::Point (rg->cx.computed, rg->cy.computed) + offset * Geom::Point(rg->cx.computed + rg->r.computed, rg->cy.computed);
1308                 }
1309                 break;
1310             case POINT_RG_MID2:
1311                 {
1312                     if (rg->vector.stops.size() < point_i) {
1313                         g_message("POINT_RG_MID2 bug trigger, see LP bug #453067");
1314                         break;
1315                     }
1316                     gdouble offset = rg->vector.stops.at(point_i).offset;
1317                     p = (1-offset) * Geom::Point (rg->cx.computed, rg->cy.computed) + offset * Geom::Point(rg->cx.computed, rg->cy.computed - rg->r.computed);
1318                 }
1319                 break;
1320             default:
1321                 g_warning( "Bad radial gradient handle type" );
1322                 break;
1323         }
1324     } else     if (SP_IS_MESHGRADIENT(gradient)) {
1325         SPMeshGradient *mg = SP_MESHGRADIENT(gradient);
1326         switch (point_type) {
1327 
1328             case POINT_MG_CORNER:
1329                 p = mg->array.corners[ point_i ]->p;
1330                 break;
1331 
1332             case POINT_MG_HANDLE: {
1333                 p = mg->array.handles[ point_i ]->p;
1334                 break;
1335             }
1336 
1337             case POINT_MG_TENSOR: {
1338                 p = mg->array.tensors[ point_i ]->p;
1339                 break;
1340             }
1341 
1342             default:
1343                 g_warning( "Bad mesh handle type" );
1344         }
1345     }
1346 
1347 
1348     if (SP_GRADIENT(gradient)->getUnits() == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) {
1349         item->document->ensureUpToDate();
1350         Geom::OptRect bbox = item->visualBounds(); // we need "true" bbox without item_i2d_affine
1351         if (bbox) {
1352             p *= Geom::Affine(bbox->dimensions()[Geom::X], 0,
1353                             0, bbox->dimensions()[Geom::Y],
1354                             bbox->min()[Geom::X], bbox->min()[Geom::Y]);
1355         }
1356     }
1357     p *= Geom::Affine(gradient->gradientTransform) * (Geom::Affine)item->i2dt_affine();
1358     return p;
1359 }
1360 
1361 /**
1362  * Sets item fill or stroke to the gradient of the specified type with given vector, creating
1363  * new private gradient, if needed.
1364  * gr has to be a normalized vector.
1365  */
1366 
sp_item_set_gradient(SPItem * item,SPGradient * gr,SPGradientType type,Inkscape::PaintTarget fill_or_stroke)1367 SPGradient *sp_item_set_gradient(SPItem *item, SPGradient *gr, SPGradientType type, Inkscape::PaintTarget fill_or_stroke)
1368 {
1369 #ifdef SP_GR_VERBOSE
1370     g_message("sp_item_set_gradient(%p, %p, %d, %d)", item, gr, type, fill_or_stroke);
1371 #endif
1372     g_return_val_if_fail(item != nullptr, NULL);
1373     g_return_val_if_fail(SP_IS_ITEM(item), NULL);
1374     g_return_val_if_fail(gr != nullptr, NULL);
1375     g_return_val_if_fail(SP_IS_GRADIENT(gr), NULL);
1376     g_return_val_if_fail(gr->state == SP_GRADIENT_STATE_VECTOR, NULL);
1377 
1378     SPStyle *style = item->style;
1379     g_assert(style != nullptr);
1380 
1381     SPPaintServer *ps = nullptr;
1382     if ((fill_or_stroke == Inkscape::FOR_FILL) ? style->fill.isPaintserver() : style->stroke.isPaintserver()) {
1383         ps = (fill_or_stroke == Inkscape::FOR_FILL) ? SP_STYLE_FILL_SERVER(style) : SP_STYLE_STROKE_SERVER(style);
1384     }
1385 
1386     if (ps
1387         && ( (type == SP_GRADIENT_TYPE_LINEAR && SP_IS_LINEARGRADIENT(ps)) ||
1388              (type == SP_GRADIENT_TYPE_RADIAL && SP_IS_RADIALGRADIENT(ps))   ) )
1389     {
1390 
1391         /* Current fill style is the gradient of the required type */
1392         SPGradient *current = SP_GRADIENT(ps);
1393 
1394         //g_message("hrefcount %d   count %d\n", current->hrefcount, count_gradient_hrefs(item, current));
1395 
1396         if (!current->isSwatch()
1397             && (current->hrefcount == 1 ||
1398             current->hrefcount == count_gradient_hrefs(item, current))) {
1399 
1400             // current is private and it's either used once, or all its uses are by children of item;
1401             // so just change its href to vector
1402 
1403             if ( current != gr && current->getVector() != gr ) {
1404                 // href is not the vector
1405                 sp_gradient_repr_set_link(current->getRepr(), gr);
1406             }
1407             item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1408             return current;
1409 
1410         } else {
1411 
1412             // the gradient is not private, or it is shared with someone else;
1413             // normalize it (this includes creating new private if necessary)
1414             SPGradient *normalized = sp_gradient_fork_private_if_necessary(current, gr, type, item);
1415 
1416             g_return_val_if_fail(normalized != nullptr, NULL);
1417 
1418             if (normalized != current) {
1419 
1420                 /* We have to change object style here; recursive because this is used from
1421                  * fill&stroke and must work for groups etc. */
1422                 sp_style_set_property_url(item, (fill_or_stroke == Inkscape::FOR_FILL) ? "fill" : "stroke", normalized, true);
1423             }
1424             item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1425             return normalized;
1426         }
1427 
1428     } else {
1429         /* Current fill style is not a gradient or wrong type, so construct everything */
1430         /* This is where mesh gradients are constructed. */
1431         g_assert(SP_IS_GRADIENT(gr)); // TEMP
1432         SPGradient *constructed = sp_gradient_get_private_normalized(item->document, gr, type);
1433         constructed = sp_gradient_reset_to_userspace(constructed, item);
1434         sp_style_set_property_url(item, ( (fill_or_stroke == Inkscape::FOR_FILL) ? "fill" : "stroke" ), constructed, true);
1435         item->requestDisplayUpdate(( SP_OBJECT_MODIFIED_FLAG |
1436                                      SP_OBJECT_STYLE_MODIFIED_FLAG ));
1437         return constructed;
1438     }
1439 }
1440 
sp_gradient_repr_set_link(Inkscape::XML::Node * repr,SPGradient * link)1441 static void sp_gradient_repr_set_link(Inkscape::XML::Node *repr, SPGradient *link)
1442 {
1443 #ifdef SP_GR_VERBOSE
1444     g_message("sp_gradient_repr_set_link(%p, %p)", repr, link);
1445 #endif
1446     g_return_if_fail(repr != nullptr);
1447     if (link) {
1448         g_return_if_fail(SP_IS_GRADIENT(link));
1449     }
1450 
1451     if (link) {
1452         Glib::ustring ref("#");
1453         ref += link->getId();
1454         repr->setAttribute("xlink:href", ref);
1455     } else {
1456         repr->removeAttribute("xlink:href");
1457     }
1458 }
1459 
1460 
addStop(Inkscape::XML::Node * parent,Glib::ustring const & color,gint opacity,gchar const * offset)1461 static void addStop( Inkscape::XML::Node *parent, Glib::ustring const &color, gint opacity, gchar const *offset )
1462 {
1463 #ifdef SP_GR_VERBOSE
1464     g_message("addStop(%p, %s, %d, %s)", parent, color.c_str(), opacity, offset);
1465 #endif
1466     Inkscape::XML::Node *stop = parent->document()->createElement("svg:stop");
1467     {
1468         gchar *tmp = g_strdup_printf( "stop-color:%s;stop-opacity:%d;", color.c_str(), opacity );
1469         stop->setAttribute( "style", tmp );
1470         g_free(tmp);
1471     }
1472 
1473     stop->setAttribute( "offset", offset );
1474 
1475     parent->appendChild(stop);
1476     Inkscape::GC::release(stop);
1477 }
1478 
1479 /*
1480  * Get default normalized gradient vector of document, create if there is none
1481  */
sp_document_default_gradient_vector(SPDocument * document,SPColor const & color,bool singleStop)1482 SPGradient *sp_document_default_gradient_vector( SPDocument *document, SPColor const &color, bool singleStop )
1483 {
1484     SPDefs *defs = document->getDefs();
1485     Inkscape::XML::Document *xml_doc = document->getReprDoc();
1486 
1487     Inkscape::XML::Node *repr = xml_doc->createElement("svg:linearGradient");
1488 
1489     if ( !singleStop ) {
1490         repr->setAttribute("inkscape:collect", "always");
1491         // set here, but removed when it's edited in the gradient editor
1492         // to further reduce clutter, we could
1493         // (1) here, search gradients by color and return what is found without duplication
1494         // (2) in fill & stroke, show only one copy of each gradient in list
1495     }
1496 
1497     Glib::ustring colorStr = color.toString();
1498     addStop( repr, colorStr, 1, "0" );
1499     if ( !singleStop ) {
1500         addStop( repr, colorStr, 0, "1" );
1501     }
1502 
1503     defs->getRepr()->addChild(repr, nullptr);
1504     Inkscape::GC::release(repr);
1505 
1506     /* fixme: This does not look like nice */
1507     SPGradient *gr = static_cast<SPGradient *>(document->getObjectByRepr(repr));
1508     g_assert(gr != nullptr);
1509     g_assert(SP_IS_GRADIENT(gr));
1510     /* fixme: Maybe add extra sanity check here */
1511     gr->state = SP_GRADIENT_STATE_VECTOR;
1512 
1513     return gr;
1514 }
1515 
sp_gradient_vector_for_object(SPDocument * const doc,SPDesktop * const desktop,SPObject * const o,Inkscape::PaintTarget const fill_or_stroke,bool singleStop)1516 SPGradient *sp_gradient_vector_for_object( SPDocument *const doc, SPDesktop *const desktop,
1517                                            SPObject *const o, Inkscape::PaintTarget const fill_or_stroke, bool singleStop )
1518 {
1519     SPColor color;
1520     if ( (o == nullptr) || (o->style == nullptr) ) {
1521         color = sp_desktop_get_color(desktop, (fill_or_stroke == Inkscape::FOR_FILL));
1522     } else {
1523         // take the color of the object
1524         SPStyle const &style = *(o->style);
1525         SPIPaint const &paint = *style.getFillOrStroke(fill_or_stroke == Inkscape::FOR_FILL);
1526         if (paint.isPaintserver()) {
1527             SPObject *server = (fill_or_stroke == Inkscape::FOR_FILL) ? o->style->getFillPaintServer() : o->style->getStrokePaintServer();
1528             if ( SP_IS_LINEARGRADIENT(server) || SP_IS_RADIALGRADIENT(server) ) {
1529                 return SP_GRADIENT(server)->getVector(true);
1530             } else {
1531                 color = sp_desktop_get_color(desktop, (fill_or_stroke == Inkscape::FOR_FILL));
1532             }
1533         } else if (paint.isColor()) {
1534             color = paint.value.color;
1535         } else {
1536             // if o doesn't use flat color, then take current color of the desktop.
1537             color = sp_desktop_get_color(desktop, (fill_or_stroke == Inkscape::FOR_FILL));
1538         }
1539     }
1540 
1541     return sp_document_default_gradient_vector( doc, color, singleStop );
1542 }
1543 
sp_gradient_invert_selected_gradients(SPDesktop * desktop,Inkscape::PaintTarget fill_or_stroke)1544 void sp_gradient_invert_selected_gradients(SPDesktop *desktop, Inkscape::PaintTarget fill_or_stroke)
1545 {
1546     Inkscape::Selection *selection = desktop->getSelection();
1547 
1548     auto list= selection->items();
1549     for (auto i = list.begin(); i != list.end(); ++i) {
1550         sp_item_gradient_invert_vector_color(*i, fill_or_stroke);
1551     }
1552 
1553     // we did an undoable action
1554     DocumentUndo::done(desktop->getDocument(), SP_VERB_CONTEXT_GRADIENT,
1555                        _("Invert gradient colors"));
1556 }
1557 
sp_gradient_reverse_selected_gradients(SPDesktop * desktop)1558 void sp_gradient_reverse_selected_gradients(SPDesktop *desktop)
1559 {
1560     Inkscape::Selection *selection = desktop->getSelection();
1561     Inkscape::UI::Tools::ToolBase *ev = desktop->getEventContext();
1562 
1563     if (!ev) {
1564         return;
1565     }
1566 
1567     GrDrag *drag = ev->get_drag();
1568 
1569     // First try selected dragger
1570     if (drag && !drag->selected.empty()) {
1571         drag->selected_reverse_vector();
1572     } else { // If no drag or no dragger selected, act on selection (both fill and stroke gradients)
1573         auto list= selection->items();
1574         for (auto i = list.begin(); i != list.end(); ++i) {
1575             sp_item_gradient_reverse_vector(*i, Inkscape::FOR_FILL);
1576             sp_item_gradient_reverse_vector(*i, Inkscape::FOR_STROKE);
1577         }
1578     }
1579 
1580     // we did an undoable action
1581     DocumentUndo::done(desktop->getDocument(), SP_VERB_CONTEXT_GRADIENT,
1582                        _("Reverse gradient"));
1583 }
1584 
sp_gradient_unset_swatch(SPDesktop * desktop,std::string const & id)1585 void sp_gradient_unset_swatch(SPDesktop *desktop, std::string const &id)
1586 {
1587     SPDocument *doc = desktop ? desktop->doc() : nullptr;
1588 
1589     if (doc) {
1590         const std::vector<SPObject *> gradients = doc->getResourceList("gradient");
1591         for (auto gradient : gradients) {
1592             SPGradient* grad = SP_GRADIENT(gradient);
1593             if ( id == grad->getId() ) {
1594                 grad->setSwatch(false);
1595                 DocumentUndo::done(doc, SP_VERB_CONTEXT_GRADIENT,
1596                                    _("Delete swatch"));
1597                 break;
1598             }
1599         }
1600     }
1601 }
1602 /*
1603   Local Variables:
1604   mode:c++
1605   c-file-style:"stroustrup"
1606   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1607   indent-tabs-mode:nil
1608   fill-column:99
1609   End:
1610 */
1611 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
1612