void RegisteredTransformedPoint::setTransform(Geom::Affine const & canvas_to_svg) { // check if matrix is singular / has inverse if ( ! canvas_to_svg.isSingular() ) { to_svg = canvas_to_svg; } else { // set back to default to_svg = Geom::identity(); } }
Geom::Point KnotHolderEntity::snap_knot_position_constrained(Geom::Point const &p, Inkscape::Snapper::SnapConstraint const &constraint, guint state) { if (state & GDK_SHIFT_MASK) { // Don't snap when shift-key is held return p; } Geom::Affine const i2d (item->i2dt_affine()); Geom::Point s = p * i2d; SnapManager &m = desktop->namedview->snap_manager; m.setup(desktop, true, item); // constrainedSnap() will first project the point p onto the constraint line and then try to snap along that line. // This way the constraint is already enforced, no need to worry about that later on Inkscape::Snapper::SnapConstraint transformed_constraint = Inkscape::Snapper::SnapConstraint(constraint.getPoint() * i2d, (constraint.getPoint() + constraint.getDirection()) * i2d - constraint.getPoint() * i2d); m.constrainedSnapReturnByRef(s, Inkscape::SNAPSOURCE_NODE_HANDLE, transformed_constraint); m.unSetup(); return s * i2d.inverse(); }
Geom::OptRect SPFlowtext::bbox(Geom::Affine const &transform, SPItem::BBoxType type) const { Geom::OptRect bbox = this->layout.bounds(transform); // Add stroke width // FIXME this code is incorrect if (bbox && type == SPItem::VISUAL_BBOX && !this->style->stroke.isNone()) { double scale = transform.descrim(); bbox->expandBy(0.5 * this->style->stroke_width.computed * scale); } return bbox; }
Geom::Affine SPStar::set_transform(Geom::Affine const &xform) { // Only set transform with proportional scaling if (!xform.withoutTranslation().isUniformScale()) { return xform; } // Allow live effects if (hasPathEffect() && pathEffectsEnabled()) { return xform; } /* Calculate star start in parent coords. */ Geom::Point pos( this->center * xform ); /* This function takes care of translation and scaling, we return whatever parts we can't handle. */ Geom::Affine ret(Geom::Affine(xform).withoutTranslation()); gdouble const s = hypot(ret[0], ret[1]); if (s > 1e-9) { ret[0] /= s; ret[1] /= s; ret[2] /= s; ret[3] /= s; } else { ret[0] = 1.0; ret[1] = 0.0; ret[2] = 0.0; ret[3] = 1.0; } this->r[0] *= s; this->r[1] *= s; /* Find start in item coords */ pos = pos * ret.inverse(); this->center = pos; this->set_shape(); // Adjust stroke width this->adjust_stroke(s); // Adjust pattern fill this->adjust_pattern(xform * ret.inverse()); // Adjust gradient fill this->adjust_gradient(xform * ret.inverse()); this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); return ret; }
Geom::Affine SPFlowtext::set_transform (Geom::Affine const &xform) { if ((this->_optimizeScaledText && !xform.withoutTranslation().isNonzeroUniformScale()) || (!this->_optimizeScaledText && !xform.isNonzeroUniformScale())) { this->_optimizeScaledText = false; return xform; } this->_optimizeScaledText = false; SPText *text = reinterpret_cast<SPText *>(this); double const ex = xform.descrim(); if (ex == 0) { return xform; } Geom::Affine ret(xform); ret[0] /= ex; ret[1] /= ex; ret[2] /= ex; ret[3] /= ex; // Adjust font size text->_adjustFontsizeRecursive (this, ex); // Adjust stroke width this->adjust_stroke_width_recursive (ex); // Adjust pattern fill this->adjust_pattern(xform * ret.inverse()); // Adjust gradient fill this->adjust_gradient(xform * ret.inverse()); this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG); return ret; }
void SPClipPath::update(SPCtx* ctx, unsigned int flags) { if (flags & SP_OBJECT_MODIFIED_FLAG) { flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; } flags &= SP_OBJECT_MODIFIED_CASCADE; GSList *l = NULL; for ( SPObject *child = this->firstChild(); child; child = child->getNext()) { sp_object_ref(child); l = g_slist_prepend(l, child); } l = g_slist_reverse(l); while (l) { SPObject *child = SP_OBJECT(l->data); l = g_slist_remove(l, child); if (flags || (child->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { child->updateDisplay(ctx, flags); } sp_object_unref(child); } for (SPClipPathView *v = this->display; v != NULL; v = v->next) { Inkscape::DrawingGroup *g = dynamic_cast<Inkscape::DrawingGroup *>(v->arenaitem); if (this->clipPathUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX && v->bbox) { Geom::Affine t = Geom::Scale(v->bbox->dimensions()); t.setTranslation(v->bbox->min()); g->setChildTransform(t); } else { g->setChildTransform(Geom::identity()); } } }
void sp_gradient_set_gs2d_matrix(SPGradient *gr, Geom::Affine const &ctm, Geom::Rect const &bbox, Geom::Affine const &gs2d) { gr->gradientTransform = gs2d * ctm.inverse(); if (gr->getUnits() == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX ) { gr->gradientTransform = ( gr->gradientTransform * Geom::Translate(-bbox.min()) * Geom::Scale(bbox.dimensions()).inverse() ); } gr->gradientTransform_set = TRUE; gr->requestModified(SP_OBJECT_MODIFIED_FLAG); }
static void sp_offset_move_compensate(Geom::Affine const *mp, SPItem */*original*/, SPOffset *self) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); guint mode = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_PARALLEL); Geom::Affine m(*mp); if (!(m.isTranslation()) || mode == SP_CLONE_COMPENSATION_NONE) { self->sourceDirty=true; self->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); return; } // calculate the compensation matrix and the advertized movement matrix self->readAttr("transform"); Geom::Affine t = self->transform; Geom::Affine offset_move = t.inverse() * m * t; Geom::Affine advertized_move; if (mode == SP_CLONE_COMPENSATION_PARALLEL) { offset_move = offset_move.inverse() * m; advertized_move = m; } else if (mode == SP_CLONE_COMPENSATION_UNMOVED) { offset_move = offset_move.inverse(); advertized_move.setIdentity(); } else { g_assert_not_reached(); } self->sourceDirty=true; // commit the compensation self->transform *= offset_move; self->doWriteTransform(self->getRepr(), self->transform, &advertized_move); self->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); }
/** * Set additional transform for the group. * This is applied after the normal transform and mainly useful for * markers, clipping paths, etc. */ void DrawingGroup::setChildTransform(Geom::Affine const &new_trans) { Geom::Affine current; if (_child_transform) { current = *_child_transform; } if (!Geom::are_near(current, new_trans, 1e-18)) { // mark the area where the object was for redraw. _markForRendering(); if (new_trans.isIdentity()) { delete _child_transform; // delete NULL; is safe _child_transform = NULL; } else { _child_transform = new Geom::Affine(new_trans); } _markForUpdate(STATE_ALL, true); } }
Geom::Affine SPLine::set_transform(Geom::Affine const &transform) { Geom::Point points[2]; points[0] = Geom::Point(this->x1.computed, this->y1.computed); points[1] = Geom::Point(this->x2.computed, this->y2.computed); points[0] *= transform; points[1] *= transform; this->x1.computed = points[0][Geom::X]; this->y1.computed = points[0][Geom::Y]; this->x2.computed = points[1][Geom::X]; this->y2.computed = points[1][Geom::Y]; this->adjust_stroke(transform.descrim()); this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); return Geom::identity(); }
/** * Writes the given transform into the repr for the given item. */ static Geom::Affine sp_path_set_transform(SPItem *item, Geom::Affine const &xform) { if (!SP_IS_PATH(item)) { return Geom::identity(); } SPPath *path = SP_PATH(item); if (!path->_curve) { // 0 nodes, nothing to transform return Geom::identity(); } // Transform the original-d path if this is a valid LPE item, other else the (ordinary) path if (path->_curve_before_lpe && sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(item))) { if (sp_lpe_item_has_path_effect_of_type(SP_LPE_ITEM(item), Inkscape::LivePathEffect::CLONE_ORIGINAL)) { // if path has the CLONE_ORIGINAL LPE applied, don't write the transform to the pathdata, but write it 'unoptimized' return xform; } else { path->_curve_before_lpe->transform(xform); } } else { path->_curve->transform(xform); } // Adjust stroke item->adjust_stroke(xform.descrim()); // Adjust pattern fill item->adjust_pattern(xform); // Adjust gradient fill item->adjust_gradient(xform); // Adjust LPE item->adjust_livepatheffect(xform); item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); // nothing remains - we've written all of the transform, so return identity return Geom::identity(); }
Geom::Affine SPLine::setTransform(SPItem *item, Geom::Affine const &xform) { SPLine *line = SP_LINE(item); Geom::Point points[2]; points[0] = Geom::Point(line->x1.computed, line->y1.computed); points[1] = Geom::Point(line->x2.computed, line->y2.computed); points[0] *= xform; points[1] *= xform; line->x1.computed = points[0][Geom::X]; line->y1.computed = points[0][Geom::Y]; line->x2.computed = points[1][Geom::X]; line->y2.computed = points[1][Geom::Y]; item->adjust_stroke(xform.descrim()); SP_OBJECT(item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); return Geom::identity(); }
static gdouble sp_pattern_extract_theta(SPPattern *pat) { Geom::Affine transf = pat->patternTransform; return Geom::atan2(transf.xAxis()); }
/** * Sensing a movement of the original, this function attempts to compensate for it in such a way * that the clone stays unmoved or moves in parallel (depending on user setting) regardless of the * clone's transform. */ void SPUse::move_compensate(Geom::Affine const *mp) { // the clone is orphaned; or this is not a real use, but a clone of another use; // we skip it, otherwise duplicate compensation will occur if (this->cloned) { return; } // never compensate uses which are used in flowtext if (parent && dynamic_cast<SPFlowregion *>(parent)) { return; } Inkscape::Preferences *prefs = Inkscape::Preferences::get(); guint mode = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_PARALLEL); // user wants no compensation if (mode == SP_CLONE_COMPENSATION_NONE) return; Geom::Affine m(*mp); Geom::Affine t = this->get_parent_transform(); Geom::Affine clone_move = t.inverse() * m * t; // this is not a simple move, do not try to compensate if (!(m.isTranslation())){ //BUT move clippaths accordingly. //if clone has a clippath, move it accordingly if(clip_ref->getObject()){ SPObject *clip = clip_ref->getObject()->firstChild() ; while(clip){ SPItem *item = (SPItem*) clip; if(item){ item->transform *= m; Geom::Affine identity; item->doWriteTransform(clip->getRepr(),item->transform, &identity); } clip = clip->getNext(); } } if(mask_ref->getObject()){ SPObject *mask = mask_ref->getObject()->firstChild() ; while(mask){ SPItem *item = (SPItem*) mask; if(item){ item->transform *= m; Geom::Affine identity; item->doWriteTransform(mask->getRepr(),item->transform, &identity); } mask = mask->getNext(); } } return; } // restore item->transform field from the repr, in case it was changed by seltrans this->readAttr ("transform"); // calculate the compensation matrix and the advertized movement matrix Geom::Affine advertized_move; if (mode == SP_CLONE_COMPENSATION_PARALLEL) { clone_move = clone_move.inverse() * m; advertized_move = m; } else if (mode == SP_CLONE_COMPENSATION_UNMOVED) { clone_move = clone_move.inverse(); advertized_move.setIdentity(); } else { g_assert_not_reached(); } //if clone has a clippath, move it accordingly if(clip_ref->getObject()){ SPObject *clip = clip_ref->getObject()->firstChild() ; while(clip){ SPItem *item = (SPItem*) clip; if(item){ item->transform *= clone_move.inverse(); Geom::Affine identity; item->doWriteTransform(clip->getRepr(),item->transform, &identity); } clip = clip->getNext(); } } if(mask_ref->getObject()){ SPObject *mask = mask_ref->getObject()->firstChild() ; while(mask){ SPItem *item = (SPItem*) mask; if(item){ item->transform *= clone_move.inverse(); Geom::Affine identity; item->doWriteTransform(mask->getRepr(),item->transform, &identity); } mask = mask->getNext(); } } // commit the compensation this->transform *= clone_move; this->doWriteTransform(this->getRepr(), this->transform, &advertized_move); this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); }
void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { if (first_time) { first_time = false; sliders[0].geometry(Point(50, 50), 100); } size_t const num_points = static_cast<size_t>(sliders[0].value()); D2<SBasis> B1 = b_handle.asBezier(); Piecewise<D2<SBasis> >B; B.concat(Piecewise<D2<SBasis> >(B1)); // testing fuse_nearby_ends std::vector< Piecewise<D2<SBasis> > > pieces; pieces = fuse_nearby_ends(split_at_discontinuities(B),9); Piecewise<D2<SBasis> > C; for (unsigned i=0; i<pieces.size(); i++){ C.concat(pieces[i]); } // testing fuse_nearby_ends cairo_set_line_width (cr, 2.); cairo_set_source_rgba (cr, 0., 0.5, 0., 1); //cairo_d2_sb(cr, B1); //cairo_pw_d2_sb(cr, C); //cairo_pw_d2_sb(cr, B); cairo_stroke(cr); Timer tm; Timer::Time als_time = tm.lap(); cairo_set_source_rgba (cr, 0., 0., 0.9, 1); //dot_plot(cr,uniform_B); cairo_stroke(cr); std::cout << B[0] << std::endl; Geom::Affine translation; Geom::Path original_path; //original_bezier.append(B[0]); //original_bezier.appendNew<CubicBezier> (B[0]); CubicBezier original_bezier(b_handle.pts); original_path.append(original_bezier); std::vector<double> initial_t; std::vector<Geom::Point> curve_points; if (randomize_times) { std::uniform_real_distribution<double> dist_t(0,1); for (size_t ii = 0; ii < num_points; ++ii) { double const t = dist_t(generator); initial_t.push_back(t); } std::sort(initial_t.begin(), initial_t.end()); double const min = initial_t.front(); double const max = initial_t.back(); for (auto& t : initial_t) { t = (t-min)/(max-min); } for (auto const t : initial_t) { curve_points.push_back(original_bezier.pointAt(t)); } } else { for (size_t ii = 0; ii < num_points; ++ii) { double const t = static_cast<double>(ii) / (num_points-1); Geom::Point const p = original_bezier.pointAt(t); initial_t.push_back(t); curve_points.push_back(p); } } cairo_set_source_rgba (cr, 0., 0., .9, 1); cairo_path(cr, original_path); draw_text(cr, original_path.initialPoint(), "original curve and old fit"); Geom::CubicBezier fitted_new; Geom::CubicBezier fitted_new_a; Geom::Point very_old_version_raw[4]; bezier_fit_cubic(very_old_version_raw, curve_points.data(), curve_points.size(), 0.); Geom::CubicBezier very_old_bezier( very_old_version_raw[0], very_old_version_raw[1], very_old_version_raw[2], very_old_version_raw[3] ); Geom::Path very_old_version_path; very_old_version_path.append(very_old_bezier); cairo_set_source_rgba (cr, .7, .7, 0., 1); cairo_stroke(cr); cairo_path(cr, very_old_version_path); cairo_set_source_rgba (cr, 0., 0., .9, 1); cairo_stroke(cr); cross_plot(cr, curve_points); if(1) { Geom::CubicBezier combination(very_old_bezier); tm.ask_for_timeslice(); tm.start(); auto new_result_ig_a = experiment::fit_bezier(combination, curve_points); als_time = tm.lap(); *notify << "Bezier fit a, old algorithm as initial guess, time = " << als_time << std::endl << "Worst residual: " << new_result_ig_a.first << " at t=" << new_result_ig_a.second << std::endl; Geom::Path combination_path; translation.setTranslation(Geom::Point(300,300)); combination_path.append(combination.transformed(translation)); cairo_set_source_rgba (cr, .0, .0, .9, 1); cross_plot(cr, curve_points, translation.translation()); cairo_path(cr, combination_path); draw_text(cr, combination_path.initialPoint(), "old fit as i.g."); } { tm.ask_for_timeslice(); tm.start(); auto new_result = fit_bezier(fitted_new, curve_points); als_time = tm.lap(); *notify << "Bezier fit, time = " << als_time << std::endl << "Worst residual: " << new_result.first << " at t=" << new_result.second << std::endl; Geom::Path fitted_new_path; translation.setTranslation(Geom::Point(300,0)); fitted_new_path.append(fitted_new.transformed(translation)); cairo_set_source_rgba (cr, .0, .9, .0, 1); cross_plot(cr, curve_points, translation.translation()); cairo_path(cr, fitted_new_path); draw_text(cr, fitted_new_path.initialPoint(), "new fit"); } { tm.ask_for_timeslice(); tm.start(); auto new_result_a = experiment::fit_bezier(fitted_new_a, curve_points); als_time = tm.lap(); *notify << "Bezier fit a, time = " << als_time << std::endl << "Worst residual: " << new_result_a.first << " at t=" << new_result_a.second << std::endl; Geom::Path fitted_new_a_path; translation.setTranslation(Geom::Point(0,300)); fitted_new_a_path.append(fitted_new_a.transformed(translation)); cairo_set_source_rgba (cr, .9, .0, .0, 1); cross_plot(cr, curve_points, translation.translation()); cairo_path(cr, fitted_new_a_path); draw_text(cr, fitted_new_a_path.initialPoint(), "new fit (a)"); } Geom::CubicBezier fixed_times_bezier; { tm.ask_for_timeslice(); tm.start(); auto fixed_times_result = experiment::fit_bezier_fixed_times(fixed_times_bezier, curve_points); als_time = tm.lap(); *notify << "Bezier fit a (fixed times), time = " << als_time << std::endl << "Worst residual: " << fixed_times_result.first << " at t=" << fixed_times_result.second << std::endl; Geom::Path fixed_times_path; translation.setTranslation(Geom::Point(600,300)); fixed_times_path.append(fixed_times_bezier.transformed(translation)); cairo_set_source_rgba (cr, .9, .0, .0, 1); cross_plot(cr, curve_points, translation.translation()); cairo_path(cr, fixed_times_path); draw_text(cr, fixed_times_path.initialPoint(), "fixed t fit (a)"); } Geom::CubicBezier fixed_times_ig_bezier = fixed_times_bezier; { tm.ask_for_timeslice(); tm.start(); auto fixed_times_ig_result = experiment::fit_bezier(fixed_times_ig_bezier, curve_points); als_time = tm.lap(); *notify << "Bezier fit a (with fixed times as i.g.), time = " << als_time << std::endl << "Worst residual: " << fixed_times_ig_result.first << " at t=" << fixed_times_ig_result.second << std::endl; Geom::Path fixed_times_path; translation.setTranslation(Geom::Point(900,300)); fixed_times_path.append(fixed_times_ig_bezier.transformed(translation)); cairo_set_source_rgba (cr, .9, .0, .0, 1); cross_plot(cr, curve_points, translation.translation()); cairo_path(cr, fixed_times_path); draw_text(cr, fixed_times_path.initialPoint(), "new (a) with fixed t as i.g."); } Geom::CubicBezier icp_bezier; { tm.ask_for_timeslice(); tm.start(); auto icp_result = experiment::fit_bezier_icp(icp_bezier, curve_points); als_time = tm.lap(); *notify << "Bezier fit icp, time = " << als_time << std::endl << "Worst residual: " << icp_result.first << " at t=" << icp_result.second << std::endl; Geom::Path icp_path; translation.setTranslation(Geom::Point(600,600)); icp_path.append(icp_bezier.transformed(translation)); cairo_set_source_rgba (cr, .9, .0, .0, 1); cross_plot(cr, curve_points, translation.translation()); cairo_path(cr, icp_path); draw_text(cr, icp_path.initialPoint(), "icp fit"); } Geom::CubicBezier icp_ig_bezier(icp_bezier); { tm.ask_for_timeslice(); tm.start(); auto icp_ig_result = experiment::fit_bezier(icp_ig_bezier, curve_points); als_time = tm.lap(); *notify << "Bezier fit with icp i.g., time = " << als_time << std::endl << "Worst residual: " << icp_ig_result.first << " at t=" << icp_ig_result.second << std::endl; Geom::Path icp_ig_path; translation.setTranslation(Geom::Point(900,600)); icp_ig_path.append(icp_ig_bezier.transformed(translation)); cairo_set_source_rgba (cr, .9, .0, .0, 1); cross_plot(cr, curve_points, translation.translation()); cairo_path(cr, icp_ig_path); draw_text(cr, icp_ig_path.initialPoint(), "bezier fit with icp as i.g."); } std::cout << "original: " << write_svg_path(original_path) << std::endl; Geom::CubicBezier initial_guess( curve_points.front(), curve_points.front(), curve_points.back(), curve_points.back() ); { experiment::get_initial_guess(initial_guess, curve_points); Geom::Path initial_guess_path; translation.setTranslation(Geom::Point(600,0)); initial_guess_path.append(initial_guess.transformed(translation)); cairo_set_source_rgba (cr, .8, .0, .8, 1); cross_plot(cr, curve_points, translation.translation()); cairo_path(cr, initial_guess_path); draw_text(cr, initial_guess_path.initialPoint(), "initial guess"); } cairo_stroke(cr); Toy::draw(cr, notify, width, height, save,timer_stream); }
Geom::OptRect SPShape::bbox(Geom::Affine const &transform, SPItem::BBoxType bboxtype) const { Geom::OptRect bbox; if (!this->_curve) { return bbox; } bbox = bounds_exact_transformed(this->_curve->get_pathvector(), transform); if (!bbox) { return bbox; } if (bboxtype == SPItem::VISUAL_BBOX) { // convert the stroke to a path and calculate that path's geometric bbox if (!this->style->stroke.isNone()) { Geom::PathVector *pathv = item_outline(this, true); // calculate bbox_only if (pathv) { bbox |= bounds_exact_transformed(*pathv, transform); delete pathv; } } // Union with bboxes of the markers, if any if ( this->hasMarkers() && !this->_curve->get_pathvector().empty() ) { /** \todo make code prettier! */ Geom::PathVector const & pathv = this->_curve->get_pathvector(); // START marker for (unsigned i = 0; i < 2; i++) { // SP_MARKER_LOC and SP_MARKER_LOC_START if ( this->_marker[i] ) { SPItem* marker_item = sp_item_first_item_child( _marker[i] ); if (marker_item) { Geom::Affine tr(sp_shape_marker_get_transform_at_start(pathv.begin()->front())); if (_marker[i]->orient_mode == MARKER_ORIENT_AUTO_START_REVERSE) { // Reverse start marker if necessary tr = Geom::Rotate::from_degrees( 180.0 ) * tr; } else if (_marker[i]->orient_mode == MARKER_ORIENT_ANGLE) { Geom::Point transl = tr.translation(); tr = Geom::Rotate::from_degrees(_marker[i]->orient) * Geom::Translate(transl); } if (_marker[i]->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) { tr = Geom::Scale(this->style->stroke_width.computed) * tr; } // total marker transform tr = marker_item->transform * _marker[i]->c2p * tr * transform; // get bbox of the marker with that transform bbox |= marker_item->visualBounds(tr); } } } // MID marker for (unsigned i = 0; i < 3; i += 2) { // SP_MARKER_LOC and SP_MARKER_LOC_MID if ( !this->_marker[i] ) { continue; } SPMarker* marker = _marker[i]; SPItem* marker_item = sp_item_first_item_child( marker ); if ( !marker_item ) { continue; } for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) { // START position if ( path_it != pathv.begin() && ! ((path_it == (pathv.end()-1)) && (path_it->size_default() == 0)) ) // if this is the last path and it is a moveto-only, there is no mid marker there { Geom::Affine tr(sp_shape_marker_get_transform_at_start(path_it->front())); if (marker->orient_mode == MARKER_ORIENT_ANGLE) { Geom::Point transl = tr.translation(); tr = Geom::Rotate::from_degrees(marker->orient) * Geom::Translate(transl); } if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) { tr = Geom::Scale(this->style->stroke_width.computed) * tr; } tr = marker_item->transform * marker->c2p * tr * transform; bbox |= marker_item->visualBounds(tr); } // MID position if ( path_it->size_default() > 1) { Geom::Path::const_iterator curve_it1 = path_it->begin(); // incoming curve Geom::Path::const_iterator curve_it2 = ++(path_it->begin()); // outgoing curve while (curve_it2 != path_it->end_default()) { /* Put marker between curve_it1 and curve_it2. * Loop to end_default (so including closing segment), because when a path is closed, * there should be a midpoint marker between last segment and closing straight line segment */ SPMarker* marker = _marker[i]; SPItem* marker_item = sp_item_first_item_child( marker ); if (marker_item) { Geom::Affine tr(sp_shape_marker_get_transform(*curve_it1, *curve_it2)); if (marker->orient_mode == MARKER_ORIENT_ANGLE) { Geom::Point transl = tr.translation(); tr = Geom::Rotate::from_degrees(marker->orient) * Geom::Translate(transl); } if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) { tr = Geom::Scale(this->style->stroke_width.computed) * tr; } tr = marker_item->transform * marker->c2p * tr * transform; bbox |= marker_item->visualBounds(tr); } ++curve_it1; ++curve_it2; } } // END position if ( path_it != (pathv.end()-1) && !path_it->empty()) { Geom::Curve const &lastcurve = path_it->back_default(); Geom::Affine tr = sp_shape_marker_get_transform_at_end(lastcurve); if (marker->orient_mode == MARKER_ORIENT_ANGLE) { Geom::Point transl = tr.translation(); tr = Geom::Rotate::from_degrees(marker->orient) * Geom::Translate(transl); } if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) { tr = Geom::Scale(this->style->stroke_width.computed) * tr; } tr = marker_item->transform * marker->c2p * tr * transform; bbox |= marker_item->visualBounds(tr); } } } // END marker for (unsigned i = 0; i < 4; i += 3) { // SP_MARKER_LOC and SP_MARKER_LOC_END if ( _marker[i] ) { SPMarker* marker = _marker[i]; SPItem* marker_item = sp_item_first_item_child( marker ); if (marker_item) { /* Get reference to last curve in the path. * For moveto-only path, this returns the "closing line segment". */ Geom::Path const &path_last = pathv.back(); unsigned int index = path_last.size_default(); if (index > 0) { index--; } Geom::Curve const &lastcurve = path_last[index]; Geom::Affine tr = sp_shape_marker_get_transform_at_end(lastcurve); if (marker->orient_mode == MARKER_ORIENT_ANGLE) { Geom::Point transl = tr.translation(); tr = Geom::Rotate::from_degrees(marker->orient) * Geom::Translate(transl); } if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) { tr = Geom::Scale(this->style->stroke_width.computed) * tr; } // total marker transform tr = marker_item->transform * marker->c2p * tr * transform; // get bbox of the marker with that transform bbox |= marker_item->visualBounds(tr); } } } } } return bbox; }
void sp_item_group_ungroup (SPGroup *group, GSList **children, bool do_done) { g_return_if_fail (group != NULL); g_return_if_fail (SP_IS_GROUP (group)); SPDocument *doc = group->document; SPRoot *root = doc->getRoot(); SPObject *defs = root->defs; SPItem *gitem = group; Inkscape::XML::Node *grepr = gitem->getRepr(); g_return_if_fail (!strcmp (grepr->name(), "svg:g") || !strcmp (grepr->name(), "svg:a") || !strcmp (grepr->name(), "svg:switch")); // this converts the gradient/pattern fill/stroke on the group, if any, to userSpaceOnUse gitem->adjust_paint_recursive (Geom::identity(), Geom::identity(), false); SPItem *pitem = SP_ITEM(gitem->parent); Inkscape::XML::Node *prepr = pitem->getRepr(); if (SP_IS_BOX3D(gitem)) { group = box3d_convert_to_group(SP_BOX3D(gitem)); gitem = group; } sp_lpe_item_remove_all_path_effects(SP_LPE_ITEM(group), false); /* Step 1 - generate lists of children objects */ GSList *items = NULL; GSList *objects = NULL; for (SPObject *child = group->firstChild() ; child; child = child->getNext() ) { if (SP_IS_ITEM (child)) { SPItem *citem = SP_ITEM (child); /* 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); sp_style_merge_from_dying_parent(child->style, gitem->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 * sp_style_merge_from_dying_parent 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; Geom::Affine const g(gitem->transform); if (SP_IS_USE(citem) && sp_use_get_original (SP_USE(citem)) && sp_use_get_original( SP_USE(citem) )->parent == SP_OBJECT(group)) { // make sure a clone's effective transform is the same as was under group ctrans = g.inverse() * citem->transform * g; } else { // We should not apply the group's transformation to both a linked offset AND to its source if (SP_IS_OFFSET(citem)) { // Do we have an offset at hand (whether it's dynamic or linked)? SPItem *source = sp_offset_get_source(SP_OFFSET(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 && SP_IS_OFFSET(source)) { source = sp_offset_get_source(SP_OFFSET(source)); } if (source != NULL && // If true then we must be dealing with a linked offset ... group->isAncestorOf(source) == false) { // ... of which the source is not in the same group ctrans = citem->transform * g; // then we should apply the transformation of the group to the offset } else { ctrans = citem->transform; } } else { ctrans = citem->transform * g; } } // 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); nrepr->setAttribute("transform", affinestr); g_free(affinestr); items = g_slist_prepend (items, nrepr); } else { Inkscape::XML::Node *nrepr = child->getRepr()->duplicate(prepr->document()); objects = g_slist_prepend (objects, 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) { Inkscape::XML::Node *last_def = defs->getRepr()->lastChild(); while (objects) { Inkscape::XML::Node *repr = (Inkscape::XML::Node *) objects->data; if (!sp_repr_is_meta_element(repr)) { defs->getRepr()->addChild(repr, last_def); } Inkscape::GC::release(repr); objects = g_slist_remove (objects, objects->data); } } /* Step 4 - add items */ while (items) { Inkscape::XML::Node *repr = (Inkscape::XML::Node *) items->data; // 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<SPItem *>(doc->getObjectByRepr(repr)); item->doWriteTransform(repr, item->transform, NULL, false); Inkscape::GC::release(repr); if (children && SP_IS_ITEM (item)) *children = g_slist_prepend (*children, item); items = g_slist_remove (items, items->data); } if (do_done) { DocumentUndo::done(doc, SP_VERB_NONE, _("Ungroup")); } }
static Geom::Point sp_pattern_extract_scale(SPPattern *pat) { Geom::Affine transf = pat->patternTransform; return Geom::Point( transf.expansionX(), transf.expansionY() ); }