diff options
Diffstat (limited to 'src/gradient-chemistry.cpp')
-rw-r--r-- | src/gradient-chemistry.cpp | 1966 |
1 files changed, 1966 insertions, 0 deletions
diff --git a/src/gradient-chemistry.cpp b/src/gradient-chemistry.cpp new file mode 100644 index 0000000..680b097 --- /dev/null +++ b/src/gradient-chemistry.cpp @@ -0,0 +1,1966 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Various utility methods for gradients + * + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * bulia byak + * Johan Engelen <j.b.c.engelen@ewi.utwente.nl> + * Jon A. Cruz <jon@joncruz.org> + * Abhishek Sharma + * Tavmjong Bah <tavmjong@free.fr> + * + * Copyright (C) 2012 Tavmjong Bah + * Copyright (C) 2010 Authors + * Copyright (C) 2007 Johan Engelen + * Copyright (C) 2001-2005 authors + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <glibmm/i18n.h> + +#include <2geom/transforms.h> +#include <2geom/bezier-curve.h> +#include <2geom/crossing.h> +#include <2geom/line.h> + +#include "desktop-style.h" +#include "desktop.h" +#include "document-undo.h" +#include "gradient-chemistry.h" +#include "gradient-drag.h" +#include "selection.h" + +#include "object/sp-defs.h" +#include "object/sp-gradient-reference.h" +#include "object/sp-linear-gradient.h" +#include "object/sp-mesh-gradient.h" +#include "object/sp-radial-gradient.h" +#include "object/sp-stop.h" +#include "object/sp-text.h" +#include "object/sp-tspan.h" +#include "object/sp-root.h" +#include "style.h" + +#include "svg/svg.h" +#include "svg/svg-color.h" +#include "svg/css-ostringstream.h" + +#include "ui/icon-names.h" +#include "ui/tools/tool-base.h" +#include "ui/widget/gradient-vector-selector.h" + +#define noSP_GR_VERBOSE + +using Inkscape::DocumentUndo; + +namespace { + +Inkscape::PaintTarget paintTargetItems[] = {Inkscape::FOR_FILL, Inkscape::FOR_STROKE}; + +std::vector<Inkscape::PaintTarget> vectorOfPaintTargets(paintTargetItems, paintTargetItems + (sizeof(paintTargetItems) / sizeof(paintTargetItems[0]))); + +} // namespace + +namespace Inkscape { + +std::vector<PaintTarget> const &allPaintTargets() +{ + return vectorOfPaintTargets; +} + +} // namespace Inkscape + +// Terminology: +// +// "vector" is a gradient that has stops but not position coords. It can be referenced by one or +// more privates. Objects should not refer to it directly. It has no radial/linear distinction. +// +// "array" is a gradient that has mesh rows and patches. It may or may not have "x" and "y" attributes. +// An array does have spacial information so it cannot be normalized like a "vector". +// +// "shared" is either a "vector" or "array" that is shared between multiple objects. +// +// "private" is a gradient that is not shared. A private linear or radial gradient has no stops but +// has position coords (e.g. center, radius etc for a radial); it references a "vector" for the +// actual colors. A mesh may or may not reference an array. Each private is only used by one object. + +static void sp_gradient_repr_set_link(Inkscape::XML::Node *repr, SPGradient *gr); + +SPGradient *sp_gradient_ensure_vector_normalized(SPGradient *gr) +{ +#ifdef SP_GR_VERBOSE + g_message("sp_gradient_ensure_vector_normalized(%p)", gr); +#endif + g_return_val_if_fail(gr != nullptr, NULL); + g_return_val_if_fail(SP_IS_GRADIENT(gr), NULL); + g_return_val_if_fail(!SP_IS_MESHGRADIENT(gr), NULL); + + /* If we are already normalized vector, just return */ + if (gr->state == SP_GRADIENT_STATE_VECTOR) return gr; + /* Fail, if we have wrong state set */ + if (gr->state != SP_GRADIENT_STATE_UNKNOWN) { + g_warning("file %s: line %d: Cannot normalize private gradient to vector (%s)", __FILE__, __LINE__, gr->getId()); + return nullptr; + } + + /* First make sure we have vector directly defined (i.e. gr has its own stops) */ + if ( !gr->hasStops() ) { + /* We do not have stops ourselves, so flatten stops as well */ + gr->ensureVector(); + g_assert(gr->vector.built); + // this adds stops from gr->vector as children to gr + gr->repr_write_vector (); + } + + /* If gr hrefs some other gradient, remove the href */ + if (gr->ref){ + if (gr->ref->getObject()) { + // We are hrefing someone, so require flattening + gr->updateRepr(SP_OBJECT_WRITE_EXT | SP_OBJECT_WRITE_ALL); + sp_gradient_repr_set_link(gr->getRepr(), nullptr); + } + } + + /* Everything is OK, set state flag */ + gr->state = SP_GRADIENT_STATE_VECTOR; + return gr; +} + +/** + * Creates new private gradient for the given shared gradient. + */ + +static SPGradient *sp_gradient_get_private_normalized(SPDocument *document, SPGradient *shared, SPGradientType type) +{ +#ifdef SP_GR_VERBOSE + g_message("sp_gradient_get_private_normalized(%p, %p, %d)", document, shared, type); +#endif + + g_return_val_if_fail(document != nullptr, NULL); + g_return_val_if_fail(shared != nullptr, NULL); + g_return_val_if_fail(SP_IS_GRADIENT(shared), NULL); + g_return_val_if_fail(shared->hasStops() || shared->hasPatches(), NULL); + + SPDefs *defs = document->getDefs(); + + Inkscape::XML::Document *xml_doc = document->getReprDoc(); + // create a new private gradient of the requested type + Inkscape::XML::Node *repr; + if (type == SP_GRADIENT_TYPE_LINEAR) { + repr = xml_doc->createElement("svg:linearGradient"); + } else if(type == SP_GRADIENT_TYPE_RADIAL) { + repr = xml_doc->createElement("svg:radialGradient"); + } else { + repr = xml_doc->createElement("svg:meshgradient"); + } + + // make auto collection optional + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/option/gradient/auto_collect", true)) { + repr->setAttribute("inkscape:collect", "always"); + } else { + repr->setAttribute("inkscape:collect", "never"); + } + + // link to shared + sp_gradient_repr_set_link(repr, shared); + + /* Append the new private gradient to defs */ + defs->getRepr()->appendChild(repr); + Inkscape::GC::release(repr); + + // get corresponding object + SPGradient *gr = static_cast<SPGradient *>(document->getObjectByRepr(repr)); + g_assert(gr != nullptr); + g_assert(SP_IS_GRADIENT(gr)); + + return gr; +} + +/** +Count how many times gr is used by the styles of o and its descendants +*/ +static guint count_gradient_hrefs(SPObject *o, SPGradient *gr) +{ + if (!o) + return 1; + + guint i = 0; + + SPStyle *style = o->style; + if (style + && style->fill.isPaintserver() + && SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style)) + && SP_GRADIENT(SP_STYLE_FILL_SERVER(style)) == gr) + { + i ++; + } + if (style + && style->stroke.isPaintserver() + && SP_IS_GRADIENT(SP_STYLE_STROKE_SERVER(style)) + && SP_GRADIENT(SP_STYLE_STROKE_SERVER(style)) == gr) + { + i ++; + } + + for (auto& child: o->children) { + i += count_gradient_hrefs(&child, gr); + } + + return i; +} + + +/** + * If gr has other users, create a new shared; also check if gr links to shared, relink if not + */ +static SPGradient *sp_gradient_fork_private_if_necessary(SPGradient *gr, SPGradient *shared, + SPGradientType type, SPObject *o) +{ +#ifdef SP_GR_VERBOSE + g_message("sp_gradient_fork_private_if_necessary(%p, %p, %d, %p)", gr, shared, type, o); +#endif + g_return_val_if_fail(gr != nullptr, NULL); + g_return_val_if_fail(SP_IS_GRADIENT(gr), NULL); + + // Orphaned gradient, no shared with stops or patches at the end of the line; this used to be + // an assert + if ( !shared || !(shared->hasStops() || shared->hasPatches()) ) { + std::cerr << "sp_gradient_fork_private_if_necessary: Orphaned gradient" << std::endl; + return (gr); + } + + // user is the object that uses this gradient; normally it's item but for tspans, we + // check its ancestor text so that tspans don't get different gradients from their + // texts. + SPObject *user = o; + while (SP_IS_TSPAN(user)) { + user = user->parent; + } + + // Check the number of uses of the gradient within this object; + // if we are private and there are no other users, + if (!shared->isSwatch() && (gr->hrefcount <= count_gradient_hrefs(user, gr))) { + // check shared + if ( gr != shared && gr->ref->getObject() != shared ) { + /* our href is not the shared, and shared is different from gr; relink */ + sp_gradient_repr_set_link(gr->getRepr(), shared); + } + return gr; + } + + SPDocument *doc = gr->document; + SPObject *defs = doc->getDefs(); + + if ((gr->hasStops()) || + (gr->hasPatches()) || + (gr->state != SP_GRADIENT_STATE_UNKNOWN) || + (gr->parent != defs) || + (gr->hrefcount > 1)) { + + // we have to clone a fresh new private gradient for the given shared + + // create an empty one + SPGradient *gr_new = sp_gradient_get_private_normalized(doc, shared, type); + + // copy all the attributes to it + Inkscape::XML::Node *repr_new = gr_new->getRepr(); + Inkscape::XML::Node *repr = gr->getRepr(); + repr_new->setAttribute("gradientUnits", repr->attribute("gradientUnits")); + repr_new->setAttribute("gradientTransform", repr->attribute("gradientTransform")); + if (SP_IS_RADIALGRADIENT(gr)) { + repr_new->setAttribute("cx", repr->attribute("cx")); + repr_new->setAttribute("cy", repr->attribute("cy")); + repr_new->setAttribute("fx", repr->attribute("fx")); + repr_new->setAttribute("fy", repr->attribute("fy")); + repr_new->setAttribute("r", repr->attribute("r" )); + repr_new->setAttribute("fr", repr->attribute("fr")); + repr_new->setAttribute("spreadMethod", repr->attribute("spreadMethod")); + } else if (SP_IS_LINEARGRADIENT(gr)) { + repr_new->setAttribute("x1", repr->attribute("x1")); + repr_new->setAttribute("y1", repr->attribute("y1")); + repr_new->setAttribute("x2", repr->attribute("x2")); + repr_new->setAttribute("y2", repr->attribute("y2")); + repr_new->setAttribute("spreadMethod", repr->attribute("spreadMethod")); + } else { // Mesh + repr_new->setAttribute("x", repr->attribute("x")); + repr_new->setAttribute("y", repr->attribute("y")); + repr_new->setAttribute("type", repr->attribute("type")); + + // We probably want a completely separate mesh gradient so + // copy the children and unset the link to the shared. + for ( Inkscape::XML::Node *child = repr->firstChild() ; child ; child = child->next() ) { + Inkscape::XML::Node *copy = child->duplicate(doc->getReprDoc()); + repr_new->appendChild( copy ); + Inkscape::GC::release( copy ); + } + sp_gradient_repr_set_link(repr_new, nullptr); + } + return gr_new; + } else { + return gr; + } +} + +SPGradient *sp_gradient_fork_vector_if_necessary(SPGradient *gr) +{ +#ifdef SP_GR_VERBOSE + g_message("sp_gradient_fork_vector_if_necessary(%p)", gr); +#endif + // Some people actually prefer their gradient vectors to be shared... + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (!prefs->getBool("/options/forkgradientvectors/value", true)) + return gr; + + if (gr->hrefcount > 1) { + SPDocument *doc = gr->document; + Inkscape::XML::Document *xml_doc = doc->getReprDoc(); + + Inkscape::XML::Node *repr = gr->getRepr()->duplicate(xml_doc); + doc->getDefs()->getRepr()->addChild(repr, nullptr); + SPGradient *gr_new = static_cast<SPGradient *>(doc->getObjectByRepr(repr)); + gr_new = sp_gradient_ensure_vector_normalized (gr_new); + Inkscape::GC::release(repr); + return gr_new; + } + return gr; +} + +/** + * Obtain the vector from the gradient. A forked vector will be created and linked to this gradient if another gradient uses it. + */ +SPGradient *sp_gradient_get_forked_vector_if_necessary(SPGradient *gradient, bool force_vector) +{ +#ifdef SP_GR_VERBOSE + g_message("sp_gradient_get_forked_vector_if_necessary(%p, %d)", gradient, force_vector); +#endif + SPGradient *vector = gradient->getVector(force_vector); + vector = sp_gradient_fork_vector_if_necessary (vector); + if ( gradient != vector && gradient->ref->getObject() != vector ) { + sp_gradient_repr_set_link(gradient->getRepr(), vector); + } + return vector; +} + + +/** + * Convert an item's gradient to userspace _without_ preserving coords, setting them to defaults + * instead. No forking or reapplying is done because this is only called for newly created privates. + * @return The new gradient. + */ +SPGradient *sp_gradient_reset_to_userspace(SPGradient *gr, SPItem *item) +{ +#ifdef SP_GR_VERBOSE + g_message("sp_gradient_reset_to_userspace(%p, %p)", gr, item); +#endif + Inkscape::XML::Node *repr = gr->getRepr(); + + // calculate the bbox of the item + item->document->ensureUpToDate(); + Geom::OptRect bbox = item->visualBounds(); // we need "true" bbox without item_i2d_affine + + if (!bbox) + return gr; + + Geom::Coord const width = bbox->dimensions()[Geom::X]; + Geom::Coord const height = bbox->dimensions()[Geom::Y]; + + Geom::Point const center = bbox->midpoint(); + + if (SP_IS_RADIALGRADIENT(gr)) { + repr->setAttributeSvgDouble("cx", center[Geom::X]); + repr->setAttributeSvgDouble("cy", center[Geom::Y]); + repr->setAttributeSvgDouble("fx", center[Geom::X]); + repr->setAttributeSvgDouble("fy", center[Geom::Y]); + repr->setAttributeSvgDouble("r", width/2); + + // we want it to be elliptic, not circular + Geom::Affine squeeze = Geom::Translate (-center) * + Geom::Scale(1, height/width) * + Geom::Translate (center); + + gr->gradientTransform = squeeze; + gr->setAttributeOrRemoveIfEmpty("gradientTransform", sp_svg_transform_write(gr->gradientTransform)); + } else if (SP_IS_LINEARGRADIENT(gr)) { + + // Assume horizontal gradient by default (as per SVG 1.1) + Geom::Point pStart = center - Geom::Point(width/2, 0); + Geom::Point pEnd = center + Geom::Point(width/2, 0); + + // Get the preferred gradient angle from prefs + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + double angle = prefs->getDouble("/dialogs/gradienteditor/angle", 0.0); + + if (angle != 0.0) { + + Geom::Line grl(center, Geom::rad_from_deg(angle)); + Geom::LineSegment bbl1(bbox->corner(0), bbox->corner(1)); + Geom::LineSegment bbl2(bbox->corner(1), bbox->corner(2)); + Geom::LineSegment bbl3(bbox->corner(2), bbox->corner(3)); + Geom::LineSegment bbl4(bbox->corner(3), bbox->corner(0)); + + // Find where our gradient line intersects the bounding box. + if (!bbl1.isDegenerate() && intersection(bbl1, grl)) { + pStart = bbl1.pointAt((*intersection(bbl1, grl)).ta); + pEnd = bbl3.pointAt((*intersection(bbl3, grl)).ta); + if (intersection(bbl1, grl.ray(grl.angle()))) { + std::swap(pStart, pEnd); + } + } else if (!bbl2.isDegenerate() && intersection(bbl2, grl)) { + pStart = bbl2.pointAt((*intersection(bbl2, grl)).ta); + pEnd = bbl4.pointAt((*intersection(bbl4, grl)).ta); + if (intersection(bbl2, grl.ray(grl.angle()))) { + std::swap(pStart, pEnd); + } + } + + } + + repr->setAttributeSvgDouble("x1", pStart[Geom::X]); + repr->setAttributeSvgDouble("y1", pStart[Geom::Y]); + repr->setAttributeSvgDouble("x2", pEnd[Geom::X]); + repr->setAttributeSvgDouble("y2", pEnd[Geom::Y]); + + } else { + // Mesh + // THIS IS BEING CALLED TWICE WHENEVER A NEW GRADIENT IS CREATED, WRITING HERE CAUSES PROBLEMS + // IN SPMeshNodeArray::create() + //repr->setAttributeSvgDouble("x", bbox->min()[Geom::X]); + //repr->setAttributeSvgDouble("y", bbox->min()[Geom::Y]); + + // We don't create a shared array gradient. + SPMeshGradient* mg = SP_MESHGRADIENT( gr ); + mg->array.create( mg, item, bbox ); + } + + // set the gradientUnits + repr->setAttribute("gradientUnits", "userSpaceOnUse"); + + return gr; +} + +/** + * Convert an item's gradient to userspace if necessary, also fork it if necessary. + * @return The new gradient. + */ +SPGradient *sp_gradient_convert_to_userspace(SPGradient *gr, SPItem *item, gchar const *property) +{ +#ifdef SP_GR_VERBOSE + g_message("sp_gradient_convert_to_userspace(%p, %p, \"%s\")", gr, item, property); +#endif + g_return_val_if_fail(SP_IS_GRADIENT(gr), NULL); + + if ( gr && gr->isSolid() ) { + return gr; + } + + // First, fork it if it is shared + if (SP_IS_LINEARGRADIENT(gr)) { + gr = sp_gradient_fork_private_if_necessary(gr, gr->getVector(), SP_GRADIENT_TYPE_LINEAR, item); + } else if (SP_IS_RADIALGRADIENT(gr)) { + gr = sp_gradient_fork_private_if_necessary(gr, gr->getVector(), SP_GRADIENT_TYPE_RADIAL, item); + } else { + gr = sp_gradient_fork_private_if_necessary(gr, gr->getArray(), SP_GRADIENT_TYPE_MESH, item); + } + + if (gr->getUnits() == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) { + + Inkscape::XML::Node *repr = gr->getRepr(); + + // calculate the bbox of the item + item->document->ensureUpToDate(); + Geom::Affine bbox2user; + Geom::OptRect bbox = item->visualBounds(); // we need "true" bbox without item_i2d_affine + if ( bbox ) { + bbox2user = Geom::Affine(bbox->dimensions()[Geom::X], 0, + 0, bbox->dimensions()[Geom::Y], + bbox->min()[Geom::X], bbox->min()[Geom::Y]); + } else { + // would be degenerate otherwise + bbox2user = Geom::identity(); + } + + /* skew is the additional transform, defined by the proportions of the item, that we need + * to apply to the gradient in order to work around this weird bit from SVG 1.1 + * (http://www.w3.org/TR/SVG11/pservers.html#LinearGradients): + * + * When gradientUnits="objectBoundingBox" and gradientTransform is the identity + * matrix, the stripes of the linear gradient are perpendicular to the gradient + * vector in object bounding box space (i.e., the abstract coordinate system where + * (0,0) is at the top/left of the object bounding box and (1,1) is at the + * bottom/right of the object bounding box). When the object's bounding box is not + * square, the stripes that are conceptually perpendicular to the gradient vector + * within object bounding box space will render non-perpendicular relative to the + * gradient vector in user space due to application of the non-uniform scaling + * transformation from bounding box space to user space. + */ + Geom::Affine skew = bbox2user; + double exp = skew.descrim(); + skew[0] /= exp; + skew[1] /= exp; + skew[2] /= exp; + skew[3] /= exp; + skew[4] = 0; + skew[5] = 0; + + // apply skew to the gradient + gr->gradientTransform = skew; + gr->setAttributeOrRemoveIfEmpty("gradientTransform", sp_svg_transform_write(gr->gradientTransform)); + + // Matrix to convert points to userspace coords; postmultiply by inverse of skew so + // as to cancel it out when it's applied to the gradient during rendering + Geom::Affine point_convert = bbox2user * skew.inverse(); + + if (SP_IS_LINEARGRADIENT(gr)) { + SPLinearGradient *lg = SP_LINEARGRADIENT(gr); + + Geom::Point p1_b = Geom::Point(lg->x1.computed, lg->y1.computed); + Geom::Point p2_b = Geom::Point(lg->x2.computed, lg->y2.computed); + + Geom::Point p1_u = p1_b * point_convert; + Geom::Point p2_u = p2_b * point_convert; + + repr->setAttributeSvgDouble("x1", p1_u[Geom::X]); + repr->setAttributeSvgDouble("y1", p1_u[Geom::Y]); + repr->setAttributeSvgDouble("x2", p2_u[Geom::X]); + repr->setAttributeSvgDouble("y2", p2_u[Geom::Y]); + + // set the gradientUnits + repr->setAttribute("gradientUnits", "userSpaceOnUse"); + + } else if (SP_IS_RADIALGRADIENT(gr)) { + SPRadialGradient *rg = SP_RADIALGRADIENT(gr); + + // original points in the bbox coords + Geom::Point c_b = Geom::Point(rg->cx.computed, rg->cy.computed); + Geom::Point f_b = Geom::Point(rg->fx.computed, rg->fy.computed); + double r_b = rg->r.computed; + + // converted points in userspace coords + Geom::Point c_u = c_b * point_convert; + Geom::Point f_u = f_b * point_convert; + double r_u = r_b * point_convert.descrim(); + + repr->setAttributeSvgDouble("cx", c_u[Geom::X]); + repr->setAttributeSvgDouble("cy", c_u[Geom::Y]); + repr->setAttributeSvgDouble("fx", f_u[Geom::X]); + repr->setAttributeSvgDouble("fy", f_u[Geom::Y]); + repr->setAttributeSvgDouble("r", r_u); + + // set the gradientUnits + repr->setAttribute("gradientUnits", "userSpaceOnUse"); + + } else { + std::cerr << "sp_gradient_convert_to_userspace: Conversion of mesh to userspace not implemented" << std::endl; + } + } + + // apply the gradient to the item (may be necessary if we forked it); not recursive + // generally because grouped items will be taken care of later (we're being called + // from sp_item_adjust_paint_recursive); however text and all its children should all + // refer to one gradient, hence the recursive call for text (because we can't/don't + // want to access tspans and set gradients on them separately) + if (SP_IS_TEXT(item)) { + sp_style_set_property_url(item, property, gr, true); + } else { + sp_style_set_property_url(item, property, gr, false); + } + + return gr; +} + +void sp_gradient_transform_multiply(SPGradient *gradient, Geom::Affine postmul, bool set) +{ +#ifdef SP_GR_VERBOSE + g_message("sp_gradient_transform_multiply(%p, , %d)", gradient, set); +#endif + if (set) { + gradient->gradientTransform = postmul; + } else { + gradient->gradientTransform *= postmul; // fixme: get gradient transform by climbing to hrefs? + } + gradient->gradientTransform_set = TRUE; + + auto c = sp_svg_transform_write(gradient->gradientTransform); + gradient->setAttributeOrRemoveIfEmpty("gradientTransform", c); +} + +SPGradient *getGradient(SPItem *item, Inkscape::PaintTarget fill_or_stroke) +{ + SPStyle *style = item->style; + SPGradient *gradient = nullptr; + + switch (fill_or_stroke) + { + case Inkscape::FOR_FILL: + if (style && (style->fill.isPaintserver())) { + SPPaintServer *server = item->style->getFillPaintServer(); + if ( SP_IS_GRADIENT(server) ) { + gradient = SP_GRADIENT(server); + } + } + break; + case Inkscape::FOR_STROKE: + if (style && (style->stroke.isPaintserver())) { + SPPaintServer *server = item->style->getStrokePaintServer(); + if ( SP_IS_GRADIENT(server) ) { + gradient = SP_GRADIENT(server); + } + } + break; + } + + return gradient; +} + +SPStop *sp_last_stop(SPGradient *gradient) +{ + for (SPStop *stop = gradient->getFirstStop(); stop != nullptr; stop = stop->getNextStop()) { + if (stop->getNextStop() == nullptr) + return stop; + } + return nullptr; +} + +std::pair<SPStop*, SPStop*> sp_get_before_after_stops(SPStop* stop) { + SPStop* before = nullptr; + SPStop* after = nullptr; + + if (stop) { + before = stop->getPrevStop(); + after = stop->getNextStop(); + } + + return std::make_pair(before, after); +} + +static std::pair<SPStop*, SPStop*> get_before_after_stops(SPGradient* gradient, double offset) { + SPStop* before = nullptr; + SPStop* after = nullptr; + + SPStop* stop = gradient->getFirstStop(); + while (stop && stop->offset < offset) { + before = stop; + stop = stop->getNextStop(); + } + + if (stop && stop->offset > offset) { + after = stop; + } + + return std::make_pair(before, after); +} + +guint sp_number_of_stops_before_stop(SPGradient* gradient, SPStop* target) { + if (!gradient) return 0; + + guint n = 0; + for (SPStop* stop = gradient->getFirstStop(); stop != nullptr; stop = stop->getNextStop()) { + if (stop == target) { + return n; + } + n++; + } + return n; +} + + +SPStop* sp_get_nth_stop(SPGradient* gradient, guint index) { + SPStop* stop = gradient->getFirstStop(); + if (!stop) return nullptr; + + for (guint i = 0; i < index; ++i) { + if (!stop) return nullptr; + + stop = stop->getNextStop(); + } + + return stop; +} + + +SPStop *sp_get_stop_i(SPGradient *gradient, guint stop_i) +{ + SPStop *stop = gradient->getFirstStop(); + if (!stop) { + return nullptr; + } + + // if this is valid but weird gradient without an offset-zero stop element, + // inkscape has created a handle for the start of gradient anyway, + // so when it asks for stop N that corresponds to stop element N-1 + if (stop->offset != 0) + { + stop_i--; + } + + for (guint i = 0; i < stop_i; i++) { + if (!stop) { + return nullptr; + } + stop = stop->getNextStop(); + } + + return stop; +} + +guint32 average_color(guint32 c1, guint32 c2, gdouble p) +{ + guint32 r = static_cast<guint32>(SP_RGBA32_R_U (c1) * (1 - p) + SP_RGBA32_R_U (c2) * p); + guint32 g = static_cast<guint32>(SP_RGBA32_G_U (c1) * (1 - p) + SP_RGBA32_G_U (c2) * p); + guint32 b = static_cast<guint32>(SP_RGBA32_B_U (c1) * (1 - p) + SP_RGBA32_B_U (c2) * p); + guint32 a = static_cast<guint32>(SP_RGBA32_A_U (c1) * (1 - p) + SP_RGBA32_A_U (c2) * p); + + return SP_RGBA32_U_COMPOSE(r, g, b, a); +} + +void sp_repr_set_css_double(Inkscape::XML::Node* node, const char* key, double value) { + if (node) { + node->setAttributeCssDouble(key, value); + } +} + +SPStop *sp_vector_add_stop(SPGradient *vector, SPStop* prev_stop, SPStop* next_stop, gfloat offset) +{ +#ifdef SP_GR_VERBOSE + g_message("sp_vector_add_stop(%p, %p, %p, %f)", vector, prev_stop, next_stop, offset); +#endif + SPStop* newstop = nullptr; + // this function doesn't deal with empty gradients + if (!prev_stop && !next_stop) return newstop; + + guint cnew = 0; // new color + Inkscape::XML::Node *new_stop_repr = nullptr; + + if (!prev_stop || !next_stop) { + // inserting stop past next or before previous is supported + SPStop* stop = prev_stop ? prev_stop : next_stop; + auto repr = stop->getRepr(); + new_stop_repr = repr->duplicate(vector->getRepr()->document()); + vector->getRepr()->addChild(new_stop_repr, prev_stop ? repr : nullptr); + + cnew = stop->get_rgba32(); + } + else { + auto repr = prev_stop->getRepr(); + new_stop_repr = repr->duplicate(vector->getRepr()->document()); + vector->getRepr()->addChild(new_stop_repr, repr); + + guint32 const c1 = prev_stop->get_rgba32(); + guint32 const c2 = next_stop->get_rgba32(); + cnew = average_color (c1, c2, (offset - prev_stop->offset) / (next_stop->offset - prev_stop->offset)); + } + + newstop = reinterpret_cast<SPStop *>(vector->document->getObjectByRepr(new_stop_repr)); + newstop->offset = offset; + newstop->getRepr()->setAttributeCssDouble("offset", (double)offset); + Inkscape::CSSOStringStream os; + gchar c[64]; + sp_svg_write_color (c, sizeof(c), cnew); + gdouble opacity = (gdouble) SP_RGBA32_A_F (cnew); + os << "stop-color:" << c << ";stop-opacity:" << opacity <<";"; + newstop->setAttributeOrRemoveIfEmpty("style", os.str()); + Inkscape::GC::release(new_stop_repr); + + return newstop; +} + +// delete gradient's stop +void sp_gradient_delete_stop(SPGradient* gradient, SPStop* stop) { + + if (!stop || !gradient) { + return; + } + + if (gradient->getStopCount() > 2) { // 2 is the minimum + gradient->getRepr()->removeChild(stop->getRepr()); + DocumentUndo::done(gradient->document, _("Delete gradient stop"), INKSCAPE_ICON("color-gradient")); + } +} + +// make gradient well-formed if needed; from gradient-vector.cpp +static bool verify_grad(SPGradient* gradient) { + bool modified = false; + int i = 0; + SPStop *stop = nullptr; + /* count stops */ + for (auto& ochild: gradient->children) { + if (SP_IS_STOP(&ochild)) { + i++; + stop = SP_STOP(&ochild); + } + } + + Inkscape::XML::Document *xml_doc; + xml_doc = gradient->getRepr()->document(); + + if (i < 1) { + Inkscape::CSSOStringStream os; + os << "stop-color: #000000;stop-opacity:" << 1.0 << ";"; + + Inkscape::XML::Node *child; + + child = xml_doc->createElement("svg:stop"); + sp_repr_set_css_double(child, "offset", 0.0); + child->setAttribute("style", os.str()); + gradient->getRepr()->addChild(child, nullptr); + Inkscape::GC::release(child); + + child = xml_doc->createElement("svg:stop"); + sp_repr_set_css_double(child, "offset", 1.0); + child->setAttribute("style", os.str()); + gradient->getRepr()->addChild(child, nullptr); + Inkscape::GC::release(child); + modified = true; + } + else if (i < 2) { + sp_repr_set_css_double(stop->getRepr(), "offset", 0.0); + Inkscape::XML::Node *child = stop->getRepr()->duplicate(gradient->getRepr()->document()); + sp_repr_set_css_double(child, "offset", 1.0); + gradient->getRepr()->addChild(child, stop->getRepr()); + Inkscape::GC::release(child); + modified = true; + } + + return modified; +} + +// add new stop to a gradient; function lifted from gradient-vector.cpp +SPStop* sp_gradient_add_stop(SPGradient* gradient, SPStop* current) { + if (!gradient || !current) return nullptr; + + if (verify_grad(gradient)) { + // gradient has been fixed by adding stop(s), don't insert another one + return nullptr; + } + + SPStop *stop = current; + Inkscape::XML::Node *new_stop_repr = nullptr; + SPStop *next = stop->getNextStop(); + + if (next == nullptr) { + SPStop *prev = stop->getPrevStop(); + if (prev != nullptr) { + next = stop; + stop = prev; + } + } + + if (next != nullptr) { + new_stop_repr = stop->getRepr()->duplicate(gradient->getRepr()->document()); + gradient->getRepr()->addChild(new_stop_repr, stop->getRepr()); + } else { + next = stop; + new_stop_repr = stop->getPrevStop()->getRepr()->duplicate(gradient->getRepr()->document()); + gradient->getRepr()->addChild(new_stop_repr, stop->getPrevStop()->getRepr()); + } + + SPStop *newstop = reinterpret_cast<SPStop *>(gradient->document->getObjectByRepr(new_stop_repr)); + + newstop->offset = (stop->offset + next->offset) * 0.5 ; + + guint32 const c1 = stop->get_rgba32(); + guint32 const c2 = next->get_rgba32(); + guint32 cnew = average_color(c1, c2); + + Inkscape::CSSOStringStream os; + gchar c[64]; + sp_svg_write_color(c, sizeof(c), cnew); + gdouble opacity = static_cast<gdouble>(SP_RGBA32_A_F(cnew)); + os << "stop-color:" << c << ";stop-opacity:" << opacity <<";"; + newstop->setAttribute("style", os.str()); + sp_repr_set_css_double(newstop->getRepr(), "offset", (double)newstop->offset); + Inkscape::GC::release(new_stop_repr); + DocumentUndo::done(gradient->document, _("Add gradient stop"), INKSCAPE_ICON("color-gradient")); + + return newstop; +} + +SPStop* sp_gradient_add_stop_at(SPGradient* gradient, double offset) { + if (!gradient) return nullptr; + + verify_grad(gradient); + + // find stops before and after given offset + + std::pair<SPStop*, SPStop*> stops = get_before_after_stops(gradient, offset); + + if (stops.first || stops.second) { + auto stop = sp_vector_add_stop(gradient, stops.first, stops.second, offset); + if (stop) { + DocumentUndo::done(gradient->document, _("Add gradient stop"), INKSCAPE_ICON("color-gradient")); + } + return stop; + } + else { + return nullptr; + } +} + +void sp_set_gradient_stop_color(SPDocument* document, SPStop* stop, SPColor color, double opacity) { + sp_repr_set_css_double(stop->getRepr(), "offset", stop->offset); + Inkscape::CSSOStringStream os; + os << "stop-color:" << color.toString() << ";stop-opacity:" << opacity <<";"; + stop->setAttribute("style", os.str()); + + DocumentUndo::done(document, _("Change gradient stop color"), INKSCAPE_ICON("color-gradient")); +} + +SPStop* sp_item_gradient_get_stop(SPItem *item, GrPointType point_type, guint point_i, Inkscape::PaintTarget fill_or_stroke) { + SPGradient *gradient = getGradient(item, fill_or_stroke); + + if (!gradient || !SP_IS_GRADIENT(gradient)) { + return nullptr; + } + + if (SP_IS_LINEARGRADIENT(gradient) || SP_IS_RADIALGRADIENT(gradient) ) { + + SPGradient *vector = gradient->getVector(); + + if (!vector) // orphan! + return nullptr; + + switch (point_type) { + case POINT_LG_BEGIN: + case POINT_RG_CENTER: + case POINT_RG_FOCUS: + return vector->getFirstStop(); + + case POINT_LG_END: + case POINT_RG_R1: + case POINT_RG_R2: + return sp_last_stop (vector); + + case POINT_LG_MID: + case POINT_RG_MID1: + case POINT_RG_MID2: + return sp_get_stop_i (vector, point_i); + + default: + g_warning( "Bad linear/radial gradient handle type" ); + break; + } + } + return nullptr; +} + +guint32 sp_item_gradient_stop_query_style(SPItem *item, GrPointType point_type, guint point_i, Inkscape::PaintTarget fill_or_stroke) +{ + SPGradient *gradient = getGradient(item, fill_or_stroke); + + if (!gradient || !SP_IS_GRADIENT(gradient)) { + return 0; + } + + if (SP_IS_LINEARGRADIENT(gradient) || SP_IS_RADIALGRADIENT(gradient) ) { + + SPGradient *vector = gradient->getVector(); + + if (!vector) // orphan! + return 0; // what else to do? + + switch (point_type) { + case POINT_LG_BEGIN: + case POINT_RG_CENTER: + case POINT_RG_FOCUS: + { + SPStop *first = vector->getFirstStop(); + if (first) { + return first->get_rgba32(); + } + } + break; + + case POINT_LG_END: + case POINT_RG_R1: + case POINT_RG_R2: + { + SPStop *last = sp_last_stop (vector); + if (last) { + return last->get_rgba32(); + } + } + break; + + case POINT_LG_MID: + case POINT_RG_MID1: + case POINT_RG_MID2: + { + SPStop *stopi = sp_get_stop_i (vector, point_i); + if (stopi) { + return stopi->get_rgba32(); + } + } + break; + + default: + g_warning( "Bad linear/radial gradient handle type" ); + break; + } + return 0; + } else if (SP_IS_MESHGRADIENT(gradient)) { + + // Mesh gradient + SPMeshGradient *mg = SP_MESHGRADIENT(gradient); + + switch (point_type) { + case POINT_MG_CORNER: { + if (point_i >= mg->array.corners.size()) { + return 0; + } + SPMeshNode const* cornerpoint = mg->array.corners[ point_i ]; + + if (cornerpoint) { + SPColor color = cornerpoint->color; + double opacity = cornerpoint->opacity; + return color.toRGBA32( opacity ); + } else { + return 0; + } + break; + } + + case POINT_MG_HANDLE: + case POINT_MG_TENSOR: + { + // Do nothing. Handles and tensors don't have color + break; + } + + default: + g_warning( "Bad mesh handle type" ); + } + return 0; + } + + return 0; +} + +void sp_item_gradient_stop_set_style(SPItem *item, GrPointType point_type, guint point_i, Inkscape::PaintTarget fill_or_stroke, SPCSSAttr *stop) +{ +#ifdef SP_GR_VERBOSE + g_message("sp_item_gradient_stop_set_style(%p, %d, %d, %d, %p)", item, point_type, point_i, fill_or_stroke, stop); +#endif + SPGradient *gradient = getGradient(item, fill_or_stroke); + + if (!gradient || !SP_IS_GRADIENT(gradient)) + return; + + if (SP_IS_LINEARGRADIENT(gradient) || SP_IS_RADIALGRADIENT(gradient) ) { + + SPGradient *vector = gradient->getVector(); + + if (!vector) // orphan! + return; + + vector = sp_gradient_fork_vector_if_necessary (vector); + if ( gradient != vector && gradient->ref->getObject() != vector ) { + sp_gradient_repr_set_link(gradient->getRepr(), vector); + } + + switch (point_type) { + case POINT_LG_BEGIN: + case POINT_RG_CENTER: + case POINT_RG_FOCUS: + { + SPStop *first = vector->getFirstStop(); + if (first) { + sp_repr_css_change(first->getRepr(), stop, "style"); + } + } + break; + + case POINT_LG_END: + case POINT_RG_R1: + case POINT_RG_R2: + { + SPStop *last = sp_last_stop (vector); + if (last) { + sp_repr_css_change(last->getRepr(), stop, "style"); + } + } + break; + + case POINT_LG_MID: + case POINT_RG_MID1: + case POINT_RG_MID2: + { + SPStop *stopi = sp_get_stop_i (vector, point_i); + if (stopi) { + sp_repr_css_change(stopi->getRepr(), stop, "style"); + } + } + break; + + default: + g_warning( "Bad linear/radial gradient handle type" ); + break; + } + } else { + + // Mesh gradient + SPMeshGradient *mg = SP_MESHGRADIENT(gradient); + + bool changed = false; + switch (point_type) { + case POINT_MG_CORNER: { + + // Update mesh array (which is not updated automatically when stop is changed?) + gchar const* color_str = sp_repr_css_property( stop, "stop-color", nullptr ); + if( color_str ) { + SPColor color( 0 ); + SPIPaint paint; + paint.read( color_str ); + if( paint.isColor() ) { + color = paint.value.color; + } + mg->array.corners[ point_i ]->color = color; + changed = true; + } + gchar const* opacity_str = sp_repr_css_property( stop, "stop-opacity", nullptr ); + if( opacity_str ) { + std::stringstream os( opacity_str ); + double opacity = 1.0; + os >> opacity; + mg->array.corners[ point_i ]->opacity = opacity; + changed = true; + } + // Update stop + if( changed ) { + SPStop *stopi = mg->array.corners[ point_i ]->stop; + if (stopi) { + sp_repr_css_change(stopi->getRepr(), stop, "style"); + } else { + std::cerr << "sp_item_gradient_stop_set_style: null stopi" << std::endl; + } + } + break; + } + + case POINT_MG_HANDLE: + case POINT_MG_TENSOR: + { + // Do nothing. Handles and tensors don't have colors. + break; + } + + default: + g_warning( "Bad mesh handle type" ); + } + } +} + +void sp_item_gradient_reverse_vector(SPItem *item, Inkscape::PaintTarget fill_or_stroke) +{ +#ifdef SP_GR_VERBOSE + g_message("sp_item_gradient_reverse_vector(%p, %d)", item, fill_or_stroke); +#endif + SPGradient *gradient = getGradient(item, fill_or_stroke); + sp_gradient_reverse_vector(gradient); +} + +void sp_gradient_reverse_vector(SPGradient* gradient) { + if (!gradient || !SP_IS_GRADIENT(gradient)) + return; + + SPGradient *vector = gradient->getVector(); + if (!vector) // orphan! + return; + + vector = sp_gradient_fork_vector_if_necessary (vector); + if ( gradient != vector && gradient->ref->getObject() != vector ) { + sp_gradient_repr_set_link(gradient->getRepr(), vector); + } + + std::vector<SPObject *> child_objects; + std::vector<Inkscape::XML::Node *>child_reprs; + std::vector<double> offsets; + double offset; + for (auto& child: vector->children) { + child_reprs.push_back(child.getRepr()); + child_objects.push_back(&child); + offset = child.getRepr()->getAttributeDouble("offset", 0); + offsets.push_back(offset); + } + + std::vector<Inkscape::XML::Node *> child_copies; + for (auto repr:child_reprs) { + Inkscape::XML::Document *xml_doc = vector->getRepr()->document(); + child_copies.push_back(repr->duplicate(xml_doc)); + } + + + for (auto i:child_objects) { + i->deleteObject(); + } + + std::vector<double>::reverse_iterator o_it = offsets.rbegin(); + for (auto c_it = child_copies.rbegin(); c_it != child_copies.rend(); ++c_it, ++o_it) { + vector->appendChildRepr(*c_it); + (*c_it)->setAttributeSvgDouble("offset", 1 - *o_it); + Inkscape::GC::release(*c_it); + } +} + +void sp_item_gradient_invert_vector_color(SPItem *item, Inkscape::PaintTarget fill_or_stroke) +{ +#ifdef SP_GR_VERBOSE + g_message("sp_item_gradient_invert_vector_color(%p, %d)", item, fill_or_stroke); +#endif + SPGradient *gradient = getGradient(item, fill_or_stroke); + if (!gradient || !SP_IS_GRADIENT(gradient)) + return; + + SPGradient *vector = gradient->getVector(); + if (!vector) // orphan! + return; + + vector = sp_gradient_fork_vector_if_necessary (vector); + if ( gradient != vector && gradient->ref->getObject() != vector ) { + sp_gradient_repr_set_link(gradient->getRepr(), vector); + } + + for (auto& child: vector->children) { + if (SP_IS_STOP(&child)) { + guint32 color = SP_STOP(&child)->get_rgba32(); + //g_message("Stop color %d", color); + gchar c[64]; + sp_svg_write_color (c, sizeof(c), + SP_RGBA32_U_COMPOSE( + (255 - SP_RGBA32_R_U(color)), + (255 - SP_RGBA32_G_U(color)), + (255 - SP_RGBA32_B_U(color)), + SP_RGBA32_A_U(color) + ) + ); + SPCSSAttr *css = sp_repr_css_attr_new (); + sp_repr_css_set_property (css, "stop-color", c); + sp_repr_css_change(child.getRepr(), css, "style"); + sp_repr_css_attr_unref (css); + } + } +} + +// HACK: linear and radial gradients may have first and/or last stops moved from their default positions +// of 0 and 1 respectively; this is not what gradient tool was built to handle; instead of making extensive +// changes to try to fix it, this hack just makes sure that midpoint draggers don't move to the true 0/1 limits; +// with that, code relying on sp_get_stop_i will work correctly +double midpoint_offset_hack(double offset) { + const double EPS = 0.0001; + + if (offset <= 0) { + offset = EPS; + } + else if (offset >= 1) { + offset = 1 - EPS; + } + + return offset; +} + +/** +Set the position of point point_type of the gradient applied to item (either fill_or_stroke) to +p_w (in desktop coordinates). Write_repr if you want the change to become permanent. +*/ +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) +{ +#ifdef SP_GR_VERBOSE + 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] ); +#endif + SPGradient *gradient = getGradient(item, fill_or_stroke); + + if (!gradient || !SP_IS_GRADIENT(gradient)) + return; + + // Needed only if units are set to SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX + gradient = sp_gradient_convert_to_userspace(gradient, item, (fill_or_stroke == Inkscape::FOR_FILL) ? "fill" : "stroke"); + + Geom::Affine i2d (item->i2dt_affine ()); + Geom::Point p = p_w * i2d.inverse(); + p *= (gradient->gradientTransform).inverse(); + // now p is in gradient's original coordinates + + Inkscape::XML::Node *repr = gradient->getRepr(); + + if (SP_IS_LINEARGRADIENT(gradient)) { + SPLinearGradient *lg = SP_LINEARGRADIENT(gradient); + switch (point_type) { + case POINT_LG_BEGIN: + if (scale) { + lg->x2.computed += (lg->x1.computed - p[Geom::X]); + lg->y2.computed += (lg->y1.computed - p[Geom::Y]); + } + lg->x1.computed = p[Geom::X]; + lg->y1.computed = p[Geom::Y]; + if (write_repr) { + if (scale) { + repr->setAttributeSvgDouble("x2", lg->x2.computed); + repr->setAttributeSvgDouble("y2", lg->y2.computed); + } + repr->setAttributeSvgDouble("x1", lg->x1.computed); + repr->setAttributeSvgDouble("y1", lg->y1.computed); + } else { + gradient->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case POINT_LG_END: + if (scale) { + lg->x1.computed += (lg->x2.computed - p[Geom::X]); + lg->y1.computed += (lg->y2.computed - p[Geom::Y]); + } + lg->x2.computed = p[Geom::X]; + lg->y2.computed = p[Geom::Y]; + if (write_repr) { + if (scale) { + repr->setAttributeSvgDouble("x1", lg->x1.computed); + repr->setAttributeSvgDouble("y1", lg->y1.computed); + } + repr->setAttributeSvgDouble("x2", lg->x2.computed); + repr->setAttributeSvgDouble("y2", lg->y2.computed); + } else { + gradient->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case POINT_LG_MID: + { + // using X-coordinates only to determine the offset, assuming p has been snapped to the vector from begin to end. + Geom::Point begin(lg->x1.computed, lg->y1.computed); + Geom::Point end(lg->x2.computed, lg->y2.computed); + double offset = Geom::LineSegment(begin, end).nearestTime(p); + offset = midpoint_offset_hack(offset); + SPGradient *vector = sp_gradient_get_forked_vector_if_necessary (lg, false); + lg->ensureVector(); + lg->vector.stops.at(point_i).offset = offset; + if (SPStop* stopi = sp_get_stop_i(vector, point_i)) { + stopi->offset = offset; + if (write_repr) { + stopi->getRepr()->setAttributeCssDouble("offset", stopi->offset); + } else { + stopi->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + } + } + } + break; + default: + g_warning( "Bad linear gradient handle type" ); + break; + } + } else if (SP_IS_RADIALGRADIENT(gradient)) { + SPRadialGradient *rg = SP_RADIALGRADIENT(gradient); + Geom::Point c (rg->cx.computed, rg->cy.computed); + Geom::Point c_w = c * gradient->gradientTransform * i2d; // now in desktop coords + if ((point_type == POINT_RG_R1 || point_type == POINT_RG_R2) && Geom::L2 (p_w - c_w) < 1e-3) { + // prevent setting a radius too close to the center + return; + } + Geom::Affine new_transform; + bool transform_set = false; + + switch (point_type) { + case POINT_RG_CENTER: + rg->fx.computed = p[Geom::X] + (rg->fx.computed - rg->cx.computed); + rg->fy.computed = p[Geom::Y] + (rg->fy.computed - rg->cy.computed); + rg->cx.computed = p[Geom::X]; + rg->cy.computed = p[Geom::Y]; + if (write_repr) { + repr->setAttributeSvgDouble("fx", rg->fx.computed); + repr->setAttributeSvgDouble("fy", rg->fy.computed); + repr->setAttributeSvgDouble("cx", rg->cx.computed); + repr->setAttributeSvgDouble("cy", rg->cy.computed); + } else { + gradient->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case POINT_RG_FOCUS: + rg->fx.computed = p[Geom::X]; + rg->fy.computed = p[Geom::Y]; + if (write_repr) { + repr->setAttributeSvgDouble("fx", rg->fx.computed); + repr->setAttributeSvgDouble("fy", rg->fy.computed); + } else { + gradient->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + break; + case POINT_RG_R1: + { + Geom::Point r1_w = (c + Geom::Point(rg->r.computed, 0)) * gradient->gradientTransform * i2d; + double r1_angle = Geom::atan2(r1_w - c_w); + double move_angle = Geom::atan2(p_w - c_w) - r1_angle; + double move_stretch = Geom::L2(p_w - c_w) / Geom::L2(r1_w - c_w); + + Geom::Affine move = Geom::Affine (Geom::Translate (-c_w)) * + Geom::Affine (Geom::Rotate(-r1_angle)) * + Geom::Affine (Geom::Scale(move_stretch, scale? move_stretch : 1)) * + Geom::Affine (Geom::Rotate(r1_angle)) * + Geom::Affine (Geom::Rotate(move_angle)) * + Geom::Affine (Geom::Translate (c_w)); + + new_transform = gradient->gradientTransform * i2d * move * i2d.inverse(); + transform_set = true; + + break; + } + case POINT_RG_R2: + { + Geom::Point r2_w = (c + Geom::Point(0, -rg->r.computed)) * gradient->gradientTransform * i2d; + double r2_angle = Geom::atan2(r2_w - c_w); + double move_angle = Geom::atan2(p_w - c_w) - r2_angle; + double move_stretch = Geom::L2(p_w - c_w) / Geom::L2(r2_w - c_w); + + Geom::Affine move = Geom::Affine (Geom::Translate (-c_w)) * + Geom::Affine (Geom::Rotate(-r2_angle)) * + Geom::Affine (Geom::Scale(move_stretch, scale? move_stretch : 1)) * + Geom::Affine (Geom::Rotate(r2_angle)) * + Geom::Affine (Geom::Rotate(move_angle)) * + Geom::Affine (Geom::Translate (c_w)); + + new_transform = gradient->gradientTransform * i2d * move * i2d.inverse(); + transform_set = true; + + break; + } + case POINT_RG_MID1: + { + Geom::Point start = Geom::Point (rg->cx.computed, rg->cy.computed); + Geom::Point end = Geom::Point (rg->cx.computed + rg->r.computed, rg->cy.computed); + double offset = Geom::LineSegment(start, end).nearestTime(p); + offset = midpoint_offset_hack(offset); + SPGradient *vector = sp_gradient_get_forked_vector_if_necessary (rg, false); + rg->ensureVector(); + rg->vector.stops.at(point_i).offset = offset; + if (SPStop* stopi = sp_get_stop_i(vector, point_i)) { + stopi->offset = offset; + if (write_repr) { + stopi->getRepr()->setAttributeCssDouble("offset", stopi->offset); + } else { + stopi->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + } + } + break; + } + case POINT_RG_MID2: + { + Geom::Point start = Geom::Point (rg->cx.computed, rg->cy.computed); + Geom::Point end = Geom::Point (rg->cx.computed, rg->cy.computed - rg->r.computed); + double offset = Geom::LineSegment(start, end).nearestTime(p); + offset = midpoint_offset_hack(offset); + SPGradient *vector = sp_gradient_get_forked_vector_if_necessary(rg, false); + rg->ensureVector(); + rg->vector.stops.at(point_i).offset = offset; + if (SPStop* stopi = sp_get_stop_i(vector, point_i)) { + stopi->offset = offset; + if (write_repr) { + stopi->getRepr()->setAttributeCssDouble("offset", stopi->offset); + } else { + stopi->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + } + } + break; + } + default: + g_warning( "Bad radial gradient handle type" ); + break; + } + + if (transform_set) { + gradient->gradientTransform = new_transform; + gradient->gradientTransform_set = TRUE; + if (write_repr) { + auto s = sp_svg_transform_write(gradient->gradientTransform); + gradient->setAttributeOrRemoveIfEmpty("gradientTransform", s); + } else { + gradient->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + } + } else if (SP_IS_MESHGRADIENT(gradient)) { + SPMeshGradient *mg = SP_MESHGRADIENT(gradient); + //Geom::Affine new_transform; + //bool transform_set = false; + + switch (point_type) { + case POINT_MG_CORNER: + { + mg->array.corners[ point_i ]->p = p; + // Handles are moved in gradient-drag.cpp + gradient->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + + case POINT_MG_HANDLE: { + mg->array.handles[ point_i ]->p = p; + gradient->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + + case POINT_MG_TENSOR: { + mg->array.tensors[ point_i ]->p = p; + gradient->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + } + + default: + g_warning( "Bad mesh handle type" ); + } + if( write_repr ) { + //std::cout << "Write mesh repr" << std::endl; + mg->array.write( mg ); + } + } + +} + +SPGradient *sp_item_gradient_get_vector(SPItem *item, Inkscape::PaintTarget fill_or_stroke) +{ + SPGradient *gradient = getGradient(item, fill_or_stroke); + + if (gradient) { + return gradient->getVector(); + } + return nullptr; +} + +SPGradientSpread sp_item_gradient_get_spread(SPItem *item, Inkscape::PaintTarget fill_or_stroke) +{ + SPGradientSpread spread = SP_GRADIENT_SPREAD_PAD; + SPGradient *gradient = getGradient(item, fill_or_stroke); + + if (gradient) { + spread = gradient->fetchSpread(); + } + return spread; +} + + +/** +Returns the position of point point_type of the gradient applied to item (either fill_or_stroke), +in desktop coordinates. +*/ +Geom::Point getGradientCoords(SPItem *item, GrPointType point_type, guint point_i, Inkscape::PaintTarget fill_or_stroke) +{ + SPGradient *gradient = getGradient(item, fill_or_stroke); +#ifdef SP_GR_VERBOSE + g_message("getGradientCoords(%p, %d, %d, %d, %p)", item, point_type, point_i, fill_or_stroke, gradient); +#endif + + Geom::Point p (0, 0); + + if (!gradient) + return p; + + if (SP_IS_LINEARGRADIENT(gradient)) { + SPLinearGradient *lg = SP_LINEARGRADIENT(gradient); + switch (point_type) { + case POINT_LG_BEGIN: + p = Geom::Point (lg->x1.computed, lg->y1.computed); + break; + case POINT_LG_END: + p = Geom::Point (lg->x2.computed, lg->y2.computed); + break; + case POINT_LG_MID: + { + if (lg->vector.stops.size() < point_i) { + g_message("POINT_LG_MID bug trigger, see LP bug #453067"); + break; + } + gdouble offset = lg->vector.stops.at(point_i).offset; + p = (1-offset) * Geom::Point(lg->x1.computed, lg->y1.computed) + offset * Geom::Point(lg->x2.computed, lg->y2.computed); + } + break; + default: + g_warning( "Bad linear gradient handle type" ); + break; + } + } else if (SP_IS_RADIALGRADIENT(gradient)) { + SPRadialGradient *rg = SP_RADIALGRADIENT(gradient); + switch (point_type) { + case POINT_RG_CENTER: + p = Geom::Point (rg->cx.computed, rg->cy.computed); + break; + case POINT_RG_FOCUS: + p = Geom::Point (rg->fx.computed, rg->fy.computed); + break; + case POINT_RG_R1: + p = Geom::Point (rg->cx.computed + rg->r.computed, rg->cy.computed); + break; + case POINT_RG_R2: + p = Geom::Point (rg->cx.computed, rg->cy.computed - rg->r.computed); + break; + case POINT_RG_MID1: + { + if (rg->vector.stops.size() < point_i) { + g_message("POINT_RG_MID1 bug trigger, see LP bug #453067"); + break; + } + gdouble offset = rg->vector.stops.at(point_i).offset; + p = (1-offset) * Geom::Point (rg->cx.computed, rg->cy.computed) + offset * Geom::Point(rg->cx.computed + rg->r.computed, rg->cy.computed); + } + break; + case POINT_RG_MID2: + { + if (rg->vector.stops.size() < point_i) { + g_message("POINT_RG_MID2 bug trigger, see LP bug #453067"); + break; + } + gdouble offset = rg->vector.stops.at(point_i).offset; + p = (1-offset) * Geom::Point (rg->cx.computed, rg->cy.computed) + offset * Geom::Point(rg->cx.computed, rg->cy.computed - rg->r.computed); + } + break; + default: + g_warning( "Bad radial gradient handle type" ); + break; + } + } else if (SP_IS_MESHGRADIENT(gradient)) { + SPMeshGradient *mg = SP_MESHGRADIENT(gradient); + switch (point_type) { + + case POINT_MG_CORNER: + p = mg->array.corners[ point_i ]->p; + break; + + case POINT_MG_HANDLE: { + p = mg->array.handles[ point_i ]->p; + break; + } + + case POINT_MG_TENSOR: { + p = mg->array.tensors[ point_i ]->p; + break; + } + + default: + g_warning( "Bad mesh handle type" ); + } + } + + + if (gradient->getUnits() == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) { + item->document->ensureUpToDate(); + Geom::OptRect bbox = item->visualBounds(); // we need "true" bbox without item_i2d_affine + if (bbox) { + p *= Geom::Affine(bbox->dimensions()[Geom::X], 0, + 0, bbox->dimensions()[Geom::Y], + bbox->min()[Geom::X], bbox->min()[Geom::Y]); + } + } + p *= Geom::Affine(gradient->gradientTransform) * (Geom::Affine)item->i2dt_affine(); + return p; +} + +/** + * Sets item fill or stroke to the gradient of the specified type with given vector, creating + * new private gradient, if needed. + * gr has to be a normalized vector. + */ + +SPGradient *sp_item_set_gradient(SPItem *item, SPGradient *gr, SPGradientType type, Inkscape::PaintTarget fill_or_stroke) +{ +#ifdef SP_GR_VERBOSE + g_message("sp_item_set_gradient(%p, %p, %d, %d)", item, gr, type, fill_or_stroke); +#endif + g_return_val_if_fail(item != nullptr, NULL); + g_return_val_if_fail(SP_IS_ITEM(item), NULL); + g_return_val_if_fail(gr != nullptr, NULL); + g_return_val_if_fail(SP_IS_GRADIENT(gr), NULL); + g_return_val_if_fail(gr->state == SP_GRADIENT_STATE_VECTOR, NULL); + + SPStyle *style = item->style; + g_assert(style != nullptr); + + SPPaintServer *ps = nullptr; + if ((fill_or_stroke == Inkscape::FOR_FILL) ? style->fill.isPaintserver() : style->stroke.isPaintserver()) { + ps = (fill_or_stroke == Inkscape::FOR_FILL) ? SP_STYLE_FILL_SERVER(style) : SP_STYLE_STROKE_SERVER(style); + } + + if (ps + && ( (type == SP_GRADIENT_TYPE_LINEAR && SP_IS_LINEARGRADIENT(ps)) || + (type == SP_GRADIENT_TYPE_RADIAL && SP_IS_RADIALGRADIENT(ps)) ) ) + { + + /* Current fill style is the gradient of the required type */ + SPGradient *current = SP_GRADIENT(ps); + + //g_message("hrefcount %d count %d\n", current->hrefcount, count_gradient_hrefs(item, current)); + + if (!current->isSwatch() + && (current->hrefcount == 1 || + current->hrefcount == count_gradient_hrefs(item, current))) { + + // current is private and it's either used once, or all its uses are by children of item; + // so just change its href to vector + + if ( current != gr && current->getVector() != gr ) { + // href is not the vector + sp_gradient_repr_set_link(current->getRepr(), gr); + } + item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + return current; + + } else { + + // the gradient is not private, or it is shared with someone else; + // normalize it (this includes creating new private if necessary) + SPGradient *normalized = sp_gradient_fork_private_if_necessary(current, gr, type, item); + + g_return_val_if_fail(normalized != nullptr, NULL); + + if (normalized != current) { + + /* We have to change object style here; recursive because this is used from + * fill&stroke and must work for groups etc. */ + sp_style_set_property_url(item, (fill_or_stroke == Inkscape::FOR_FILL) ? "fill" : "stroke", normalized, true); + } + item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + return normalized; + } + + } else { + /* Current fill style is not a gradient or wrong type, so construct everything */ + /* This is where mesh gradients are constructed. */ + g_assert(SP_IS_GRADIENT(gr)); // TEMP + SPGradient *constructed = sp_gradient_get_private_normalized(item->document, gr, type); + constructed = sp_gradient_reset_to_userspace(constructed, item); + sp_style_set_property_url(item, ( (fill_or_stroke == Inkscape::FOR_FILL) ? "fill" : "stroke" ), constructed, true); + item->requestDisplayUpdate(( SP_OBJECT_MODIFIED_FLAG | + SP_OBJECT_STYLE_MODIFIED_FLAG )); + return constructed; + } +} + +static void sp_gradient_repr_set_link(Inkscape::XML::Node *repr, SPGradient *link) +{ +#ifdef SP_GR_VERBOSE + g_message("sp_gradient_repr_set_link(%p, %p)", repr, link); +#endif + g_return_if_fail(repr != nullptr); + if (link) { + g_return_if_fail(SP_IS_GRADIENT(link)); + } + + if (link) { + Glib::ustring ref("#"); + ref += link->getId(); + repr->setAttribute("xlink:href", ref); + } else { + repr->removeAttribute("xlink:href"); + } +} + + +static void addStop( Inkscape::XML::Node *parent, Glib::ustring const &color, gint opacity, gchar const *offset ) +{ +#ifdef SP_GR_VERBOSE + g_message("addStop(%p, %s, %d, %s)", parent, color.c_str(), opacity, offset); +#endif + Inkscape::XML::Node *stop = parent->document()->createElement("svg:stop"); + { + gchar *tmp = g_strdup_printf( "stop-color:%s;stop-opacity:%d;", color.c_str(), opacity ); + stop->setAttribute( "style", tmp ); + g_free(tmp); + } + + stop->setAttribute( "offset", offset ); + + parent->appendChild(stop); + Inkscape::GC::release(stop); +} + +/* + * Get default normalized gradient vector of document, create if there is none + */ +SPGradient *sp_document_default_gradient_vector( SPDocument *document, SPColor const &color, bool singleStop ) +{ + SPDefs *defs = document->getDefs(); + Inkscape::XML::Document *xml_doc = document->getReprDoc(); + + Inkscape::XML::Node *repr = xml_doc->createElement("svg:linearGradient"); + + if ( !singleStop ) { + // make auto collection optional + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/option/gradient/auto_collect", true)) { + repr->setAttribute("inkscape:collect", "always"); + } else { + repr->setAttribute("inkscape:collect", "never"); + } + + // set here, but removed when it's edited in the gradient editor + // to further reduce clutter, we could + // (1) here, search gradients by color and return what is found without duplication + // (2) in fill & stroke, show only one copy of each gradient in list + } + + Glib::ustring colorStr = color.toString(); + addStop( repr, colorStr, 1, "0" ); + if ( !singleStop ) { + addStop( repr, colorStr, 0, "1" ); + } + + defs->getRepr()->addChild(repr, nullptr); + Inkscape::GC::release(repr); + + /* fixme: This does not look like nice */ + SPGradient *gr = static_cast<SPGradient *>(document->getObjectByRepr(repr)); + g_assert(gr != nullptr); + g_assert(SP_IS_GRADIENT(gr)); + /* fixme: Maybe add extra sanity check here */ + gr->state = SP_GRADIENT_STATE_VECTOR; + + return gr; +} + +SPGradient *sp_gradient_vector_for_object( SPDocument *const doc, SPDesktop *const desktop, + SPObject *const o, Inkscape::PaintTarget const fill_or_stroke, bool singleStop ) +{ + SPColor color; + if ( (o == nullptr) || (o->style == nullptr) ) { + color = sp_desktop_get_color(desktop, (fill_or_stroke == Inkscape::FOR_FILL)); + } else { + // take the color of the object + SPStyle const &style = *(o->style); + SPIPaint const &paint = *style.getFillOrStroke(fill_or_stroke == Inkscape::FOR_FILL); + if (paint.isPaintserver()) { + SPObject *server = (fill_or_stroke == Inkscape::FOR_FILL) ? o->style->getFillPaintServer() : o->style->getStrokePaintServer(); + if ( SP_IS_LINEARGRADIENT(server) || SP_IS_RADIALGRADIENT(server) ) { + return SP_GRADIENT(server)->getVector(true); + } else { + color = sp_desktop_get_color(desktop, (fill_or_stroke == Inkscape::FOR_FILL)); + } + } else if (paint.isColor()) { + color = paint.value.color; + } else { + // if o doesn't use flat color, then take current color of the desktop. + color = sp_desktop_get_color(desktop, (fill_or_stroke == Inkscape::FOR_FILL)); + } + } + + return sp_document_default_gradient_vector( doc, color, singleStop ); +} + +void sp_gradient_invert_selected_gradients(SPDesktop *desktop, Inkscape::PaintTarget fill_or_stroke) +{ + Inkscape::Selection *selection = desktop->getSelection(); + + auto list= selection->items(); + for (auto i = list.begin(); i != list.end(); ++i) { + sp_item_gradient_invert_vector_color(*i, fill_or_stroke); + } + + // we did an undoable action + DocumentUndo::done(desktop->getDocument(), _("Invert gradient colors"), INKSCAPE_ICON("color-gradient")); +} + +void sp_gradient_reverse_selected_gradients(SPDesktop *desktop) +{ + Inkscape::Selection *selection = desktop->getSelection(); + Inkscape::UI::Tools::ToolBase *ev = desktop->getEventContext(); + + if (!ev) { + return; + } + + GrDrag *drag = ev->get_drag(); + + // First try selected dragger + if (drag && !drag->selected.empty()) { + drag->selected_reverse_vector(); + } else { // If no drag or no dragger selected, act on selection (both fill and stroke gradients) + auto list= selection->items(); + for (auto i = list.begin(); i != list.end(); ++i) { + sp_item_gradient_reverse_vector(*i, Inkscape::FOR_FILL); + sp_item_gradient_reverse_vector(*i, Inkscape::FOR_STROKE); + } + } + + // we did an undoable action + DocumentUndo::done(desktop->getDocument(), _("Reverse gradient"), INKSCAPE_ICON("color-gradient")); +} + +void sp_gradient_unset_swatch(SPDesktop *desktop, std::string const &id) +{ + SPDocument *doc = desktop ? desktop->doc() : nullptr; + + if (doc) { + const std::vector<SPObject *> gradients = doc->getResourceList("gradient"); + for (auto gradient : gradients) { + SPGradient* grad = SP_GRADIENT(gradient); + if ( id == grad->getId() ) { + grad->setSwatch(false); + DocumentUndo::done(doc, _("Delete swatch"), INKSCAPE_ICON("color-gradient")); + break; + } + } + } +} + +/* + * Return a SPItem's gradient + */ +SPGradient* sp_item_get_gradient(SPItem *item, bool fillorstroke) +{ + SPIPaint *item_paint = item->style->getFillOrStroke(fillorstroke); + if (item_paint->isPaintserver()) { + + SPPaintServer *item_server = fillorstroke ? item->style->getFillPaintServer() : item->style->getStrokePaintServer(); + + if (SP_IS_LINEARGRADIENT(item_server) || SP_IS_RADIALGRADIENT(item_server) || + (SP_IS_GRADIENT(item_server) && SP_GRADIENT(item_server)->getVector()->isSwatch())) { + + return SP_GRADIENT(item_server)->getVector(); + } + } + + return nullptr; +} + +static void get_all_doc_items(std::vector<SPItem*> &list, SPObject *from) +{ + for (auto& child: from->children) { + if (SP_IS_ITEM(&child)) { + list.push_back(SP_ITEM(&child)); + } + get_all_doc_items(list, &child); + } +} + +std::vector<SPItem*> sp_get_all_document_items(SPDocument* document) { + std::vector<SPItem*> items; + if (document) { + get_all_doc_items(items, document->getRoot()); + } + return items; +} + +int sp_get_gradient_refcount(SPDocument* document, SPGradient* gradient) { + if (!document || !gradient) return 0; + + int count = 0; + for (auto item : sp_get_all_document_items(document)) { + if (!item->getId()) { + continue; + } + SPGradient* fill = sp_item_get_gradient(item, true); // fill + if (fill == gradient) { + ++count; + } + SPGradient* stroke = sp_item_get_gradient(item, false); // stroke + if (stroke == gradient) { + ++count; + } + } + + return count; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : |