summaryrefslogtreecommitdiffstats
path: root/src/object/sp-tref.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/object/sp-tref.cpp530
1 files changed, 530 insertions, 0 deletions
diff --git a/src/object/sp-tref.cpp b/src/object/sp-tref.cpp
new file mode 100644
index 0000000..54503ee
--- /dev/null
+++ b/src/object/sp-tref.cpp
@@ -0,0 +1,530 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ * SVG <tref> implementation - All character data within the referenced
+ * element, including character data enclosed within additional markup,
+ * will be rendered.
+ *
+ * This file was created based on skeleton.cpp
+ */
+/*
+ * Authors:
+ * Gail Banaszkiewicz <Gail.Banaszkiewicz@gmail.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2007 Gail Banaszkiewicz
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "sp-tref.h"
+
+#include <glibmm/i18n.h>
+
+#include "bad-uri-exception.h"
+#include "attributes.h"
+#include "document.h"
+#include "sp-factory.h"
+#include "sp-text.h"
+#include "style.h"
+#include "text-editing.h"
+
+//#define DEBUG_TREF
+#ifdef DEBUG_TREF
+# define debug(f, a...) { g_message("%s(%d) %s:", \
+ __FILE__,__LINE__,__FUNCTION__); \
+ g_message(f, ## a); \
+ g_message("\n"); \
+ }
+#else
+# define debug(f, a...) /**/
+#endif
+
+
+static void build_string_from_root(Inkscape::XML::Node *root, Glib::ustring *retString);
+
+/* TRef base class */
+static void sp_tref_href_changed(SPObject *old_ref, SPObject *ref, SPTRef *tref);
+static void sp_tref_delete_self(SPObject *deleted, SPTRef *self);
+
+SPTRef::SPTRef() : SPItem() {
+ this->stringChild = nullptr;
+
+ this->href = nullptr;
+ this->uriOriginalRef = new SPTRefReference(this);
+
+ this->_changed_connection =
+ this->uriOriginalRef->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_tref_href_changed), this));
+}
+
+SPTRef::~SPTRef() {
+ delete this->uriOriginalRef;
+}
+
+void SPTRef::build(SPDocument *document, Inkscape::XML::Node *repr) {
+ SPItem::build(document, repr);
+
+ this->readAttr(SPAttr::XLINK_HREF);
+ this->readAttr(SPAttr::X);
+ this->readAttr(SPAttr::Y);
+ this->readAttr(SPAttr::DX);
+ this->readAttr(SPAttr::DY);
+ this->readAttr(SPAttr::ROTATE);
+}
+
+void SPTRef::release() {
+ //this->attributes.~TextTagAttributes();
+
+ this->_delete_connection.disconnect();
+ this->_changed_connection.disconnect();
+
+ g_free(this->href);
+ this->href = nullptr;
+
+ this->uriOriginalRef->detach();
+
+ SPItem::release();
+}
+
+void SPTRef::set(SPAttr key, const gchar* value) {
+ debug("0x%p %s(%u): '%s'",this,
+ sp_attribute_name(key),key,value ? value : "<no value>");
+
+ if (this->attributes.readSingleAttribute(key, value, style, &viewport)) { // x, y, dx, dy, rotate
+ this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ } else if (key == SPAttr::XLINK_HREF) { // xlink:href
+ if ( !value ) {
+ // No value
+ g_free(this->href);
+ this->href = nullptr;
+ this->uriOriginalRef->detach();
+ } else if ((this->href && strcmp(value, this->href) != 0) || (!this->href)) {
+ // Value has changed
+
+ if ( this->href ) {
+ g_free(this->href);
+ this->href = nullptr;
+ }
+
+ this->href = g_strdup(value);
+
+ try {
+ this->uriOriginalRef->attach(Inkscape::URI(value));
+ this->uriOriginalRef->updateObserver();
+ } catch ( Inkscape::BadURIException &e ) {
+ g_warning("%s", e.what());
+ this->uriOriginalRef->detach();
+ }
+
+ // No matter what happened, an update should be in order
+ this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ }
+ } else { // default
+ SPItem::set(key, value);
+ }
+}
+
+void SPTRef::update(SPCtx *ctx, guint flags) {
+ debug("0x%p",this);
+
+ unsigned childflags = flags;
+ if (flags & SP_OBJECT_MODIFIED_FLAG) {
+ childflags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
+ }
+ childflags &= SP_OBJECT_MODIFIED_CASCADE;
+
+ SPObject *child = this->stringChild;
+
+ if (child) {
+ if ( childflags || ( child->uflags & SP_OBJECT_MODIFIED_FLAG )) {
+ child->updateDisplay(ctx, childflags);
+ }
+ }
+
+ SPItem::update(ctx, flags);
+}
+
+void SPTRef::modified(unsigned int flags) {
+ if (flags & SP_OBJECT_MODIFIED_FLAG) {
+ flags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
+ }
+
+ flags &= SP_OBJECT_MODIFIED_CASCADE;
+
+ SPObject *child = this->stringChild;
+
+ if (child) {
+ sp_object_ref(child);
+
+ if (flags || (child->mflags & SP_OBJECT_MODIFIED_FLAG)) {
+ child->emitModified(flags);
+ }
+
+ sp_object_unref(child);
+ }
+}
+
+Inkscape::XML::Node* SPTRef::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) {
+ debug("0x%p",this);
+
+ if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
+ repr = xml_doc->createElement("svg:tref");
+ }
+
+ this->attributes.writeTo(repr);
+
+ if (this->uriOriginalRef->getURI()) {
+ auto uri = this->uriOriginalRef->getURI()->str();
+ auto uri_string = uri.c_str();
+ debug("uri_string=%s", uri_string);
+ repr->setAttribute("xlink:href", uri_string);
+ }
+
+ SPItem::write(xml_doc, repr, flags);
+
+ return repr;
+}
+
+Geom::OptRect SPTRef::bbox(Geom::Affine const &transform, SPItem::BBoxType type) const {
+ Geom::OptRect bbox;
+ // find out the ancestor text which holds our layout
+ SPObject const *parent_text = this;
+
+ while ( parent_text && !SP_IS_TEXT(parent_text) ) {
+ parent_text = parent_text->parent;
+ }
+
+ if (parent_text == nullptr) {
+ return bbox;
+ }
+
+ // get the bbox of our portion of the layout
+ return SP_TEXT(parent_text)->layout.bounds(transform,
+ type == SPItem::VISUAL_BBOX,
+ sp_text_get_length_upto(parent_text, this),
+ sp_text_get_length_upto(this, nullptr) - 1);
+}
+
+const char* SPTRef::typeName() const {
+ return "text-data";
+}
+
+const char* SPTRef::displayName() const {
+ return _("Cloned Character Data");
+}
+
+gchar* SPTRef::description() const {
+ SPObject const *referred = this->getObjectReferredTo();
+
+ if (referred) {
+ char *child_desc;
+
+ if (SP_IS_ITEM(referred)) {
+ child_desc = SP_ITEM(referred)->detailedDescription();
+ } else {
+ child_desc = g_strdup("");
+ }
+
+ char *ret = g_strdup_printf("%s%s",
+ (SP_IS_ITEM(referred) ? _(" from ") : ""), child_desc);
+ g_free(child_desc);
+
+ return ret;
+ }
+
+ return g_strdup(_("[orphaned]"));
+}
+
+
+/* For the sigc::connection changes (i.e. when the object being referred to changes) */
+static void
+sp_tref_href_changed(SPObject */*old_ref*/, SPObject */*ref*/, SPTRef *tref)
+{
+ if (tref)
+ {
+ // Save a pointer to the original object being referred to
+ SPObject *refRoot = tref->getObjectReferredTo();
+
+ tref->_delete_connection.disconnect();
+
+ if (tref->stringChild) {
+ tref->detach(tref->stringChild);
+ tref->stringChild = nullptr;
+ }
+
+ // Ensure that we are referring to a legitimate object
+ if (tref->href && refRoot && sp_tref_reference_allowed(tref, refRoot)) {
+
+ // Update the text being referred to (will create a new string child)
+ sp_tref_update_text(tref);
+
+ // Restore the delete connection now that we're done messing with stuff
+ tref->_delete_connection = refRoot->connectDelete(sigc::bind(sigc::ptr_fun(&sp_tref_delete_self), tref));
+ }
+
+ }
+}
+
+
+/**
+ * Delete the tref object
+ */
+static void
+sp_tref_delete_self(SPObject */*deleted*/, SPTRef *self)
+{
+ self->deleteObject();
+}
+
+/**
+ * Return the object referred to via the URI reference
+ */
+SPObject * SPTRef::getObjectReferredTo()
+{
+ SPObject *referredObject = nullptr;
+
+ if (uriOriginalRef) {
+ referredObject = uriOriginalRef->getObject();
+ }
+
+ return referredObject;
+}
+
+/**
+ * Return the object referred to via the URI reference
+ */
+SPObject const *SPTRef::getObjectReferredTo() const {
+ SPObject *referredObject = nullptr;
+
+ if (uriOriginalRef) {
+ referredObject = uriOriginalRef->getObject();
+ }
+
+ return referredObject;
+}
+
+
+/**
+ * Returns true when the given tref is allowed to refer to a particular object
+ */
+bool
+sp_tref_reference_allowed(SPTRef *tref, SPObject *possible_ref)
+{
+ bool allowed = false;
+
+ if (tref && possible_ref) {
+ if (tref != possible_ref) {
+ bool ancestor = false;
+ for (SPObject *obj = tref; obj; obj = obj->parent) {
+ if (possible_ref == obj) {
+ ancestor = true;
+ break;
+ }
+ }
+ allowed = !ancestor;
+ }
+ }
+
+ return allowed;
+}
+
+
+/**
+ * Returns true if a tref is fully contained in the confines of the given
+ * iterators and layout (or if there is no tref).
+ */
+bool
+sp_tref_fully_contained(SPObject *start_item, Glib::ustring::iterator &start,
+ SPObject *end_item, Glib::ustring::iterator &end)
+{
+ bool fully_contained = false;
+
+ if (start_item && end_item) {
+
+ // If neither the beginning or the end is a tref then we return true (whether there
+ // is a tref in the innards or not, because if there is one then it must be totally
+ // contained)
+ if (!(SP_IS_STRING(start_item) && SP_IS_TREF(start_item->parent))
+ && !(SP_IS_STRING(end_item) && SP_IS_TREF(end_item->parent))) {
+ fully_contained = true;
+ }
+
+ // Both the beginning and end are trefs; but in this case, the string iterators
+ // must be at the right places
+ else if ((SP_IS_STRING(start_item) && SP_IS_TREF(start_item->parent))
+ && (SP_IS_STRING(end_item) && SP_IS_TREF(end_item->parent))) {
+ if (start == SP_STRING(start_item)->string.begin()
+ && end == SP_STRING(start_item)->string.end()) {
+ fully_contained = true;
+ }
+ }
+
+ // If the beginning is a string that is a child of a tref, the iterator has to be
+ // at the beginning of the item
+ else if ((SP_IS_STRING(start_item) && SP_IS_TREF(start_item->parent))
+ && !(SP_IS_STRING(end_item) && SP_IS_TREF(end_item->parent))) {
+ if (start == SP_STRING(start_item)->string.begin()) {
+ fully_contained = true;
+ }
+ }
+
+ // Same, but the for the end
+ else if (!(SP_IS_STRING(start_item) && SP_IS_TREF(start_item->parent))
+ && (SP_IS_STRING(end_item) && SP_IS_TREF(end_item->parent))) {
+ if (end == SP_STRING(start_item)->string.end()) {
+ fully_contained = true;
+ }
+ }
+ }
+
+ return fully_contained;
+}
+
+
+void sp_tref_update_text(SPTRef *tref)
+{
+ if (tref) {
+ // Get the character data that will be used with this tref
+ Glib::ustring charData = "";
+ build_string_from_root(tref->getObjectReferredTo()->getRepr(), &charData);
+
+ if (tref->stringChild) {
+ tref->detach(tref->stringChild);
+ tref->stringChild = nullptr;
+ }
+
+ // Create the node and SPString to be the tref's child
+ Inkscape::XML::Document *xml_doc = tref->document->getReprDoc();
+
+ Inkscape::XML::Node *newStringRepr = xml_doc->createTextNode(charData.c_str());
+ tref->stringChild = SPFactory::createObject(NodeTraits::get_type_string(*newStringRepr));
+
+ // Add this SPString as a child of the tref
+ tref->attach(tref->stringChild, tref->lastChild());
+ sp_object_unref(tref->stringChild, nullptr);
+ (tref->stringChild)->invoke_build(tref->document, newStringRepr, TRUE);
+
+ Inkscape::GC::release(newStringRepr);
+ }
+}
+
+
+
+/**
+ * Using depth-first search, build up a string by concatenating all SPStrings
+ * found in the tree starting at the root
+ */
+static void
+build_string_from_root(Inkscape::XML::Node *root, Glib::ustring *retString)
+{
+ if (root && retString) {
+
+ // Stop and concatenate when a SPString is found
+ if (root->type() == Inkscape::XML::NodeType::TEXT_NODE) {
+ *retString += (root->content());
+
+ debug("%s", retString->c_str());
+
+ // Otherwise, continue searching down the tree (with the assumption that no children nodes
+ // of a SPString are actually legal)
+ } else {
+ Inkscape::XML::Node *childNode;
+ for (childNode = root->firstChild(); childNode; childNode = childNode->next()) {
+ build_string_from_root(childNode, retString);
+ }
+ }
+ }
+}
+
+/**
+ * This function will create a new tspan element with the same attributes as
+ * the tref had and add the same text as a child. The tref is replaced in the
+ * tree with the new tspan.
+ * The code is based partially on sp_use_unlink
+ */
+SPObject *
+sp_tref_convert_to_tspan(SPObject *obj)
+{
+ SPObject * new_tspan = nullptr;
+
+ ////////////////////
+ // BASE CASE
+ ////////////////////
+ if (SP_IS_TREF(obj)) {
+
+ SPTRef *tref = SP_TREF(obj);
+
+ if (tref && tref->stringChild) {
+ Inkscape::XML::Node *tref_repr = tref->getRepr();
+ Inkscape::XML::Node *tref_parent = tref_repr->parent();
+
+ SPDocument *document = tref->document;
+ Inkscape::XML::Document *xml_doc = document->getReprDoc();
+
+ Inkscape::XML::Node *new_tspan_repr = xml_doc->createElement("svg:tspan");
+
+ // Add the new tspan element just after the current tref
+ tref_parent->addChild(new_tspan_repr, tref_repr);
+ Inkscape::GC::release(new_tspan_repr);
+
+ new_tspan = document->getObjectByRepr(new_tspan_repr);
+
+ // Create a new string child for the tspan
+ Inkscape::XML::Node *new_string_repr = tref->stringChild->getRepr()->duplicate(xml_doc);
+ new_tspan_repr->addChild(new_string_repr, nullptr);
+
+ //SPObject * new_string_child = document->getObjectByRepr(new_string_repr);
+
+ // Merge style from the tref
+ new_tspan->style->merge( tref->style );
+ new_tspan->style->cascade( new_tspan->parent->style );
+ new_tspan->updateRepr();
+
+ // Hold onto our SPObject and repr for now.
+ sp_object_ref(tref);
+ Inkscape::GC::anchor(tref_repr);
+
+ // Remove ourselves, not propagating delete events to avoid a
+ // chain-reaction with other elements that might reference us.
+ tref->deleteObject(false);
+
+ // Give the copy our old id and let go of our old repr.
+ new_tspan_repr->setAttribute("id", tref_repr->attribute("id"));
+ Inkscape::GC::release(tref_repr);
+
+ // Establish the succession and let go of our object.
+ tref->setSuccessor(new_tspan);
+ sp_object_unref(tref);
+ }
+ }
+ ////////////////////
+ // RECURSIVE CASE
+ ////////////////////
+ else {
+ std::vector<SPObject *> l;
+ for (auto& child: obj->children) {
+ sp_object_ref(&child, obj);
+ l.push_back(&child);
+ }
+ for(auto child:l) {
+ // Note that there may be more than one conversion happening here, so if it's not a
+ // tref being passed into this function, the returned value can't be specifically known
+ new_tspan = sp_tref_convert_to_tspan(child);
+
+ sp_object_unref(child, obj);
+ }
+ }
+
+ return new_tspan;
+}
+
+
+/*
+ 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 :