diff --git a/src/ui/clipboard.cpp b/src/ui/clipboard.cpp index 0de8fef7dfa9bda17c42c90a5b8f0ad8afe9513e..d6f850a2f793fadc24548eab9a4ae94cc687274b 100644 --- a/src/ui/clipboard.cpp +++ b/src/ui/clipboard.cpp @@ -208,14 +208,9 @@ private: void _cleanStyle(SPCSSAttr *); void _copySelection(ObjectSet *); void _copyCompleteStyle(SPItem *item, Inkscape::XML::Node *target, bool child = false); - void _copyUsedDefs(SPItem *); - void _copyGradient(SPGradient *); - void _copyPattern(SPPattern *); - void _copyHatch(SPHatch *); - void _copyTextPath(SPTextPath *); + void _copyUsedDefs(std::vector const &); bool _copyNodes(SPDesktop *desktop, ObjectSet *set); Inkscape::XML::Node *_copyNode(Inkscape::XML::Node *, Inkscape::XML::Document *, Inkscape::XML::Node *); - Inkscape::XML::Node *_copyIgnoreDup(Inkscape::XML::Node *, Inkscape::XML::Document *, Inkscape::XML::Node *); bool _pasteImage(SPDocument *doc); bool _pasteText(SPDesktop *desktop); @@ -243,7 +238,6 @@ private: Inkscape::XML::Node *_root; ///< Reference to the clipboard's root node Inkscape::XML::Node *_clipnode; ///< The node that holds extra information Inkscape::XML::Document *_doc; ///< Reference to the clipboard's Inkscape::XML::Document - std::set cloned_elements; std::vector te_selected_style; std::vector te_selected_style_positions; @@ -390,7 +384,7 @@ void ClipboardManagerImpl::copySymbol(Inkscape::XML::Node* symbol, gchar const* // bypasses the "prevent_id_classes" routine. We'll get rid of it // when we paste. auto original = cast(source->getObjectByRepr(symbol)); - _copyUsedDefs(original); + _copyUsedDefs({original}); Inkscape::XML::Node *repr = symbol->duplicate(_doc); Glib::ustring symbol_name; // disambiguate symbols from various symbol sets @@ -1064,6 +1058,20 @@ std::vector ClipboardManagerImpl::getElementsOfType(SPDesktop *de return result; } +/** + * Get the page from first item in the selection which is liked to a page. + */ +static SPPage *get_first_page_from_items(ObjectSet *selection) +{ + for (auto *item : selection->items()) { + if (auto *page = item->document->getPageManager().getPageFor(item, false)) { + return page; + } + } + + return nullptr; +} + /** * Iterate over a list of items and copy them to the clipboard. */ @@ -1071,94 +1079,40 @@ void ClipboardManagerImpl::_copySelection(ObjectSet *selection) { auto prefs = Preferences::get(); auto const copy_computed = prefs->getBool("/options/copycomputedstyle/value", true); - SPPage *page = nullptr; - // copy the defs used by all items - auto itemlist = selection->items(); - cloned_elements.clear(); - std::vector items(itemlist.begin(), itemlist.end()); - for (auto item : itemlist) { - if (!page) { - page = item->document->getPageManager().getPageFor(item, false); - } - auto lpeitem = cast(item); - if (lpeitem) { - for (auto satellite : lpeitem->get_satellites(false, true)) { - if (satellite) { - auto item2 = cast(satellite); - if (item2 && std::find(items.begin(), items.end(), item2) == items.end()) { - items.push_back(item2); - } - } - } - } - } - cloned_elements.clear(); - for (auto item : items) { - if (item) { - _copyUsedDefs(item); - } else { - g_assert_not_reached(); - } - } - - // copy the representation of the items - std::vector sorted_items(items.begin(), items.end()); - { - // Get external text references and add them to sorted_items - auto ext_refs = text_categorize_refs(selection->document(), - sorted_items.begin(), sorted_items.end(), - TEXT_REF_EXTERNAL); - for (auto const &ext_ref : ext_refs) { - sorted_items.push_back(selection->document()->getObjectById(ext_ref.first)); - } - } + std::vector sorted_items = selection->items_vector(); sort(sorted_items.begin(), sorted_items.end(), sp_object_compare_position_bool); - //remove already copied elements from cloned_elements - std::vectortr; - for(auto cloned_element : cloned_elements){ - if(std::find(sorted_items.begin(),sorted_items.end(),cloned_element)!=sorted_items.end()) - tr.push_back(cloned_element); - } - for(auto & it : tr){ - cloned_elements.erase(it); - } + // copy the defs used by all items + _copyUsedDefs(sorted_items); // One group per shared parent std::map groups; - sorted_items.insert(sorted_items.end(),cloned_elements.begin(),cloned_elements.end()); - for(auto sorted_item : sorted_items){ - auto item = cast(sorted_item); - if (item) { - // Create a group with the parent transform. This group will be ungrouped when pasting - // und takes care of transform relationships of clones, text-on-path, etc. - auto &group = groups[item->parent]; - if (!group) { - group = _doc->createElement("svg:g"); - _root->appendChild(group); - Inkscape::GC::release(group); - - if (auto parent = cast(item->parent)) { - auto transform_str = sp_svg_transform_write(parent->i2doc_affine()); - group->setAttributeOrRemoveIfEmpty("transform", transform_str); - } + // copy the representation of the items + for (auto *item : sorted_items) { + // Create a group with the parent transform. This group will be ungrouped when pasting + // and takes care of transform relationships of clones, text-on-path, etc. + auto &group = groups[item->parent]; + if (!group) { + group = _doc->createElement("svg:g"); + _root->appendChild(group); + Inkscape::GC::release(group); + + if (auto parent = cast(item->parent)) { + auto transform_str = sp_svg_transform_write(parent->i2doc_affine()); + group->setAttributeOrRemoveIfEmpty("transform", transform_str); } + } - Inkscape::XML::Node *obj = item->getRepr(); - Inkscape::XML::Node *obj_copy; - if(cloned_elements.find(item)==cloned_elements.end()) - obj_copy = _copyNode(obj, _doc, group); - else - obj_copy = _copyNode(obj, _doc, _clipnode); + Inkscape::XML::Node *obj_copy = _copyNode(item->getRepr(), _doc, group); - if (copy_computed) { - // copy complete inherited style - _copyCompleteStyle(item, obj_copy); - } + if (copy_computed) { + // copy complete inherited style + _copyCompleteStyle(item, obj_copy); } } + // copy style for Paste Style action if (auto item = selection->singleItem()) { if (copy_computed) { @@ -1185,7 +1139,7 @@ void ClipboardManagerImpl::_copySelection(ObjectSet *selection) _clipnode->setAttributePoint("geom-min", geom_size->min()); _clipnode->setAttributePoint("geom-max", geom_size->max()); } - if (page) { + if (auto *page = get_first_page_from_items(selection)) { auto page_rect = page->getDesktopRect(); _clipnode->setAttributePoint("page-min", page_rect.min()); _clipnode->setAttributePoint("page-max", page_rect.max()); @@ -1232,228 +1186,113 @@ void ClipboardManagerImpl::_copyCompleteStyle(SPItem *item, Inkscape::XML::Node } /** - * Recursively copy all the definitions used by a given item to the clipboard defs. + * True if obj is a child of a element */ -void ClipboardManagerImpl::_copyUsedDefs(SPItem *item) +static bool has_defs_anchestor(SPObject const *obj) { - bool recurse = true; - - if (auto use = cast(item)) { - if (auto original = use->get_original()) { - if (original->document != use->document) { - recurse = false; - } else { - cloned_elements.insert(original); - } + for (auto *ancestor = obj->parent; ancestor; ancestor = ancestor->parent) { + if (is(ancestor)) { + return true; } } + return false; +} - // copy fill and stroke styles (patterns and gradients) - SPStyle *style = item->style; - - if (style && (style->fill.isPaintserver())) { - SPPaintServer *server = item->style->getFillPaintServer(); - if (is(server) || is(server) || is(server) ) { - _copyGradient(cast(server)); - } - auto pattern = cast(server); - if (pattern) { - _copyPattern(pattern); - } - auto hatch = cast(server); - if (hatch) { - _copyHatch(hatch); - } - } - if (style && (style->stroke.isPaintserver())) { - SPPaintServer *server = item->style->getStrokePaintServer(); - if (is(server) || is(server) || is(server) ) { - _copyGradient(cast(server)); - } - auto pattern = cast(server); - if (pattern) { - _copyPattern(pattern); - } - auto hatch = cast(server); - if (hatch) { - _copyHatch(hatch); - } - } +/** + * Build a mapping from "owner" objects to their directly linked dependencies + * inside the given document. + * + * @pre Document is up-to-date + */ +static auto buildLinkedDependenciesMapping(SPDocument *document) +{ + std::map> mapping; - // For shapes, copy all of the shape's markers - auto shape = cast(item); - if (shape) { - for (auto & i : shape->_marker) { - if (i) { - _copyNode(i->getRepr(), _doc, _defs); + std::function const traverse = [&](SPObject *obj) { + if (!obj->cloned) { + for (auto const *owner : obj->hrefList) { + mapping[owner].push_back(obj); + } + for (auto &child : obj->children) { + traverse(&child); } } - } + }; - // For 3D boxes, copy perspectives - if (auto box = cast(item)) { - if (auto perspective = box->get_perspective()) { - _copyNode(perspective->getRepr(), _doc, _defs); - } - } + traverse(document->getRoot()); - // Copy text paths - { - auto text = cast(item); - SPTextPath *textpath = text ? cast(text->firstChild()) : nullptr; - if (textpath) { - _copyTextPath(textpath); - } - if (text) { - for (auto &&shape_prop_ptr : { - reinterpret_cast(&SPStyle::shape_inside), - reinterpret_cast(&SPStyle::shape_subtract) }) { - for (auto *href : (text->style->*shape_prop_ptr).hrefs) { - auto shape_obj = href->getObject(); - if (!shape_obj) - continue; - auto shape_repr = shape_obj->getRepr(); - if (sp_repr_is_def(shape_repr)) { - _copyIgnoreDup(shape_repr, _doc, _defs); - } - } - } - } - } + return mapping; +} - // Copy clipping objects - if (SPObject *clip = item->getClipObject()) { - _copyNode(clip->getRepr(), _doc, _defs); - // recurse - for (auto &o : clip->children) { - if (auto childItem = cast(&o)) { - _copyUsedDefs(childItem); - } - } - } - // Copy mask objects - if (SPObject *mask = item->getMaskObject()) { - _copyNode(mask->getRepr(), _doc, _defs); - // recurse into the mask for its gradients etc. - for(auto& o: mask->children) { - auto childItem = cast(&o); - if (childItem) { - _copyUsedDefs(childItem); - } - } +/** + * Collect all elements which are referenced (directly or indirectly) by the + * given items, are in the same document, and are not in the items list itself. + */ +static std::vector collectLinkedDependencies(std::vector const &items) +{ + if (items.empty()) { + return {}; } - // Copy filters - if (style->getFilter()) { - SPObject *filter = style->getFilter(); - if (is(filter)) { - _copyNode(filter->getRepr(), _doc, _defs); - } - } + std::set done_elements; + std::vector ref_elements; - // For lpe items, copy lpe stack if applicable - auto lpeitem = cast(item); - if (lpeitem) { - if (lpeitem->hasPathEffect()) { - PathEffectList path_effect_list( *lpeitem->path_effect_list); - for (auto &lperef : path_effect_list) { - LivePathEffectObject *lpeobj = lperef->lpeobject; - if (lpeobj) { - _copyNode(lpeobj->getRepr(), _doc, _defs); - } + std::function const add_to_done_recursively = [&](SPObject const *obj) { + if (!obj->cloned) { + done_elements.insert(obj); + for (auto &child : obj->children) { + add_to_done_recursively(&child); } } - } + }; - if (!recurse) { - return; + for (auto *item : items) { + add_to_done_recursively(item); } - // recurse - for(auto& o: item->children) { - auto childItem = cast(&o); - if (childItem) { - _copyUsedDefs(childItem); - } - } -} + auto back_references = buildLinkedDependenciesMapping(items.front()->document); -/** - * Copy a single gradient to the clipboard's defs element. - */ -void ClipboardManagerImpl::_copyGradient(SPGradient *gradient) -{ - while (gradient) { - // climb up the refs, copying each one in the chain - _copyNode(gradient->getRepr(), _doc, _defs); - if (gradient->ref){ - gradient = gradient->ref->getObject(); - } - else { - gradient = nullptr; - } - } -} + auto prune_descendants_of = [&ref_elements](SPObject *ref) { + auto is_descendant_of_ref = [ref](SPObject const *obj) { return ref->isAncestorOf(obj); }; + ref_elements.erase(std::remove_if(ref_elements.begin(), ref_elements.end(), is_descendant_of_ref), + ref_elements.end()); + }; -/** - * Copy a single pattern to the clipboard document's defs element. - */ -void ClipboardManagerImpl::_copyPattern(SPPattern *pattern) -{ - // climb up the references, copying each one in the chain - while (pattern) { - _copyNode(pattern->getRepr(), _doc, _defs); - - // items in the pattern may also use gradients and other patterns, so recurse - for (auto& child: pattern->children) { - auto childItem = cast(&child); - if (childItem) { - _copyUsedDefs(childItem); + for (bool repeat = true; repeat;) { + repeat = false; + for (auto *obj : decltype(done_elements)(done_elements)) { + for (auto *ref : back_references[obj]) { + if (!done_elements.count(ref)) { + add_to_done_recursively(ref); + prune_descendants_of(ref); + ref_elements.push_back(ref); + repeat = true; + } } } - pattern = pattern->ref.getObject(); } + + return ref_elements; } /** - * Copy a single hatch to the clipboard document's defs element. + * Copy all the definitions used by the given items to the clipboard defs. */ -void ClipboardManagerImpl::_copyHatch(SPHatch *hatch) +void ClipboardManagerImpl::_copyUsedDefs(std::vector const & items) { - // climb up the references, copying each one in the chain - while (hatch) { - _copyNode(hatch->getRepr(), _doc, _defs); - - for (auto &child : hatch->children) { - auto childItem = cast(&child); - if (childItem) { - _copyUsedDefs(childItem); - } - } - if (hatch->ref) { - hatch = hatch->ref->getObject(); + auto ref_elements = collectLinkedDependencies(items); + + sort(ref_elements.begin(), ref_elements.end(), sp_object_compare_position_bool); + + for (auto *obj : ref_elements) { + if (has_defs_anchestor(obj)) { + _copyNode(obj->getRepr(), _doc, _defs); } else { - hatch = nullptr; + _copyNode(obj->getRepr(), _doc, _clipnode); } } } -/** - * Copy a text path to the clipboard's defs element. - */ -void ClipboardManagerImpl::_copyTextPath(SPTextPath *tp) -{ - SPItem *path = sp_textpath_get_path_item(tp); - if (!path) { - return; - } - // textpaths that aren't in defs (on the canvas) shouldn't be copied because if - // both objects are being copied already, this ends up stealing the refs id. - if(path->parent && is(path->parent)) { - _copyIgnoreDup(path->getRepr(), _doc, _defs); - } -} - /** * Copy a single XML node from one document to another. * @param node The node to be copied @@ -1469,18 +1308,6 @@ Inkscape::XML::Node *ClipboardManagerImpl::_copyNode(Inkscape::XML::Node *node, return dup; } -Inkscape::XML::Node *ClipboardManagerImpl::_copyIgnoreDup(Inkscape::XML::Node *node, Inkscape::XML::Document *target_doc, Inkscape::XML::Node *parent) -{ - if (sp_repr_lookup_child(_root, "id", node->attribute("id"))) { - // node already copied - return nullptr; - } - Inkscape::XML::Node *dup = node->duplicate(target_doc); - parent->appendChild(dup); - Inkscape::GC::release(dup); - return dup; -} - /** * Retrieve a bitmap image from the clipboard and paste it into the active document. */