summaryrefslogtreecommitdiffstats
path: root/src/object/sp-pattern.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/object/sp-pattern.cpp')
-rw-r--r--src/object/sp-pattern.cpp742
1 files changed, 742 insertions, 0 deletions
diff --git a/src/object/sp-pattern.cpp b/src/object/sp-pattern.cpp
new file mode 100644
index 0000000..b5417aa
--- /dev/null
+++ b/src/object/sp-pattern.cpp
@@ -0,0 +1,742 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * SVG <pattern> implementation
+ *
+ * Author:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2002 Lauris Kaplinski
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "sp-pattern.h"
+
+#include <string>
+#include <cstring>
+
+#include <2geom/transforms.h>
+
+#include <glibmm.h>
+
+#include "attributes.h"
+#include "bad-uri-exception.h"
+#include "document.h"
+
+#include "sp-defs.h"
+#include "sp-factory.h"
+#include "sp-item.h"
+
+#include "display/cairo-utils.h"
+#include "display/drawing-context.h"
+#include "display/drawing-surface.h"
+#include "display/drawing.h"
+#include "display/drawing-group.h"
+#include "display/drawing-pattern.h"
+
+#include "svg/svg.h"
+#include "xml/href-attribute-helper.h"
+
+SPPatternReference::SPPatternReference(SPPattern *owner)
+ : URIReference(owner)
+{
+}
+
+SPPattern *SPPatternReference::getObject() const
+{
+ return static_cast<SPPattern*>(URIReference::getObject());
+}
+
+bool SPPatternReference::_acceptObject(SPObject *obj) const
+{
+ return is<SPPattern>(obj) && URIReference::_acceptObject(obj);
+}
+
+/*
+ *
+ */
+
+SPPattern::SPPattern()
+ : ref(this)
+ , _pattern_units(UNITS_OBJECTBOUNDINGBOX)
+ , _pattern_units_set(false)
+ , _pattern_content_units(UNITS_USERSPACEONUSE)
+ , _pattern_content_units_set(false)
+ , _pattern_transform_set(false)
+ , shown(nullptr)
+{
+ ref.changedSignal().connect(sigc::mem_fun(*this, &SPPattern::_onRefChanged));
+}
+
+SPPattern::~SPPattern() = default;
+
+void SPPattern::build(SPDocument *doc, Inkscape::XML::Node *repr)
+{
+ SPPaintServer::build(doc, repr);
+
+ readAttr(SPAttr::PATTERNUNITS);
+ readAttr(SPAttr::PATTERNCONTENTUNITS);
+ readAttr(SPAttr::PATTERNTRANSFORM);
+ readAttr(SPAttr::X);
+ readAttr(SPAttr::Y);
+ readAttr(SPAttr::WIDTH);
+ readAttr(SPAttr::HEIGHT);
+ readAttr(SPAttr::VIEWBOX);
+ readAttr(SPAttr::PRESERVEASPECTRATIO);
+ readAttr(SPAttr::XLINK_HREF);
+ readAttr(SPAttr::STYLE);
+
+ doc->addResource("pattern", this);
+}
+
+void SPPattern::release()
+{
+ if (document) {
+ document->removeResource("pattern", this);
+ }
+
+ // Should have been unattached by their owners on the release signal.
+ assert(attached_views.empty());
+
+ set_shown(nullptr);
+ views.clear();
+
+ _modified_connection.disconnect();
+ ref.detach();
+
+ SPPaintServer::release();
+}
+
+void SPPattern::set(SPAttr key, char const *value)
+{
+ switch (key) {
+ case SPAttr::PATTERNUNITS:
+ _pattern_units = UNITS_OBJECTBOUNDINGBOX;
+ _pattern_units_set = false;
+
+ if (value) {
+ if (!std::strcmp(value, "userSpaceOnUse")) {
+ _pattern_units = UNITS_USERSPACEONUSE;
+ _pattern_units_set = true;
+ } else if (!std::strcmp(value, "objectBoundingBox")) {
+ _pattern_units_set = true;
+ }
+ }
+
+ requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ break;
+
+ case SPAttr::PATTERNCONTENTUNITS:
+ _pattern_content_units = UNITS_USERSPACEONUSE;
+ _pattern_content_units_set = false;
+
+ if (value) {
+ if (!std::strcmp(value, "userSpaceOnUse")) {
+ _pattern_content_units_set = true;
+ } else if (!std::strcmp(value, "objectBoundingBox")) {
+ _pattern_content_units = UNITS_OBJECTBOUNDINGBOX;
+ _pattern_content_units_set = true;
+ }
+ }
+
+ requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ break;
+
+ case SPAttr::PATTERNTRANSFORM: {
+ _pattern_transform = Geom::identity();
+ _pattern_transform_set = false;
+
+ if (value) {
+ Geom::Affine t;
+ if (sp_svg_transform_read(value, &t)) {
+ _pattern_transform = t;
+ _pattern_transform_set = true;
+ }
+ }
+
+ requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ break;
+ }
+ case SPAttr::X:
+ _x.readOrUnset(value);
+ requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ break;
+
+ case SPAttr::Y:
+ _y.readOrUnset(value);
+ requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ break;
+
+ case SPAttr::WIDTH:
+ _width.readOrUnset(value);
+ requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ break;
+
+ case SPAttr::HEIGHT:
+ _height.readOrUnset(value);
+ requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ break;
+
+ case SPAttr::VIEWBOX:
+ set_viewBox(value);
+ requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG);
+ break;
+
+ case SPAttr::PRESERVEASPECTRATIO:
+ set_preserveAspectRatio(value);
+ requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG);
+ break;
+
+ case SPAttr::XLINK_HREF:
+ if (!value) {
+ if (href.empty()) {
+ break;
+ }
+ href.clear();
+ ref.detach();
+ } else {
+ if (href == value) {
+ break;
+ }
+ href = value;
+
+ // Attempt to attach ref, which emits the changed signal.
+ try {
+ ref.attach(Inkscape::URI(href.data()));
+ } catch (Inkscape::BadURIException const &e) {
+ g_warning("%s", e.what());
+ href.clear();
+ ref.detach();
+ }
+ }
+ requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ break;
+
+ default:
+ SPPaintServer::set(key, value);
+ break;
+ }
+}
+
+void SPPattern::update(SPCtx *ctx, unsigned flags)
+{
+ auto const cflags = cascade_flags(flags);
+
+ for (auto c : childList(true)) {
+ if (cflags || (c->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) {
+ c->updateDisplay(ctx, cflags);
+ }
+ sp_object_unref(c, nullptr);
+ }
+
+ for (auto &v : views) {
+ update_view(v);
+ }
+}
+
+void SPPattern::update_view(View &v)
+{
+ // * "width" and "height" determine tile size.
+ // * "viewBox" (if defined) or "patternContentUnits" determines placement of content inside tile.
+ // * "x", "y", and "patternTransform" transform tile to user space after tile is generated.
+
+ // These functions recursively search up the tree to find the values.
+ double tile_x = x();
+ double tile_y = y();
+ double tile_width = width();
+ double tile_height = height();
+ if (v.bbox && patternUnits() == UNITS_OBJECTBOUNDINGBOX) {
+ tile_x *= v.bbox->width();
+ tile_y *= v.bbox->height();
+ tile_width *= v.bbox->width();
+ tile_height *= v.bbox->height();
+ }
+
+ // Pattern size in pattern space
+ auto pattern_tile = Geom::Rect::from_xywh(0, 0, tile_width, tile_height);
+
+ // Content to tile (pattern space)
+ Geom::Affine content2ps;
+ if (auto effective_view_box = viewbox()) {
+ // viewBox to pattern server (using SPViewBox)
+ viewBox = *effective_view_box;
+ c2p.setIdentity();
+ apply_viewbox(pattern_tile);
+ content2ps = c2p;
+ }
+ else {
+ // Content to bbox
+ if (v.bbox && patternContentUnits() == UNITS_OBJECTBOUNDINGBOX) {
+ content2ps = Geom::Affine(v.bbox->width(), 0.0, 0.0, v.bbox->height(), 0, 0);
+ }
+ }
+
+ // Tile (pattern space) to user.
+ Geom::Affine ps2user = Geom::Translate(tile_x, tile_y) * getTransform();
+
+ v.drawingitem->setTileRect(pattern_tile);
+ v.drawingitem->setChildTransform(content2ps);
+ v.drawingitem->setPatternToUserTransform(ps2user);
+}
+
+void SPPattern::modified(unsigned flags)
+{
+ auto const cflags = cascade_flags(flags);
+
+ for (auto c : childList(true)) {
+ if (cflags || (c->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) {
+ c->emitModified(cflags);
+ }
+ sp_object_unref(c);
+ }
+
+ set_shown(rootPattern());
+}
+
+// The following three functions are based on SPGroup.
+
+void SPPattern::child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref)
+{
+ SPPaintServer::child_added(child, ref);
+
+ auto last_child = lastChild();
+ if (last_child && last_child->getRepr() == child) {
+ if (auto item = cast<SPItem>(last_child)) {
+ for (auto &v : attached_views) {
+ auto ac = item->invoke_show(v.drawingitem->drawing(), v.key, SP_ITEM_SHOW_DISPLAY);
+ if (ac) {
+ v.drawingitem->appendChild(ac);
+ }
+ }
+ }
+ } else {
+ if (auto item = cast<SPItem>(get_child_by_repr(child))) {
+ unsigned position = item->pos_in_parent();
+ for (auto &v : attached_views) {
+ auto ac = item->invoke_show(v.drawingitem->drawing(), v.key, SP_ITEM_SHOW_DISPLAY);
+ if (ac) {
+ v.drawingitem->prependChild(ac);
+ ac->setZOrder(position);
+ }
+ }
+ }
+ }
+
+ requestModified(SP_OBJECT_MODIFIED_FLAG);
+}
+
+void SPPattern::remove_child(Inkscape::XML::Node *child)
+{
+ SPPaintServer::remove_child(child);
+ // no need to do anything as child will automatically remove itself
+ requestModified(SP_OBJECT_MODIFIED_FLAG);
+}
+
+void SPPattern::order_changed(Inkscape::XML::Node *child, Inkscape::XML::Node *old_prev, Inkscape::XML::Node *new_prev)
+{
+ SPPaintServer::order_changed(child, old_prev, new_prev);
+
+ if (auto item = cast<SPItem>(get_child_by_repr(child))) {
+ unsigned position = item->pos_in_parent();
+ for (auto &v : attached_views) {
+ auto ac = item->get_arenaitem(v.key);
+ ac->setZOrder(position);
+ }
+ }
+
+ requestModified(SP_OBJECT_MODIFIED_FLAG);
+}
+
+void SPPattern::_onRefChanged(SPObject *old_ref, SPObject *ref)
+{
+ if (old_ref) {
+ _modified_connection.disconnect();
+ }
+
+ if (is<SPPattern>(ref)) {
+ _modified_connection = ref->connectModified(sigc::mem_fun(*this, &SPPattern::_onRefModified));
+ }
+
+ _onRefModified(ref, 0);
+}
+
+void SPPattern::_onRefModified(SPObject */*ref*/, unsigned /*flags*/)
+{
+ requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+}
+
+void SPPattern::set_shown(SPPattern *new_shown)
+{
+ if (shown == new_shown) {
+ return;
+ }
+
+ if (shown) {
+ for (auto &v : views) {
+ shown->unattach_view(v.drawingitem.get());
+ }
+
+ shown_released_connection.disconnect();
+ }
+
+ shown = new_shown;
+
+ if (shown) {
+ for (auto &v : views) {
+ shown->attach_view(v.drawingitem.get(), v.key);
+ }
+
+ shown_released_connection = shown->connectRelease([this] (auto) {
+ set_shown(nullptr);
+ });
+ }
+}
+
+void SPPattern::attach_view(Inkscape::DrawingPattern *di, unsigned key)
+{
+ attached_views.push_back({di, key});
+
+ for (auto &c : children) {
+ if (auto child = cast<SPItem>(&c)) {
+ auto item = child->invoke_show(di->drawing(), key, SP_ITEM_SHOW_DISPLAY);
+ di->appendChild(item);
+ }
+ }
+}
+
+void SPPattern::unattach_view(Inkscape::DrawingPattern *di)
+{
+ auto it = std::find_if(attached_views.begin(), attached_views.end(), [di] (auto const &v) {
+ return v.drawingitem == di;
+ });
+ assert(it != attached_views.end());
+
+ for (auto &c : children) {
+ if (auto child = cast<SPItem>(&c)) {
+ child->invoke_hide(it->key);
+ }
+ }
+
+ attached_views.erase(it);
+}
+
+unsigned SPPattern::_countHrefs(SPObject *o) const
+{
+ if (!o)
+ return 1;
+
+ guint i = 0;
+
+ SPStyle *style = o->style;
+ if (style && style->fill.isPaintserver() && is<SPPattern>(SP_STYLE_FILL_SERVER(style)) &&
+ cast<SPPattern>(SP_STYLE_FILL_SERVER(style)) == this) {
+ i++;
+ }
+ if (style && style->stroke.isPaintserver() && is<SPPattern>(SP_STYLE_STROKE_SERVER(style)) &&
+ cast<SPPattern>(SP_STYLE_STROKE_SERVER(style)) == this) {
+ i++;
+ }
+
+ for (auto& child: o->children) {
+ i += _countHrefs(&child);
+ }
+
+ return i;
+}
+
+SPPattern *SPPattern::_chain() const
+{
+ Inkscape::XML::Document *xml_doc = document->getReprDoc();
+ Inkscape::XML::Node *defsrepr = document->getDefs()->getRepr();
+
+ Inkscape::XML::Node *repr = xml_doc->createElement("svg:pattern");
+ repr->setAttribute("inkscape:collect", "always");
+ Glib::ustring parent_ref = Glib::ustring::compose("#%1", getRepr()->attribute("id"));
+ Inkscape::setHrefAttribute(*repr, parent_ref);
+ // this attribute is used to express uniform pattern scaling in pattern editor, so keep it
+ repr->setAttribute("preserveAspectRatio", getRepr()->attribute("preserveAspectRatio"));
+
+ defsrepr->addChild(repr, nullptr);
+ SPObject *child = document->getObjectByRepr(repr);
+ assert(child == document->getObjectById(repr->attribute("id")));
+ g_assert(is<SPPattern>(child));
+
+ return cast<SPPattern>(child);
+}
+
+SPPattern *SPPattern::clone_if_necessary(SPItem *item, const gchar *property)
+{
+ SPPattern *pattern = this;
+ if (pattern->href.empty() || pattern->hrefcount > _countHrefs(item)) {
+ pattern = _chain();
+ Glib::ustring href = Glib::ustring::compose("url(#%1)", pattern->getRepr()->attribute("id"));
+
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ sp_repr_css_set_property(css, property, href.c_str());
+ sp_repr_css_change_recursive(item->getRepr(), css, "style");
+ }
+ return pattern;
+}
+
+// do not remove identity transform in pattern elements; when patterns are referenced then linking
+// pattern transform overrides root/referenced pattern transform; if it disappears then root transform
+// takes over and that's not what we want
+static std::string write_transform(const Geom::Affine& transform) {
+ if (transform.isIdentity()) {
+ return "scale(1)";
+ }
+ return sp_svg_transform_write(transform);
+}
+
+void SPPattern::transform_multiply(Geom::Affine postmul, bool set)
+{
+ // this formula is for a different interpretation of pattern transforms as described in (*) in sp-pattern.cpp
+ // for it to work, we also need sp_object_read_attr( item, "transform");
+ // pattern->patternTransform = premul * item->transform * pattern->patternTransform * item->transform.inverse() *
+ // postmul;
+
+ // otherwise the formula is much simpler
+ if (set) {
+ _pattern_transform = postmul;
+ }
+ else {
+ _pattern_transform = getTransform() * postmul;
+ }
+ _pattern_transform_set = true;
+
+ setAttributeOrRemoveIfEmpty("patternTransform", write_transform(_pattern_transform));
+}
+
+char const *SPPattern::produce(std::vector<Inkscape::XML::Node*> const &reprs, Geom::Rect const &bounds,
+ SPDocument *document, Geom::Affine const &transform, Geom::Affine const &move)
+{
+ Inkscape::XML::Document *xml_doc = document->getReprDoc();
+ Inkscape::XML::Node *defsrepr = document->getDefs()->getRepr();
+
+ Inkscape::XML::Node *repr = xml_doc->createElement("svg:pattern");
+ repr->setAttribute("patternUnits", "userSpaceOnUse");
+ repr->setAttributeSvgDouble("width", bounds.dimensions()[Geom::X]);
+ repr->setAttributeSvgDouble("height", bounds.dimensions()[Geom::Y]);
+ repr->setAttributeOrRemoveIfEmpty("patternTransform", write_transform(transform));
+ // by default use uniform scaling
+ repr->setAttribute("preserveAspectRatio", "xMidYMid");
+ defsrepr->appendChild(repr);
+ const gchar *pd = repr->attribute("id");
+ SPObject *pat_object = document->getObjectById(pd);
+ bool can_colorize = false;
+
+ for (auto node : reprs) {
+ auto copy = cast<SPItem>(pat_object->appendChildRepr(node));
+
+ if (!repr->attribute("inkscape:label") && node->attribute("inkscape:label")) {
+ repr->setAttribute("inkscape:label", node->attribute("inkscape:label"));
+ }
+
+ // if some elements have undefined color or solid black, then their fill color is customizable
+ if (copy->style && copy->style->isSet(SPAttr::FILL)) {
+ if (auto paint = copy->style->getFillOrStroke(true)) {
+ if (paint->isColor() && paint->value.color.toRGBA32(255) == 255) { // black color set?
+ can_colorize = true;
+ // remove black fill, it will be inherited from pattern
+ paint->clear();
+ }
+ }
+ }
+ else {
+ // no color - it will be inherited
+ can_colorize = true;
+ }
+
+ Geom::Affine dup_transform;
+ if (!sp_svg_transform_read(node->attribute("transform"), &dup_transform))
+ dup_transform = Geom::identity();
+ dup_transform *= move;
+
+ copy->doWriteTransform(dup_transform, nullptr, false);
+ }
+
+ if (can_colorize && pat_object->style) {
+ // add black fill style to the pattern object - it will tell pattern editor to enable color selector
+ pat_object->style->readIfUnset(SPAttr::FILL, "black");
+ }
+
+ Inkscape::GC::release(repr);
+ return pd;
+}
+
+SPPattern const *SPPattern::rootPattern() const
+{
+ for (auto p = this; p; p = p->ref.getObject()) {
+ if (p->firstChild()) { // find the first one with children
+ return p;
+ }
+ }
+ return this; // document is broken, we can't get to root; but at least we can return ourself which is supposedly a valid pattern
+}
+
+SPPattern *SPPattern::rootPattern()
+{
+ return const_cast<SPPattern*>(std::as_const(*this).rootPattern());
+}
+
+// Access functions that look up fields up the chain of referenced patterns and return the first one which is set
+
+SPPattern::PatternUnits SPPattern::patternUnits() const
+{
+ for (auto p = this; p; p = p->ref.getObject()) {
+ if (p->_pattern_units_set)
+ return p->_pattern_units;
+ }
+ return _pattern_units;
+}
+
+SPPattern::PatternUnits SPPattern::patternContentUnits() const
+{
+ for (auto p = this; p; p = p->ref.getObject()) {
+ if (p->_pattern_content_units_set)
+ return p->_pattern_content_units;
+ }
+ return _pattern_content_units;
+}
+
+Geom::Affine const &SPPattern::getTransform() const
+{
+ for (auto p = this; p; p = p->ref.getObject()) {
+ if (p->_pattern_transform_set)
+ return p->_pattern_transform;
+ }
+ return _pattern_transform;
+}
+
+const Geom::Affine& SPPattern::get_this_transform() const {
+ return _pattern_transform;
+}
+
+double SPPattern::x() const
+{
+ for (auto p = this; p; p = p->ref.getObject()) {
+ if (p->_x._set)
+ return p->_x.computed;
+ }
+ return 0;
+}
+
+double SPPattern::y() const
+{
+ for (auto p = this; p; p = p->ref.getObject()) {
+ if (p->_y._set)
+ return p->_y.computed;
+ }
+ return 0;
+}
+
+double SPPattern::width() const
+{
+ for (auto p = this; p; p = p->ref.getObject()) {
+ if (p->_width._set)
+ return p->_width.computed;
+ }
+ return 0;
+}
+
+double SPPattern::height() const
+{
+ for (auto p = this; p; p = p->ref.getObject()) {
+ if (p->_height._set)
+ return p->_height.computed;
+ }
+ return 0;
+}
+
+Geom::OptRect SPPattern::viewbox() const
+{
+ Geom::OptRect viewbox;
+ for (auto p = this; p; p = p->ref.getObject()) {
+ if (p->viewBox_set) {
+ viewbox = p->viewBox;
+ break;
+ }
+ }
+ return viewbox;
+}
+
+bool SPPattern::_hasItemChildren() const
+{
+ for (auto &child : children) {
+ if (is<SPItem>(&child)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool SPPattern::isValid() const
+{
+ return width() > 0 && height() > 0;
+}
+
+Inkscape::DrawingPattern *SPPattern::show(Inkscape::Drawing &drawing, unsigned key, Geom::OptRect const &bbox)
+{
+ views.emplace_back(make_drawingitem<Inkscape::DrawingPattern>(drawing), bbox, key);
+ auto &v = views.back();
+ auto root = v.drawingitem.get();
+
+ if (shown) {
+ shown->attach_view(root, key);
+ }
+
+ root->setStyle(style);
+
+ update_view(v);
+
+ return root;
+}
+
+void SPPattern::hide(unsigned key)
+{
+ auto it = std::find_if(views.begin(), views.end(), [=] (auto &v) {
+ return v.key == key;
+ });
+
+ if (it == views.end()) {
+ return;
+ }
+
+ if (shown) {
+ shown->unattach_view(it->drawingitem.get());
+ }
+
+ views.erase(it);
+}
+
+void SPPattern::setBBox(unsigned key, Geom::OptRect const &bbox)
+{
+ auto it = std::find_if(views.begin(), views.end(), [=] (auto &v) {
+ return v.key == key;
+ });
+ assert(it != views.end());
+ auto &v = *it;
+
+ v.bbox = bbox;
+ update_view(v);
+}
+
+SPPattern::View::View(DrawingItemPtr<Inkscape::DrawingPattern> drawingitem, Geom::OptRect const &bbox, unsigned key)
+ : drawingitem(std::move(drawingitem))
+ , bbox(bbox)
+ , key(key) {}
+
+/*
+ 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 :