From 267299811df952d08324a39008f52c19641de9e0 Mon Sep 17 00:00:00 2001 From: Tavmjong Bah Date: Tue, 30 Jan 2018 09:33:01 +0100 Subject: Move classes derived from SPObject to own directory. A lot of header clean-up. --- src/object/sp-item-group.cpp | 1022 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1022 insertions(+) create mode 100644 src/object/sp-item-group.cpp (limited to 'src/object/sp-item-group.cpp') diff --git a/src/object/sp-item-group.cpp b/src/object/sp-item-group.cpp new file mode 100644 index 000000000..73c1dcb6c --- /dev/null +++ b/src/object/sp-item-group.cpp @@ -0,0 +1,1022 @@ +/* + * SVG implementation + * + * Authors: + * Lauris Kaplinski + * bulia byak + * Johan Engelen + * Jon A. Cruz + * Abhishek Sharma + * + * Copyright (C) 1999-2006 authors + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include "attributes.h" +#include "document.h" +#include "document-undo.h" +#include "selection-chemistry.h" +#include "verbs.h" + +#include "display/drawing-group.h" +#include "display/curve.h" +#include "live_effects/effect.h" +#include "live_effects/lpeobject.h" +#include "live_effects/lpeobject-reference.h" +#include "svg/svg.h" +#include "svg/css-ostringstream.h" +#include "xml/repr.h" +#include "xml/sp-css-attr.h" + +#include "box3d.h" +#include "persp3d.h" +#include "sp-defs.h" +#include "sp-item-transform.h" +#include "sp-root.h" +#include "sp-rect.h" +#include "sp-offset.h" +#include "sp-clippath.h" +#include "sp-mask.h" +#include "sp-path.h" +#include "sp-use.h" +#include "sp-title.h" +#include "sp-desc.h" +#include "sp-switch.h" +#include "sp-textpath.h" +#include "sp-flowtext.h" +#include "style.h" + +using Inkscape::DocumentUndo; + +static void sp_group_perform_patheffect(SPGroup *group, SPGroup *top_group, bool write); + +SPGroup::SPGroup() : SPLPEItem(), + _expanded(false), + _insert_bottom(false), + _layer_mode(SPGroup::GROUP) +{ +} + +SPGroup::~SPGroup() { +} + +void SPGroup::build(SPDocument *document, Inkscape::XML::Node *repr) { + this->readAttr( "inkscape:groupmode" ); + + SPLPEItem::build(document, repr); +} + +void SPGroup::release() { + if (this->_layer_mode == SPGroup::LAYER) { + this->document->removeResource("layer", this); + } + + SPLPEItem::release(); +} + +void SPGroup::child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref) { + SPLPEItem::child_added(child, ref); + + SPObject *last_child = this->lastChild(); + + if (last_child && last_child->getRepr() == child) { + // optimization for the common special case where the child is being added at the end + SPItem *item = dynamic_cast(last_child); + if ( item ) { + /* TODO: this should be moved into SPItem somehow */ + SPItemView *v; + + for (v = this->display; v != NULL; v = v->next) { + Inkscape::DrawingItem *ac = item->invoke_show (v->arenaitem->drawing(), v->key, v->flags); + + if (ac) { + v->arenaitem->appendChild(ac); + } + } + } + } else { // general case + SPItem *item = dynamic_cast(get_child_by_repr(child)); + if ( item ) { + /* TODO: this should be moved into SPItem somehow */ + SPItemView *v; + unsigned position = item->pos_in_parent(); + + for (v = this->display; v != NULL; v = v->next) { + Inkscape::DrawingItem *ac = item->invoke_show (v->arenaitem->drawing(), v->key, v->flags); + + if (ac) { + v->arenaitem->prependChild(ac); + ac->setZOrder(position); + } + } + } + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +/* fixme: hide (Lauris) */ + +void SPGroup::remove_child(Inkscape::XML::Node *child) { + SPLPEItem::remove_child(child); + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +void SPGroup::order_changed (Inkscape::XML::Node *child, Inkscape::XML::Node *old_ref, Inkscape::XML::Node *new_ref) +{ + SPLPEItem::order_changed(child, old_ref, new_ref); + + SPItem *item = dynamic_cast(get_child_by_repr(child)); + if ( item ) { + /* TODO: this should be moved into SPItem somehow */ + SPItemView *v; + unsigned position = item->pos_in_parent(); + for ( v = item->display ; v != NULL ; v = v->next ) { + v->arenaitem->setZOrder(position); + } + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +void SPGroup::update(SPCtx *ctx, unsigned int flags) { + // std::cout << "SPGroup::update(): " << (getId()?getId():"null") << std::endl; + SPItemCtx *ictx, cctx; + + ictx = (SPItemCtx *) ctx; + cctx = *ictx; + + unsigned childflags = flags; + + if (flags & SP_OBJECT_MODIFIED_FLAG) { + childflags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + childflags &= SP_OBJECT_MODIFIED_CASCADE; + std::vector l=this->childList(true, SPObject::ActionUpdate); + for(std::vector ::const_iterator i=l.begin();i!=l.end();++i){ + SPObject *child = *i; + + if (childflags || (child->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + SPItem *item = dynamic_cast(child); + if (item) { + cctx.i2doc = item->transform * ictx->i2doc; + cctx.i2vp = item->transform * ictx->i2vp; + child->updateDisplay((SPCtx *)&cctx, childflags); + } else { + child->updateDisplay(ctx, childflags); + } + } + + sp_object_unref(child); + } + + // For a group, we need to update ourselves *after* updating children. + // this is because the group might contain shapes such as rect or ellipse, + // which recompute their equivalent path (a.k.a curve) in the update callback, + // and this is in turn used when computing bbox. + SPLPEItem::update(ctx, flags); + + if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) { + for (SPItemView *v = this->display; v != NULL; v = v->next) { + Inkscape::DrawingGroup *group = dynamic_cast(v->arenaitem); + if( this->parent ) { + this->context_style = this->parent->context_style; + } + group->setStyle(this->style, this->context_style); + } + } +} + +void SPGroup::modified(guint flags) { + // std::cout << "SPGroup::modified(): " << (getId()?getId():"null") << std::endl; + SPLPEItem::modified(flags); + if (flags & SP_OBJECT_MODIFIED_FLAG) { + flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + flags &= SP_OBJECT_MODIFIED_CASCADE; + + if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) { + for (SPItemView *v = this->display; v != NULL; v = v->next) { + Inkscape::DrawingGroup *group = dynamic_cast(v->arenaitem); + group->setStyle(this->style); + } + } + + std::vector l=this->childList(true); + for(std::vector::const_iterator i=l.begin();i!=l.end();++i){ + SPObject *child = *i; + + if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(flags); + } + + sp_object_unref(child); + } +} + +Inkscape::XML::Node* SPGroup::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if (flags & SP_OBJECT_WRITE_BUILD) { + std::vector l; + + if (!repr) { + if (dynamic_cast(this)) { + repr = xml_doc->createElement("svg:switch"); + } else { + repr = xml_doc->createElement("svg:g"); + } + } + + for (auto& child: children) { + if ( !dynamic_cast(&child) && !dynamic_cast(&child) ) { + Inkscape::XML::Node *crepr = child.updateRepr(xml_doc, NULL, flags); + + if (crepr) { + l.push_back(crepr); + } + } + } + for (auto i=l.rbegin();i!=l.rend();++i) { + repr->addChild(*i, NULL); + Inkscape::GC::release(*i); + } + } else { + for (auto& child: children) { + if ( !dynamic_cast(&child) && !dynamic_cast(&child) ) { + child.updateRepr(flags); + } + } + } + + if ( flags & SP_OBJECT_WRITE_EXT ) { + const char *value; + if ( _layer_mode == SPGroup::LAYER ) { + value = "layer"; + } else if ( _layer_mode == SPGroup::MASK_HELPER ) { + value = "maskhelper"; + } else if ( flags & SP_OBJECT_WRITE_ALL ) { + value = "group"; + } else { + value = NULL; + } + + repr->setAttribute("inkscape:groupmode", value); + } + + SPLPEItem::write(xml_doc, repr, flags); + + return repr; +} + +Geom::OptRect SPGroup::bbox(Geom::Affine const &transform, SPItem::BBoxType bboxtype) const +{ + Geom::OptRect bbox; + + // TODO CPPIFY: replace this const_cast later + std::vector l = const_cast(this)->childList(false, SPObject::ActionBBox); + for(std::vector::const_iterator i=l.begin();i!=l.end();++i){ + SPObject *o = *i; + SPItem *item = dynamic_cast(o); + if (item && !item->isHidden()) { + Geom::Affine const ct(item->transform * transform); + bbox |= item->bounds(bboxtype, ct); + } + } + + return bbox; +} + +void SPGroup::print(SPPrintContext *ctx) { + for(auto& child: children){ + SPObject *o = &child; + SPItem *item = dynamic_cast(o); + if (item) { + item->invoke_print(ctx); + } + } +} + +const char *SPGroup::displayName() const { + return _("Group"); +} + +gchar *SPGroup::description() const { + gint len = this->getItemCount(); + return g_strdup_printf( + ngettext(_("of %d object"), _("of %d objects"), len), len); +} + +void SPGroup::set(unsigned int key, gchar const* value) { + switch (key) { + case SP_ATTR_INKSCAPE_GROUPMODE: + if ( value && !strcmp(value, "layer") ) { + this->setLayerMode(SPGroup::LAYER); + } else if ( value && !strcmp(value, "maskhelper") ) { + this->setLayerMode(SPGroup::MASK_HELPER); + } else { + this->setLayerMode(SPGroup::GROUP); + } + break; + + default: + SPLPEItem::set(key, value); + break; + } +} + +Inkscape::DrawingItem *SPGroup::show (Inkscape::Drawing &drawing, unsigned int key, unsigned int flags) { + // std::cout << "SPGroup::show(): " << (getId()?getId():"null") << std::endl; + Inkscape::DrawingGroup *ai; + + ai = new Inkscape::DrawingGroup(drawing); + ai->setPickChildren(this->effectiveLayerMode(key) == SPGroup::LAYER); + if( this->parent ) { + this->context_style = this->parent->context_style; + } + ai->setStyle(this->style, this->context_style); + + this->_showChildren(drawing, ai, key, flags); + return ai; +} + +void SPGroup::hide (unsigned int key) { + std::vector l=this->childList(false, SPObject::ActionShow); + for(std::vector::const_iterator i=l.begin();i!=l.end();++i){ + SPObject *o = *i; + + SPItem *item = dynamic_cast(o); + if (item) { + item->invoke_hide(key); + } + } + +// SPLPEItem::onHide(key); +} + + +void SPGroup::snappoints(std::vector &p, Inkscape::SnapPreferences const *snapprefs) const { + for (auto& o: children) + { + SPItem const *item = dynamic_cast(&o); + if (item) { + item->getSnappoints(p, snapprefs); + } + } +} + +void sp_item_group_ungroup_handle_clones(SPItem *parent, Geom::Affine const g) +{ + for(std::list::const_iterator refd=parent->hrefList.begin();refd!=parent->hrefList.end();++refd){ + SPItem *citem = dynamic_cast(*refd); + if (citem && !citem->cloned) { + SPUse *useitem = dynamic_cast(citem); + if (useitem && useitem->get_original() == parent) { + Geom::Affine ctrans; + ctrans = g.inverse() * citem->transform; + gchar *affinestr = sp_svg_transform_write(ctrans); + citem->setAttribute("transform", affinestr); + g_free(affinestr); + } + } + } +} + +void +sp_recursive_scale_text_size(Inkscape::XML::Node *repr, double scale){ + for (Inkscape::XML::Node *child = repr->firstChild() ; child; child = child->next() ){ + if ( child) { + sp_recursive_scale_text_size(child, scale); + } + } + SPCSSAttr * css = sp_repr_css_attr(repr,"style"); + Glib::ustring element = g_quark_to_string(repr->code()); + if ((css && element == "svg:text") || element == "svg:tspan") { + gchar const *w = sp_repr_css_property(css, "font-size", NULL); + if (w) { + gchar *units = NULL; + double wd = g_ascii_strtod(w, &units); + wd *= scale; + if (w != units) { + Inkscape::CSSOStringStream os; + os << wd << units; // reattach units + sp_repr_css_set_property(css, "font-size", os.str().c_str()); + Glib::ustring css_str; + sp_repr_css_write_string(css,css_str); + repr->setAttribute("style", css_str.c_str()); + } + } + w = NULL; + w = sp_repr_css_property(css, "letter-spacing", NULL); + if (w) { + gchar *units = NULL; + double wd = g_ascii_strtod(w, &units); + wd *= scale; + if (w != units) { + Inkscape::CSSOStringStream os; + os << wd << units; // reattach units + sp_repr_css_set_property(css, "letter-spacing", os.str().c_str()); + Glib::ustring css_str; + sp_repr_css_write_string(css,css_str); + repr->setAttribute("style", css_str.c_str()); + } + } + w = NULL; + w = sp_repr_css_property(css, "word-spacing", NULL); + if (w) { + gchar *units = NULL; + double wd = g_ascii_strtod(w, &units); + wd *= scale; + if (w != units) { + Inkscape::CSSOStringStream os; + os << wd << units; // reattach units + sp_repr_css_set_property(css, "word-spacing", os.str().c_str()); + Glib::ustring css_str; + sp_repr_css_write_string(css,css_str); + repr->setAttribute("style", css_str.c_str()); + } + } + gchar const *dx = repr->attribute("dx"); + if (dx) { + gchar ** dxarray = g_strsplit(dx, " ", 0); + Inkscape::SVGOStringStream dx_data; + while (*dxarray != NULL) { + double pos; + sp_svg_number_read_d(*dxarray, &pos); + pos *= scale; + dx_data << pos << " "; + dxarray++; + } + repr->setAttribute("dx", dx_data.str().c_str()); + } + gchar const *dy = repr->attribute("dy"); + if (dy) { + gchar ** dyarray = g_strsplit(dy, " ", 0); + Inkscape::SVGOStringStream dy_data; + while (*dyarray != NULL) { + double pos; + sp_svg_number_read_d(*dyarray, &pos); + pos *= scale; + dy_data << pos << " "; + dyarray++; + } + repr->setAttribute("dy", dy_data.str().c_str()); + } + } +} + +void +sp_item_group_ungroup (SPGroup *group, std::vector &children, bool do_done) +{ + g_return_if_fail (group != NULL); + + SPDocument *doc = group->document; + SPRoot *root = doc->getRoot(); + SPObject *defs = root->defs; + + Inkscape::XML::Node *grepr = group->getRepr(); + + g_return_if_fail (!strcmp (grepr->name(), "svg:g") + || !strcmp (grepr->name(), "svg:a") + || !strcmp (grepr->name(), "svg:switch") + || !strcmp (grepr->name(), "svg:svg")); + + // this converts the gradient/pattern fill/stroke on the group, if any, to userSpaceOnUse + group->adjust_paint_recursive (Geom::identity(), Geom::identity(), false); + + SPItem *pitem = dynamic_cast(group->parent); + g_assert(pitem); + Inkscape::XML::Node *prepr = pitem->getRepr(); + + { + SPBox3D *box = dynamic_cast(group); + if (box) { + group = box3d_convert_to_group(box); + } + } + + group->removeAllPathEffects(false); + + /* Step 1 - generate lists of children objects */ + std::vector items; + std::vector objects; + Geom::Affine const g(group->transform); + + for (auto& child: group->children) { + if (SPItem *citem = dynamic_cast(&child)) { + sp_item_group_ungroup_handle_clones(citem, g); + } + } + + for (auto& child: group->children) { + SPItem *citem = dynamic_cast(&child); + if (citem) { + /* Merging of style */ + // this converts the gradient/pattern fill/stroke, if any, to userSpaceOnUse; we need to do + // it here _before_ the new transform is set, so as to use the pre-transform bbox + citem->adjust_paint_recursive (Geom::identity(), Geom::identity(), false); + + child.style->merge( group->style ); + /* + * fixme: We currently make no allowance for the case where child is cloned + * and the group has any style settings. + * + * (This should never occur with documents created solely with the current + * version of inkscape without using the XML editor: we usually apply group + * style changes to children rather than to the group itself.) + * + * If the group has no style settings, then style->merge() should be a no-op. Otherwise + * (i.e. if we change the child's style to compensate for its parent going away) + * then those changes will typically be reflected in any clones of child, + * whereas we'd prefer for Ungroup not to affect the visual appearance. + * + * The only way of preserving styling appearance in general is for child to + * be put into a new group -- a somewhat surprising response to an Ungroup + * command. We could add a new groupmode:transparent that would mostly + * hide the existence of such groups from the user (i.e. editing behaves as + * if the transparent group's children weren't in a group), though that's + * extra complication & maintenance burden and this case is rare. + */ + + child.updateRepr(); + + Inkscape::XML::Node *nrepr = child.getRepr()->duplicate(prepr->document()); + + // Merging transform + Geom::Affine ctrans = citem->transform * g; + // We should not apply the group's transformation to both a linked offset AND to its source + if (dynamic_cast(citem)) { // Do we have an offset at hand (whether it's dynamic or linked)? + SPItem *source = sp_offset_get_source(dynamic_cast(citem)); + // When dealing with a chain of linked offsets, the transformation of an offset will be + // tied to the transformation of the top-most source, not to any of the intermediate + // offsets. So let's find the top-most source + while (source != NULL && dynamic_cast(source)) { + source = sp_offset_get_source(dynamic_cast(source)); + } + if (source != NULL && // If true then we must be dealing with a linked offset ... + group->isAncestorOf(source) ) { // ... of which the source is in the same group + ctrans = citem->transform; // then we should apply the transformation of the group to the offset + } + } + + // FIXME: constructing a transform that would fully preserve the appearance of a + // textpath if it is ungrouped with its path seems to be impossible in general + // case. E.g. if the group was squeezed, to keep the ungrouped textpath squeezed + // as well, we'll need to relink it to some "virtual" path which is inversely + // stretched relative to the actual path, and then squeeze the textpath back so it + // would both fit the actual path _and_ be squeezed as before. It's a bummer. + + // This is just a way to temporarily remember the transform in repr. When repr is + // reattached outside of the group, the transform will be written more properly + // (i.e. optimized into the object if the corresponding preference is set) + gchar *affinestr=sp_svg_transform_write(ctrans); + SPText * text = dynamic_cast(citem); + if (text) { + //this causes a change in text-on-path appearance when there is a non-conformal transform, see bug #1594565 + SPTextPath * text_path = dynamic_cast(text->firstChild()); + if (!text_path) { + nrepr->setAttribute("transform", affinestr); + } else { + // The following breaks roundtripping group -> ungroup + // double scale = (ctrans.expansionX() + ctrans.expansionY()) / 2.0; + // sp_recursive_scale_text_size(nrepr, scale); + Geom::Affine ttrans = ctrans.inverse() * SP_ITEM(text)->transform * ctrans; + gchar *affinestr = sp_svg_transform_write(ttrans); + nrepr->setAttribute("transform", affinestr); + g_free(affinestr); + } + } else { + nrepr->setAttribute("transform", affinestr); + } + g_free(affinestr); + + items.push_back(nrepr); + + } else { + Inkscape::XML::Node *nrepr = child.getRepr()->duplicate(prepr->document()); + objects.push_back(nrepr); + } + } + + /* Step 2 - clear group */ + // remember the position of the group + gint pos = group->getRepr()->position(); + + // the group is leaving forever, no heir, clones should take note; its children however are going to reemerge + group->deleteObject(true, false); + + /* Step 3 - add nonitems */ + if (!objects.empty()) { + Inkscape::XML::Node *last_def = defs->getRepr()->lastChild(); + for (auto i=objects.rbegin();i!=objects.rend();++i) { + Inkscape::XML::Node *repr = *i; + if (!sp_repr_is_meta_element(repr)) { + defs->getRepr()->addChild(repr, last_def); + } + Inkscape::GC::release(repr); + } + } + + /* Step 4 - add items */ + for (auto i=items.rbegin();i!=items.rend();++i) { + Inkscape::XML::Node *repr = *i; + // add item + prepr->appendChild(repr); + // restore position; since the items list was prepended (i.e. reverse), we now add + // all children at the same pos, which inverts the order once again + repr->setPosition(pos > 0 ? pos : 0); + + // fill in the children list if non-null + SPItem *item = static_cast(doc->getObjectByRepr(repr)); + + if (item) { + item->doWriteTransform(item->transform, NULL, false); + children.insert(children.begin(),item); + } else { + g_assert_not_reached(); + } + + Inkscape::GC::release(repr); + } + + if (do_done) { + DocumentUndo::done(doc, SP_VERB_NONE, _("Ungroup")); + } +} + +/* + * some API for list aspect of SPGroup + */ + +std::vector sp_item_group_item_list(SPGroup * group) +{ + std::vector s; + g_return_val_if_fail(group != NULL, s); + + for (auto& o: group->children) { + if ( dynamic_cast(&o) ) { + s.push_back((SPItem*)&o); + } + } + return s; +} + +SPObject *sp_item_group_get_child_by_name(SPGroup *group, SPObject *ref, const gchar *name) +{ + SPObject *child = (ref) ? ref->getNext() : group->firstChild(); + while ( child && strcmp(child->getRepr()->name(), name) ) { + child = child->getNext(); + } + return child; +} + +void SPGroup::setLayerMode(LayerMode mode) { + if ( _layer_mode != mode ) { + if ( mode == LAYER ) { + this->document->addResource("layer", this); + } else if ( _layer_mode == LAYER ) { + this->document->removeResource("layer", this); + } + _layer_mode = mode; + _updateLayerMode(); + } +} + +SPGroup::LayerMode SPGroup::layerDisplayMode(unsigned int dkey) const { + std::map::const_iterator iter; + iter = _display_modes.find(dkey); + if ( iter != _display_modes.end() ) { + return (*iter).second; + } else { + return GROUP; + } +} + +void SPGroup::setExpanded(bool isexpanded) { + if ( _expanded != isexpanded ){ + _expanded = isexpanded; + } +} + +void SPGroup::setInsertBottom(bool insertbottom) { + if ( _insert_bottom != insertbottom) { + _insert_bottom = insertbottom; + } +} + +void SPGroup::setLayerDisplayMode(unsigned int dkey, SPGroup::LayerMode mode) { + if ( layerDisplayMode(dkey) != mode ) { + _display_modes[dkey] = mode; + _updateLayerMode(dkey); + } +} + +void SPGroup::_updateLayerMode(unsigned int display_key) { + SPItemView *view; + for ( view = this->display ; view ; view = view->next ) { + if ( !display_key || view->key == display_key ) { + Inkscape::DrawingGroup *g = dynamic_cast(view->arenaitem); + if (g) { + g->setPickChildren(effectiveLayerMode(view->key) == SPGroup::LAYER); + } + } + } +} + +void SPGroup::translateChildItems(Geom::Translate const &tr) +{ + if ( hasChildren() ) { + for (auto& o: children) { + SPItem *item = dynamic_cast(&o); + if ( item ) { + sp_item_move_rel(item, tr); + } + } + } +} + +// Recursively (or not) scale child items around a point +void SPGroup::scaleChildItemsRec(Geom::Scale const &sc, Geom::Point const &p, bool noRecurse) +{ + if ( hasChildren() ) { + for (auto& o: children) { + if ( SPDefs *defs = dynamic_cast(&o) ) { // select symbols from defs, ignore clips, masks, patterns + for (auto& defschild: defs->children) { + SPGroup *defsgroup = dynamic_cast(&defschild); + if (defsgroup) + defsgroup->scaleChildItemsRec(sc, p, false); + } + } else if ( SPItem *item = dynamic_cast(&o) ) { + SPGroup *group = dynamic_cast(item); + if (group && !dynamic_cast(item)) { + /* Using recursion breaks clipping because transforms are applied + in coordinates for draws but nothing in defs is changed + instead change the transform on the entire group, and the transform + is applied after any references to clipping paths. However NOT using + recursion apparently breaks as of r13544 other parts of Inkscape + involved with showing/modifying units. So offer both for use + in different contexts. + */ + if(noRecurse) { + // used for EMF import + Geom::Translate const s(p); + Geom::Affine final = s.inverse() * sc * s; + Geom::Affine tAff = item->i2dt_affine() * final; + item->set_i2d_affine(tAff); + tAff = item->transform; + // Eliminate common rounding error affecting EMF/WMF input. + // When the rounding error persists it converts the simple + // transform=scale() to transform=matrix(). + if(std::abs(tAff[4]) < 1.0e-5 && std::abs(tAff[5]) < 1.0e-5){ + tAff[4] = 0.0; + tAff[5] = 0.0; + } + item->doWriteTransform(tAff, NULL, true); + } else { + // used for other import + SPItem *sub_item = NULL; + if (item->clip_ref->getObject()) { + sub_item = dynamic_cast(item->clip_ref->getObject()->firstChild()); + } + if (sub_item != NULL) { + sub_item->doWriteTransform(sub_item->transform*sc, NULL, true); + } + sub_item = NULL; + if (item->mask_ref->getObject()) { + sub_item = dynamic_cast(item->mask_ref->getObject()->firstChild()); + } + if (sub_item != NULL) { + sub_item->doWriteTransform(sub_item->transform*sc, NULL, true); + } + item->doWriteTransform(sc.inverse()*item->transform*sc, NULL, true); + group->scaleChildItemsRec(sc, p, false); + } + } else { +// Geom::OptRect bbox = item->desktopVisualBounds(); +// if (bbox) { // test not needed, this was causing a failure to scale and in the clipboard, see LP Bug 1365451 + // Scale item + Geom::Translate const s(p); + Geom::Affine final = s.inverse() * sc * s; + + gchar const *conn_type = NULL; + SPText *text_item = dynamic_cast(item); + bool is_text_path = text_item && text_item->firstChild() && dynamic_cast(text_item->firstChild()); + if (is_text_path) { + text_item->optimizeTextpathText(); + } else { + SPFlowtext *flowText = dynamic_cast(item); + if (flowText) { + flowText->optimizeScaledText(); + } else { + SPBox3D *box = dynamic_cast(item); + if (box) { + // Force recalculation from perspective + box3d_position_set(box); + } else if (item->getAttribute("inkscape:connector-type") != NULL + && (item->getAttribute("inkscape:connection-start") == NULL + || item->getAttribute("inkscape:connection-end") == NULL)) { + // Remove and store connector type for transform if disconnected + conn_type = item->getAttribute("inkscape:connector-type"); + item->removeAttribute("inkscape:connector-type"); + } + } + } + + Persp3D *persp = dynamic_cast(item); + if (persp) { + persp3d_apply_affine_transformation(persp, final); + } else if (is_text_path && !item->transform.isIdentity()) { + // Save and reset current transform + Geom::Affine tmp(item->transform); + item->transform = Geom::Affine(); + // Apply scale + item->set_i2d_affine(item->i2dt_affine() * sc); + item->doWriteTransform(item->transform, NULL, true); + // Scale translation and restore original transform + tmp[4] *= sc[0]; + tmp[5] *= sc[1]; + item->doWriteTransform(tmp, NULL, true); + } else if (dynamic_cast(item)) { + // calculate the matrix we need to apply to the clone + // to cancel its induced transform from its original + Geom::Affine move = final.inverse() * item->transform * final; + item->doWriteTransform(move, &move, true); + } else { + item->doWriteTransform(item->transform*sc, NULL, true); + } + + if (conn_type != NULL) { + item->setAttribute("inkscape:connector-type", conn_type); + } + + if (item->isCenterSet() && !(final.isTranslation() || final.isIdentity())) { + item->scaleCenter(sc); // All coordinates have been scaled, so also the center must be scaled + item->updateRepr(); + } +// } + } + } + } + } +} + +gint SPGroup::getItemCount() const { + gint len = 0; + for (auto& child: children) { + if (dynamic_cast(&child)) { + len++; + } + } + + return len; +} + +void SPGroup::_showChildren (Inkscape::Drawing &drawing, Inkscape::DrawingItem *ai, unsigned int key, unsigned int flags) { + Inkscape::DrawingItem *ac = NULL; + std::vector l=this->childList(false, SPObject::ActionShow); + for(std::vector::const_iterator i=l.begin();i!=l.end();++i){ + SPObject *o = *i; + SPItem * child = dynamic_cast(o); + if (child) { + ac = child->invoke_show (drawing, key, flags); + if (ac) { + ai->appendChild(ac); + } + } + } +} + +void SPGroup::update_patheffect(bool write) { +#ifdef GROUP_VERBOSE + g_message("sp_group_update_patheffect: %p\n", lpeitem); +#endif + + std::vector const item_list = sp_item_group_item_list(this); + + for ( std::vector::const_iterator iter=item_list.begin();iter!=item_list.end();++iter) { + SPObject *sub_item = *iter; + + SPLPEItem *lpe_item = dynamic_cast(sub_item); + if (lpe_item) { + lpe_item->update_patheffect(write); + } + } + + if (hasPathEffect() && pathEffectsEnabled()) { + for (PathEffectList::iterator it = this->path_effect_list->begin(); it != this->path_effect_list->end(); ++it) + { + LivePathEffectObject *lpeobj = (*it)->lpeobject; + + if (lpeobj && lpeobj->get_lpe()) { + lpeobj->get_lpe()->doBeforeEffect_impl(this); + } + } + + sp_group_perform_patheffect(this, this, write); + + for (PathEffectList::iterator it = this->path_effect_list->begin(); it != this->path_effect_list->end(); ++it) + { + LivePathEffectObject *lpeobj = (*it)->lpeobject; + + if (lpeobj && lpeobj->get_lpe()) { + lpeobj->get_lpe()->doAfterEffect(this); + } + } + } +} + +static void +sp_group_perform_patheffect(SPGroup *group, SPGroup *top_group, bool write) +{ + std::vector const item_list = sp_item_group_item_list(group); + + for ( std::vector::const_iterator iter=item_list.begin();iter!=item_list.end();++iter) { + SPObject *sub_item = *iter; + + SPGroup *sub_group = dynamic_cast(sub_item); + if (sub_group) { + sp_group_perform_patheffect(sub_group, top_group, write); + } else { + SPShape *sub_shape = dynamic_cast(sub_item); + if (sub_shape) { + SPCurve * c = NULL; + // If item is a SPRect, convert it to path first: + if ( dynamic_cast(sub_shape) ) { + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop) { + Inkscape::Selection *sel = desktop->getSelection(); + if ( sel && !sel->isEmpty() ) { + sel->clear(); + sel->add(SP_ITEM(sub_shape)); + sel->toCurves(); + sub_item = sel->singleItem(); + sub_shape = dynamic_cast(sub_item); + if (!sub_shape) { + continue; + } + sel->clear(); + sel->add(SP_ITEM(top_group)); + } + } + } + c = sub_shape->getCurve(); + bool success = false; + // only run LPEs when the shape has a curve defined + if (c) { + c->transform(i2anc_affine(sub_item, top_group)); + success = top_group->performPathEffect(c, sub_shape); + c->transform(i2anc_affine(sub_item, top_group).inverse()); + Inkscape::XML::Node *repr = sub_item->getRepr(); + if (c && success) { + SPPath *sub_path = dynamic_cast(sub_item); + if (!sub_path) { + sub_shape->setCurveInsync( sub_shape->getCurveBeforeLPE(), TRUE); + sub_shape->setCurve(c, TRUE); + sub_shape->setCurveInsync( c, TRUE); + } + if (write) { + gchar *str = sp_svg_write_path(c->get_pathvector()); + repr->setAttribute("d", str); +#ifdef GROUP_VERBOSE + g_message("sp_group_perform_patheffect writes 'd' attribute"); +#endif + g_free(str); + } + c->unref(); + } else { + // LPE was unsuccessful or doeffect stack return null. Read the old 'd'-attribute. + if (gchar const * value = repr->attribute("d")) { + Geom::PathVector pv = sp_svg_read_pathv(value); + SPCurve *oldcurve = new (std::nothrow) SPCurve(pv); + if (oldcurve) { + sub_shape->setCurve(oldcurve, TRUE); + oldcurve->unref(); + } + } + } + } + } + } + } +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : -- cgit v1.2.3