1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /** \file
3  * SVG <tref> implementation - All character data within the referenced
4  * element, including character data enclosed within additional markup,
5  * will be rendered.
6  *
7  * This file was created based on skeleton.cpp
8  */
9 /*
10  * Authors:
11  *   Gail Banaszkiewicz <Gail.Banaszkiewicz@gmail.com>
12  *   Jon A. Cruz <jon@joncruz.org>
13  *   Abhishek Sharma
14  *
15  * Copyright (C) 2007 Gail Banaszkiewicz
16  *
17  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
18  */
19 
20 #include "sp-tref.h"
21 
22 #include <glibmm/i18n.h>
23 
24 #include "bad-uri-exception.h"
25 #include "attributes.h"
26 #include "document.h"
27 #include "sp-factory.h"
28 #include "sp-text.h"
29 #include "style.h"
30 #include "text-editing.h"
31 
32 //#define DEBUG_TREF
33 #ifdef DEBUG_TREF
34 # define debug(f, a...) { g_message("%s(%d) %s:", \
35                                   __FILE__,__LINE__,__FUNCTION__); \
36                           g_message(f, ## a); \
37                           g_message("\n"); \
38                         }
39 #else
40 # define debug(f, a...) /**/
41 #endif
42 
43 
44 static void build_string_from_root(Inkscape::XML::Node *root, Glib::ustring *retString);
45 
46 /* TRef base class */
47 static void sp_tref_href_changed(SPObject *old_ref, SPObject *ref, SPTRef *tref);
48 static void sp_tref_delete_self(SPObject *deleted, SPTRef *self);
49 
SPTRef()50 SPTRef::SPTRef() : SPItem() {
51 	this->stringChild = nullptr;
52 
53     this->href = nullptr;
54     this->uriOriginalRef = new SPTRefReference(this);
55 
56     this->_changed_connection =
57         this->uriOriginalRef->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_tref_href_changed), this));
58 }
59 
~SPTRef()60 SPTRef::~SPTRef() {
61 	delete this->uriOriginalRef;
62 }
63 
build(SPDocument * document,Inkscape::XML::Node * repr)64 void SPTRef::build(SPDocument *document, Inkscape::XML::Node *repr) {
65     SPItem::build(document, repr);
66 
67     this->readAttr(SPAttr::XLINK_HREF);
68     this->readAttr(SPAttr::X);
69     this->readAttr(SPAttr::Y);
70     this->readAttr(SPAttr::DX);
71     this->readAttr(SPAttr::DY);
72     this->readAttr(SPAttr::ROTATE);
73 }
74 
release()75 void SPTRef::release() {
76     //this->attributes.~TextTagAttributes();
77 
78     this->_delete_connection.disconnect();
79     this->_changed_connection.disconnect();
80 
81     g_free(this->href);
82     this->href = nullptr;
83 
84     this->uriOriginalRef->detach();
85 
86     SPItem::release();
87 }
88 
set(SPAttr key,const gchar * value)89 void SPTRef::set(SPAttr key, const gchar* value) {
90     debug("0x%p %s(%u): '%s'",this,
91             sp_attribute_name(key),key,value ? value : "<no value>");
92 
93     if (this->attributes.readSingleAttribute(key, value, style, &viewport)) { // x, y, dx, dy, rotate
94         this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
95     } else if (key == SPAttr::XLINK_HREF) { // xlink:href
96         if ( !value ) {
97             // No value
98             g_free(this->href);
99             this->href = nullptr;
100             this->uriOriginalRef->detach();
101         } else if ((this->href && strcmp(value, this->href) != 0) || (!this->href)) {
102             // Value has changed
103 
104             if ( this->href ) {
105                 g_free(this->href);
106                 this->href = nullptr;
107             }
108 
109             this->href = g_strdup(value);
110 
111             try {
112                 this->uriOriginalRef->attach(Inkscape::URI(value));
113                 this->uriOriginalRef->updateObserver();
114             } catch ( Inkscape::BadURIException &e ) {
115                 g_warning("%s", e.what());
116                 this->uriOriginalRef->detach();
117             }
118 
119             // No matter what happened, an update should be in order
120             this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
121         }
122     } else { // default
123         SPItem::set(key, value);
124     }
125 }
126 
update(SPCtx * ctx,guint flags)127 void SPTRef::update(SPCtx *ctx, guint flags) {
128     debug("0x%p",this);
129 
130     unsigned childflags = flags;
131     if (flags & SP_OBJECT_MODIFIED_FLAG) {
132         childflags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
133     }
134     childflags &= SP_OBJECT_MODIFIED_CASCADE;
135 
136     SPObject *child = this->stringChild;
137 
138     if (child) {
139         if ( childflags || ( child->uflags & SP_OBJECT_MODIFIED_FLAG )) {
140             child->updateDisplay(ctx, childflags);
141         }
142     }
143 
144     SPItem::update(ctx, flags);
145 }
146 
modified(unsigned int flags)147 void SPTRef::modified(unsigned int flags) {
148     if (flags & SP_OBJECT_MODIFIED_FLAG) {
149         flags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
150     }
151 
152     flags &= SP_OBJECT_MODIFIED_CASCADE;
153 
154     SPObject *child = this->stringChild;
155 
156     if (child) {
157         sp_object_ref(child);
158 
159         if (flags || (child->mflags & SP_OBJECT_MODIFIED_FLAG)) {
160             child->emitModified(flags);
161         }
162 
163         sp_object_unref(child);
164     }
165 }
166 
write(Inkscape::XML::Document * xml_doc,Inkscape::XML::Node * repr,guint flags)167 Inkscape::XML::Node* SPTRef::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) {
168     debug("0x%p",this);
169 
170     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
171         repr = xml_doc->createElement("svg:tref");
172     }
173 
174     this->attributes.writeTo(repr);
175 
176     if (this->uriOriginalRef->getURI()) {
177         auto uri = this->uriOriginalRef->getURI()->str();
178         auto uri_string = uri.c_str();
179         debug("uri_string=%s", uri_string);
180         repr->setAttribute("xlink:href", uri_string);
181     }
182 
183     SPItem::write(xml_doc, repr, flags);
184 
185     return repr;
186 }
187 
bbox(Geom::Affine const & transform,SPItem::BBoxType type) const188 Geom::OptRect SPTRef::bbox(Geom::Affine const &transform, SPItem::BBoxType type) const {
189     Geom::OptRect bbox;
190     // find out the ancestor text which holds our layout
191     SPObject const *parent_text = this;
192 
193     while ( parent_text && !SP_IS_TEXT(parent_text) ) {
194         parent_text = parent_text->parent;
195     }
196 
197     if (parent_text == nullptr) {
198         return bbox;
199     }
200 
201     // get the bbox of our portion of the layout
202     bbox = SP_TEXT(parent_text)->layout.bounds(transform,
203         sp_text_get_length_upto(parent_text, this), sp_text_get_length_upto(this, nullptr) - 1);
204 
205     // Add stroke width
206     // FIXME this code is incorrect
207     if (bbox && type == SPItem::VISUAL_BBOX && !this->style->stroke.isNone()) {
208         double scale = transform.descrim();
209         bbox->expandBy(0.5 * this->style->stroke_width.computed * scale);
210     }
211 
212     return bbox;
213 }
214 
displayName() const215 const char* SPTRef::displayName() const {
216     return _("Cloned Character Data");
217 }
218 
description() const219 gchar* SPTRef::description() const {
220     SPObject const *referred = this->getObjectReferredTo();
221 
222     if (referred) {
223 	char *child_desc;
224 
225 	if (SP_IS_ITEM(referred)) {
226 	    child_desc = SP_ITEM(referred)->detailedDescription();
227 	} else {
228 	    child_desc = g_strdup("");
229 	}
230 
231 	char *ret = g_strdup_printf("%s%s",
232 	    (SP_IS_ITEM(referred) ? _(" from ") : ""), child_desc);
233 	g_free(child_desc);
234 
235 	return ret;
236     }
237 
238     return g_strdup(_("[orphaned]"));
239 }
240 
241 
242 /* For the sigc::connection changes (i.e. when the object being referred to changes) */
243 static void
sp_tref_href_changed(SPObject *,SPObject *,SPTRef * tref)244 sp_tref_href_changed(SPObject */*old_ref*/, SPObject */*ref*/, SPTRef *tref)
245 {
246     if (tref)
247     {
248         // Save a pointer to the original object being referred to
249         SPObject *refRoot = tref->getObjectReferredTo();
250 
251         tref->_delete_connection.disconnect();
252 
253         if (tref->stringChild) {
254             tref->detach(tref->stringChild);
255             tref->stringChild = nullptr;
256         }
257 
258         // Ensure that we are referring to a legitimate object
259         if (tref->href && refRoot && sp_tref_reference_allowed(tref, refRoot)) {
260 
261             // Update the text being referred to (will create a new string child)
262             sp_tref_update_text(tref);
263 
264             // Restore the delete connection now that we're done messing with stuff
265             tref->_delete_connection = refRoot->connectDelete(sigc::bind(sigc::ptr_fun(&sp_tref_delete_self), tref));
266         }
267 
268     }
269 }
270 
271 
272 /**
273  * Delete the tref object
274  */
275 static void
sp_tref_delete_self(SPObject *,SPTRef * self)276 sp_tref_delete_self(SPObject */*deleted*/, SPTRef *self)
277 {
278     self->deleteObject();
279 }
280 
281 /**
282  * Return the object referred to via the URI reference
283  */
getObjectReferredTo()284 SPObject * SPTRef::getObjectReferredTo()
285 {
286     SPObject *referredObject = nullptr;
287 
288     if (uriOriginalRef) {
289         referredObject = uriOriginalRef->getObject();
290     }
291 
292     return referredObject;
293 }
294 
295 /**
296  * Return the object referred to via the URI reference
297  */
getObjectReferredTo() const298 SPObject const *SPTRef::getObjectReferredTo() const {
299     SPObject *referredObject = nullptr;
300 
301     if (uriOriginalRef) {
302         referredObject = uriOriginalRef->getObject();
303     }
304 
305     return referredObject;
306 }
307 
308 
309 /**
310  * Returns true when the given tref is allowed to refer to a particular object
311  */
312 bool
sp_tref_reference_allowed(SPTRef * tref,SPObject * possible_ref)313 sp_tref_reference_allowed(SPTRef *tref, SPObject *possible_ref)
314 {
315     bool allowed = false;
316 
317     if (tref && possible_ref) {
318         if (tref != possible_ref) {
319             bool ancestor = false;
320             for (SPObject *obj = tref; obj; obj = obj->parent) {
321                 if (possible_ref == obj) {
322                     ancestor = true;
323                     break;
324                 }
325             }
326             allowed = !ancestor;
327         }
328     }
329 
330     return allowed;
331 }
332 
333 
334 /**
335  * Returns true if a tref is fully contained in the confines of the given
336  * iterators and layout (or if there is no tref).
337  */
338 bool
sp_tref_fully_contained(SPObject * start_item,Glib::ustring::iterator & start,SPObject * end_item,Glib::ustring::iterator & end)339 sp_tref_fully_contained(SPObject *start_item, Glib::ustring::iterator &start,
340                              SPObject *end_item, Glib::ustring::iterator &end)
341 {
342     bool fully_contained = false;
343 
344     if (start_item && end_item) {
345 
346         // If neither the beginning or the end is a tref then we return true (whether there
347         // is a tref in the innards or not, because if there is one then it must be totally
348         // contained)
349         if (!(SP_IS_STRING(start_item) && SP_IS_TREF(start_item->parent))
350                 && !(SP_IS_STRING(end_item) && SP_IS_TREF(end_item->parent))) {
351             fully_contained = true;
352         }
353 
354         // Both the beginning and end are trefs; but in this case, the string iterators
355         // must be at the right places
356         else if ((SP_IS_STRING(start_item) && SP_IS_TREF(start_item->parent))
357                 && (SP_IS_STRING(end_item) && SP_IS_TREF(end_item->parent))) {
358             if (start == SP_STRING(start_item)->string.begin()
359                     && end == SP_STRING(start_item)->string.end()) {
360                 fully_contained = true;
361             }
362         }
363 
364         // If the beginning is a string that is a child of a tref, the iterator has to be
365         // at the beginning of the item
366         else if ((SP_IS_STRING(start_item) && SP_IS_TREF(start_item->parent))
367                     && !(SP_IS_STRING(end_item) && SP_IS_TREF(end_item->parent))) {
368             if (start == SP_STRING(start_item)->string.begin()) {
369                 fully_contained = true;
370             }
371         }
372 
373         // Same, but the for the end
374         else if (!(SP_IS_STRING(start_item) && SP_IS_TREF(start_item->parent))
375                     && (SP_IS_STRING(end_item) && SP_IS_TREF(end_item->parent))) {
376             if (end == SP_STRING(start_item)->string.end()) {
377                 fully_contained = true;
378             }
379         }
380     }
381 
382     return fully_contained;
383 }
384 
385 
sp_tref_update_text(SPTRef * tref)386 void sp_tref_update_text(SPTRef *tref)
387 {
388     if (tref) {
389         // Get the character data that will be used with this tref
390         Glib::ustring charData = "";
391         build_string_from_root(tref->getObjectReferredTo()->getRepr(), &charData);
392 
393         if (tref->stringChild) {
394             tref->detach(tref->stringChild);
395             tref->stringChild = nullptr;
396         }
397 
398         // Create the node and SPString to be the tref's child
399         Inkscape::XML::Document *xml_doc = tref->document->getReprDoc();
400 
401         Inkscape::XML::Node *newStringRepr = xml_doc->createTextNode(charData.c_str());
402         tref->stringChild = SPFactory::createObject(NodeTraits::get_type_string(*newStringRepr));
403 
404         // Add this SPString as a child of the tref
405         tref->attach(tref->stringChild, tref->lastChild());
406         sp_object_unref(tref->stringChild, nullptr);
407         (tref->stringChild)->invoke_build(tref->document, newStringRepr, TRUE);
408 
409         Inkscape::GC::release(newStringRepr);
410     }
411 }
412 
413 
414 
415 /**
416  * Using depth-first search, build up a string by concatenating all SPStrings
417  * found in the tree starting at the root
418  */
419 static void
build_string_from_root(Inkscape::XML::Node * root,Glib::ustring * retString)420 build_string_from_root(Inkscape::XML::Node *root, Glib::ustring *retString)
421 {
422     if (root && retString) {
423 
424         // Stop and concatenate when a SPString is found
425         if (root->type() == Inkscape::XML::NodeType::TEXT_NODE) {
426             *retString += (root->content());
427 
428             debug("%s", retString->c_str());
429 
430         // Otherwise, continue searching down the tree (with the assumption that no children nodes
431         // of a SPString are actually legal)
432         } else {
433             Inkscape::XML::Node *childNode;
434             for (childNode = root->firstChild(); childNode; childNode = childNode->next()) {
435                 build_string_from_root(childNode, retString);
436             }
437         }
438     }
439 }
440 
441 /**
442  * This function will create a new tspan element with the same attributes as
443  * the tref had and add the same text as a child.  The tref is replaced in the
444  * tree with the new tspan.
445  * The code is based partially on sp_use_unlink
446  */
447 SPObject *
sp_tref_convert_to_tspan(SPObject * obj)448 sp_tref_convert_to_tspan(SPObject *obj)
449 {
450     SPObject * new_tspan = nullptr;
451 
452     ////////////////////
453     // BASE CASE
454     ////////////////////
455     if (SP_IS_TREF(obj)) {
456 
457         SPTRef *tref = SP_TREF(obj);
458 
459         if (tref && tref->stringChild) {
460             Inkscape::XML::Node *tref_repr = tref->getRepr();
461             Inkscape::XML::Node *tref_parent = tref_repr->parent();
462 
463             SPDocument *document = tref->document;
464             Inkscape::XML::Document *xml_doc = document->getReprDoc();
465 
466             Inkscape::XML::Node *new_tspan_repr = xml_doc->createElement("svg:tspan");
467 
468             // Add the new tspan element just after the current tref
469             tref_parent->addChild(new_tspan_repr, tref_repr);
470             Inkscape::GC::release(new_tspan_repr);
471 
472             new_tspan = document->getObjectByRepr(new_tspan_repr);
473 
474             // Create a new string child for the tspan
475             Inkscape::XML::Node *new_string_repr = tref->stringChild->getRepr()->duplicate(xml_doc);
476             new_tspan_repr->addChild(new_string_repr, nullptr);
477 
478             //SPObject * new_string_child = document->getObjectByRepr(new_string_repr);
479 
480             // Merge style from the tref
481             new_tspan->style->merge( tref->style );
482             new_tspan->style->cascade( new_tspan->parent->style );
483             new_tspan->updateRepr();
484 
485             // Hold onto our SPObject and repr for now.
486             sp_object_ref(tref);
487             Inkscape::GC::anchor(tref_repr);
488 
489             // Remove ourselves, not propagating delete events to avoid a
490             // chain-reaction with other elements that might reference us.
491             tref->deleteObject(false);
492 
493             // Give the copy our old id and let go of our old repr.
494             new_tspan_repr->setAttribute("id", tref_repr->attribute("id"));
495             Inkscape::GC::release(tref_repr);
496 
497             // Establish the succession and let go of our object.
498             tref->setSuccessor(new_tspan);
499             sp_object_unref(tref);
500         }
501     }
502     ////////////////////
503     // RECURSIVE CASE
504     ////////////////////
505     else {
506         std::vector<SPObject *> l;
507         for (auto& child: obj->children) {
508             sp_object_ref(&child, obj);
509             l.push_back(&child);
510         }
511         for(auto child:l) {
512             // Note that there may be more than one conversion happening here, so if it's not a
513             // tref being passed into this function, the returned value can't be specifically known
514             new_tspan = sp_tref_convert_to_tspan(child);
515 
516             sp_object_unref(child, obj);
517         }
518     }
519 
520     return new_tspan;
521 }
522 
523 
524 /*
525   Local Variables:
526   mode:c++
527   c-file-style:"stroustrup"
528   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
529   indent-tabs-mode:nil
530   fill-column:99
531   End:
532 */
533 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
534