Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
sp-object.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * SPObject implementation.
4 *
5 * Authors:
6 * Lauris Kaplinski <lauris@kaplinski.com>
7 * bulia byak <buliabyak@users.sf.net>
8 * Stephen Silver <sasilver@users.sourceforge.net>
9 * Jon A. Cruz <jon@joncruz.org>
10 * Abhishek Sharma
11 * Adrian Boguszewski
12 *
13 * Copyright (C) 1999-2016 authors
14 * Copyright (C) 2001-2002 Ximian, Inc.
15 *
16 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
17 */
18
19#include <cstring>
20#include <string>
21#include <vector>
22#include <limits>
23#include <glibmm.h>
24
25#include <boost/range/adaptor/transformed.hpp>
26
27#include "attributes.h"
28#include "attribute-rel-util.h"
29#include "color-profile.h"
30#include "document.h"
31#include "io/fix-broken-links.h"
32#include "preferences.h"
33#include "style.h"
35#include "sp-factory.h"
36#include "sp-font.h"
37#include "sp-paint-server.h"
38#include "sp-root.h"
39#include "sp-use.h"
40#include "sp-use-reference.h"
41#include "sp-style-elem.h"
42#include "sp-script.h"
43#include "streq.h"
44#include "strneq.h"
45#include "xml/node-fns.h"
47#include "debug/event-tracker.h"
48#include "debug/simple-event.h"
49#include "debug/demangle.h"
51#include "util/format.h"
53
54#define noSP_OBJECT_DEBUG_CASCADE
55
56#define noSP_OBJECT_DEBUG
57
58#ifdef SP_OBJECT_DEBUG
59# define debug(f, a...) { g_print("%s(%d) %s:", \
60 __FILE__,__LINE__,__FUNCTION__); \
61 g_print(f, ## a); \
62 g_print("\n"); \
63 }
64#else
65# define debug(f, a...) /* */
66#endif
67
68// Define to enable indented tracing of SPObject.
69//#define OBJECT_TRACE
70static unsigned indent_level = 0;
71
75class SPObjectImpl
76{
77public:
78
84 static void setIdNull( SPObject* obj ) {
85 if (obj) {
86 obj->id = nullptr;
87 }
88 }
89
96 static void setId( SPObject* obj, gchar const* id ) {
97 if (obj && (id != obj->id) ) {
98 if (obj->id) {
99 g_free(obj->id);
100 obj->id = nullptr;
101 }
102 if (id) {
103 obj->id = g_strdup(id);
104 }
105 }
106 }
107};
108
113 : cloned{0}
114 , uflags{0}
115 , mflags{0}
116{
117 debug("id=%p, typename=%s", this, g_type_name_from_instance((GTypeInstance *)this));
118
119 SPObjectImpl::setIdNull(this);
120
121 // FIXME: now we create style for all objects, but per SVG, only the following can have style attribute:
122 // vg, g, defs, desc, title, symbol, use, image, switch, path, rect, circle, ellipse, line, polyline,
123 // polygon, text, tspan, tref, textPath, altGlyph, glyphRef, marker, linearGradient, radialGradient,
124 // stop, pattern, clipPath, mask, filter, feImage, a, font, glyph, missing-glyph, foreignObject
125 style = new SPStyle(nullptr, this);
126 context_style = nullptr;
127}
128
133{
134 g_free(this->_label);
135 g_free(this->_default_label);
136
137 if (this->_successor) {
138 sp_object_unref(this->_successor, nullptr);
139 this->_successor = nullptr;
140 }
141 if (this->_tmpsuccessor) {
142 sp_object_unref(this->_tmpsuccessor, nullptr);
143 this->_tmpsuccessor = nullptr;
144 }
145 if (parent) {
146 parent->children.erase(parent->children.iterator_to(*this));
147 }
148
149 delete style;
150 this->document = nullptr;
151 this->repr = nullptr;
152}
153
154// CPPIFY: make pure virtual
156 //throw;
157}
158
159void SPObject::update(SPCtx* /*ctx*/, unsigned int /*flags*/) {
160 //throw;
161}
162
163void SPObject::modified(unsigned int /*flags*/) {
164#ifdef OBJECT_TRACE
165 objectTrace( "SPObject::modified (default) (empty function)" );
166 objectTrace( "SPObject::modified (default)", false );
167#endif
168 //throw;
169}
170
171namespace {
172
173namespace Debug = Inkscape::Debug;
174namespace Util = Inkscape::Util;
175
176typedef Debug::SimpleEvent<Debug::Event::REFCOUNT> BaseRefCountEvent;
177
178class RefCountEvent : public BaseRefCountEvent {
179public:
180 RefCountEvent(SPObject *object, int bias, char const *name)
181 : BaseRefCountEvent(name)
182 {
183 _addProperty("object", Util::format("%p", object).pointer());
184 _addProperty("class", Debug::demangle(typeid(*object).name()));
185 _addProperty("new-refcount", Util::format("%d", object->refCount + bias).pointer());
186 }
187};
188
189class RefEvent : public RefCountEvent {
190public:
191 RefEvent(SPObject *object)
192 : RefCountEvent(object, 1, "sp-object-ref")
193 {}
194};
195
196class UnrefEvent : public RefCountEvent {
197public:
198 UnrefEvent(SPObject *object)
199 : RefCountEvent(object, -1, "sp-object-unref")
200 {}
201};
202
203} // namespace
204
205gchar const* SPObject::getId() const {
206 return id;
207}
208
212void SPObject::getIds(std::set<std::string> &ret) const {
213 if (id) {
214 ret.insert(std::string(id));
215 }
216 for (auto &child : children) {
217 child.getIds(ret);
218 }
219}
220
224std::string SPObject::getUrl() const {
225 if (id) {
226 return std::string("url(#") + id + ")";
227 }
228 return "";
229}
230
234
236 return repr;
237}
238
239
241{
242 g_return_val_if_fail(object != nullptr, NULL);
243
245
246 object->refCount++;
247
248 return object;
249}
250
252{
253 g_return_val_if_fail(object != nullptr, NULL);
254
256
257 object->refCount--;
258
259 if (object->refCount <= 0) {
260 delete object;
261 }
262
263 return nullptr;
264}
265
267{
268 // if (owner) std::cout << " owner: " << *owner << std::endl;
269
270 // If owner is a clone, do not increase hrefcount, it's already href'ed by original.
271 if (!owner || !owner->cloned) {
272 hrefcount++;
274 }
275
276 if(owner)
277 hrefList.push_front(owner);
278}
279
281{
282 if (!owner || !owner->cloned) {
283 g_return_if_fail(hrefcount > 0);
284
285 hrefcount--;
287 }
288
289 if(owner)
290 hrefList.remove(owner);
291}
292
294 SPObject *topmost_collectable = nullptr;
295 for ( SPObject *iter = this ; iter ; iter = iter->parent ) {
296 iter->_total_hrefcount += increment;
297 if ( iter->_total_hrefcount < iter->hrefcount ) {
298 g_critical("HRefs overcounted");
299 }
300 if ( iter->_total_hrefcount == 0 &&
301 iter->_collection_policy != COLLECT_WITH_PARENT )
302 {
303 topmost_collectable = iter;
304 }
305 }
306 if (topmost_collectable) {
307 topmost_collectable->requestOrphanCollection();
308 }
309}
310
311void SPObject::getLinked(std::vector<SPObject *> &objects, LinkedObjectNature direction) const
312{
313 if (direction == LinkedObjectNature::ANY || direction == LinkedObjectNature::DEPENDENT) {
314 // href list is all back links
315 for (auto linked : hrefList) {
316 objects.push_back(linked);
317 }
318 }
319}
320
321bool SPObject::isAncestorOf(SPObject const *object) const
322{
323 g_return_val_if_fail(object != nullptr, false);
324 object = object->parent;
325 while (object) {
326 if ( object == this ) {
327 return true;
328 }
329 object = object->parent;
330 }
331 return false;
332}
333
335 g_return_val_if_fail(object != nullptr, NULL);
336
338 return nearest_common_ancestor<SPObject::ConstParentIterator>(this, object, nullptr);
339}
340
341static SPObject const *AncestorSon(SPObject const *obj, SPObject const *ancestor) {
342 SPObject const *result = nullptr;
343 if ( obj && ancestor ) {
344 if (obj->parent == ancestor) {
345 result = obj;
346 } else {
347 result = AncestorSon(obj->parent, ancestor);
348 }
349 }
350 return result;
351}
352
353int sp_object_compare_position(SPObject const *first, SPObject const *second)
354{
355 int result = 0;
356 if (first != second) {
357 SPObject const *ancestor = first->nearestCommonAncestor(second);
358 // Need a common ancestor to be able to compare
359 if ( ancestor ) {
360 // we have an object and its ancestor (should not happen when sorting selection)
361 if (ancestor == first) {
362 result = 1;
363 } else if (ancestor == second) {
364 result = -1;
365 } else {
366 SPObject const *to_first = AncestorSon(first, ancestor);
367 SPObject const *to_second = AncestorSon(second, ancestor);
368
369 g_assert(to_second->parent == to_first->parent);
370
371 result = sp_repr_compare_position(to_first->getRepr(), to_second->getRepr());
372 }
373 }
374 }
375 return result;
376}
377
378bool sp_object_compare_position_bool(SPObject const *first, SPObject const *second){
379 return sp_object_compare_position(first,second)<0;
380}
381
383 if ( !cloned ) {
386 } else {
387 g_critical("Attempt to append repr as child of cloned object");
388 return nullptr;
389 }
390}
391
392void SPObject::setCSS(SPCSSAttr *css, gchar const *attr)
393{
394 g_assert(this->getRepr() != nullptr);
395 sp_repr_css_set(this->getRepr(), css, attr);
396}
397
398void SPObject::changeCSS(SPCSSAttr *css, gchar const *attr)
399{
400 g_assert(this->getRepr() != nullptr);
401 sp_repr_css_change(this->getRepr(), css, attr);
402}
403
404std::vector<SPObject*> SPObject::childList(bool add_ref, Action) {
405 std::vector<SPObject*> l;
406 for (auto& child: children) {
407 if (add_ref) {
409 }
410 l.push_back(&child);
411 }
412 return l;
413}
414
415std::vector<SPObject*> SPObject::ancestorList(bool root_to_tip)
416{
417 std::vector<SPObject *> ancestors;
418 for (SPObject::ParentIterator iter=parent ; iter ; ++iter) {
419 ancestors.push_back(iter);
420 }
421 if (root_to_tip) {
422 std::reverse(ancestors.begin(), ancestors.end());
423 }
424 return ancestors;
425}
426
427gchar const *SPObject::label() const {
428 return _label;
429}
430
431gchar const *SPObject::defaultLabel() const {
432 if (_label) {
433 return _label;
434 } else {
435 if (!_default_label) {
436 if (getId()) {
437 _default_label = g_strdup_printf("#%s", getId());
438 } else if (getRepr()) {
439 _default_label = g_strdup_printf("<%s>", getRepr()->name());
440 } else {
441 _default_label = g_strdup("Default label");
442 }
443 }
444 return _default_label;
445 }
446}
447
448void SPObject::setLabel(gchar const *label)
449{
450 getRepr()->setAttribute("inkscape:label", label);
451 // Update anything that's watching the object's label
452 _modified_signal.emit(this, SP_OBJECT_MODIFIED_FLAG);
453}
454
455
457 g_return_if_fail(document != nullptr);
459
460 // do not remove style or script elements (Bug #276244)
461 if (is<SPStyleElem>(this)) {
462 // leave it
463 } else if (is<SPScript>(this)) {
464 // leave it
465 } else if (is<SPFont>(this)) {
466 // leave it
467 } else if (!prefs->getBool("/options/cleanupswatches/value", false) && is<SPPaintServer>(this) && static_cast<SPPaintServer*>(this)->isSwatch()) {
468 // leave it
469 } else if (is<Inkscape::ColorProfile>(this)) {
470 // leave it
471 } else if (is<LivePathEffectObject>(this)) {
473 } else {
475
486 this->requestModified(SP_OBJECT_CHILD_MODIFIED_FLAG);
487 }
488}
489
491 for (auto& child: children) {
492 child._delete_signal.emit(&child);
493 child._sendDeleteSignalRecursive();
494 }
495}
496
497void SPObject::deleteObject(bool propagate, bool propagate_descendants)
498{
499 sp_object_ref(this, nullptr);
500 if (is<SPLPEItem>(this)) {
501 cast<SPLPEItem>(this)->removeAllPathEffects(false, propagate_descendants);
502 }
503 if (propagate) {
504 _delete_signal.emit(this);
505 }
506 if (propagate_descendants) {
508 }
509
511 if (repr && repr->parent()) {
513 }
514
515 if (_successor) {
516 _successor->deleteObject(propagate, propagate_descendants);
517 }
518 sp_object_unref(this, nullptr);
519}
520
522{
523 std::vector<SPObject *> toDelete;
524 for (auto &child : children) {
525 if (is<SPItem>(&child)) {
526 if (child.isAncestorOf(except)) {
527 child.cropToObject(except);
528 } else if (&child != except) {
529 sp_object_ref(&child, nullptr);
530 toDelete.push_back(&child);
531 }
532 }
533 }
534 for (auto &i : toDelete) {
535 i->deleteObject(true, true);
536 sp_object_unref(i, nullptr);
537 }
538}
539
551void SPObject::cropToObjects(std::vector<SPObject *> except_objects)
552{
553 if (except_objects.empty()) {
554 return;
555 }
556 std::vector<SPObject *> links;
557 std::vector<SPObject *> toDelete;
558
559 // Get a list of all forward links so we don't delete them
560 for (auto item : except_objects) {
562 }
563
564 except_objects.insert(except_objects.end(), links.begin(), links.end());
565
566 // Collect a list of objects we expect to delete.
567 getObjectsExcept(toDelete, except_objects);
568
569 for (auto &i : toDelete) {
570 // Don't propergate the delete signal as we may delete clones later
571 i->deleteObject(false, false);
572 }
573}
574
575void SPObject::getObjectsExcept(std::vector<SPObject *> &objects, const std::vector<SPObject *> &excepts)
576{
577 for (auto &child : children) {
578 if (is<SPItem>(&child)) {
579 int child_flag = 1;
580 for (auto except : excepts) {
581 if (&child == except) {
582 child_flag = 0;
583 break;
584 }
585 if (child.isAncestorOf(except)) {
586 child_flag = 2;
587 }
588 }
589 if (child_flag == 1) {
590 objects.push_back(&child);
591 } else if (child_flag == 2) {
592 child.getObjectsExcept(objects, excepts);
593 }
594 }
595 }
596}
597
598void SPObject::getLinkedRecursive(std::vector<SPObject *> &objects, LinkedObjectNature direction) const
599{
600 // Recurse through multiple links
601 for (auto link : getLinked(direction)) {
602 // Make sure we never recurse objects multiple times.
603 if (std::find(objects.begin(), objects.end(), link) == objects.end()) {
604 objects.push_back(link);
605 link->getLinkedRecursive(objects, direction);
606 }
607 }
608
609 for (auto &child : children) {
610 if (is<SPItem>(&child)) {
611 // Recurse through all ancestors
612 child.getLinkedRecursive(objects, direction);
613 }
614 }
615}
616
618{
619 g_return_if_fail(object != nullptr);
620 g_return_if_fail(!prev || prev->parent == this);
621 g_return_if_fail(!object->parent);
622
623 sp_object_ref(object, this);
624 object->parent = this;
626
627 auto it = children.begin();
628 if (prev != nullptr) {
629 it = ++children.iterator_to(*prev);
630 }
631 children.insert(it, *object);
632
633 if (!object->xml_space.set)
634 object->xml_space.value = this->xml_space.value;
635}
636
638 g_return_if_fail(obj != nullptr);
639 g_return_if_fail(obj->parent);
640 g_return_if_fail(obj->parent == this);
641 g_return_if_fail(obj != prev);
642 g_return_if_fail(!prev || prev->parent == obj->parent);
643
644 auto it = children.begin();
645 if (prev != nullptr) {
646 it = ++children.iterator_to(*prev);
647 }
648
649 children.splice(it, children, children.iterator_to(*obj));
650}
651
653{
654 g_return_if_fail(object != nullptr);
655 g_return_if_fail(object->parent == this);
656
657 children.erase(children.iterator_to(*object));
658 object->releaseReferences();
659
660 object->parent = nullptr;
661
663 sp_object_unref(object, this);
664}
665
667{
668 g_return_val_if_fail(repr != nullptr, NULL);
669 SPObject *result = nullptr;
670
671 if (children.size() > 0 && children.back().getRepr() == repr) {
672 result = &children.back(); // optimization for common scenario
673 } else {
674 for (auto& child: children) {
675 if (child.getRepr() == repr) {
676 result = &child;
677 break;
678 }
679 }
680 }
681 return result;
682}
683
693{
694 for (; ref; ref = ref->prev()) {
695 // The most likely situation is that `ref` is indeed a child of `obj`,
696 // so try that first, before checking getObjectByRepr.
697 if (auto result = obj.get_child_by_repr(ref)) {
698 return result;
699 }
700
701 // Only continue if `ref` is not an SPObject, but e.g. an XML comment
702 if (obj.document->getObjectByRepr(ref)) {
703 break;
704 }
705 }
706
707 return nullptr;
708}
709
711 SPObject* object = this;
712
713 const std::string type_string = NodeTraits::get_type_string(*child);
714
715 SPObject* ochild = SPFactory::createObject(type_string);
716 if (ochild == nullptr) {
717 // Currently, there are many node types that do not have
718 // corresponding classes in the SPObject tree.
719 // (rdf:RDF, inkscape:clipboard, ...)
720 // Thus, simply ignore this case for now.
721 return;
722 }
723
724 SPObject *prev = get_closest_child_by_repr(*object, ref);
725 object->attach(ochild, prev);
726 sp_object_unref(ochild, nullptr);
727
728 ochild->invoke_build(object->document, child, object->cloned);
729}
730
732 SPObject* object = this;
733 debug("id=%p, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
734
735 style->filter.clear();
736 style->fill.href.reset();
737 style->stroke.href.reset();
738 style->shape_inside.clear();
739 style->shape_subtract.clear();
740
741 auto tmp = children | boost::adaptors::transformed([](SPObject& obj){return &obj;});
742 std::vector<SPObject *> toRelease(tmp.begin(), tmp.end());
743
744 for (auto& p: toRelease) {
745 object->detach(p);
746 }
747}
748
750 debug("id=%p, typename=%s", this, g_type_name_from_instance((GTypeInstance*)this));
751
752 SPObject *ochild = this->get_child_by_repr(child);
753
754 // If the xml node has got a corresponding child in the object tree
755 if (ochild) {
756 this->detach(ochild);
757 }
758}
759
761 SPObject* object = this;
762
763 SPObject *ochild = object->get_child_by_repr(child);
764 g_return_if_fail(ochild != nullptr);
765 SPObject *prev = get_closest_child_by_repr(*object, new_ref);
766 object->reorder(ochild, prev);
767 ochild->_position_changed_signal.emit(ochild);
768}
769
770void SPObject::tag_name_changed(gchar const* oldname, gchar const* newname) {
771 g_warning("XML Element renamed from %s to %s!", oldname, newname);
772}
773
775
776#ifdef OBJECT_TRACE
777 objectTrace( "SPObject::build" );
778#endif
779 SPObject* object = this;
780
781 /* Nothing specific here */
782 debug("id=%p, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
783
784 object->readAttr(SPAttr::XML_SPACE);
785 object->readAttr(SPAttr::LANG);
786 object->readAttr(SPAttr::XML_LANG); // "xml:lang" overrides "lang" per spec, read it last.
787 object->readAttr(SPAttr::INKSCAPE_LABEL);
788 object->readAttr(SPAttr::INKSCAPE_COLLECT);
789
790 // Inherit if not set
791 if (lang.empty() && object->parent) {
792 lang = object->parent->lang;
793 }
794
795 if(object->cloned && (repr->attribute("id")) ) // The cases where this happens are when the "original" has no id. This happens
796 // if it is a SPString (a TextNode, e.g. in a <title>), or when importing
797 // stuff externally modified to have no id.
798 object->clone_original = document->getObjectById(repr->attribute("id"));
799
800 for (Inkscape::XML::Node *rchild = repr->firstChild() ; rchild != nullptr; rchild = rchild->next()) {
801 const std::string typeString = NodeTraits::get_type_string(*rchild);
802
804 if (child == nullptr) {
805 // Currently, there are many node types that do not have
806 // corresponding classes in the SPObject tree.
807 // (rdf:RDF, inkscape:clipboard, ...)
808 // Thus, simply ignore this case for now.
809 continue;
810 }
811
812 object->attach(child, object->lastChild());
813 sp_object_unref(child, nullptr);
814 child->invoke_build(document, rchild, object->cloned);
815 }
816
817#ifdef OBJECT_TRACE
818 objectTrace( "SPObject::build", false );
819#endif
820}
821
822void SPObject::invoke_build(SPDocument *document, Inkscape::XML::Node *repr, unsigned int cloned)
823{
824#ifdef OBJECT_TRACE
825 objectTrace( "SPObject::invoke_build" );
826#endif
827 debug("id=%p, typename=%s", this, g_type_name_from_instance((GTypeInstance*)this));
828
829 g_assert(document != nullptr);
830 g_assert(repr != nullptr);
831
832 g_assert(this->document == nullptr);
833 g_assert(this->repr == nullptr);
834 g_assert(this->getId() == nullptr);
835
836 /* Bookkeeping */
837
838 this->document = document;
839 this->repr = repr;
840 if (!cloned) {
842 }
843 this->cloned = cloned;
844
845 /* Invoke derived methods, if any */
846 this->build(document, repr);
847
848 if ( !cloned ) {
849 this->document->bindObjectToRepr(this->repr, this);
850
851 if (Inkscape::XML::id_permitted(this->repr)) {
852 /* If we are not cloned, and not seeking, force unique id */
853 gchar const *id = this->repr->attribute("id");
854 if (!document->isSeeking()) {
855 auto realid = generate_unique_id(id);
856 this->document->bindObjectToId(realid.c_str(), this);
857 SPObjectImpl::setId(this, realid.c_str());
858
859 /* Redefine ID, if required */
860 if (!id || std::strcmp(id, getId()) != 0) {
861 this->repr->setAttribute("id", getId());
862 }
863 } else if (id) {
864 // bind if id, but no conflict -- otherwise, we can expect
865 // a subsequent setting of the id attribute
866 if (!this->document->getObjectById(id)) {
867 this->document->bindObjectToId(id, this);
868 SPObjectImpl::setId(this, id);
869 }
870 }
871 }
872 } else {
873 g_assert(this->getId() == nullptr);
874 }
875
876 this->document->process_pending_resource_changes();
877
878 /* Signalling (should be connected AFTER processing derived methods */
879 repr->addObserver(*this);
880
881#ifdef OBJECT_TRACE
882 objectTrace( "SPObject::invoke_build", false );
883#endif
884}
885
886int SPObject::getIntAttribute(char const *key, int def)
887{
888 return getRepr()->getAttributeInt(key, def);
889}
890
892 g_assert(this->repr);
893
894 return repr->position();
895}
896
898 g_assert(this->repr);
899
900 repr->appendChild(child);
901}
902
904 g_assert(this->repr);
905 if (hasChildren()) {
906 std::vector<SPObject*> l;
907 unsigned counter = 0;
908 for (auto& child: children) {
909 if (counter == index) {
910 return &child;
911 }
912 counter++;
913 }
914 }
915 return nullptr;
916}
917
919{
920 g_assert(this->repr);
921
922 repr->addChild(child,prev);
923}
924
926 g_assert(this->document);
927 g_assert(this->repr);
928 g_assert(cloned || repr->_anchored_refcount() > 0);
929
930 repr->removeObserver(*this);
931
932 this->_release_signal.emit(this);
933
934 this->release();
935
936 /* all hrefs should be released by the "release" handlers */
937 g_assert(this->hrefcount == 0);
938
939 if (!cloned) {
940 if (this->id) {
941 this->document->bindObjectToId(this->id, nullptr);
942 }
943 g_free(this->id);
944 this->id = nullptr;
945
946 g_free(this->_default_label);
947 this->_default_label = nullptr;
948
949 this->document->bindObjectToRepr(this->repr, nullptr);
950
952 } else {
953 g_assert(!this->id);
954 }
955
956 this->document = nullptr;
957 this->repr = nullptr;
958}
959
961{
962 SPObject *prev = nullptr;
963 if (parent && !parent->children.empty() && &parent->children.front() != this) {
964 prev = &*(--parent->children.iterator_to(*this));
965 }
966 return prev;
967}
968
970{
971 SPObject *next = nullptr;
972 if (parent && !parent->children.empty() && &parent->children.back() != this) {
973 next = &*(++parent->children.iterator_to(*this));
974 }
975 return next;
976}
977
982
987
993
995{
996 auto const oldname = g_quark_to_string(old_name);
997 auto const newname = g_quark_to_string(new_name);
998
999 tag_name_changed(oldname, newname);
1000}
1001
1002void SPObject::set(SPAttr key, gchar const* value) {
1003#ifdef OBJECT_TRACE
1004 std::stringstream temp;
1005 temp << "SPObject::set: " << sp_attribute_name(key) << " " << (value?value:"null");
1006 objectTrace( temp.str() );
1007#endif
1008
1009 g_assert(key != SPAttr::INVALID);
1010
1011 SPObject* object = this;
1012
1013 switch (key) {
1014
1015 case SPAttr::ID:
1016
1017 //XML Tree being used here.
1018 if ( !object->cloned && object->getRepr()->type() == Inkscape::XML::NodeType::ELEMENT_NODE ) {
1019 SPDocument *document=object->document;
1020 SPObject *conflict=nullptr;
1021
1022 gchar const *new_id = value;
1023
1024 if (new_id) {
1025 conflict = document->getObjectById((char const *)new_id);
1026 }
1027
1028 if ( conflict && conflict != object ) {
1029 if (!document->isSeeking()) {
1030 sp_object_ref(conflict, nullptr);
1031 // give the conflicting object a new ID
1032 auto new_conflict_id = conflict->generate_unique_id();
1033 conflict->setAttribute("id", new_conflict_id);
1034 sp_object_unref(conflict, nullptr);
1035 } else {
1036 new_id = nullptr;
1037 }
1038 }
1039
1040 if (object->getId()) {
1041 document->bindObjectToId(object->getId(), nullptr);
1042 SPObjectImpl::setId(object, nullptr);
1043 }
1044
1045 if (new_id) {
1046 SPObjectImpl::setId(object, new_id);
1047 document->bindObjectToId(object->getId(), object);
1048 }
1049
1050 g_free(object->_default_label);
1051 object->_default_label = nullptr;
1052 }
1053 break;
1054
1056 g_free(object->_label);
1057 if (value) {
1058 object->_label = g_strdup(value);
1059 } else {
1060 object->_label = nullptr;
1061 }
1062 g_free(object->_default_label);
1063 object->_default_label = nullptr;
1064 break;
1065
1067 if ( value && !std::strcmp(value, "always") ) {
1068 object->setCollectionPolicy(SPObject::ALWAYS_COLLECT);
1069 } else {
1070 object->setCollectionPolicy(SPObject::COLLECT_WITH_PARENT);
1071 }
1072 break;
1073
1074 case SPAttr::XML_SPACE:
1075 if (value && !std::strcmp(value, "preserve")) {
1076 object->xml_space.value = SP_XML_SPACE_PRESERVE;
1077 object->xml_space.set = TRUE;
1078 } else if (value && !std::strcmp(value, "default")) {
1079 object->xml_space.value = SP_XML_SPACE_DEFAULT;
1080 object->xml_space.set = TRUE;
1081 } else if (object->parent) {
1083 parent = object->parent;
1085 }
1086 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1087 break;
1088
1089 case SPAttr::LANG:
1090 if (value) {
1091 lang = value;
1092 // To do: sanity check
1093 }
1094 break;
1095
1096 case SPAttr::XML_LANG:
1097 if (value) {
1098 lang = value;
1099 // To do: sanity check
1100 }
1101 break;
1102
1103 case SPAttr::STYLE:
1104 object->style->readFromObject( object );
1105 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1106 break;
1107
1108 default:
1109 break;
1110 }
1111
1112#ifdef OBJECT_TRACE
1113 objectTrace( "SPObject::set", false );
1114#endif
1115}
1116
1117void SPObject::setKeyValue(SPAttr key, gchar const *value)
1118{
1119 this->set(key, value);
1120}
1121
1123{
1124 if (keyid == SPAttr::XLINK_HREF) {
1125 auto value = Inkscape::getHrefAttribute(*getRepr()).second;
1126 setKeyValue(keyid, value);
1127 return;
1128 }
1129
1130 char const *key = sp_attribute_name(keyid);
1131
1132 assert(key != nullptr);
1133 assert(getRepr() != nullptr);
1134
1135 char const *value = getRepr()->attribute(key);
1136
1137 setKeyValue(keyid, value);
1138}
1139
1140void SPObject::readAttr(gchar const *key)
1141{
1142 g_assert(key != nullptr);
1143
1144 //XML Tree being used here.
1145 g_assert(this->getRepr() != nullptr);
1146
1147 auto keyid = sp_attribute_lookup(key);
1148 if (keyid != SPAttr::INVALID) {
1149 /* Retrieve the 'key' attribute from the object's XML representation */
1150 gchar const *value = getRepr()->attribute(key);
1151
1152 setKeyValue(keyid, value);
1153 }
1154}
1155
1157{
1158 auto const key = g_quark_to_string(key_);
1159 readAttr(key);
1160}
1161
1166
1170static gchar const *sp_xml_get_space_string(unsigned int space)
1171{
1172 switch (space) {
1174 return "default";
1176 return "preserve";
1177 default:
1178 return nullptr;
1179 }
1180}
1181
1183#ifdef OBJECT_TRACE
1184 objectTrace( "SPObject::write" );
1185#endif
1186
1187 if (!repr && (flags & SP_OBJECT_WRITE_BUILD)) {
1188 repr = this->getRepr()->duplicate(doc);
1189 if (!( flags & SP_OBJECT_WRITE_EXT )) {
1190 repr->removeAttribute("inkscape:collect");
1191 }
1192 } else if (repr) {
1193 repr->setAttribute("id", this->getId());
1194
1195 if (this->xml_space.set) {
1196 char const *xml_space;
1197 xml_space = sp_xml_get_space_string(this->xml_space.value);
1198 repr->setAttribute("xml:space", xml_space);
1199 }
1200
1201 if ( flags & SP_OBJECT_WRITE_EXT &&
1203 {
1204 repr->setAttribute("inkscape:collect", "always");
1205 } else {
1206 repr->removeAttribute("inkscape:collect");
1207 }
1208
1209 if (style) {
1210 // Write if property set by style attribute in this object
1211 Glib::ustring style_prop = style->write(SPStyleSrc::STYLE_PROP);
1212
1213 // Write style attributes (SPStyleSrc::ATTRIBUTE) back to xml object
1214 bool any_written = false;
1215 auto properties = style->properties();
1216 for (auto * prop : properties) {
1218 // WARNING: We don't know for sure if the css names are the same as the attribute names
1219 auto val = repr->attribute(prop->name().c_str());
1220 auto new_val = prop->get_value();
1221 if (new_val.empty() && !val || new_val != val) {
1222 repr->setAttributeOrRemoveIfEmpty(prop->name(), new_val);
1223 any_written = true;
1224 }
1225 }
1226 }
1227 if(any_written) {
1228 // We need to ask the object to update the style and keep things in sync
1229 // see `case SPAttr::STYLE` above for how the style attr itself does this.
1230 style->readFromObject(this);
1231 requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1232 }
1233
1234 // Check for valid attributes. This may be time consuming.
1235 // It is useful, though, for debugging Inkscape code.
1237 if( prefs->getBool("/options/svgoutput/check_on_editing") ) {
1238 // Check styles only if it is an element node
1240 unsigned int flags = sp_attribute_clean_get_prefs();
1241 style_prop = sp_attribute_clean_style(repr, style_prop.c_str(), flags);
1242 }
1243 }
1244
1245 repr->setAttributeOrRemoveIfEmpty("style", style_prop);
1246 } else {
1254 char const *style_str = repr->attribute("style");
1255 if (!style_str) {
1256 style_str = "NULL";
1257 }
1258 g_warning("Item's style is NULL; repr style attribute is %s", style_str);
1259 }
1260 }
1261
1262#ifdef OBJECT_TRACE
1263 objectTrace( "SPObject::write", false );
1264#endif
1265 return repr;
1266}
1267
1272void
1274 assert(tmpsuccessor != NULL);
1275 assert(_tmpsuccessor == NULL);
1276 assert(tmpsuccessor->_tmpsuccessor == NULL);
1277 sp_object_ref(tmpsuccessor, nullptr);
1278 _tmpsuccessor = tmpsuccessor;
1279 if (repr) {
1280 char const *linked_fill_id = getAttribute("inkscape:linked-fill");
1281 if (linked_fill_id && document) {
1282 SPObject *lfill = document->getObjectById(linked_fill_id);
1283 if (lfill && lfill->_tmpsuccessor) {
1284 lfill->_tmpsuccessor->setAttribute("inkscape:linked-fill",lfill->_tmpsuccessor->getId());
1285 }
1286 }
1287
1288 if (children.size() == _tmpsuccessor->children.size()) {
1289 for (auto &obj : children) {
1290 auto tmpsuccessorchild = _tmpsuccessor->nthChild(obj.getPosition());
1291 if (tmpsuccessorchild && !obj._tmpsuccessor) {
1292 obj.setTmpSuccessor(tmpsuccessorchild);
1293 }
1294 }
1295 }
1296 }
1297}
1298
1302void
1304 for (auto &obj : children) {
1305 obj.fixTmpSuccessors();
1306 }
1307 if (_tmpsuccessor) {
1308 char const *linked_fill_id = getAttribute("inkscape:linked-fill");
1309 if (linked_fill_id && document) {
1310 SPObject *lfill = document->getObjectById(linked_fill_id);
1311 if (lfill && lfill->_tmpsuccessor) {
1312 _tmpsuccessor->setAttribute("inkscape:linked-fill", lfill->_tmpsuccessor->getId());
1313 }
1314 }
1315 }
1316}
1317
1318void
1320 for (auto &object : children) {
1321 object.unsetTmpSuccessor();
1322 }
1323 if (_tmpsuccessor) {
1325 _tmpsuccessor = nullptr;
1326 }
1327}
1328
1333 auto group = cast<SPGroup>(parent);
1334 if (group && group->layerMode() != SPGroup::LAYER) {
1335 return group->getTopAncestorNonLayer();
1336 } else {
1337 return this;
1338 }
1339};
1340
1341
1343{
1344#ifdef OBJECT_TRACE
1345 objectTrace( "SPObject::updateRepr 1" );
1346#endif
1347
1348 if ( !cloned ) {
1350 if (repr) {
1351#ifdef OBJECT_TRACE
1352 objectTrace( "SPObject::updateRepr 1", false );
1353#endif
1354 return updateRepr(repr->document(), repr, flags);
1355 } else {
1356 g_critical("Attempt to update non-existent repr");
1357#ifdef OBJECT_TRACE
1358 objectTrace( "SPObject::updateRepr 1", false );
1359#endif
1360 return nullptr;
1361 }
1362 } else {
1363 /* cloned objects have no repr */
1364#ifdef OBJECT_TRACE
1365 objectTrace( "SPObject::updateRepr 1", false );
1366#endif
1367 return nullptr;
1368 }
1369}
1370
1372{
1373#ifdef OBJECT_TRACE
1374 objectTrace( "SPObject::updateRepr 2" );
1375#endif
1376
1377 g_assert(doc != nullptr);
1378
1379 if (cloned) {
1380 /* cloned objects have no repr */
1381#ifdef OBJECT_TRACE
1382 objectTrace( "SPObject::updateRepr 2", false );
1383#endif
1384 return nullptr;
1385 }
1386
1387 if (!(flags & SP_OBJECT_WRITE_BUILD) && !repr) {
1388 repr = getRepr();
1389 }
1390
1391#ifdef OBJECT_TRACE
1392 Inkscape::XML::Node *node = write(doc, repr, flags);
1393 objectTrace( "SPObject::updateRepr 2", false );
1394 return node;
1395#else
1396 return this->write(doc, repr, flags);
1397#endif
1398
1399}
1400
1401/* Modification */
1402
1403void SPObject::requestDisplayUpdate(unsigned int flags)
1404{
1405 g_return_if_fail( this->document != nullptr );
1406
1407#ifndef NDEBUG
1408 // expect no nested update calls
1410 // observed with LPE on <rect>
1411 g_warning("WARNING: Requested update while update in progress, counter = %d", document->update_in_progress);
1412 }
1413#endif
1414
1415 /* requestModified must be used only to set one of SP_OBJECT_MODIFIED_FLAG or
1416 * SP_OBJECT_CHILD_MODIFIED_FLAG */
1417 g_return_if_fail(!(flags & SP_OBJECT_PARENT_MODIFIED_FLAG));
1418 g_return_if_fail((flags & SP_OBJECT_MODIFIED_FLAG) || (flags & SP_OBJECT_CHILD_MODIFIED_FLAG));
1419 g_return_if_fail(!((flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_CHILD_MODIFIED_FLAG)));
1420
1421#ifdef OBJECT_TRACE
1422 objectTrace( "SPObject::requestDisplayUpdate" );
1423#endif
1424
1425 bool already_propagated = (!(this->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG)));
1426 //https://stackoverflow.com/a/7841333
1427 if ((this->uflags & flags) != flags ) {
1428 this->uflags |= flags;
1429 }
1430 /* If requestModified has already been called on this object or one of its children, then we
1431 * don't need to set CHILD_MODIFIED on our ancestors because it's already been done.
1432 */
1433 if (already_propagated) {
1434 if(this->document) {
1435 if (parent) {
1436 parent->requestDisplayUpdate(SP_OBJECT_CHILD_MODIFIED_FLAG);
1437 } else {
1438 this->document->requestModified();
1439 }
1440 }
1441 }
1442
1443#ifdef OBJECT_TRACE
1444 objectTrace( "SPObject::requestDisplayUpdate", false );
1445#endif
1446
1447}
1448
1449void SPObject::updateDisplay(SPCtx *ctx, unsigned int flags)
1450{
1451 g_return_if_fail(!(flags & ~SP_OBJECT_MODIFIED_CASCADE));
1452
1453#ifdef OBJECT_TRACE
1454 objectTrace( "SPObject::updateDisplay" );
1455#endif
1456
1457 assert(++(document->update_in_progress));
1458
1459#ifdef SP_OBJECT_DEBUG_CASCADE
1460 g_print("Update %s:%s %x %x %x\n", g_type_name_from_instance((GTypeInstance *) this), getId(), flags, this->uflags, this->mflags);
1461#endif
1462
1463 /* Get this flags */
1464 flags |= this->uflags;
1465 /* Copy flags to modified cascade for later processing */
1466 this->mflags |= this->uflags;
1467 /* We have to clear flags here to allow rescheduling update */
1468 this->uflags = 0;
1469
1470 // Merge style if we have good reasons to think that parent style is changed */
1476 if (style) {
1478 if ((flags & SP_OBJECT_STYLESHEET_MODIFIED_FLAG)) {
1479 style->readFromObject(this);
1480 } else if (parent && (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) && (flags & SP_OBJECT_PARENT_MODIFIED_FLAG)) {
1481 style->cascade( this->parent->style );
1482 }
1484 }
1485
1486 try
1487 {
1488 this->update(ctx, flags);
1489 }
1490 catch(...)
1491 {
1497 g_warning("SPObject::updateDisplay(SPCtx *ctx, unsigned int flags) : throw in ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update(this, ctx, flags);");
1498 }
1499
1500 assert((document->update_in_progress)--);
1501
1502#ifdef OBJECT_TRACE
1503 objectTrace( "SPObject::updateDisplay", false );
1504#endif
1505}
1506
1507void SPObject::requestModified(unsigned int flags)
1508{
1509 g_return_if_fail( this->document != nullptr );
1510
1511 /* requestModified must be used only to set one of SP_OBJECT_MODIFIED_FLAG or
1512 * SP_OBJECT_CHILD_MODIFIED_FLAG */
1513 g_return_if_fail(!(flags & SP_OBJECT_PARENT_MODIFIED_FLAG));
1514 g_return_if_fail((flags & SP_OBJECT_MODIFIED_FLAG) || (flags & SP_OBJECT_CHILD_MODIFIED_FLAG));
1515 g_return_if_fail(!((flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_CHILD_MODIFIED_FLAG)));
1516
1517#ifdef OBJECT_TRACE
1518 objectTrace( "SPObject::requestModified" );
1519#endif
1520
1521 bool already_propagated = (!(this->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG)));
1522
1523 this->mflags |= flags;
1524
1525 /* If requestModified has already been called on this object or one of its children, then we
1526 * don't need to set CHILD_MODIFIED on our ancestors because it's already been done.
1527 */
1528 if (already_propagated) {
1529 if (parent) {
1530 parent->requestModified(SP_OBJECT_CHILD_MODIFIED_FLAG);
1531 } else {
1533 }
1534 }
1535#ifdef OBJECT_TRACE
1536 objectTrace( "SPObject::requestModified", false );
1537#endif
1538}
1539
1540void SPObject::emitModified(unsigned int flags)
1541{
1542 /* only the MODIFIED_CASCADE flag is legal here */
1543 g_return_if_fail(!(flags & ~SP_OBJECT_MODIFIED_CASCADE));
1544
1545#ifdef OBJECT_TRACE
1546 objectTrace( "SPObject::emitModified", true, flags );
1547#endif
1548
1549#ifdef SP_OBJECT_DEBUG_CASCADE
1550 g_print("Modified %s:%s %x %x %x\n", g_type_name_from_instance((GTypeInstance *) this), getId(), flags, this->uflags, this->mflags);
1551#endif
1552
1553 flags |= this->mflags;
1554 /* We have to clear mflags beforehand, as signal handlers may
1555 * make changes and therefore queue new modification notifications
1556 * themselves. */
1557 this->mflags = 0;
1558
1559 sp_object_ref(this);
1560
1561 this->modified(flags);
1562
1563 _modified_signal.emit(this, flags);
1564 sp_object_unref(this);
1565
1566#ifdef OBJECT_TRACE
1567 objectTrace( "SPObject::emitModified", false );
1568#endif
1569}
1570
1571gchar const *SPObject::getTagName() const
1572{
1573 g_assert(repr != nullptr);
1574
1576 //XML Tree being used here.
1577 return getRepr()->name();
1578}
1579
1580gchar const *SPObject::getAttribute(gchar const *key) const
1581{
1582 g_assert(this->repr != nullptr);
1583
1585 //XML Tree being used here.
1586 return (gchar const *) getRepr()->attribute(key);
1587}
1588
1591{
1592 g_assert(this->repr != nullptr);
1593
1595 //XML Tree being used here.
1596 getRepr()->setAttribute(key, value);
1597}
1598
1601 os << value;
1602 setAttribute(key, os.str());
1603}
1604
1606{
1608 //XML Tree being used here.
1610}
1611
1612bool SPObject::storeAsDouble( gchar const *key, double *val ) const
1613{
1614 g_assert(this->getRepr()!= nullptr);
1615 double nan = std::numeric_limits<double>::quiet_NaN();
1616 double temp_val = ((Inkscape::XML::Node *)(this->getRepr()))->getAttributeDouble(key, nan);
1617 if (std::isnan(temp_val)) {
1618 return false;
1619 }
1620 *val = temp_val;
1621 return true;
1622}
1623
1624std::string SPObject::generate_unique_id(char const *default_id) const
1625{
1626 if (default_id && !document->getObjectById(default_id)) {
1627 return default_id;
1628 }
1629
1630 //XML Tree being used here.
1631 auto name = repr->name();
1632 g_assert(name);
1633
1634 if (auto local = std::strchr(name, ':')) {
1635 name = local + 1;
1636 }
1637
1639}
1640
1642 for (SPObject::ParentIterator iter = this; iter; ++iter) {
1643 if (auto root = cast<SPRoot>(static_cast<SPObject *>(iter))) {
1644 if (root->svg.getVersion() < version) {
1645 root->svg.version = version;
1646 }
1647 }
1648 }
1649}
1650
1651// Titles and descriptions
1652
1653/* Note:
1654 Titles and descriptions are stored in 'title' and 'desc' child elements
1655 (see section 5.4 of the SVG 1.0 and 1.1 specifications). The spec allows
1656 an element to have more than one 'title' child element, but strongly
1657 recommends against this and requires using the first one if a choice must
1658 be made. The same applies to 'desc' elements. Therefore, these functions
1659 ignore all but the first 'title' child element and first 'desc' child
1660 element, except when deleting a title or description.
1661
1662 This will change in SVG 2, where multiple 'title' and 'desc' elements will
1663 be allowed with different localized strings.
1664*/
1665
1666gchar * SPObject::title() const
1667{
1668 return getTitleOrDesc("svg:title");
1669}
1670
1671bool SPObject::setTitle(gchar const *title, bool verbatim)
1672{
1673 return setTitleOrDesc(title, "svg:title", verbatim);
1674}
1675
1676gchar * SPObject::desc() const
1677{
1678 return getTitleOrDesc("svg:desc");
1679}
1680
1681bool SPObject::setDesc(gchar const *desc, bool verbatim)
1682{
1683 return setTitleOrDesc(desc, "svg:desc", verbatim);
1684}
1685
1686char * SPObject::getTitleOrDesc(gchar const *svg_tagname) const
1687{
1688 char *result = nullptr;
1689 SPObject *elem = findFirstChild(svg_tagname);
1690 if ( elem ) {
1691 //This string copy could be avoided by changing
1692 //the return type of SPObject::getTitleOrDesc
1693 //to std::unique_ptr<Glib::ustring>
1694 result = g_strdup(elem->textualContent().c_str());
1695 }
1696 return result;
1697}
1698
1699bool SPObject::setTitleOrDesc(gchar const *value, gchar const *svg_tagname, bool verbatim)
1700{
1701 if (!verbatim) {
1702 // If the new title/description is just whitespace,
1703 // treat it as though it were NULL.
1704 if (value) {
1705 bool just_whitespace = true;
1706 for (const gchar *cp = value; *cp; ++cp) {
1707 if (!std::strchr("\r\n \t", *cp)) {
1708 just_whitespace = false;
1709 break;
1710 }
1711 }
1712 if (just_whitespace) {
1713 value = nullptr;
1714 }
1715 }
1716 // Don't stomp on mark-up if there is no real change.
1717 if (value) {
1718 gchar *current_value = getTitleOrDesc(svg_tagname);
1719 if (current_value) {
1720 bool different = std::strcmp(current_value, value);
1721 g_free(current_value);
1722 if (!different) {
1723 return false;
1724 }
1725 }
1726 }
1727 }
1728
1729 SPObject *elem = findFirstChild(svg_tagname);
1730
1731 if (value == nullptr) {
1732 if (elem == nullptr) {
1733 return false;
1734 }
1735 // delete the title/description(s)
1736 while (elem) {
1737 elem->deleteObject();
1738 elem = findFirstChild(svg_tagname);
1739 }
1740 return true;
1741 }
1742
1744
1745 if (elem == nullptr) {
1746 // create a new 'title' or 'desc' element, putting it at the
1747 // beginning (in accordance with the spec's recommendations)
1748 Inkscape::XML::Node *xml_elem = xml_doc->createElement(svg_tagname);
1749 repr->addChild(xml_elem, nullptr);
1750 elem = document->getObjectByRepr(xml_elem);
1751 Inkscape::GC::release(xml_elem);
1752 }
1753 else {
1754 // remove the current content of the 'text' or 'desc' element
1755 auto tmp = elem->children | boost::adaptors::transformed([](SPObject& obj) { return &obj; });
1756 std::vector<SPObject*> vec(tmp.begin(), tmp.end());
1757 for (auto &child: vec) {
1758 child->deleteObject();
1759 }
1760 }
1761
1762 // add the new content
1763 elem->appendChildRepr(xml_doc->createTextNode(value));
1764 return true;
1765}
1766
1767SPObject* SPObject::findFirstChild(gchar const *tagname) const
1768{
1769 for (auto& child: const_cast<SPObject*>(this)->children)
1770 {
1771 if (child.repr->type() == Inkscape::XML::NodeType::ELEMENT_NODE &&
1772 !std::strcmp(child.repr->name(), tagname)) {
1773 return &child;
1774 }
1775 }
1776 return nullptr;
1777}
1778
1779Glib::ustring SPObject::textualContent() const
1780{
1781 Glib::ustring text;
1782
1783 for (auto& child: children)
1784 {
1785 Inkscape::XML::NodeType child_type = child.repr->type();
1786
1787 if (child_type == Inkscape::XML::NodeType::ELEMENT_NODE) {
1788 text += child.textualContent();
1789 }
1790 else if (child_type == Inkscape::XML::NodeType::TEXT_NODE) {
1791 text += child.repr->content();
1792 }
1793 }
1794 return text;
1795}
1796
1797Glib::ustring SPObject::getExportFilename() const
1798{
1799 if (auto filename = repr->attribute("inkscape:export-filename")) {
1800 return Glib::ustring(filename);
1801 }
1802 return "";
1803}
1804
1805void SPObject::setExportFilename(Glib::ustring filename)
1806{
1807 // Is this svg has been saved before.
1808 const char *doc_filename = document->getDocumentFilename();
1809 std::string base = Glib::path_get_dirname(doc_filename ? doc_filename : filename.c_str());
1810
1811 filename = Inkscape::optimizePath(filename, base);
1812 repr->setAttributeOrRemoveIfEmpty("inkscape:export-filename", filename.c_str());
1813}
1814
1816{
1817 return Geom::Point(
1818 repr->getAttributeDouble("inkscape:export-xdpi", 0.0),
1819 repr->getAttributeDouble("inkscape:export-ydpi", 0.0));
1820}
1821
1823{
1824 if (!dpi.x() || !dpi.y()) {
1825 repr->removeAttribute("inkscape:export-xdpi");
1826 repr->removeAttribute("inkscape:export-ydpi");
1827 } else {
1828 repr->setAttributeSvgDouble("inkscape:export-xdpi", dpi.x());
1829 repr->setAttributeSvgDouble("inkscape:export-ydpi", dpi.y());
1830 }
1831}
1832
1833// For debugging: Print SP tree structure.
1834void SPObject::recursivePrintTree( unsigned level )
1835{
1836 if (level == 0) {
1837 std::cout << "SP Object Tree" << std::endl;
1838 }
1839 std::cout << "SP: ";
1840 for (unsigned i = 0; i < level; ++i) {
1841 std::cout << " ";
1842 }
1843 std::cout << (getId()?getId():"No object id")
1844 << " clone: " << std::boolalpha << (bool)cloned
1845 << " hrefcount: " << hrefcount << std::endl;
1846 for (auto& child: children) {
1847 child.recursivePrintTree(level + 1);
1848 }
1849}
1850
1851// Function to allow tracing of program flow through SPObject and derived classes.
1852// To trace function, add at entrance ('in' = true) and exit of function ('in' = false).
1853void SPObject::objectTrace( std::string const &text, bool in, unsigned flags ) {
1854 if( in ) {
1855 for (unsigned i = 0; i < indent_level; ++i) {
1856 std::cout << " ";
1857 }
1858 std::cout << text << ":"
1859 << " entrance: "
1860 << (id?id:"null")
1861 // << " uflags: " << uflags
1862 // << " mflags: " << mflags
1863 // << " flags: " << flags
1864 << std::endl;
1865 ++indent_level;
1866 } else {
1867 --indent_level;
1868 for (unsigned i = 0; i < indent_level; ++i) {
1869 std::cout << " ";
1870 }
1871 std::cout << text << ":"
1872 << " exit: "
1873 << (id?id:"null")
1874 // << " uflags: " << uflags
1875 // << " mflags: " << mflags
1876 // << " flags: " << flags
1877 << std::endl;
1878 }
1879}
1880
1881std::ostream &operator<<(std::ostream &out, const SPObject &o)
1882{
1883 out << (o.getId()?o.getId():"No ID")
1884 << " cloned: " << std::boolalpha << (bool)o.cloned
1885 << " ref: " << o.refCount
1886 << " href: " << o.hrefcount
1887 << " total href: " << o._total_hrefcount;
1888 return out;
1889}
1890/*
1891 Local Variables:
1892 mode:c++
1893 c-file-style:"stroustrup"
1894 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1895 indent-tabs-mode:nil
1896 fill-column:99
1897 End:
1898*/
1899// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
unsigned int sp_attribute_clean_get_prefs()
Get preferences.
void sp_attribute_clean_style(Node *repr, unsigned int flags)
Clean CSS style on an element.
Utility functions related to parsing and validation of XML attributes.
gchar const * sp_attribute_name(SPAttr id)
Get attribute name by id.
SPAttr sp_attribute_lookup(gchar const *key)
Get attribute id by name.
Lookup dictionary for attributes/properties.
SPAttr
Definition attributes.h:27
@ XLINK_HREF
@ INKSCAPE_LABEL
@ INKSCAPE_COLLECT
@ INVALID
Must have value 0.
Two-dimensional point that doubles as a vector.
Definition point.h:66
constexpr Coord y() const noexcept
Definition point.h:106
constexpr Coord x() const noexcept
Definition point.h:104
A thin wrapper around std::ostringstream, but writing floating point numbers in the format required b...
unsigned _anchored_refcount() const
Definition gc-anchored.h:52
Preference storage class.
Definition preferences.h:66
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
static Preferences * get()
Access the singleton Preferences object.
Non-owning reference to 'const char*' Main-purpose: avoid overloads of type f(char*,...
char const * pointer() const
Definition share.h:33
Interface for refcounted XML nodes.
Definition node.h:80
virtual Node * parent()=0
Get the parent of this node.
virtual void addChild(Node *child, Node *after)=0
Insert another node as a child of this node.
virtual void appendChild(Node *child)=0
Append a node as the last child of this node.
virtual char const * name() const =0
Get the name of the element node.
double getAttributeDouble(Util::const_char_ptr key, double default_value=0.0) const
Definition node.cpp:76
void setAttributeOrRemoveIfEmpty(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:167
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
virtual Node * duplicate(Document *doc) const =0
Create a duplicate of this node.
virtual Node * firstChild()=0
Get the first child of this node.
virtual unsigned position() const =0
Get the index of this node in parent's child order.
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
void removeAttribute(Inkscape::Util::const_char_ptr key)
Remove an attribute of this node.
Definition node.h:280
virtual void addObserver(NodeObserver &observer)=0
Add an object that will be notified of the changes to this node.
virtual Document * document()=0
Get the node's associated document.
int getAttributeInt(Util::const_char_ptr key, int default_value=0) const
Definition node.cpp:67
virtual NodeType type() const =0
Get the type of the node.
virtual void removeObserver(NodeObserver &observer)=0
Remove an object from the list of observers.
bool setAttributeSvgDouble(Util::const_char_ptr key, double val)
For attributes where an exponent is allowed.
Definition node.cpp:111
Typed SVG document implementation.
Definition document.h:103
bool isSeeking() const
Definition document.h:361
char const * getDocumentFilename() const
Definition document.h:231
std::string generate_unique_id(char const *prefix)
Generate a document-wide unique id.
unsigned update_in_progress
For sanity check in SPObject::requestDisplayUpdate.
Definition document.h:106
SPObject * getObjectById(std::string const &id) const
void process_pending_resource_changes()
void requestModified()
void bindObjectToId(char const *id, SPObject *object)
Inkscape::XML::Document * getReprDoc()
Our Inkscape::XML::Document.
Definition document.h:213
void bindObjectToRepr(Inkscape::XML::Node *repr, SPObject *object)
void queueForOrphanCollection(SPObject *object)
Definition document.cpp:321
SPObject * getObjectByRepr(Inkscape::XML::Node *repr) const
unsigned int value
Definition sp-object.h:111
unsigned int set
Definition sp-object.h:110
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
CollectionPolicy collectionPolicy() const
Set the policy under which this object will be orphan-collected.
Definition sp-object.h:425
char const * label() const
Gets the author-visible label property for the object or a default if no label is defined.
SPObject * getNext()
int getIntAttribute(char const *key, int def)
Inkscape::XML::Node * repr
Definition sp-object.h:193
SPObject * _successor
Definition sp-object.h:730
char * desc() const
Returns the description of this object, or NULL if there is none.
void requestOrphanCollection()
Requests a later automatic call to collectOrphan().
unsigned int uflags
Definition sp-object.h:182
void setAttributeDouble(Inkscape::Util::const_char_ptr key, double value)
void appendChild(Inkscape::XML::Node *child)
void _updateTotalHRefCount(int increment)
Adds increment to _total_hrefcount of object and its parents.
bool storeAsDouble(char const *key, double *val) const
void setAttribute(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value)
SPObject()
Constructor, sets all attributes to default values.
void detach(SPObject *object)
Remove object from parent's children, release and unref it.
void requestModified(unsigned int flags)
Requests that a modification notification signal be emitted later (e.g.
void removeAttribute(char const *key)
Glib::ustring getExportFilename() const
Get and set the exportable filename on this object.
Geom::Point getExportDpi() const
Get and set the exported DPI for this objet, if available.
void notifyChildOrderChanged(Inkscape::XML::Node &node, Inkscape::XML::Node &child, Inkscape::XML::Node *old_prev, Inkscape::XML::Node *new_prev) final
Child order change callback.
void setKeyValue(SPAttr key, char const *value)
Call virtual set() function of object.
bool setDesc(char const *desc, bool verbatim=false)
Sets the description of this object.
void setExportDpi(Geom::Point dpi)
SPIXmlSpace xml_space
Definition sp-object.h:184
void setExportFilename(Glib::ustring filename)
std::vector< SPObject * > childList(bool add_ref, Action action=ActionGeneral)
Retrieves the children as a std vector object, optionally ref'ing the children in the process,...
void setTmpSuccessor(SPObject *tmpsuccessor)
Indicates that another object supercedes temporaty this one.
char * getTitleOrDesc(char const *svg_tagname) const
Returns the title or description of this object, or NULL if there is none.
void getObjectsExcept(std::vector< SPObject * > &objects, const std::vector< SPObject * > &except)
Get all child objects except for any in the list.
SPObject * lastChild()
Definition sp-object.h:318
SPObject * get_child_by_repr(Inkscape::XML::Node *repr)
Return object's child whose node pointer equals repr.
Glib::ustring lang
Definition sp-object.h:185
virtual void read_content()
unsigned int mflags
Definition sp-object.h:183
void invoke_build(SPDocument *document, Inkscape::XML::Node *repr, unsigned int cloned)
void notifyAttributeChanged(Inkscape::XML::Node &node, GQuark key, Inkscape::Util::ptr_shared oldval, Inkscape::Util::ptr_shared newval) final
Attribute change callback.
void attach(SPObject *object, SPObject *prev)
Put object into object tree, under parent, and behind prev; also update object's XML space.
SPDocument * document
Definition sp-object.h:188
virtual void set(SPAttr key, const char *value)
char * id
Definition sp-object.h:192
void updateDisplay(SPCtx *ctx, unsigned int flags)
Updates the object's display immediately.
SPObject * _tmpsuccessor
Definition sp-object.h:731
virtual void remove_child(Inkscape::XML::Node *child)
void emitModified(unsigned int flags)
Emits the MODIFIED signal with the object's flags.
void getLinkedRecursive(std::vector< SPObject * > &objects, LinkedObjectNature direction=LinkedObjectNature::ANY) const
Grows the input list with all linked items recursively in both child nodes and links of links.
char const * getId() const
Returns the objects current ID string.
bool setTitleOrDesc(char const *value, char const *svg_tagname, bool verbatim)
Sets or deletes the title or description of this object.
void changeCSS(SPCSSAttr *css, char const *attr)
void notifyChildRemoved(Inkscape::XML::Node &node, Inkscape::XML::Node &child, Inkscape::XML::Node *prev) final
Child removal callback.
void recursivePrintTree(unsigned level=0)
sigc::signal< void(SPObject *)> _position_changed_signal
Definition sp-object.h:728
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
bool setTitle(char const *title, bool verbatim=false)
Sets the title of this object.
std::vector< SPObject * > ancestorList(bool root_to_tip)
Retrieves a list of ancestors of the object, as an easy to use vector.
virtual Inkscape::XML::Node * write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, unsigned int flags)
void notifyChildAdded(Inkscape::XML::Node &node, Inkscape::XML::Node &child, Inkscape::XML::Node *prev) final
Child addition callback.
char * _label
Definition sp-object.h:733
Glib::ustring textualContent() const
Return the full textual content of an element (typically all the content except the tags).
sigc::signal< void(SPObject *, unsigned int)> _modified_signal
Definition sp-object.h:729
~SPObject() override
Destructor, frees the used memory and unreferences a potential successor of the object.
SPObject * getPrev()
Returns previous object in sibling list or NULL.
char const * defaultLabel() const
Returns a default label property for this object.
SPObject * parent
Definition sp-object.h:189
unsigned getPosition()
void setLabel(char const *label)
Sets the author-visible label for this object.
virtual void update(SPCtx *ctx, unsigned int flags)
virtual void release()
SPStyle * context_style
Represents the style that should be used to resolve 'context-fill' and 'context-stroke'.
Definition sp-object.h:253
SPObject * findFirstChild(char const *tagname) const
Find the first child of this object with a given tag name, and return it.
void unhrefObject(SPObject *owner=nullptr)
Decrease weak refcount.
Inkscape::XML::Node * updateRepr(unsigned int flags=SP_OBJECT_WRITE_EXT)
Updates the object's repr based on the object's state.
void _requireSVGVersion(unsigned major, unsigned minor)
Definition sp-object.h:719
@ COLLECT_WITH_PARENT
Definition sp-object.h:164
@ ALWAYS_COLLECT
Definition sp-object.h:165
virtual void modified(unsigned int flags)
void readAttr(char const *key)
Read value of key attribute from XML node into object.
void _sendDeleteSignalRecursive()
Sends the delete signal to all children of this object recursively.
void reorder(SPObject *obj, SPObject *prev)
In list of object's children, move object behind prev.
sigc::signal< void(SPObject *)> _delete_signal
Definition sp-object.h:727
bool hasChildren() const
Definition sp-object.h:313
std::string generate_unique_id(char const *default_id=nullptr) const
Generate a document-wide unique id for this object.
void hrefObject(SPObject *owner=nullptr)
Increase weak refcount.
void deleteObject(bool propagate, bool propagate_descendants)
Deletes an object, unparenting it from its parent.
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
virtual void child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref)
char const * getAttribute(char const *name) const
void fixTmpSuccessors()
Fix temporary successors in duple stamp.
char * title() const
Returns the title of this object, or NULL if there is none.
void cropToObjects(std::vector< SPObject * > except_objects)
Removes objects which are not related to given list of objects.
virtual void getLinked(std::vector< SPObject * > &objects, LinkedObjectNature direction=LinkedObjectNature::ANY) const
Get objects which are linked to this object as either a source or a target.
SPObject * appendChildRepr(Inkscape::XML::Node *repr)
Append repr as child of this object.
bool isAncestorOf(SPObject const *object) const
True if object is non-NULL and this is some in/direct parent of object.
unsigned int cloned
Definition sp-object.h:180
LinkedObjectNature
Definition sp-object.h:168
std::string getUrl() const
Get the id in a URL format.
void objectTrace(std::string const &, bool in=true, unsigned flags=0)
SPObject * nthChild(unsigned index)
void cropToObject(SPObject *except)
Removes all children except for the given object, it's children and it's ancesstors.
void addChild(Inkscape::XML::Node *child, Inkscape::XML::Node *prev=nullptr)
std::list< SPObject * > hrefList
Definition sp-object.h:197
char * _default_label
Definition sp-object.h:734
virtual void build(SPDocument *doc, Inkscape::XML::Node *repr)
SPObject const * nearestCommonAncestor(SPObject const *object) const
Returns youngest object being parent to this and object.
void getIds(std::set< std::string > &ret) const
Accumulate this id and all it's descendants ids.
void unsetTmpSuccessor()
Unset object supercedes.
virtual void order_changed(Inkscape::XML::Node *child, Inkscape::XML::Node *old_repr, Inkscape::XML::Node *new_repr)
char const * getTagName() const
ChildrenList children
Definition sp-object.h:907
SPObject const * getTopAncestorNonLayer() const
Returns ancestor non layer.
int refCount
Definition sp-object.h:196
void setCSS(SPCSSAttr *css, char const *attr)
sigc::signal< void(SPObject *)> _release_signal
Definition sp-object.h:726
void notifyContentChanged(Inkscape::XML::Node &node, Inkscape::Util::ptr_shared oldcontent, Inkscape::Util::ptr_shared newcontent) final
Content change callback.
void notifyElementNameChanged(Inkscape::XML::Node &node, GQuark old_name, GQuark new_name) final
Element name change callback.
unsigned int hrefcount
Definition sp-object.h:186
void requestDisplayUpdate(unsigned int flags)
Queues an deferred update of this object's display.
virtual void tag_name_changed(gchar const *oldname, gchar const *newname)
void releaseReferences()
Cleans up an SPObject, releasing its references and requesting that references to it be released.
unsigned int _total_hrefcount
Definition sp-object.h:187
bool isSwatch() const
An SVG style object.
Definition style.h:45
T< SPAttr::FILL, SPIPaint > fill
fill
Definition style.h:240
bool block_filter_bbox_updates
(hack) Temporarily set to true to block filter changes from updating the object's bbox in situations ...
Definition style.h:303
T< SPAttr::STROKE, SPIPaint > stroke
stroke
Definition style.h:247
void readFromObject(SPObject *object)
Read style properties from object's repr.
Definition style.cpp:610
Glib::ustring write(unsigned flags, SPStyleSrc style_src_req, SPStyle const *base=nullptr) const
const std::vector< SPIBase * > properties()
Definition style.cpp:494
T< SPAttr::SHAPE_INSIDE, SPIShapes > shape_inside
SVG2 Text Wrapping.
Definition style.h:179
T< SPAttr::FILTER, SPIFilter > filter
Filter effect.
Definition style.h:275
T< SPAttr::SHAPE_SUBTRACT, SPIShapes > shape_subtract
Definition style.h:180
void cascade(SPStyle const *parent)
Sets computed values in style, which may involve inheriting from (or in some other way calculating fr...
Definition style.cpp:819
SPObject of the color-profile object found a direct child of defs.
RootCluster root
TODO: insert short description here.
std::shared_ptr< Css const > css
Css & result
SPItem * item
Inkscape::XML::Node * node
Glib::ustring label
ForwardIterator nearest_common_ancestor(ForwardIterator a, ForwardIterator b, ForwardIterator end)
Time costs:
Debugging utilities.
Definition demangle.cpp:22
std::shared_ptr< std::string > demangle(char const *name)
Definition demangle.cpp:37
static R & anchor(R &r)
Increments the reference count of a anchored object.
Definition gc-anchored.h:92
static R & release(R &r)
Decrements the reference count of a anchored object.
Miscellaneous supporting code.
Definition document.h:95
ptr_shared format(char const *format,...) G_GNUC_PRINTF(1
Definition format.h:33
bool id_permitted(Node const *node)
Definition node-fns.cpp:52
NodeType
Enumeration containing all supported node types.
Definition node.h:40
@ ELEMENT_NODE
Regular element node, e.g. <group />.
@ TEXT_NODE
Text node, e.g. "Some text" in <group>Some text</group> is represented by a text node.
std::pair< char const *, char const * > getHrefAttribute(XML::Node const &node)
Get the 'href' or 'xlink:href' (fallback) attribute from an XML node.
std::string optimizePath(std::string const &path, std::string const &base, unsigned int parents)
Convert an absolute path into a relative one if possible to do in the given number of parent steps.
Helper functions for XML nodes.
static cairo_user_data_key_t key
static gint counter
Definition box3d.cpp:39
Singleton class to access the preferences file in a convenient way.
Ocnode * child[8]
Definition quantize.cpp:33
Ocnode ** ref
Definition quantize.cpp:32
void sp_repr_css_change(Node *repr, SPCSSAttr *css, gchar const *attr)
Creates a new SPCSAttr with the values filled from a repr, merges in properties from the given SPCSAt...
Definition repr-css.cpp:358
void sp_repr_css_set(Node *repr, SPCSSAttr *css, gchar const *attr)
Sets an attribute (e.g.
Definition repr-css.cpp:265
int sp_repr_compare_position(Inkscape::XML::Node const *first, Inkscape::XML::Node const *second)
Works for different-parent objects, so long as they have a common ancestor.
void sp_repr_unparent(Inkscape::XML::Node *repr)
Remove repr from children of its parent node.
Definition repr.h:107
guint32 GQuark
bool sp_object_compare_position_bool(SPObject const *first, SPObject const *second)
static SPObject * get_closest_child_by_repr(SPObject &obj, Inkscape::XML::Node *ref)
Get closest child to a reference representation.
static SPObject const * AncestorSon(SPObject const *obj, SPObject const *ancestor)
static unsigned indent_level
Definition sp-object.cpp:70
int sp_object_compare_position(SPObject const *first, SPObject const *second)
Compares height of objects in tree.
static gchar const * sp_xml_get_space_string(unsigned int space)
Return string representation of space value.
SPObject * sp_object_unref(SPObject *object, SPObject *owner)
Decrease reference count of object, with possible debugging and finalization.
SPObject * sp_object_ref(SPObject *object, SPObject *owner)
Increase reference count of object, with possible debugging.
@ SP_XML_SPACE_PRESERVE
Definition sp-object.h:101
@ SP_XML_SPACE_DEFAULT
Definition sp-object.h:100
SPObject * sp_object_unref(SPObject *object, SPObject *owner=nullptr)
Decrease reference count of object, with possible debugging and finalization.
SPObject * sp_object_ref(SPObject *object, SPObject *owner=nullptr)
Increase reference count of object, with possible debugging.
SPRoot: SVG <svg> implementation.
TODO: insert short description here.
TODO: insert short description here.
TODO: insert short description here.
Interface for XML documents.
Definition document.h:43
virtual Node * createTextNode(char const *content)=0
virtual Node * createElement(char const *name)=0
static std::string get_type_string(Inkscape::XML::Node const &node)
Unused.
Definition sp-object.h:94
static SPObject * createObject(std::string const &id)
static const unsigned SP_STYLE_FLAG_IFSET(1<< 0)
static const unsigned SP_STYLE_FLAG_IFSRC(1<< 3)
SPStyle - a style object for SPItem objects.
int index
Glib::ustring name
Definition toolbars.cpp:55