1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * SPDocument manipulation
4  *
5  * Authors:
6  *   Lauris Kaplinski <lauris@kaplinski.com>
7  *   MenTaLguY <mental@rydia.net>
8  *   bulia byak <buliabyak@users.sf.net>
9  *   Jon A. Cruz <jon@joncruz.org>
10  *   Abhishek Sharma
11  *   Tavmjong Bah <tavmjong@free.fr>
12  *
13  * Copyright (C) 2004-2005 MenTaLguY
14  * Copyright (C) 1999-2002 Lauris Kaplinski
15  * Copyright (C) 2000-2001 Ximian, Inc.
16  * Copyright (C) 2012 Tavmjong Bah
17  *
18  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
19  */
20 
21 /** \class SPDocument
22  * SPDocument serves as the container of both model trees (agnostic XML
23  * and typed object tree), and implements all of the document-level
24  * functionality used by the program. Many document level operations, like
25  * load, save, print, export and so on, use SPDocument as their basic datatype.
26  *
27  * SPDocument implements undo and redo stacks and an id-based object
28  * dictionary.  Thanks to unique id attributes, the latter can be used to
29  * map from the XML tree back to the object tree.
30  *
31  * SPDocument performs the basic operations needed for asynchronous
32  * update notification (SPObject ::modified virtual method), and implements
33  * the 'modified' signal, as well.
34  */
35 
36 
37 #define noSP_DOCUMENT_DEBUG_IDLE
38 #define noSP_DOCUMENT_DEBUG_UNDO
39 
40 #include <vector>
41 #include <string>
42 #include <cstring>
43 
44 #include <2geom/transforms.h>
45 
46 #include "desktop.h"
47 #include "io/dir-util.h"
48 #include "document-undo.h"
49 #include "file.h"
50 #include "id-clash.h"
51 #include "inkscape-version.h"
52 #include "inkscape.h"
53 #include "inkscape-window.h"
54 #include "profile-manager.h"
55 #include "rdf.h"
56 
57 #include "actions/actions-canvas-snapping.h"
58 
59 #include "display/drawing.h"
60 
61 #include "3rdparty/adaptagrams/libavoid/router.h"
62 
63 #include "3rdparty/libcroco/cr-parser.h"
64 #include "3rdparty/libcroco/cr-sel-eng.h"
65 #include "3rdparty/libcroco/cr-selector.h"
66 
67 #include "live_effects/lpeobject.h"
68 #include "object/persp3d.h"
69 #include "object/sp-defs.h"
70 #include "object/sp-factory.h"
71 #include "object/sp-root.h"
72 #include "object/sp-symbol.h"
73 
74 #include "widgets/desktop-widget.h"
75 
76 #include "xml/croco-node-iface.h"
77 #include "xml/rebase-hrefs.h"
78 #include "xml/simple-document.h"
79 
80 using Inkscape::DocumentUndo;
81 using Inkscape::Util::unit_table;
82 
83 // Higher number means lower priority.
84 #define SP_DOCUMENT_UPDATE_PRIORITY (G_PRIORITY_HIGH_IDLE - 2)
85 
86 // Should have a lower priority than SP_DOCUMENT_UPDATE_PRIORITY,
87 // since we want it to happen when there are no more updates.
88 #define SP_DOCUMENT_REROUTING_PRIORITY (G_PRIORITY_HIGH_IDLE - 1)
89 
90 bool sp_no_convert_text_baseline_spacing = false;
91 
92 //gboolean sp_document_resource_list_free(gpointer key, gpointer value, gpointer data);
93 
94 static gint doc_count = 0;
95 static gint doc_mem_count = 0;
96 
97 static unsigned long next_serial = 0;
98 
SPDocument()99 SPDocument::SPDocument() :
100     keepalive(false),
101     virgin(true),
102     rdoc(nullptr),
103     rroot(nullptr),
104     root(nullptr),
105     style_cascade(cr_cascade_new(nullptr, nullptr, nullptr)),
106     document_uri(nullptr),
107     document_base(nullptr),
108     document_name(nullptr),
109     actionkey(),
110     profileManager(nullptr), // deferred until after other initialization
111     router(new Avoid::Router(Avoid::PolyLineRouting|Avoid::OrthogonalRouting)),
112     oldSignalsConnected(false),
113     current_persp3d(nullptr),
114     current_persp3d_impl(nullptr),
115     _parent_document(nullptr),
116     _node_cache_valid(false),
117     _activexmltree(nullptr)
118 {
119     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
120 
121     if (!prefs->getBool("/options/yaxisdown", true)) {
122         _doc2dt[3] = -1;
123     }
124 
125     // Penalise libavoid for choosing paths with needless extra segments.
126     // This results in much better looking orthogonal connector paths.
127     router->setRoutingPenalty(Avoid::segmentPenalty);
128 
129     _serial = next_serial++;
130 
131     sensitive = false;
132     partial = nullptr;
133     history_size = 0;
134     seeking = false;
135 
136     // Once things are set, hook in the manager
137     profileManager = new Inkscape::ProfileManager(this);
138 
139     // XXX only for testing!
140     undoStackObservers.add(console_output_undo_observer);
141     _node_cache = std::deque<SPItem*>();
142 
143     // Actions
144     action_group = Gio::SimpleActionGroup::create();
145     add_actions_canvas_snapping(this);
146 }
147 
~SPDocument()148 SPDocument::~SPDocument() {
149     destroySignal.emit();
150 
151     // kill/unhook this first
152     if ( profileManager ) {
153         delete profileManager;
154         profileManager = nullptr;
155     }
156 
157     if (router) {
158         delete router;
159         router = nullptr;
160     }
161 
162     if (oldSignalsConnected) {
163         selChangeConnection.disconnect();
164         desktopActivatedConnection.disconnect();
165     } else {
166         _selection_changed_connection.disconnect();
167         _desktop_activated_connection.disconnect();
168     }
169 
170     if (partial) {
171         sp_repr_free_log(partial);
172         partial = nullptr;
173     }
174 
175     DocumentUndo::clearRedo(this);
176     DocumentUndo::clearUndo(this);
177 
178     if (root) {
179         root->releaseReferences();
180         sp_object_unref(root);
181         root = nullptr;
182     }
183 
184     if (rdoc) Inkscape::GC::release(rdoc);
185 
186     /* Free resources */
187     resources.clear();
188 
189     // This also destroys all attached stylesheets
190     cr_cascade_unref(style_cascade);
191     style_cascade = nullptr;
192 
193     if (document_name) {
194         g_free(document_name);
195         document_name = nullptr;
196     }
197     if (document_base) {
198         g_free(document_base);
199         document_base = nullptr;
200     }
201     if (document_uri) {
202         g_free(document_uri);
203         document_uri = nullptr;
204     }
205 
206     modified_connection.disconnect();
207     rerouting_connection.disconnect();
208 
209     if (keepalive) {
210         inkscape_unref(INKSCAPE);
211         keepalive = false;
212     }
213 
214     if (this->current_persp3d_impl)
215         delete this->current_persp3d_impl;
216     this->current_persp3d_impl = nullptr;
217 
218     // This is at the end of the destructor, because preceding code adds new orphans to the queue
219     collectOrphans();
220 }
221 
getReprNamedView()222 Inkscape::XML::Node *SPDocument::getReprNamedView()
223 {
224     return sp_repr_lookup_name (rroot, "sodipodi:namedview");
225 }
226 
227 /**
228  * Get the namedview for this document, creates it if it's not found.
229  *
230  * @returns SPNamedView object, existing or created.
231  */
getNamedView()232 SPNamedView *SPDocument::getNamedView()
233 {
234     auto xml = getReprNamedView();
235     if (!xml) {
236         xml = rdoc->createElement("sodipodi:namedview");
237         rroot->addChildAtPos(xml, 0);
238     }
239     return dynamic_cast<SPNamedView *> (getObjectByRepr(xml));
240 }
241 
getDefs()242 SPDefs *SPDocument::getDefs()
243 {
244     if (!root) {
245         return nullptr;
246     }
247     return root->defs;
248 }
249 
getCurrentPersp3D()250 Persp3D *SPDocument::getCurrentPersp3D() {
251     // Check if current_persp3d is still valid
252     std::vector<Persp3D*> plist;
253     getPerspectivesInDefs(plist);
254     for (auto & i : plist) {
255         if (current_persp3d == i)
256             return current_persp3d;
257     }
258 
259     // If not, return the first perspective in defs (which may be NULL of none exists)
260     current_persp3d = Persp3D::document_first_persp (this);
261 
262     return current_persp3d;
263 }
264 
setCurrentPersp3D(Persp3D * const persp)265 void SPDocument::setCurrentPersp3D(Persp3D * const persp) {
266     current_persp3d = persp;
267     //current_persp3d_impl = persp->perspective_impl;
268 }
269 
getPerspectivesInDefs(std::vector<Persp3D * > & list) const270 void SPDocument::getPerspectivesInDefs(std::vector<Persp3D*> &list) const
271 {
272     for (auto& i: root->defs->children) {
273         if (SP_IS_PERSP3D(&i)) {
274             list.push_back(SP_PERSP3D(&i));
275         }
276     }
277 }
278 
279 /**
280 void SPDocument::initialize_current_persp3d()
281 {
282     this->current_persp3d = Persp3D::document_first_persp(this);
283     if (!this->current_persp3d) {
284         this->current_persp3d = Persp3D::create_xml_element(this);
285     }
286 }
287 **/
288 
queueForOrphanCollection(SPObject * object)289 void SPDocument::queueForOrphanCollection(SPObject *object) {
290     g_return_if_fail(object != nullptr);
291     g_return_if_fail(object->document == this);
292 
293     sp_object_ref(object, nullptr);
294     _collection_queue.push_back(object);
295 }
296 
collectOrphans()297 void SPDocument::collectOrphans() {
298     while (!_collection_queue.empty()) {
299         std::vector<SPObject *> objects(_collection_queue);
300         _collection_queue.clear();
301         for (auto object : objects) {
302             object->collectOrphan();
303             sp_object_unref(object, nullptr);
304         }
305     }
306 }
307 
createDoc(Inkscape::XML::Document * rdoc,gchar const * document_uri,gchar const * document_base,gchar const * document_name,bool keepalive,SPDocument * parent)308 SPDocument *SPDocument::createDoc(Inkscape::XML::Document *rdoc,
309                                   gchar const *document_uri,
310                                   gchar const *document_base,
311                                   gchar const *document_name,
312                                   bool keepalive,
313                                   SPDocument *parent)
314 {
315     SPDocument *document = new SPDocument();
316 
317     Inkscape::XML::Node *rroot = rdoc->root();
318 
319     document->keepalive = keepalive;
320 
321     document->rdoc = rdoc;
322     document->rroot = rroot;
323     if (parent) {
324         document->_parent_document = parent;
325         parent->_child_documents.push_back(document);
326     }
327 
328     if (document->document_uri){
329         g_free(document->document_uri);
330         document->document_uri = nullptr;
331     }
332     if (document->document_base){
333         g_free(document->document_base);
334         document->document_base = nullptr;
335     }
336     if (document->document_name){
337         g_free(document->document_name);
338         document->document_name = nullptr;
339     }
340 #ifndef _WIN32
341     document->document_uri = prepend_current_dir_if_relative(document_uri);
342 #else
343     // FIXME: it may be that prepend_current_dir_if_relative works OK on windows too, test!
344     document->document_uri = document_uri? g_strdup(document_uri) : NULL;
345 #endif
346 
347     // base is simply the part of the path before filename; e.g. when running "inkscape ../file.svg" the base is "../"
348     // which is why we use g_get_current_dir() in calculating the abs path above
349     //This is NULL for a new document
350     if (document_base) {
351         document->document_base = g_strdup(document_base);
352     } else {
353         document->document_base = nullptr;
354     }
355     document->document_name = g_strdup(document_name);
356 
357     // Create SPRoot element
358     const std::string typeString = NodeTraits::get_type_string(*rroot);
359     SPObject* rootObj = SPFactory::createObject(typeString);
360     document->root = dynamic_cast<SPRoot*>(rootObj);
361 
362     if (document->root == nullptr) {
363     	// Node is not a valid root element
364     	delete rootObj;
365 
366     	// fixme: what to do here?
367     	throw;
368     }
369 
370     // Recursively build object tree
371     document->root->invoke_build(document, rroot, false);
372 
373     /* Eliminate obsolete sodipodi:docbase, for privacy reasons */
374     rroot->removeAttribute("sodipodi:docbase");
375 
376     /* Eliminate any claim to adhere to a profile, as we don't try to */
377     rroot->removeAttribute("baseProfile");
378 
379     // loading or creating namedview.
380     auto nv = document->getNamedView();
381 
382     // Set each of the defaults in new or existing namedview (allows for per-attr overriding)
383     nv->setDefaultAttribute("pagecolor",                 "/template/base/pagecolor", "");
384     nv->setDefaultAttribute("bordercolor",               "/template/base/bordercolor", "");
385     nv->setDefaultAttribute("borderopacity",             "/template/base/borderopacity", "");
386     nv->setDefaultAttribute("inkscape:pageshadow",       "/template/base/pageshadow", "2");
387     nv->setDefaultAttribute("inkscape:pageopacity",      "/template/base/pageopacity", "0.0");
388     nv->setDefaultAttribute("inkscape:pagecheckerboard", "/template/base/pagecheckerboard", "0");
389 
390     // If no units are set in the document, try and guess them from the width/height
391     if (document->root->width.isAbsolute()) {
392         nv->setDefaultAttribute("inkscape:document-units", "", document->root->width.getUnit());
393     } else if (document->root->height.isAbsolute()) {
394         nv->setDefaultAttribute("inkscape:document-units", "", document->root->height.getUnit());
395     }
396 
397     // Defs
398     if (!document->root->defs) {
399         Inkscape::XML::Node *r = rdoc->createElement("svg:defs");
400         rroot->addChild(r, nullptr);
401         Inkscape::GC::release(r);
402         g_assert(document->root->defs);
403     }
404 
405     /* Default RDF */
406     rdf_set_defaults( document );
407 
408     if (keepalive) {
409         inkscape_ref(INKSCAPE);
410     }
411 
412     // Check if the document already has a perspective (e.g., when opening an existing
413     // document). If not, create a new one and set it as the current perspective.
414     document->setCurrentPersp3D(Persp3D::document_first_persp(document));
415     if (!document->getCurrentPersp3D()) {
416         //document->setCurrentPersp3D(Persp3D::create_xml_element (document));
417         Persp3DImpl *persp_impl = new Persp3DImpl();
418         document->setCurrentPersp3DImpl(persp_impl);
419     }
420 
421     DocumentUndo::setUndoSensitive(document, true);
422 
423     // reset undo key when selection changes, so that same-key actions on different objects are not coalesced
424     document->selChangeConnection = INKSCAPE.signal_selection_changed.connect(
425                 sigc::hide(sigc::bind(
426                 sigc::ptr_fun(&DocumentUndo::resetKey), document)
427     ));
428     document->desktopActivatedConnection = INKSCAPE.signal_activate_desktop.connect(
429                 sigc::hide(sigc::bind(
430                 sigc::ptr_fun(&DocumentUndo::resetKey), document)
431     ));
432     document->oldSignalsConnected = true;
433 
434 
435     // ************* Fix Document **************
436     // Move to separate function?
437 
438     /** Fix OSB **/
439     sp_file_fix_osb(document->getRoot());
440 
441 
442     /** Fix baseline spacing (pre-92 files) **/
443     if ( (!sp_no_convert_text_baseline_spacing)
444          && sp_version_inside_range( document->root->version.inkscape, 0, 1, 0, 92 ) ) {
445         sp_file_convert_text_baseline_spacing(document);
446     }
447 
448     /** Fix font names in legacy documents (pre-92 files) **/
449     if ( sp_version_inside_range( document->root->version.inkscape, 0, 1, 0, 92 ) ) {
450         sp_file_convert_font_name(document);
451     }
452 
453     /** Fix first line spacing in legacy documents (pre-1.0 files) **/
454     if (sp_version_inside_range(document->root->version.inkscape, 0, 1, 1, 0)) {
455         sp_file_fix_empty_lines(document);
456     }
457 
458     /** Fix dpi (pre-92 files). With GUI fixed in Inkscape::Application::fix_document. **/
459     if ( !(INKSCAPE.use_gui()) && sp_version_inside_range( document->root->version.inkscape, 0, 1, 0, 92 ) ) {
460         sp_file_convert_dpi(document);
461     }
462 
463     // Update LPE's   See: Related bug:#1769679 #18
464     SPDefs * defs = document->getDefs();
465     if (defs) {
466         defs->emitModified(SP_OBJECT_MODIFIED_CASCADE);
467     }
468 
469     // Update document level action settings
470     set_actions_canvas_snapping(document);
471 
472     return document;
473 }
474 
475 /**
476  * Create a copy of the document, useful for modifying during save & export.
477  */
copy() const478 std::unique_ptr<SPDocument> SPDocument::copy() const
479 {
480     // New SimpleDocument object where we will put all the same data
481     Inkscape::XML::Document *new_rdoc = new Inkscape::XML::SimpleDocument();
482 
483     // Duplicate the svg root node AND any PI and COMMENT nodes, this should be put
484     // into xml/simple-document.h at some point to fix it's duplicate implementation.
485     for (Inkscape::XML::Node *child = rdoc->firstChild(); child; child = child->next()) {
486         if (child) {
487             // Get a new xml repr for the svg root node
488             Inkscape::XML::Node *new_child = child->duplicate(new_rdoc);
489 
490             // Add the duplicated svg node as the document's rdoc
491             new_rdoc->appendChild(new_child);
492             Inkscape::GC::release(new_child);
493         }
494     }
495 
496     auto doc = createDoc(new_rdoc, document_uri, document_base, document_name, keepalive, nullptr);
497     doc->_original_document = this->doRef();
498     Inkscape::GC::release(new_rdoc);
499 
500     return std::unique_ptr<SPDocument>(doc);
501 }
502 
503 /**
504  * Fetches a document and attaches it to the current document as a child href
505  */
createChildDoc(std::string const & document_uri)506 SPDocument *SPDocument::createChildDoc(std::string const &document_uri)
507 {
508     SPDocument *parent = this;
509     SPDocument *document = nullptr;
510 
511     while(parent != nullptr && parent->getDocumentURI() != nullptr && document == nullptr) {
512         // Check myself and any parents in the chain
513         if(document_uri == parent->getDocumentURI()) {
514             document = parent;
515             break;
516         }
517         // Then check children of those.
518         boost::ptr_list<SPDocument>::iterator iter;
519         for (iter = parent->_child_documents.begin();
520           iter != parent->_child_documents.end(); ++iter) {
521             if(document_uri == iter->getDocumentURI()) {
522                 document = &*iter;
523                 break;
524             }
525         }
526         parent = parent->_parent_document;
527     }
528 
529     // Load a fresh document from the svg source.
530     if(!document) {
531         std::string path;
532         if (Glib::path_is_absolute(document_uri)) {
533             path = document_uri;
534         } else {
535             path = this->getDocumentBase() + document_uri;
536         }
537         document = createNewDoc(path.c_str(), false, false, this);
538     }
539     return document;
540 }
541 /**
542  * Fetches document from URI, or creates new, if NULL; public document
543  * appears in document list.
544  */
createNewDoc(gchar const * document_uri,bool keepalive,bool make_new,SPDocument * parent)545 SPDocument *SPDocument::createNewDoc(gchar const *document_uri, bool keepalive, bool make_new, SPDocument *parent)
546 {
547     Inkscape::XML::Document *rdoc = nullptr;
548     gchar *document_base = nullptr;
549     gchar *document_name = nullptr;
550 
551     if (document_uri) {
552         Inkscape::XML::Node *rroot;
553         /* Try to fetch repr from file */
554         rdoc = sp_repr_read_file(document_uri, SP_SVG_NS_URI);
555         /* If file cannot be loaded, return NULL without warning */
556         if (rdoc == nullptr) return nullptr;
557         rroot = rdoc->root();
558         /* If xml file is not svg, return NULL without warning */
559         /* fixme: destroy document */
560         if (strcmp(rroot->name(), "svg:svg") != 0) return nullptr;
561 
562         // Opening a template that points to a sister file should still work
563         // this also includes tutorials which point to png files.
564         document_base = g_path_get_dirname(document_uri);
565 
566         if (make_new) {
567             document_uri = nullptr;
568             document_name = g_strdup_printf(_("New document %d"), ++doc_count);
569         } else {
570             document_name = g_path_get_basename(document_uri);
571             if (strcmp(document_base, ".") == 0) {
572                 g_free(document_base);
573                 document_base = nullptr;
574             }
575         }
576     } else {
577         if (make_new) {
578             document_name = g_strdup_printf(_("Memory document %d"), ++doc_mem_count);
579         }
580 
581         rdoc = sp_repr_document_new("svg:svg");
582     }
583 
584     //# These should be set by now
585     g_assert(document_name);
586 
587     SPDocument *doc = createDoc(rdoc, document_uri, document_base, document_name, keepalive, parent);
588 
589     g_free(document_base);
590     g_free(document_name);
591 
592     return doc;
593 }
594 
createNewDocFromMem(gchar const * buffer,gint length,bool keepalive)595 SPDocument *SPDocument::createNewDocFromMem(gchar const *buffer, gint length, bool keepalive)
596 {
597     SPDocument *doc = nullptr;
598 
599     Inkscape::XML::Document *rdoc = sp_repr_read_mem(buffer, length, SP_SVG_NS_URI);
600     if ( rdoc ) {
601         // Only continue to create a non-null doc if it could be loaded
602         Inkscape::XML::Node *rroot = rdoc->root();
603         if ( strcmp(rroot->name(), "svg:svg") != 0 ) {
604             // If xml file is not svg, return NULL without warning
605             // TODO fixme: destroy document
606         } else {
607             Glib::ustring document_name = Glib::ustring::compose( _("Memory document %1"), ++doc_mem_count );
608             doc = createDoc(rdoc, nullptr, nullptr, document_name.c_str(), keepalive, nullptr);
609         }
610     }
611 
612     return doc;
613 }
614 
doRef()615 std::unique_ptr<SPDocument> SPDocument::doRef()
616 {
617     Inkscape::GC::anchor(this);
618     return std::unique_ptr<SPDocument>(this);
619 }
doRef() const620 std::unique_ptr<SPDocument const> SPDocument::doRef() const
621 {
622     return const_cast<SPDocument*>(this)->doRef();
623 }
624 
625 /// guaranteed not to return nullptr
getDisplayUnit() const626 Inkscape::Util::Unit const* SPDocument::getDisplayUnit() const
627 {
628     // The document may not have a namedview (cleaned) so don't crash if it doesn't exist.
629     auto nv_repr = sp_repr_lookup_name (rroot, "sodipodi:namedview");
630     if (!nv_repr) {
631         return unit_table.getUnit("px");
632     }
633     auto nv = dynamic_cast<SPNamedView *> (getObjectByRepr(nv_repr));
634     return nv ? nv->getDisplayUnit() : unit_table.getUnit("px");
635 }
636 
637 /// Sets document scale (by changing viewBox)
setDocumentScale(double scaleX,double scaleY)638 void SPDocument::setDocumentScale( double scaleX, double scaleY ) {
639 
640     root->viewBox = Geom::Rect::from_xywh(
641         root->viewBox.left(),
642         root->viewBox.top(),
643         root->width.computed  * scaleX,
644         root->height.computed * scaleY );
645     root->viewBox_set = true;
646     root->updateRepr();
647 }
648 
649 /// Sets document scale (by changing viewBox, x and y scaling equal)
setDocumentScale(double scale)650 void SPDocument::setDocumentScale( double scale ) {
651     setDocumentScale( scale, scale );
652 }
653 
654 /// Returns document scale as defined by width/height (in pixels) and viewBox (real world to
655 /// user-units).
getDocumentScale() const656 Geom::Scale SPDocument::getDocumentScale() const
657 {
658     Geom::Scale scale;
659     if( root->viewBox_set ) {
660         double scale_x = 1.0;
661         double scale_y = 1.0;
662         if( root->viewBox.width() > 0.0 ) {
663             scale_x = root->width.computed / root->viewBox.width();
664         }
665         if( root->viewBox.height() > 0.0 ) {
666             scale_y = root->height.computed / root->viewBox.height();
667         }
668         scale = Geom::Scale(scale_x, scale_y);
669     }
670     // std::cout << "SPDocument::getDocumentScale():\n" << scale << std::endl;
671     return scale;
672 }
673 
674 // Avoid calling root->updateRepr() twice by combining setting width and height.
675 // (As done on every delete as clipboard calls this via fitToRect(). Also called in page-sizer.cpp)
setWidthAndHeight(const Inkscape::Util::Quantity & width,const Inkscape::Util::Quantity & height,bool changeSize)676 void SPDocument::setWidthAndHeight(const Inkscape::Util::Quantity &width, const Inkscape::Util::Quantity &height, bool changeSize)
677 {
678     Inkscape::Util::Unit const *old_width_units = unit_table.getUnit("px");
679     if (root->width.unit)
680         old_width_units = unit_table.getUnit(root->width.unit);
681     gdouble old_width_converted;  // old width converted to new units
682     if (root->width.unit == SVGLength::PERCENT)
683         old_width_converted = Inkscape::Util::Quantity::convert(root->width.computed, "px", width.unit);
684     else
685         old_width_converted = Inkscape::Util::Quantity::convert(root->width.value, old_width_units, width.unit);
686 
687     root->width.computed = width.value("px");
688     root->width.value = width.quantity;
689     root->width.unit = (SVGLength::Unit) width.unit->svgUnit();
690 
691     Inkscape::Util::Unit const *old_height_units = unit_table.getUnit("px");
692     if (root->height.unit)
693         old_height_units = unit_table.getUnit(root->height.unit);
694     gdouble old_height_converted;  // old height converted to new units
695     if (root->height.unit == SVGLength::PERCENT)
696         old_height_converted = Inkscape::Util::Quantity::convert(root->height.computed, "px", height.unit);
697     else
698         old_height_converted = Inkscape::Util::Quantity::convert(root->height.value, old_height_units, height.unit);
699 
700     root->height.computed = height.value("px");
701     root->height.value = height.quantity;
702     root->height.unit = (SVGLength::Unit) height.unit->svgUnit();
703 
704     // viewBox scaled by relative change in page size (maintains document scale).
705     if (root->viewBox_set && changeSize) {
706         root->viewBox.setMax(Geom::Point(
707         root->viewBox.left() + (root->width.value /  old_width_converted ) * root->viewBox.width(),
708         root->viewBox.top()  + (root->height.value / old_height_converted) * root->viewBox.height()));
709     }
710     root->updateRepr();
711 }
712 
getWidth() const713 Inkscape::Util::Quantity SPDocument::getWidth() const
714 {
715     g_return_val_if_fail(this->root != nullptr, Inkscape::Util::Quantity(0.0, unit_table.getUnit("")));
716 
717     gdouble result = root->width.value;
718     SVGLength::Unit u = root->width.unit;
719     if (root->width.unit == SVGLength::PERCENT && root->viewBox_set) {
720         result = root->viewBox.width();
721         u = SVGLength::PX;
722     }
723     if (u == SVGLength::NONE) {
724         u = SVGLength::PX;
725     }
726     return Inkscape::Util::Quantity(result, unit_table.getUnit(u));
727 }
728 
setWidth(const Inkscape::Util::Quantity & width,bool changeSize)729 void SPDocument::setWidth(const Inkscape::Util::Quantity &width, bool changeSize)
730 {
731     Inkscape::Util::Unit const *old_width_units = unit_table.getUnit("px");
732     if (root->width.unit)
733         old_width_units = unit_table.getUnit(root->width.unit);
734     gdouble old_width_converted;  // old width converted to new units
735     if (root->width.unit == SVGLength::PERCENT)
736         old_width_converted = Inkscape::Util::Quantity::convert(root->width.computed, "px", width.unit);
737     else
738         old_width_converted = Inkscape::Util::Quantity::convert(root->width.value, old_width_units, width.unit);
739 
740     root->width.computed = width.value("px");
741     root->width.value = width.quantity;
742     root->width.unit = (SVGLength::Unit) width.unit->svgUnit();
743 
744     if (root->viewBox_set && changeSize)
745         root->viewBox.setMax(Geom::Point(root->viewBox.left() + (root->width.value / old_width_converted) * root->viewBox.width(), root->viewBox.bottom()));
746 
747     root->updateRepr();
748 }
749 
750 
getHeight() const751 Inkscape::Util::Quantity SPDocument::getHeight() const
752 {
753     g_return_val_if_fail(this->root != nullptr, Inkscape::Util::Quantity(0.0, unit_table.getUnit("")));
754 
755     gdouble result = root->height.value;
756     SVGLength::Unit u = root->height.unit;
757     if (root->height.unit == SVGLength::PERCENT && root->viewBox_set) {
758         result = root->viewBox.height();
759         u = SVGLength::PX;
760     }
761     if (u == SVGLength::NONE) {
762         u = SVGLength::PX;
763     }
764     return Inkscape::Util::Quantity(result, unit_table.getUnit(u));
765 }
766 
setHeight(const Inkscape::Util::Quantity & height,bool changeSize)767 void SPDocument::setHeight(const Inkscape::Util::Quantity &height, bool changeSize)
768 {
769     Inkscape::Util::Unit const *old_height_units = unit_table.getUnit("px");
770     if (root->height.unit)
771         old_height_units = unit_table.getUnit(root->height.unit);
772     gdouble old_height_converted;  // old height converted to new units
773     if (root->height.unit == SVGLength::PERCENT)
774         old_height_converted = Inkscape::Util::Quantity::convert(root->height.computed, "px", height.unit);
775     else
776         old_height_converted = Inkscape::Util::Quantity::convert(root->height.value, old_height_units, height.unit);
777 
778     root->height.computed = height.value("px");
779     root->height.value = height.quantity;
780     root->height.unit = (SVGLength::Unit) height.unit->svgUnit();
781 
782     if (root->viewBox_set && changeSize)
783         root->viewBox.setMax(Geom::Point(root->viewBox.right(), root->viewBox.top() + (root->height.value / old_height_converted) * root->viewBox.height()));
784 
785     root->updateRepr();
786 }
787 
doc2dt() const788 const Geom::Affine &SPDocument::doc2dt() const
789 {
790     if (root && !is_yaxisdown()) {
791         _doc2dt[5] = root->height.computed;
792     }
793 
794     return _doc2dt;
795 }
796 
getViewBox() const797 Geom::Rect SPDocument::getViewBox() const
798 {
799     Geom::Rect viewBox;
800     if (root->viewBox_set) {
801         viewBox = root->viewBox;
802     } else {
803         viewBox = Geom::Rect::from_xywh( 0, 0, getWidth().value("px"), getHeight().value("px"));
804     }
805     return viewBox;
806 }
807 
808 /**
809  * Set default viewbox calculated from document properties.
810  */
setViewBox()811 void SPDocument::setViewBox()
812 {
813     setViewBox(Geom::Rect(0,
814                           0,
815                           getWidth().value(getDisplayUnit()),
816                           getHeight().value(getDisplayUnit())));
817 }
818 
setViewBox(const Geom::Rect & viewBox)819 void SPDocument::setViewBox(const Geom::Rect &viewBox)
820 {
821     root->viewBox_set = true;
822     root->viewBox = viewBox;
823     root->updateRepr();
824 }
825 
getDimensions() const826 Geom::Point SPDocument::getDimensions() const
827 {
828     return Geom::Point(getWidth().value("px"), getHeight().value("px"));
829 }
830 
preferredBounds() const831 Geom::OptRect SPDocument::preferredBounds() const
832 {
833     return Geom::OptRect( Geom::Point(0, 0), getDimensions() );
834 }
835 
836 /**
837  * Given a Geom::Rect that may, for example, correspond to the bbox of an object,
838  * this function fits the canvas to that rect by resizing the canvas
839  * and translating the document root into position.
840  * \param rect fit document size to this, in document coordinates
841  * \param with_margins add margins to rect, by taking margins from this
842  *        document's namedview (<sodipodi:namedview> "fit-margin-..."
843  *        attributes, and "units")
844  */
fitToRect(Geom::Rect const & rect,bool with_margins)845 void SPDocument::fitToRect(Geom::Rect const &rect, bool with_margins)
846 {
847     double const w = rect.width();
848     double const h = rect.height();
849 
850     Inkscape::Util::Unit const *nv_units = unit_table.getUnit("px");
851     if (root->height.unit && (root->height.unit != SVGLength::PERCENT))
852         nv_units = unit_table.getUnit(root->height.unit);
853     SPNamedView *nv = sp_document_namedview(this, nullptr);
854 
855     /* in px */
856     double margin_top = 0.0;
857     double margin_left = 0.0;
858     double margin_right = 0.0;
859     double margin_bottom = 0.0;
860 
861     if (with_margins && nv) {
862         if (nv != nullptr) {
863             margin_top = nv->getMarginLength("fit-margin-top", nv_units, unit_table.getUnit("px"), w, h, false);
864             margin_left = nv->getMarginLength("fit-margin-left", nv_units, unit_table.getUnit("px"), w, h, true);
865             margin_right = nv->getMarginLength("fit-margin-right", nv_units, unit_table.getUnit("px"), w, h, true);
866             margin_bottom = nv->getMarginLength("fit-margin-bottom", nv_units, unit_table.getUnit("px"), w, h, false);
867             margin_top = Inkscape::Util::Quantity::convert(margin_top, nv_units, "px");
868             margin_left = Inkscape::Util::Quantity::convert(margin_left, nv_units, "px");
869             margin_right = Inkscape::Util::Quantity::convert(margin_right, nv_units, "px");
870             margin_bottom = Inkscape::Util::Quantity::convert(margin_bottom, nv_units, "px");
871         }
872     }
873 
874     double y_dir = yaxisdir();
875 
876     Geom::Rect const rect_with_margins(
877             rect.min() - Geom::Point(margin_left, margin_top),
878             rect.max() + Geom::Point(margin_right, margin_bottom));
879 
880     // rect in desktop coordinates before changing document dimensions
881     auto rect_with_margins_dt_old = rect_with_margins * doc2dt();
882 
883     setWidthAndHeight(
884         Inkscape::Util::Quantity(Inkscape::Util::Quantity::convert(rect_with_margins.width(),  "px", nv_units), nv_units),
885         Inkscape::Util::Quantity(Inkscape::Util::Quantity::convert(rect_with_margins.height(), "px", nv_units), nv_units)
886         );
887 
888     // rect in desktop coordinates after changing document dimensions
889     auto rect_with_margins_dt_new = rect_with_margins * doc2dt();
890 
891     Geom::Translate const tr(-rect_with_margins_dt_new.min());
892     root->translateChildItems(tr);
893 
894     if(nv) {
895         Geom::Translate tr2(-rect_with_margins_dt_old.min());
896         nv->translateGuides(tr2);
897         nv->translateGrids(tr2);
898 
899         // update the viewport so the drawing appears to stay where it was
900         nv->scrollAllDesktops(-tr2[0], -tr2[1] * y_dir, false);
901     }
902 }
903 
setDocumentBase(gchar const * document_base)904 void SPDocument::setDocumentBase( gchar const* document_base )
905 {
906     if (this->document_base) {
907         g_free(this->document_base);
908         this->document_base = nullptr;
909     }
910     if (document_base) {
911         this->document_base = g_strdup(document_base);
912     }
913 }
914 
do_change_uri(gchar const * const filename,bool const rebase)915 void SPDocument::do_change_uri(gchar const *const filename, bool const rebase)
916 {
917     gchar *new_document_base = nullptr;
918     gchar *new_document_name = nullptr;
919     gchar *new_document_uri = nullptr;
920     if (filename) {
921 
922 #ifndef _WIN32
923         new_document_uri = prepend_current_dir_if_relative(filename);
924 #else
925         // FIXME: it may be that prepend_current_dir_if_relative works OK on windows too, test!
926         new_document_uri = g_strdup(filename);
927 #endif
928 
929         new_document_base = g_path_get_dirname(new_document_uri);
930         new_document_name = g_path_get_basename(new_document_uri);
931     } else {
932         new_document_uri = g_strdup_printf(_("Unnamed document %d"), ++doc_count);
933         new_document_base = nullptr;
934         new_document_name = g_strdup(this->document_uri);
935     }
936 
937     // Update saveable repr attributes.
938     Inkscape::XML::Node *repr = getReprRoot();
939 
940     // Changing uri in the document repr must not be not undoable.
941     bool const saved = DocumentUndo::getUndoSensitive(this);
942     DocumentUndo::setUndoSensitive(this, false);
943 
944     if (rebase) {
945         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
946         bool use_sodipodi_absref = prefs->getBool("/options/svgoutput/usesodipodiabsref", false);
947         Inkscape::XML::rebase_hrefs(this, new_document_base, use_sodipodi_absref);
948     }
949 
950     if (strncmp(new_document_name, "ink_ext_XXXXXX", 14))	// do not use temporary filenames
951         repr->setAttribute("sodipodi:docname", new_document_name);
952     DocumentUndo::setUndoSensitive(this, saved);
953 
954 
955     g_free(this->document_name);
956     g_free(this->document_base);
957     g_free(this->document_uri);
958     this->document_name = new_document_name;
959     this->document_base = new_document_base;
960     this->document_uri = new_document_uri;
961 
962     this->uri_set_signal.emit(this->document_uri);
963 }
964 
965 /**
966  * Sets base, name and uri members of \a document.  Doesn't update
967  * any relative hrefs in the document: thus, this is primarily for
968  * newly-created documents.
969  *
970  * \see sp_document_change_uri_and_hrefs
971  */
setDocumentUri(gchar const * filename)972 void SPDocument::setDocumentUri(gchar const *filename)
973 {
974     do_change_uri(filename, false);
975 }
976 
977 /**
978  * Changes the base, name and uri members of \a document, and updates any
979  * relative hrefs in the document to be relative to the new base.
980  *
981  * \see sp_document_set_uri
982  */
changeUriAndHrefs(gchar const * filename)983 void SPDocument::changeUriAndHrefs(gchar const *filename)
984 {
985     do_change_uri(filename, true);
986 }
987 
bindObjectToId(gchar const * id,SPObject * object)988 void SPDocument::bindObjectToId(gchar const *id, SPObject *object) {
989     GQuark idq = g_quark_from_string(id);
990 
991     if (object) {
992         if(object->getId())
993             iddef.erase(object->getId());
994         g_assert(iddef.find(id)==iddef.end());
995         iddef[id] = object;
996     } else {
997         g_assert(iddef.find(id)!=iddef.end());
998         iddef.erase(id);
999     }
1000 
1001     SPDocument::IDChangedSignalMap::iterator pos;
1002 
1003     pos = id_changed_signals.find(idq);
1004     if ( pos != id_changed_signals.end() ) {
1005         if (!(*pos).second.empty()) {
1006             (*pos).second.emit(object);
1007         } else { // discard unused signal
1008             id_changed_signals.erase(pos);
1009         }
1010     }
1011 }
1012 
1013 /**
1014  * Assign IDs to selected objects that don't have an ID attribute
1015  * Checks if the object's id attribute is NULL. If it is, assign it a unique ID
1016  */
enforceObjectIds()1017 void SPDocument::enforceObjectIds()
1018 {
1019     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1020     Inkscape::Selection *selection = desktop->getSelection();
1021     bool showInfoDialog = false;
1022     Glib::ustring msg = _("Selected objects require IDs.\nThe following IDs have been assigned:\n");
1023     auto items = selection->items();
1024     for (auto iter = items.begin(); iter != items.end(); ++iter) {
1025         SPItem *item = *iter;
1026         if(!item->getId())
1027         {
1028             // Selected object does not have an ID, so assign it a unique ID
1029             gchar *id = sp_object_get_unique_id(item, nullptr);
1030             item->setAttribute("id", id);
1031             item->updateRepr();
1032             msg += Glib::ustring::compose(_(" %1\n"), id);
1033             g_free(id);
1034             showInfoDialog = true;
1035         }
1036     }
1037     if(showInfoDialog) {
1038         desktop->showInfoDialog(msg);
1039         setModifiedSinceSave(true);
1040     }
1041     return;
1042 }
1043 
getObjectById(Glib::ustring const & id) const1044 SPObject *SPDocument::getObjectById(Glib::ustring const &id) const
1045 {
1046     if (iddef.empty()) {
1047         return nullptr;
1048     }
1049 
1050     std::map<std::string, SPObject *>::const_iterator rv = iddef.find(id);
1051     if (rv != iddef.end()) {
1052         return (rv->second);
1053     } else {
1054         return nullptr;
1055     }
1056 }
1057 
getObjectById(gchar const * id) const1058 SPObject *SPDocument::getObjectById(gchar const *id) const
1059 {
1060     if (id == nullptr) {
1061         return nullptr;
1062     }
1063 
1064     return getObjectById(Glib::ustring(id));
1065 }
1066 
_getObjectsByClassRecursive(Glib::ustring const & klass,SPObject * parent,std::vector<SPObject * > & objects)1067 void _getObjectsByClassRecursive(Glib::ustring const &klass, SPObject *parent, std::vector<SPObject *> &objects)
1068 {
1069     if (parent) {
1070         char const *temp = parent->getAttribute("class");
1071         if (temp) {
1072             std::istringstream classes(temp);
1073             Glib::ustring token;
1074             while (classes >> token) {
1075                 // we can have multiple class
1076                 if (classes.str() == " ") {
1077                     token = "";
1078                     continue;
1079                 }
1080                 if (token == klass) {
1081                     objects.push_back(parent);
1082                     break;
1083                 }
1084             }
1085         }
1086 
1087         // Check children
1088         for (auto& child : parent->children) {
1089             _getObjectsByClassRecursive( klass, &child, objects );
1090         }
1091     }
1092 }
1093 
getObjectsByClass(Glib::ustring const & klass) const1094 std::vector<SPObject *> SPDocument::getObjectsByClass(Glib::ustring const &klass) const
1095 {
1096     std::vector<SPObject *> objects;
1097     g_return_val_if_fail(!klass.empty(), objects);
1098 
1099     _getObjectsByClassRecursive(klass, root, objects);
1100     return objects;
1101 }
1102 
_getObjectsByElementRecursive(Glib::ustring const & element,SPObject * parent,std::vector<SPObject * > & objects)1103 void _getObjectsByElementRecursive(Glib::ustring const &element, SPObject *parent,
1104                                    std::vector<SPObject *> &objects)
1105 {
1106     if (parent) {
1107         Glib::ustring prefixed = "svg:" + element;
1108         if (parent->getRepr()->name() == prefixed) {
1109             objects.push_back(parent);
1110         }
1111 
1112         // Check children
1113         for (auto& child : parent->children) {
1114             _getObjectsByElementRecursive(element, &child, objects);
1115         }
1116     }
1117 }
1118 
getObjectsByElement(Glib::ustring const & element) const1119 std::vector<SPObject *> SPDocument::getObjectsByElement(Glib::ustring const &element) const
1120 {
1121     std::vector<SPObject *> objects;
1122     g_return_val_if_fail(!element.empty(), objects);
1123 
1124     _getObjectsByElementRecursive(element, root, objects);
1125     return objects;
1126 }
1127 
_getObjectsBySelectorRecursive(SPObject * parent,CRSelEng * sel_eng,CRSimpleSel * simple_sel,std::vector<SPObject * > & objects)1128 void _getObjectsBySelectorRecursive(SPObject *parent,
1129                                     CRSelEng *sel_eng, CRSimpleSel *simple_sel,
1130                                     std::vector<SPObject *> &objects)
1131 {
1132     if (parent) {
1133         gboolean result = false;
1134         cr_sel_eng_matches_node( sel_eng, simple_sel, parent->getRepr(), &result );
1135         if (result) {
1136             objects.push_back(parent);
1137         }
1138 
1139         // Check children
1140         for (auto& child : parent->children) {
1141             _getObjectsBySelectorRecursive(&child, sel_eng, simple_sel, objects);
1142         }
1143     }
1144 }
1145 
getObjectsBySelector(Glib::ustring const & selector) const1146 std::vector<SPObject *> SPDocument::getObjectsBySelector(Glib::ustring const &selector) const
1147 {
1148     // std::cout << "\nSPDocument::getObjectsBySelector: " << selector << std::endl;
1149 
1150     std::vector<SPObject *> objects;
1151     g_return_val_if_fail(!selector.empty(), objects);
1152 
1153     static CRSelEng *sel_eng = nullptr;
1154     if (!sel_eng) {
1155         sel_eng = cr_sel_eng_new(&Inkscape::XML::croco_node_iface);
1156     }
1157 
1158     Glib::ustring my_selector = selector + " {";  // Parsing fails sometimes without '{'. Fix me
1159     CRSelector *cr_selector = cr_selector_parse_from_buf ((guchar*)my_selector.c_str(), CR_UTF_8);
1160     // char * cr_string = (char*)cr_selector_to_string( cr_selector );
1161     // std::cout << "  selector: |" << (cr_string?cr_string:"Empty") << "|" << std::endl;
1162     CRSelector const *cur = nullptr;
1163     for (cur = cr_selector; cur; cur = cur->next) {
1164         if (cur->simple_sel ) {
1165             _getObjectsBySelectorRecursive(root, sel_eng, cur->simple_sel, objects);
1166         }
1167     }
1168     return objects;
1169 }
1170 
bindObjectToRepr(Inkscape::XML::Node * repr,SPObject * object)1171 void SPDocument::bindObjectToRepr(Inkscape::XML::Node *repr, SPObject *object)
1172 {
1173     if (object) {
1174         g_assert(reprdef.find(repr)==reprdef.end());
1175         reprdef[repr] = object;
1176     } else {
1177         g_assert(reprdef.find(repr)!=reprdef.end());
1178         reprdef.erase(repr);
1179     }
1180 }
1181 
getObjectByRepr(Inkscape::XML::Node * repr) const1182 SPObject *SPDocument::getObjectByRepr(Inkscape::XML::Node *repr) const
1183 {
1184     g_return_val_if_fail(repr != nullptr, NULL);
1185     std::map<Inkscape::XML::Node *, SPObject *>::const_iterator rv = reprdef.find(repr);
1186     if(rv != reprdef.end())
1187         return (rv->second);
1188     else
1189         return nullptr;
1190 }
1191 
1192 /** Returns preferred document languages (from most to least preferred)
1193  *
1194  * This currently includes (in order):
1195  * - language set in RDF metadata
1196  * - languages suitable for system locale (influenced by Inkscape GUI locale preference)
1197  */
getLanguages() const1198 std::vector<Glib::ustring> SPDocument::getLanguages() const
1199 {
1200     std::vector<Glib::ustring> document_languages;
1201 
1202     // get language from RDF
1203     gchar const *rdf_language = rdf_get_work_entity(this, rdf_find_entity("language"));
1204     if (rdf_language) {
1205         gchar *rdf_language_stripped = g_strstrip(g_strdup(rdf_language));
1206         if (strcmp(rdf_language_stripped, "") != 0) {
1207             document_languages.emplace_back(rdf_language_stripped);
1208         }
1209         g_free(rdf_language_stripped);
1210     }
1211 
1212     // get language from system locale (will also match the interface language preference as we set LANG accordingly)
1213     // TODO: This includes locales with encodings like "de_DE.UTF-8" - is this useful or should we skip these?
1214     // TODO: This includes the default "C" locale - is this useful or should we skip it?
1215     const gchar * const * names = g_get_language_names();
1216     for (int i=0; names[i]; i++) {
1217         document_languages.emplace_back(names[i]);
1218     }
1219 
1220     return document_languages;
1221 }
1222 
1223 /* Object modification root handler */
1224 
requestModified()1225 void SPDocument::requestModified()
1226 {
1227     if (modified_connection.empty()) {
1228         modified_connection =
1229             Glib::signal_idle().connect(sigc::mem_fun(*this, &SPDocument::idle_handler),
1230                                         SP_DOCUMENT_UPDATE_PRIORITY);
1231     }
1232 
1233     if (rerouting_connection.empty()) {
1234         rerouting_connection =
1235             Glib::signal_idle().connect(sigc::mem_fun(*this, &SPDocument::rerouting_handler),
1236                                         SP_DOCUMENT_REROUTING_PRIORITY);
1237     }
1238 }
1239 
setupViewport(SPItemCtx * ctx)1240 void SPDocument::setupViewport(SPItemCtx *ctx)
1241 {
1242     ctx->flags = 0;
1243     ctx->i2doc = Geom::identity();
1244     // Set up viewport in case svg has it defined as percentages
1245     if (root->viewBox_set) { // if set, take from viewBox
1246         ctx->viewport = root->viewBox;
1247     } else { // as a last resort, set size to A4
1248         ctx->viewport = Geom::Rect::from_xywh(0, 0, Inkscape::Util::Quantity::convert(210, "mm", "px"), Inkscape::Util::Quantity::convert(297, "mm", "px"));
1249     }
1250     ctx->i2vp = Geom::identity();
1251 }
1252 
1253 /**
1254  * Tries to update the document state based on the modified and
1255  * "update required" flags, and return true if the document has
1256  * been brought fully up to date.
1257  */
1258 bool
_updateDocument(int update_flags)1259 SPDocument::_updateDocument(int update_flags)
1260 {
1261     /* Process updates */
1262     if (this->root->uflags || this->root->mflags) {
1263         if (this->root->uflags) {
1264             SPItemCtx ctx;
1265             setupViewport(&ctx);
1266 
1267             DocumentUndo::ScopedInsensitive _no_undo(this);
1268 
1269             this->root->updateDisplay((SPCtx *)&ctx, update_flags);
1270         }
1271         this->_emitModified();
1272     }
1273 
1274     return !(this->root->uflags || this->root->mflags);
1275 }
1276 
1277 
1278 /**
1279  * Repeatedly works on getting the document updated, since sometimes
1280  * it takes more than one pass to get the document updated.  But it
1281  * usually should not take more than a few loops, and certainly never
1282  * more than 32 iterations.  So we bail out if we hit 32 iterations,
1283  * since this typically indicates we're stuck in an update loop.
1284  */
ensureUpToDate()1285 gint SPDocument::ensureUpToDate()
1286 {
1287     // Bring the document up-to-date, specifically via the following:
1288     //   1a) Process all document updates.
1289     //   1b) When completed, process connector routing changes.
1290     //   2a) Process any updates resulting from connector reroutings.
1291     int counter = 32;
1292     for (unsigned int pass = 1; pass <= 2; ++pass) {
1293         // Process document updates.
1294         while (!_updateDocument(0)) {
1295             if (counter == 0) {
1296                 g_warning("More than 32 iteration while updating document '%s'", document_uri);
1297                 break;
1298             }
1299             counter--;
1300         }
1301         if (counter == 0)
1302         {
1303             break;
1304         }
1305 
1306         // After updates on the first pass we get libavoid to process all the
1307         // changed objects and provide new routings.  This may cause some objects
1308             // to be modified, hence the second update pass.
1309         if (pass == 1) {
1310             router->processTransaction();
1311         }
1312     }
1313 
1314     // Remove handlers
1315     modified_connection.disconnect();
1316     rerouting_connection.disconnect();
1317 
1318     return (counter > 0);
1319 }
1320 
1321 /**
1322  * An idle handler to update the document.  Returns true if
1323  * the document needs further updates.
1324  */
1325 bool
idle_handler()1326 SPDocument::idle_handler()
1327 {
1328     bool status = !_updateDocument(0); // method TRUE if it does NOT need further modification, so invert
1329     if (!status) {
1330         modified_connection.disconnect();
1331     }
1332     return status;
1333 }
1334 
1335 /**
1336  * An idle handler to reroute connectors in the document.
1337  */
1338 bool
rerouting_handler()1339 SPDocument::rerouting_handler()
1340 {
1341     // Process any queued movement actions and determine new routings for
1342     // object-avoiding connectors.  Callbacks will be used to update and
1343     // redraw affected connectors.
1344     router->processTransaction();
1345 
1346     // We don't need to handle rerouting again until there are further
1347     // diagram updates.
1348     return false;
1349 }
1350 
is_within(Geom::Rect const & area,Geom::Rect const & box)1351 static bool is_within(Geom::Rect const &area, Geom::Rect const &box)
1352 {
1353     return area.contains(box);
1354 }
1355 
overlaps(Geom::Rect const & area,Geom::Rect const & box)1356 static bool overlaps(Geom::Rect const &area, Geom::Rect const &box)
1357 {
1358     return area.intersects(box);
1359 }
1360 
1361 /**
1362  * @param area Area in document coordinates
1363  */
find_items_in_area(std::vector<SPItem * > & s,SPGroup * group,unsigned int dkey,Geom::Rect const & area,bool (* test)(Geom::Rect const &,Geom::Rect const &),bool take_hidden=false,bool take_insensitive=false,bool take_groups=true,bool enter_groups=false)1364 static std::vector<SPItem*> &find_items_in_area(std::vector<SPItem*> &s,
1365                                                 SPGroup *group, unsigned int dkey,
1366                                                 Geom::Rect const &area,
1367                                                 bool (*test)(Geom::Rect const &, Geom::Rect const &),
1368                                                 bool take_hidden = false,
1369                                                 bool take_insensitive = false,
1370                                                 bool take_groups = true,
1371                                                 bool enter_groups = false)
1372 {
1373     g_return_val_if_fail(SP_IS_GROUP(group), s);
1374 
1375     for (auto& o: group->children) {
1376         if (SPItem *item = dynamic_cast<SPItem *>(&o)) {
1377             if (!take_insensitive && item->isLocked()) {
1378                 continue;
1379             }
1380 
1381             if (!take_hidden && item->isHidden()) {
1382                 continue;
1383             }
1384 
1385             if (SPGroup * childgroup = dynamic_cast<SPGroup *>(item)) {
1386                 bool is_layer = childgroup->effectiveLayerMode(dkey) == SPGroup::LAYER;
1387                 if (is_layer || (enter_groups)) {
1388                     s = find_items_in_area(s, childgroup, dkey, area, test, take_hidden, take_insensitive, take_groups, enter_groups);
1389                 }
1390                 if (!take_groups || is_layer) {
1391                     continue;
1392                 }
1393             }
1394             Geom::OptRect box = item->documentVisualBounds();
1395             if (box && test(area, *box)) {
1396                 s.push_back(item);
1397             }
1398         }
1399     }
1400     return s;
1401 }
1402 
getItemFromListAtPointBottom(unsigned int dkey,SPGroup * group,std::vector<SPItem * > const & list,Geom::Point const & p,bool take_insensitive)1403 SPItem *SPDocument::getItemFromListAtPointBottom(unsigned int dkey, SPGroup *group, std::vector<SPItem*> const &list,Geom::Point const &p, bool take_insensitive)
1404 {
1405     g_return_val_if_fail(group, NULL);
1406     SPItem *bottomMost = nullptr;
1407 
1408     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1409     gdouble delta = prefs->getDouble("/options/cursortolerance/value", 1.0);
1410 
1411     for (auto& o: group->children) {
1412         if (bottomMost) {
1413             break;
1414         }
1415         if (SP_IS_ITEM(&o)) {
1416             SPItem *item = SP_ITEM(&o);
1417             Inkscape::DrawingItem *arenaitem = item->get_arenaitem(dkey);
1418 
1419             if (arenaitem) {
1420                 arenaitem->drawing().update();
1421             }
1422 
1423             if (arenaitem && arenaitem->pick(p, delta, 1) != nullptr
1424                 && (take_insensitive || item->isVisibleAndUnlocked(dkey))) {
1425                 if (find(list.begin(), list.end(), item) != list.end()) {
1426                     bottomMost = item;
1427                 }
1428             }
1429 
1430             if (!bottomMost && SP_IS_GROUP(&o)) {
1431                 // return null if not found:
1432                 bottomMost = getItemFromListAtPointBottom(dkey, SP_GROUP(&o), list, p, take_insensitive);
1433             }
1434         }
1435     }
1436     return bottomMost;
1437 }
1438 
1439 /**
1440 Turn the SVG DOM into a flat list of nodes that can be searched from top-down.
1441 The list can be persisted, which improves "find at multiple points" speed.
1442 */
1443 // TODO: study add `gboolean with_groups = false` as parameter.
build_flat_item_list(unsigned int dkey,SPGroup * group,gboolean into_groups) const1444 void SPDocument::build_flat_item_list(unsigned int dkey, SPGroup *group, gboolean into_groups) const
1445 {
1446     for (auto& o: group->children) {
1447         if (!SP_IS_ITEM(&o)) {
1448             continue;
1449         }
1450 
1451         if (SP_IS_GROUP(&o) && (SP_GROUP(&o)->effectiveLayerMode(dkey) == SPGroup::LAYER || into_groups)) {
1452             build_flat_item_list(dkey, SP_GROUP(&o), into_groups);
1453         } else {
1454             SPItem *child = SP_ITEM(&o);
1455             if (child->isVisibleAndUnlocked(dkey)) {
1456                 _node_cache.push_front(child);
1457             }
1458         }
1459     }
1460 }
1461 
1462 /**
1463 Returns the topmost (in z-order) item from the descendants of group (recursively) which
1464 is at the point p, or NULL if none. Honors into_groups on whether to recurse into
1465 non-layer groups or not. Honors take_insensitive on whether to return insensitive
1466 items. If upto != NULL, then if item upto is encountered (at any level), stops searching
1467 upwards in z-order and returns what it has found so far (i.e. the found item is
1468 guaranteed to be lower than upto). Requires a list of nodes built by
1469 build_flat_item_list.
1470  */
find_item_at_point(std::deque<SPItem * > * nodes,unsigned int dkey,Geom::Point const & p,SPItem * upto=nullptr)1471 static SPItem *find_item_at_point(std::deque<SPItem*> *nodes, unsigned int dkey, Geom::Point const &p, SPItem* upto=nullptr)
1472 {
1473     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1474     gdouble delta = prefs->getDouble("/options/cursortolerance/value", 1.0);
1475 
1476     SPItem *seen = nullptr;
1477     SPItem *child;
1478     bool seen_upto = (!upto);
1479     for (auto node : *nodes) {
1480         child = node;
1481         if (!seen_upto){
1482             if(child == upto)
1483                 seen_upto = true;
1484             continue;
1485         }
1486         Inkscape::DrawingItem *arenaitem = child->get_arenaitem(dkey);
1487         if (arenaitem) {
1488             arenaitem->drawing().update();
1489             if (arenaitem->pick(p, delta, 1) != nullptr) {
1490                 seen = child;
1491                 break;
1492             }
1493         }
1494     }
1495 
1496     return seen;
1497 }
1498 
1499 /**
1500 Returns the topmost non-layer group from the descendants of group which is at point
1501 p, or NULL if none. Recurses into layers but not into groups.
1502  */
find_group_at_point(unsigned int dkey,SPGroup * group,Geom::Point const & p)1503 static SPItem *find_group_at_point(unsigned int dkey, SPGroup *group, Geom::Point const &p)
1504 {
1505     SPItem *seen = nullptr;
1506     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1507     gdouble delta = prefs->getDouble("/options/cursortolerance/value", 1.0);
1508 
1509     for (auto& o: group->children) {
1510         if (!SP_IS_ITEM(&o)) {
1511             continue;
1512         }
1513         if (SP_IS_GROUP(&o) && SP_GROUP(&o)->effectiveLayerMode(dkey) == SPGroup::LAYER) {
1514             SPItem *newseen = find_group_at_point(dkey, SP_GROUP(&o), p);
1515             if (newseen) {
1516                 seen = newseen;
1517             }
1518         }
1519         if (SP_IS_GROUP(&o) && SP_GROUP(&o)->effectiveLayerMode(dkey) != SPGroup::LAYER ) {
1520             SPItem *child = SP_ITEM(&o);
1521             Inkscape::DrawingItem *arenaitem = child->get_arenaitem(dkey);
1522             if (arenaitem) {
1523                 arenaitem->drawing().update();
1524             }
1525 
1526             // seen remembers the last (topmost) of groups pickable at this point
1527             if (arenaitem && arenaitem->pick(p, delta, 1) != nullptr) {
1528                 seen = child;
1529             }
1530         }
1531     }
1532     return seen;
1533 }
1534 
1535 
1536 /**
1537  * Return list of items, contained in box
1538  *
1539  * @param box area to find items, in document coordinates
1540  */
1541 
getItemsInBox(unsigned int dkey,Geom::Rect const & box,bool take_hidden,bool take_insensitive,bool take_groups,bool enter_groups) const1542 std::vector<SPItem*> SPDocument::getItemsInBox(unsigned int dkey, Geom::Rect const &box, bool take_hidden, bool take_insensitive, bool take_groups, bool enter_groups) const
1543 {
1544     std::vector<SPItem*> x;
1545     return find_items_in_area(x, SP_GROUP(this->root), dkey, box, is_within, take_hidden, take_insensitive, take_groups, enter_groups);
1546 }
1547 
1548 /**
1549  * Get items whose bounding box overlaps with given area.
1550  * @param dkey desktop view in use
1551  * @param box area to find items, in document coordinates
1552  * @param take_hidden get hidden items
1553  * @param take_insensitive get insensitive items
1554  * @param take_groups get also the groups
1555  * @param enter_groups get items inside groups
1556  * @return Return list of items, that the parts of the item contained in box
1557  */
1558 
getItemsPartiallyInBox(unsigned int dkey,Geom::Rect const & box,bool take_hidden,bool take_insensitive,bool take_groups,bool enter_groups) const1559 std::vector<SPItem*> SPDocument::getItemsPartiallyInBox(unsigned int dkey, Geom::Rect const &box, bool take_hidden, bool take_insensitive, bool take_groups, bool enter_groups) const
1560 {
1561     std::vector<SPItem*> x;
1562     return find_items_in_area(x, SP_GROUP(this->root), dkey, box, overlaps, take_hidden, take_insensitive, take_groups, enter_groups);
1563 }
1564 
getItemsAtPoints(unsigned const key,std::vector<Geom::Point> points,bool all_layers,size_t limit) const1565 std::vector<SPItem*> SPDocument::getItemsAtPoints(unsigned const key, std::vector<Geom::Point> points, bool all_layers, size_t limit) const
1566 {
1567     std::vector<SPItem*> items;
1568     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1569 
1570     // When picking along the path, we don't want small objects close together
1571     // (such as hatching strokes) to obscure each other by their deltas,
1572     // so we temporarily set delta to a small value
1573     gdouble saved_delta = prefs->getDouble("/options/cursortolerance/value", 1.0);
1574     prefs->setDouble("/options/cursortolerance/value", 0.25);
1575 
1576     // Cache a flattened SVG DOM to speed up selection.
1577     if(!_node_cache_valid){
1578         _node_cache.clear();
1579         build_flat_item_list(key, SP_GROUP(this->root), true);
1580         _node_cache_valid=true;
1581     }
1582     SPObject *current_layer = nullptr;
1583     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1584     Inkscape::LayerModel *layer_model = nullptr;
1585     if(desktop){
1586         current_layer = desktop->currentLayer();
1587         layer_model = desktop->layers;
1588     }
1589     size_t item_counter = 0;
1590     for(int i = points.size()-1;i>=0; i--) {
1591         SPItem *item = find_item_at_point(&_node_cache, key, points[i]);
1592         if (item && items.end()==find(items.begin(),items.end(), item))
1593             if(all_layers || (layer_model && layer_model->layerForObject(item) == current_layer)){
1594                 items.push_back(item);
1595                 item_counter++;
1596                 //limit 0 = no limit
1597                 if(item_counter == limit){
1598                     prefs->setDouble("/options/cursortolerance/value", saved_delta);
1599                     return items;
1600                 }
1601             }
1602     }
1603 
1604     // and now we restore it back
1605     prefs->setDouble("/options/cursortolerance/value", saved_delta);
1606 
1607     return items;
1608 }
1609 
getItemAtPoint(unsigned const key,Geom::Point const & p,bool const into_groups,SPItem * upto) const1610 SPItem *SPDocument::getItemAtPoint( unsigned const key, Geom::Point const &p,
1611                                     bool const into_groups, SPItem *upto) const
1612 {
1613     // Build a flattened SVG DOM for find_item_at_point.
1614     std::deque<SPItem*> bak(_node_cache);
1615     if(!into_groups){
1616         _node_cache.clear();
1617         build_flat_item_list(key, SP_GROUP(this->root), into_groups);
1618     }
1619     if(!_node_cache_valid && into_groups){
1620         _node_cache.clear();
1621         build_flat_item_list(key, SP_GROUP(this->root), true);
1622         _node_cache_valid=true;
1623     }
1624 
1625     SPItem *res = find_item_at_point(&_node_cache, key, p, upto);
1626     if(!into_groups)
1627         _node_cache = bak;
1628     return res;
1629 }
1630 
getGroupAtPoint(unsigned int key,Geom::Point const & p) const1631 SPItem *SPDocument::getGroupAtPoint(unsigned int key, Geom::Point const &p) const
1632 {
1633     return find_group_at_point(key, SP_GROUP(this->root), p);
1634 }
1635 
1636 // Resource management
1637 
addResource(gchar const * key,SPObject * object)1638 bool SPDocument::addResource(gchar const *key, SPObject *object)
1639 {
1640     g_return_val_if_fail(key != nullptr, false);
1641     g_return_val_if_fail(*key != '\0', false);
1642     g_return_val_if_fail(object != nullptr, false);
1643     g_return_val_if_fail(SP_IS_OBJECT(object), false);
1644 
1645     bool result = false;
1646 
1647     if ( !object->cloned ) {
1648         std::vector<SPObject *> rlist = resources[key];
1649         g_return_val_if_fail(std::find(rlist.begin(),rlist.end(),object) == rlist.end(), false);
1650         resources[key].insert(resources[key].begin(),object);
1651 
1652         GQuark q = g_quark_from_string(key);
1653 
1654         /*in general, do not send signal if the object has no id (yet),
1655         it means the object is not completely built.
1656         (happens when pasting swatches across documents, cf bug 1495106)
1657         [this check should be more generally presend on emit() calls since
1658         the backtrace is unusable with crashed from this cause]
1659         */
1660         if(object->getId() || dynamic_cast<SPGroup*>(object) )
1661             resources_changed_signals[q].emit();
1662 
1663         result = true;
1664     }
1665 
1666     return result;
1667 }
1668 
removeResource(gchar const * key,SPObject * object)1669 bool SPDocument::removeResource(gchar const *key, SPObject *object)
1670 {
1671     g_return_val_if_fail(key != nullptr, false);
1672     g_return_val_if_fail(*key != '\0', false);
1673     g_return_val_if_fail(object != nullptr, false);
1674     g_return_val_if_fail(SP_IS_OBJECT(object), false);
1675 
1676     bool result = false;
1677 
1678     if ( !object->cloned ) {
1679         std::vector<SPObject *> rlist = resources[key];
1680         g_return_val_if_fail(!rlist.empty(), false);
1681         std::vector<SPObject*>::iterator it = std::find(resources[key].begin(),resources[key].end(),object);
1682         g_return_val_if_fail(it != rlist.end(), false);
1683         resources[key].erase(it);
1684 
1685         GQuark q = g_quark_from_string(key);
1686         resources_changed_signals[q].emit();
1687 
1688         result = true;
1689     }
1690 
1691     return result;
1692 }
1693 
getResourceList(gchar const * key)1694 std::vector<SPObject *> const SPDocument::getResourceList(gchar const *key)
1695 {
1696     std::vector<SPObject *> emptyset;
1697     g_return_val_if_fail(key != nullptr, emptyset);
1698     g_return_val_if_fail(*key != '\0', emptyset);
1699 
1700     return resources[key];
1701 }
1702 
1703 /* Helpers */
1704 
count_objects_recursive(SPObject * obj,unsigned int count)1705 static unsigned int count_objects_recursive(SPObject *obj, unsigned int count)
1706 {
1707     count++; // obj itself
1708 
1709     for (auto& i: obj->children) {
1710         count = count_objects_recursive(&i, count);
1711     }
1712 
1713     return count;
1714 }
1715 
1716 /**
1717  * Count the number of objects in a given document recursively using the count_objects_recursive helper function
1718  *
1719  * @param[in] document Pointer to the document for counting objects
1720  * @return Number of objects in the document
1721  */
objects_in_document(SPDocument * document)1722 static unsigned int objects_in_document(SPDocument *document)
1723 {
1724     return count_objects_recursive(document->getRoot(), 0);
1725 }
1726 
1727 /**
1728  * Remove unused definitions etc. recursively from an object and its siblings
1729  *
1730  * @param[inout] obj Object which shall be "cleaned"
1731  */
vacuum_document_recursive(SPObject * obj)1732 static void vacuum_document_recursive(SPObject *obj)
1733 {
1734     if (SP_IS_DEFS(obj)) {
1735         for (auto& def: obj->children) {
1736             // fixme: some inkscape-internal nodes in the future might not be collectable
1737             def.requestOrphanCollection();
1738         }
1739     } else {
1740         for (auto& i: obj->children) {
1741             vacuum_document_recursive(&i);
1742         }
1743     }
1744 }
1745 
1746 /**
1747  * Remove unused definitions etc. recursively from an entire document.
1748  *
1749  * @return Number of removed objects
1750  */
vacuumDocument()1751 unsigned int SPDocument::vacuumDocument()
1752 {
1753     unsigned int start = objects_in_document(this);
1754     unsigned int end;
1755     unsigned int newend = start;
1756 
1757     unsigned int iterations = 0;
1758 
1759     do {
1760         end = newend;
1761 
1762         vacuum_document_recursive(root);
1763         this->collectOrphans();
1764         iterations++;
1765 
1766         newend = objects_in_document(this);
1767 
1768     } while (iterations < 100 && newend < end);
1769     // We stop if vacuum_document_recursive doesn't remove any more objects or after 100 iterations, whichever occurs first.
1770 
1771     return start - newend;
1772 }
1773 
1774 /**
1775  * Indicate to the user if the document has been modified since the last save by displaying a "*" in front of the name of the file in the window title.
1776  *
1777  * @param[in] modified True if the document has been modified.
1778  */
setModifiedSinceSave(bool modified)1779 void SPDocument::setModifiedSinceSave(bool modified) {
1780     this->modified_since_save = modified;
1781     this->modified_since_autosave = modified;
1782     if (SP_ACTIVE_DESKTOP) {
1783         InkscapeWindow *window = SP_ACTIVE_DESKTOP->getInkscapeWindow();
1784         if (window) { // during load, SP_ACTIVE_DESKTOP may be !nullptr, but parent might still be nullptr
1785             SPDesktopWidget *dtw = window->get_desktop_widget();
1786             dtw->updateTitle( this->getDocumentName() );
1787         }
1788     }
1789 }
1790 
1791 
1792 /**
1793  * Paste SVG defs from the document retrieved from the clipboard or imported document into the active document.
1794  * @param clipdoc The document to paste.
1795  * @pre @c clipdoc != NULL and pasting into the active document is possible.
1796  */
importDefs(SPDocument * source)1797 void SPDocument::importDefs(SPDocument *source)
1798 {
1799     Inkscape::XML::Node *root = source->getReprRoot();
1800     Inkscape::XML::Node *target_defs = this->getDefs()->getRepr();
1801     std::vector<Inkscape::XML::Node const *> defsNodes = sp_repr_lookup_name_many(root, "svg:defs");
1802 
1803     prevent_id_clashes(source, this);
1804 
1805     for (auto & defsNode : defsNodes) {
1806        _importDefsNode(source, const_cast<Inkscape::XML::Node *>(defsNode), target_defs);
1807     }
1808 }
1809 
_importDefsNode(SPDocument * source,Inkscape::XML::Node * defs,Inkscape::XML::Node * target_defs)1810 void SPDocument::_importDefsNode(SPDocument *source, Inkscape::XML::Node *defs, Inkscape::XML::Node *target_defs)
1811 {
1812     int stagger=0;
1813 
1814     /*  Note, "clipboard" throughout the comments means "the document that is either the clipboard
1815         or an imported document", as importDefs is called in both contexts.
1816 
1817         The order of the records in the clipboard is unpredictable and there may be both
1818         forward and backwards references to other records within it.  There may be definitions in
1819         the clipboard that duplicate definitions in the present document OR that duplicate other
1820         definitions in the clipboard.  (Inkscape will not have created these, but they may be read
1821         in from other SVG sources.)
1822 
1823         There are 3 passes to clean this up:
1824 
1825         In the first find and mark definitions in the clipboard that are duplicates of those in the
1826         present document.  Change the ID to "RESERVED_FOR_INKSCAPE_DUPLICATE_DEF_XXXXXXXXX".
1827         (Inkscape will not reuse an ID, and the XXXXXXXXX keeps it from automatically creating new ones.)
1828         References in the clipboard to the old clipboard name are converted to the name used
1829         in the current document.
1830 
1831         In the second find and mark definitions in the clipboard that are duplicates of earlier
1832         definitions in the clipbard.  Unfortunately this is O(n^2) and could be very slow for a large
1833         SVG with thousands of definitions.  As before, references are adjusted to reflect the name
1834         going forward.
1835 
1836         In the final cycle copy over those records not marked with that ID.
1837 
1838         If an SVG file uses the special ID it will cause problems!
1839 
1840         If this function is called because of the paste of a true clipboard the caller will have passed in a
1841         COPY of the clipboard items.  That is good, because this routine modifies that document.  If the calling
1842         behavior ever changes, so that the same document is passed in on multiple pastes, this routine will break
1843         as in the following example:
1844         1.  Paste clipboard containing B same as A into document containing A.  Result, B is dropped
1845         and all references to it will point to A.
1846         2.  Paste same clipboard into a new document.  It will not contain A, so there will be unsatisfied
1847         references in that window.
1848     */
1849 
1850     std::string DuplicateDefString = "RESERVED_FOR_INKSCAPE_DUPLICATE_DEF";
1851 
1852     /* First pass: remove duplicates in clipboard of definitions in document */
1853     for (Inkscape::XML::Node *def = defs->firstChild() ; def ; def = def->next()) {
1854         if(def->type() != Inkscape::XML::NodeType::ELEMENT_NODE)continue;
1855         /* If this  clipboard has been pasted into one document, and is now being pasted into another,
1856         or pasted again into the same, it will already have been processed.  If we detect that then
1857         skip the rest of this pass. */
1858         Glib::ustring defid = def->attribute("id");
1859         if( defid.find( DuplicateDefString ) != Glib::ustring::npos )break;
1860 
1861         SPObject *src = source->getObjectByRepr(def);
1862 
1863         // Prevent duplicates of solid swatches by checking if equivalent swatch already exists
1864         SPGradient *s_gr = dynamic_cast<SPGradient *>(src);
1865         LivePathEffectObject *s_lpeobj = dynamic_cast<LivePathEffectObject *>(src);
1866         if (src && (s_gr || s_lpeobj)) {
1867             for (auto& trg: getDefs()->children) {
1868                 SPGradient *t_gr = dynamic_cast<SPGradient *>(&trg);
1869                 if (src != &trg && s_gr && t_gr) {
1870                     if (s_gr->isEquivalent(t_gr)) {
1871                         // Change object references to the existing equivalent gradient
1872                         Glib::ustring newid = trg.getId();
1873                         if (newid != defid) { // id could be the same if it is a second paste into the same document
1874                             change_def_references(src, &trg);
1875                         }
1876                         gchar *longid = g_strdup_printf("%s_%9.9d", DuplicateDefString.c_str(), stagger++);
1877                         def->setAttribute("id", longid);
1878                         g_free(longid);
1879                         // do NOT break here, there could be more than 1 duplicate!
1880                     }
1881                 }
1882                 LivePathEffectObject *t_lpeobj = dynamic_cast<LivePathEffectObject *>(&trg);
1883                 if (src != &trg && s_lpeobj && t_lpeobj) {
1884                     if (t_lpeobj->is_similar(s_lpeobj)) {
1885                         // Change object references to the existing equivalent gradient
1886                         Glib::ustring newid = trg.getId();
1887                         if (newid != defid) { // id could be the same if it is a second paste into the same document
1888                             change_def_references(src, &trg);
1889                         }
1890                         gchar *longid = g_strdup_printf("%s_%9.9d", DuplicateDefString.c_str(), stagger++);
1891                         def->setAttribute("id", longid);
1892                         g_free(longid);
1893                         // do NOT break here, there could be more than 1 duplicate!
1894                     }
1895                 }
1896             }
1897         }
1898     }
1899 
1900     /* Second pass: remove duplicates in clipboard of earlier definitions in clipboard */
1901     for (Inkscape::XML::Node *def = defs->firstChild() ; def ; def = def->next()) {
1902         if(def->type() != Inkscape::XML::NodeType::ELEMENT_NODE)continue;
1903         Glib::ustring defid = def->attribute("id");
1904         if( defid.find( DuplicateDefString ) != Glib::ustring::npos )continue; // this one already handled
1905         SPObject *src = source->getObjectByRepr(def);
1906         LivePathEffectObject *s_lpeobj = dynamic_cast<LivePathEffectObject *>(src);
1907         SPGradient *s_gr = dynamic_cast<SPGradient *>(src);
1908         if (src && (s_gr || s_lpeobj)) {
1909             for (Inkscape::XML::Node *laterDef = def->next() ; laterDef ; laterDef = laterDef->next()) {
1910                 SPObject *trg = source->getObjectByRepr(laterDef);
1911                 SPGradient *t_gr = dynamic_cast<SPGradient *>(trg);
1912                 if (trg && (src != trg) && s_gr && t_gr) {
1913                     Glib::ustring newid = trg->getId();
1914                     if (newid.find(DuplicateDefString) != Glib::ustring::npos)
1915                         continue; // this one already handled
1916                     if (t_gr && s_gr->isEquivalent(t_gr)) {
1917                         // Change object references to the existing equivalent gradient
1918                         // two id's in the clipboard should never be the same, so always change references
1919                         change_def_references(trg, src);
1920                         gchar *longid = g_strdup_printf("%s_%9.9d", DuplicateDefString.c_str(), stagger++);
1921                         laterDef->setAttribute("id", longid);
1922                         g_free(longid);
1923                         // do NOT break here, there could be more than 1 duplicate!
1924                     }
1925                 }
1926                 LivePathEffectObject *t_lpeobj = dynamic_cast<LivePathEffectObject *>(trg);
1927                 if (trg && (src != trg) && s_lpeobj && t_lpeobj) {
1928                     Glib::ustring newid = trg->getId();
1929                     if (newid.find(DuplicateDefString) != Glib::ustring::npos)
1930                         continue; // this one already handled
1931                     if (t_lpeobj->is_similar(s_lpeobj)) {
1932                         // Change object references to the existing equivalent gradient
1933                         // two id's in the clipboard should never be the same, so always change references
1934                         change_def_references(trg, src);
1935                         gchar *longid = g_strdup_printf("%s_%9.9d", DuplicateDefString.c_str(), stagger++);
1936                         laterDef->setAttribute("id", longid);
1937                         g_free(longid);
1938                         // do NOT break here, there could be more than 1 duplicate!
1939                     }
1940                 }
1941             }
1942         }
1943     }
1944 
1945     /* Final pass: copy over those parts which are not duplicates  */
1946     for (Inkscape::XML::Node *def = defs->firstChild() ; def ; def = def->next()) {
1947         if(def->type() != Inkscape::XML::NodeType::ELEMENT_NODE)continue;
1948 
1949         /* Ignore duplicate defs marked in the first pass */
1950         Glib::ustring defid = def->attribute("id");
1951         if( defid.find( DuplicateDefString ) != Glib::ustring::npos )continue;
1952 
1953         bool duplicate = false;
1954         SPObject *src = source->getObjectByRepr(def);
1955 
1956         // Prevent duplication of symbols... could be more clever.
1957         // The tag "_inkscape_duplicate" is added to "id" by ClipboardManagerImpl::copySymbol().
1958         // We assume that symbols are in defs section (not required by SVG spec).
1959         if (src && SP_IS_SYMBOL(src)) {
1960 
1961             Glib::ustring id = src->getRepr()->attribute("id");
1962             size_t pos = id.find( "_inkscape_duplicate" );
1963             if( pos != Glib::ustring::npos ) {
1964 
1965                 // This is our symbol, now get rid of tag
1966                 id.erase( pos );
1967 
1968                 // Check that it really is a duplicate
1969                 for (auto& trg: getDefs()->children) {
1970                     if (SP_IS_SYMBOL(&trg) && src != &trg) {
1971                         std::string id2 = trg.getRepr()->attribute("id");
1972 
1973                         if( !id.compare( id2 ) ) {
1974                             duplicate = true;
1975                             break;
1976                         }
1977                     }
1978                 }
1979                 if ( !duplicate ) {
1980                     src->setAttribute("id", id);
1981                 }
1982             }
1983         }
1984 
1985         if (!duplicate) {
1986             Inkscape::XML::Node * dup = def->duplicate(this->getReprDoc());
1987             target_defs->appendChild(dup);
1988             Inkscape::GC::release(dup);
1989         }
1990     }
1991 }
1992 
1993 // Signals ------------------------------
1994 
1995 void
addUndoObserver(Inkscape::UndoStackObserver & observer)1996 SPDocument::addUndoObserver(Inkscape::UndoStackObserver& observer)
1997 {
1998     this->undoStackObservers.add(observer);
1999 }
2000 
2001 void
removeUndoObserver(Inkscape::UndoStackObserver & observer)2002 SPDocument::removeUndoObserver(Inkscape::UndoStackObserver& observer)
2003 {
2004     this->undoStackObservers.remove(observer);
2005 }
2006 
connectDestroy(sigc::signal<void>::slot_type slot)2007 sigc::connection SPDocument::connectDestroy(sigc::signal<void>::slot_type slot)
2008 {
2009     return destroySignal.connect(slot);
2010 }
2011 
connectModified(SPDocument::ModifiedSignal::slot_type slot)2012 sigc::connection SPDocument::connectModified(SPDocument::ModifiedSignal::slot_type slot)
2013 {
2014     return modified_signal.connect(slot);
2015 }
2016 
connectURISet(SPDocument::URISetSignal::slot_type slot)2017 sigc::connection SPDocument::connectURISet(SPDocument::URISetSignal::slot_type slot)
2018 {
2019     return uri_set_signal.connect(slot);
2020 }
2021 
connectResized(SPDocument::ResizedSignal::slot_type slot)2022 sigc::connection SPDocument::connectResized(SPDocument::ResizedSignal::slot_type slot)
2023 {
2024     return resized_signal.connect(slot);
2025 }
2026 
connectCommit(SPDocument::CommitSignal::slot_type slot)2027 sigc::connection SPDocument::connectCommit(SPDocument::CommitSignal::slot_type slot)
2028 {
2029     return commit_signal.connect(slot);
2030 }
2031 
connectIdChanged(gchar const * id,SPDocument::IDChangedSignal::slot_type slot)2032 sigc::connection SPDocument::connectIdChanged(gchar const *id,
2033                                               SPDocument::IDChangedSignal::slot_type slot)
2034 {
2035     return id_changed_signals[g_quark_from_string(id)].connect(slot);
2036 }
2037 
connectResourcesChanged(gchar const * key,SPDocument::ResourcesChangedSignal::slot_type slot)2038 sigc::connection SPDocument::connectResourcesChanged(gchar const *key,
2039                                                      SPDocument::ResourcesChangedSignal::slot_type slot)
2040 {
2041     GQuark q = g_quark_from_string(key);
2042     return resources_changed_signals[q].connect(slot);
2043 }
2044 
2045 sigc::connection
connectReconstructionStart(SPDocument::ReconstructionStart::slot_type slot)2046 SPDocument::connectReconstructionStart(SPDocument::ReconstructionStart::slot_type slot)
2047 {
2048     return _reconstruction_start_signal.connect(slot);
2049 }
2050 
2051 sigc::connection
connectReconstructionFinish(SPDocument::ReconstructionFinish::slot_type slot)2052 SPDocument::connectReconstructionFinish(SPDocument::ReconstructionFinish::slot_type  slot)
2053 {
2054     return _reconstruction_finish_signal.connect(slot);
2055 }
2056 
_emitModified()2057 void SPDocument::_emitModified() {
2058     static guint const flags = SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG;
2059     root->emitModified(0);
2060     modified_signal.emit(flags);
2061     _node_cache_valid=false;
2062 }
2063 
2064 void
emitReconstructionStart()2065 SPDocument::emitReconstructionStart()
2066 {
2067     // printf("Starting Reconstruction\n");
2068     _reconstruction_start_signal.emit();
2069 }
2070 
2071 void
emitReconstructionFinish()2072 SPDocument::emitReconstructionFinish()
2073 {
2074     // printf("Finishing Reconstruction\n");
2075     _reconstruction_finish_signal.emit();
2076     // indicates that gradients are reloaded (to rebuild the Auto palette)
2077     resources_changed_signals[g_quark_from_string("gradient")].emit();
2078     resources_changed_signals[g_quark_from_string("filter")].emit();
2079 
2080 /**
2081     // Reference to the old persp3d object is invalid after reconstruction.
2082     initialize_current_persp3d();
2083 **/
2084 }
2085 
emitResizedSignal(gdouble width,gdouble height)2086 void SPDocument::emitResizedSignal(gdouble width, gdouble height)
2087 {
2088     this->resized_signal.emit(width, height);
2089 }
2090 
2091 
2092 /*
2093   Local Variables:
2094   mode:c++
2095   c-file-style:"stroustrup"
2096   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2097   indent-tabs-mode:nil
2098   fill-column:99
2099   End:
2100 */
2101 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
2102