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