// SPDX-License-Identifier: GPL-2.0-or-later /** \file * SVG 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 * Jon A. Cruz * Abhishek Sharma * * Copyright (C) 2007 Gail Banaszkiewicz * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include "sp-tref.h" #include #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" #include "xml/href-attribute-helper.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() , href(nullptr) , uriOriginalRef(this) , stringChild(nullptr) { _changed_connection = uriOriginalRef.changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_tref_href_changed), this)); } SPTRef::~SPTRef() { } 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(href); href = nullptr; 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 : ""); 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(href); href = nullptr; 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; } href = g_strdup(value); try { uriOriginalRef.attach(Inkscape::URI(value)); uriOriginalRef.updateObserver(); } catch (Inkscape::BadURIException const &e) { g_warning("%s", e.what()); uriOriginalRef.detach(); } // No matter what happened, an update should be in order 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 (uriOriginalRef.getURI()) { auto uri = uriOriginalRef.getURI()->str(); auto uri_string = uri.c_str(); debug("uri_string=%s", uri_string); Inkscape::setHrefAttribute(*repr, 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 && !is(parent_text) ) { parent_text = parent_text->parent; } if (parent_text == nullptr) { return bbox; } // get the bbox of our portion of the layout return cast(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 (is(referred)) { child_desc = cast(referred)->detailedDescription(); } else { child_desc = g_strdup(""); } char *ret = g_strdup_printf("%s%s", (is(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() { return uriOriginalRef.getObject(); } /** * Return the object referred to via the URI reference */ SPObject const *SPTRef::getObjectReferredTo() const { return uriOriginalRef.getObject(); } /** * 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 (!(is(start_item) && is(start_item->parent)) && !(is(end_item) && is(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 ((is(start_item) && is(start_item->parent)) && (is(end_item) && is(end_item->parent))) { if (start == cast(start_item)->string.begin() && end == cast(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 ((is(start_item) && is(start_item->parent)) && !(is(end_item) && is(end_item->parent))) { if (start == cast(start_item)->string.begin()) { fully_contained = true; } } // Same, but the for the end else if (!(is(start_item) && is(start_item->parent)) && (is(end_item) && is(end_item->parent))) { if (end == cast(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, FALSE); 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 (is(obj)) { auto tref = cast(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 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 :