// FIXME: why is 'transform' argument not used? void PrintLatex::print_pathvector(SVGOStringStream &os, Geom::PathVector const &pathv_in, const Geom::Affine & /*transform*/) { if (pathv_in.empty()) return; // Geom::Affine tf=transform; // why was this here? Geom::Affine tf_stack=m_tr_stack.top(); // and why is transform argument not used? Geom::PathVector pathv = pathv_in * tf_stack; // generates new path, which is a bit slow, but this doesn't have to be performance optimized os << "\\newpath\n"; for(Geom::PathVector::const_iterator it = pathv.begin(); it != pathv.end(); ++it) { os << "\\moveto(" << it->initialPoint()[Geom::X] << "," << it->initialPoint()[Geom::Y] << ")\n"; for(Geom::Path::const_iterator cit = it->begin(); cit != it->end_open(); ++cit) { print_2geomcurve(os, *cit); } if (it->closed()) { os << "\\closepath\n"; } } }
/** Feeds path-creating calls to the cairo context translating them from the PathVector * One must have done cairo_new_path(ct); before calling this function. */ void feed_pathvector_to_cairo (cairo_t *ct, Geom::PathVector const &pathv) { if (pathv.empty()) return; for(Geom::PathVector::const_iterator it = pathv.begin(); it != pathv.end(); ++it) { feed_path_to_cairo(ct, *it); } }
/* * Converts all segments in all paths to Geom::LineSegment or Geom::HLineSegment or * Geom::VLineSegment or Geom::CubicBezier. */ Geom::PathVector pathv_to_linear_and_cubic_beziers( Geom::PathVector const &pathv ) { Geom::PathVector output; for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) { output.push_back( Geom::Path() ); output.back().start( pit->initialPoint() ); output.back().close( pit->closed() ); for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_open(); ++cit) { if (is_straight_curve(*cit)) { Geom::LineSegment l(cit->initialPoint(), cit->finalPoint()); output.back().append(l); } else { Geom::BezierCurve const *curve = dynamic_cast<Geom::BezierCurve const *>(&*cit); if (curve && curve->order() == 3) { Geom::CubicBezier b((*curve)[0], (*curve)[1], (*curve)[2], (*curve)[3]); output.back().append(b); } else { // convert all other curve types to cubicbeziers Geom::Path cubicbezier_path = Geom::cubicbezierpath_from_sbasis(cit->toSBasis(), 0.1); output.back().append(cubicbezier_path); } } } } return output; }
/** Feeds path-creating calls to the cairo context translating them from the PathVector, with the given transform and shift * One must have done cairo_new_path(ct); before calling this function. */ void feed_pathvector_to_cairo (cairo_t *ct, Geom::PathVector const &pathv, Geom::Affine trans, Geom::OptRect area, bool optimize_stroke, double stroke_width) { if (!area) return; if (pathv.empty()) return; for(Geom::PathVector::const_iterator it = pathv.begin(); it != pathv.end(); ++it) { feed_path_to_cairo(ct, *it, trans, area, optimize_stroke, stroke_width); } }
int main(int argc, char **argv) { if (argc > 1) { SVGPathTestPrinter sink; Geom::parse_svg_path(&*argv[1], sink); std::cout << "Try real pathsink:" << std::endl; Geom::PathVector testpath = Geom::parse_svg_path(&*argv[1]); std::cout << "Geom::PathVector length: " << testpath.size() << std::endl; if ( !testpath.empty() ) std::cout << "Path curves: " << testpath.front().size() << std::endl; std::cout << "success!" << std::endl; } return 0; };
/** * Returns a list of new curves corresponding to the subpaths in \a curve. * 2geomified */ GSList * SPCurve::split() const { GSList *l = NULL; for (Geom::PathVector::const_iterator path_it = _pathv.begin(); path_it != _pathv.end(); ++path_it) { Geom::PathVector newpathv; newpathv.push_back(*path_it); SPCurve * newcurve = new SPCurve(newpathv); l = g_slist_prepend(l, newcurve); } return l; }
Geom::OptRect bounds_exact_transformed(Geom::PathVector const & pv, Geom::Affine const & t) { if (pv.empty()) return Geom::OptRect(); Geom::Point initial = pv.front().initialPoint() * t; Geom::Rect bbox(initial, initial); // obtain well defined bbox as starting point to unionWith for (Geom::PathVector::const_iterator it = pv.begin(); it != pv.end(); ++it) { bbox.expandTo(it->initialPoint() * t); // don't loop including closing segment, since that segment can never increase the bbox for (Geom::Path::const_iterator cit = it->begin(); cit != it->end_open(); ++cit) { Geom::Curve const &c = *cit; unsigned order = 0; if (Geom::BezierCurve const* b = dynamic_cast<Geom::BezierCurve const*>(&c)) { order = b->order(); } if (order == 1) { // line segment bbox.expandTo(c.finalPoint() * t); // TODO: we can make the case for quadratics faster by degree elevating them to // cubic and then taking the bbox of that. } else if (order == 3) { // cubic bezier Geom::CubicBezier const &cubic_bezier = static_cast<Geom::CubicBezier const&>(c); Geom::Point c0 = cubic_bezier[0] * t; Geom::Point c1 = cubic_bezier[1] * t; Geom::Point c2 = cubic_bezier[2] * t; Geom::Point c3 = cubic_bezier[3] * t; cubic_bbox(c0[0], c0[1], c1[0], c1[1], c2[0], c2[1], c3[0], c3[1], bbox); } else { // should handle all not-so-easy curves: Geom::Curve *ctemp = cit->transformed(t); bbox.unionWith( ctemp->boundsExact()); delete ctemp; } } } //return Geom::bounds_exact(pv * t); return bbox; }
/* * Converts all segments in all paths to Geom::LineSegment. There is an intermediate * stage where some may be converted to beziers. maxdisp is the maximum displacement from * the line segment to the bezier curve; ** maxdisp is not used at this moment **. * * This is NOT a terribly fast method, but it should give a solution close to the one with the * fewest points. */ Geom::PathVector pathv_to_linear( Geom::PathVector const &pathv, double /*maxdisp*/) { Geom::PathVector output; Geom::PathVector tmppath = pathv_to_linear_and_cubic_beziers(pathv); // Now all path segments are either already lines, or they are beziers. for (Geom::PathVector::const_iterator pit = tmppath.begin(); pit != tmppath.end(); ++pit) { output.push_back( Geom::Path() ); output.back().start( pit->initialPoint() ); output.back().close( pit->closed() ); for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_open(); ++cit) { if (is_straight_curve(*cit)) { Geom::LineSegment ls(cit->initialPoint(), cit->finalPoint()); output.back().append(ls); } else { /* all others must be Bezier curves */ Geom::BezierCurve const *curve = dynamic_cast<Geom::BezierCurve const *>(&*cit); Geom::CubicBezier b((*curve)[0], (*curve)[1], (*curve)[2], (*curve)[3]); std::vector<Geom::Point> bzrpoints = b.points(); Geom::Point A = bzrpoints[0]; Geom::Point B = bzrpoints[1]; Geom::Point C = bzrpoints[2]; Geom::Point D = bzrpoints[3]; std::vector<Geom::Point> pointlist; pointlist.push_back(A); recursive_bezier4( A[X], A[Y], B[X], B[Y], C[X], C[Y], D[X], D[Y], pointlist, 0); pointlist.push_back(D); Geom::Point r1 = pointlist[0]; for (unsigned int i=1; i<pointlist.size();i++){ Geom::Point prev_r1 = r1; r1 = pointlist[i]; Geom::LineSegment ls(prev_r1, r1); output.back().append(ls); } pointlist.clear(); } } } return output; }
int main(int argc, char **argv) { if (argc > 1) { Geom::PathVector originald = Geom::parse_svg_path(&*argv[1]); Geom::Piecewise<Geom::D2<Geom::SBasis> > originaldpwd2; for (unsigned int i=0; i < originald.size(); i++) { originaldpwd2.concat( originald[i].toPwSb() ); } Geom::PathVector pattern = Geom::parse_svg_path(&*argv[2]); Geom::Piecewise<Geom::D2<Geom::SBasis> > patternpwd2; for (unsigned int i=0; i < pattern.size(); i++) { patternpwd2.concat( pattern[i].toPwSb() ); } doEffect_pwd2(originaldpwd2, patternpwd2); } return 0; };
Geom::OptRect bounds_exact_transformed(Geom::PathVector const & pv, Geom::Affine const & t) { if (pv.empty()) return Geom::OptRect(); Geom::Point initial = pv.front().initialPoint() * t; Geom::Rect bbox(initial, initial); // obtain well defined bbox as starting point to unionWith for (Geom::PathVector::const_iterator it = pv.begin(); it != pv.end(); ++it) { bbox.expandTo(it->initialPoint() * t); // don't loop including closing segment, since that segment can never increase the bbox for (Geom::Path::const_iterator cit = it->begin(); cit != it->end_open(); ++cit) { Geom::Curve const &c = *cit; if( is_straight_curve(c) ) { bbox.expandTo( c.finalPoint() * t ); } else if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const *>(&c)) { Geom::Point c0 = (*cubic_bezier)[0] * t; Geom::Point c1 = (*cubic_bezier)[1] * t; Geom::Point c2 = (*cubic_bezier)[2] * t; Geom::Point c3 = (*cubic_bezier)[3] * t; cubic_bbox( c0[0], c0[1], c1[0], c1[1], c2[0], c2[1], c3[0], c3[1], bbox ); } else { // should handle all not-so-easy curves: Geom::Curve *ctemp = cit->transformed(t); bbox.unionWith( ctemp->boundsExact()); delete ctemp; } } } //return Geom::bounds_exact(pv * t); return bbox; }
/* Calculates... and returns ... in *wind and the distance to ... in *dist. Returns bounding box in *bbox if bbox!=NULL. */ void pathv_matrix_point_bbox_wind_distance (Geom::PathVector const & pathv, Geom::Affine const &m, Geom::Point const &pt, Geom::Rect *bbox, int *wind, Geom::Coord *dist, Geom::Coord tolerance, Geom::Rect const *viewbox) { if (pathv.empty()) { if (wind) *wind = 0; if (dist) *dist = Geom::infinity(); return; } // remember last point of last curve Geom::Point p0(0,0); // remembering the start of subpath Geom::Point p_start(0,0); bool start_set = false; for (Geom::PathVector::const_iterator it = pathv.begin(); it != pathv.end(); ++it) { if (start_set) { // this is a new subpath if (wind && (p0 != p_start)) // for correct fill picking, each subpath must be closed geom_line_wind_distance (p0[X], p0[Y], p_start[X], p_start[Y], pt, wind, dist); } p0 = it->initialPoint() * m; p_start = p0; start_set = true; if (bbox) { bbox->expandTo(p0); } // loop including closing segment if path is closed for (Geom::Path::const_iterator cit = it->begin(); cit != it->end_default(); ++cit) { geom_curve_bbox_wind_distance(*cit, m, pt, bbox, wind, dist, tolerance, viewbox, p0); } } if (start_set) { if (wind && (p0 != p_start)) // for correct picking, each subpath must be closed geom_line_wind_distance (p0[X], p0[Y], p_start[X], p_start[Y], pt, wind, dist); } }
void KnotHolderEntityAttachPt::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state) { using namespace Geom; LPETangentToCurve* lpe = dynamic_cast<LPETangentToCurve *>(_effect); Geom::Point const s = snap_knot_position(p, state); // FIXME: There must be a better way of converting the path's SPCurve* to pwd2. SPCurve *curve = SP_PATH(item)->get_curve_for_edit(); Geom::PathVector pathv = curve->get_pathvector(); Piecewise<D2<SBasis> > pwd2; for (unsigned int i=0; i < pathv.size(); i++) { pwd2.concat(pathv[i].toPwSb()); } double t0 = nearest_point(s, pwd2); lpe->t_attach.param_set_value(t0); // FIXME: this should not directly ask for updating the item. It should write to SVG, which triggers updating. sp_lpe_item_update_patheffect (SP_LPE_ITEM(item), false, true); }
/* * interpolate path_in[0] to path_in[1] */ Geom::PathVector LPEInterpolate::doEffect_path (Geom::PathVector const & path_in) { if ( (path_in.size() < 2) || (number_of_steps < 2)) { return path_in; } // Don't allow empty path parameter: if ( trajectory_path.get_pathvector().empty() ) { return path_in; } Geom::PathVector path_out; Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_A = path_in[0].toPwSb(); Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_B = path_in[1].toPwSb(); // Transform both paths to (0,0) midpoint, so they can easily be positioned along interpolate_path if (Geom::OptRect bounds = Geom::bounds_exact(pwd2_A)) { pwd2_A -= bounds->midpoint(); } if (Geom::OptRect bounds = Geom::bounds_exact(pwd2_B)) { pwd2_B -= bounds->midpoint(); } // Make sure both paths have the same number of segments and cuts at the same locations pwd2_B.setDomain(pwd2_A.domain()); Geom::Piecewise<Geom::D2<Geom::SBasis> > pA = Geom::partition(pwd2_A, pwd2_B.cuts); Geom::Piecewise<Geom::D2<Geom::SBasis> > pB = Geom::partition(pwd2_B, pwd2_A.cuts); Geom::Piecewise<Geom::D2<Geom::SBasis> > trajectory = trajectory_path.get_pathvector()[0].toPwSb(); if (equidistant_spacing) trajectory = Geom::arc_length_parametrization(trajectory); Geom::Interval trajectory_domain = trajectory.domain(); for (int i = 0; i < number_of_steps; ++i) { double fraction = i / (number_of_steps-1); Geom::Piecewise<Geom::D2<Geom::SBasis> > pResult = pA*(1-fraction) + pB*fraction; pResult += trajectory.valueAt(trajectory_domain.min() + fraction*trajectory_domain.extent()); Geom::PathVector pathv = Geom::path_from_piecewise(pResult, LPE_CONVERSION_TOLERANCE); path_out.push_back( pathv[0] ); } return path_out; }
Geom::PathVector LPEMirrorSymmetry::doEffect_path (Geom::PathVector const & path_in) { // Don't allow empty path parameter: if ( reflection_line.get_pathvector().empty() ) { return path_in; } Geom::PathVector path_out; if (!discard_orig_path) { path_out = path_in; } Geom::PathVector mline(reflection_line.get_pathvector()); Geom::Point A(mline.front().initialPoint()); Geom::Point B(mline.back().finalPoint()); Geom::Affine m1(1.0, 0.0, 0.0, 1.0, A[0], A[1]); double hyp = Geom::distance(A, B); double c = (B[0] - A[0]) / hyp; // cos(alpha) double s = (B[1] - A[1]) / hyp; // sin(alpha) Geom::Affine m2(c, -s, s, c, 0.0, 0.0); Geom::Affine sca(1.0, 0.0, 0.0, -1.0, 0.0, 0.0); Geom::Affine m = m1.inverse() * m2; m = m * sca; m = m * m2.inverse(); m = m * m1; for (int i = 0; i < static_cast<int>(path_in.size()); ++i) { path_out.push_back(path_in[i] * m); } return path_out; }
void font_instance::LoadGlyph(int glyph_id) { if ( pFont == NULL ) { return; } InitTheFace(); #ifndef USE_PANGO_WIN32 if ( !FT_IS_SCALABLE(theFace) ) { return; // bitmap font } #endif if ( id_to_no.find(glyph_id) == id_to_no.end() ) { Geom::PathBuilder path_builder; if ( nbGlyph >= maxGlyph ) { maxGlyph=2*nbGlyph+1; glyphs=(font_glyph*)realloc(glyphs,maxGlyph*sizeof(font_glyph)); } font_glyph n_g; n_g.pathvector=NULL; n_g.bbox[0]=n_g.bbox[1]=n_g.bbox[2]=n_g.bbox[3]=0; n_g.h_advance = 0; n_g.v_advance = 0; n_g.h_width = 0; n_g.v_width = 0; bool doAdd=false; #ifdef USE_PANGO_WIN32 #ifndef GGO_UNHINTED // For compatibility with old SDKs. #define GGO_UNHINTED 0x0100 #endif MAT2 identity = {{0,1},{0,0},{0,0},{0,1}}; OUTLINETEXTMETRIC otm; GetOutlineTextMetrics(daddy->hScreenDC, sizeof(otm), &otm); GLYPHMETRICS metrics; DWORD bufferSize=GetGlyphOutline (daddy->hScreenDC, glyph_id, GGO_GLYPH_INDEX | GGO_NATIVE | GGO_UNHINTED, &metrics, 0, NULL, &identity); double scale=1.0/daddy->fontSize; n_g.h_advance=metrics.gmCellIncX*scale; n_g.v_advance=otm.otmTextMetrics.tmHeight*scale; n_g.h_width=metrics.gmBlackBoxX*scale; n_g.v_width=metrics.gmBlackBoxY*scale; if ( bufferSize == GDI_ERROR) { // shit happened } else if ( bufferSize == 0) { // character has no visual representation, but is valid (eg whitespace) doAdd=true; } else { char *buffer = new char[bufferSize]; if ( GetGlyphOutline (daddy->hScreenDC, glyph_id, GGO_GLYPH_INDEX | GGO_NATIVE | GGO_UNHINTED, &metrics, bufferSize, buffer, &identity) <= 0 ) { // shit happened } else { // Platform SDK is rubbish, read KB87115 instead DWORD polyOffset=0; while ( polyOffset < bufferSize ) { TTPOLYGONHEADER const *polyHeader=(TTPOLYGONHEADER const *)(buffer+polyOffset); if (polyOffset+polyHeader->cb > bufferSize) break; if (polyHeader->dwType == TT_POLYGON_TYPE) { path_builder.moveTo(pointfx_to_nrpoint(polyHeader->pfxStart, scale)); DWORD curveOffset=polyOffset+sizeof(TTPOLYGONHEADER); while ( curveOffset < polyOffset+polyHeader->cb ) { TTPOLYCURVE const *polyCurve=(TTPOLYCURVE const *)(buffer+curveOffset); POINTFX const *p=polyCurve->apfx; POINTFX const *endp=p+polyCurve->cpfx; switch (polyCurve->wType) { case TT_PRIM_LINE: while ( p != endp ) path_builder.lineTo(pointfx_to_nrpoint(*p++, scale)); break; case TT_PRIM_QSPLINE: { g_assert(polyCurve->cpfx >= 2); // The list of points specifies one or more control points and ends with the end point. // The intermediate points (on the curve) are the points between the control points. Geom::Point this_control = pointfx_to_nrpoint(*p++, scale); while ( p+1 != endp ) { // Process all "midpoints" (all points except the last) Geom::Point new_control = pointfx_to_nrpoint(*p++, scale); path_builder.quadTo(this_control, (new_control+this_control)/2); this_control = new_control; } Geom::Point end = pointfx_to_nrpoint(*p++, scale); path_builder.quadTo(this_control, end); } break; case 3: // TT_PRIM_CSPLINE g_assert(polyCurve->cpfx % 3 == 0); while ( p != endp ) { path_builder.curveTo(pointfx_to_nrpoint(p[0], scale), pointfx_to_nrpoint(p[1], scale), pointfx_to_nrpoint(p[2], scale)); p += 3; } break; } curveOffset += sizeof(TTPOLYCURVE)+sizeof(POINTFX)*(polyCurve->cpfx-1); } } polyOffset += polyHeader->cb; } doAdd=true; } delete [] buffer; } #else if (FT_Load_Glyph (theFace, glyph_id, FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP)) { // shit happened } else { if ( FT_HAS_HORIZONTAL(theFace) ) { n_g.h_advance=((double)theFace->glyph->metrics.horiAdvance)/((double)theFace->units_per_EM); n_g.h_width=((double)theFace->glyph->metrics.width)/((double)theFace->units_per_EM); } else { n_g.h_width=n_g.h_advance=((double)(theFace->bbox.xMax-theFace->bbox.xMin))/((double)theFace->units_per_EM); } if ( FT_HAS_VERTICAL(theFace) ) { n_g.v_advance=((double)theFace->glyph->metrics.vertAdvance)/((double)theFace->units_per_EM); n_g.v_width=((double)theFace->glyph->metrics.height)/((double)theFace->units_per_EM); } else { n_g.v_width=n_g.v_advance=((double)theFace->height)/((double)theFace->units_per_EM); } if ( theFace->glyph->format == ft_glyph_format_outline ) { FT_Outline_Funcs ft2_outline_funcs = { ft2_move_to, ft2_line_to, ft2_conic_to, ft2_cubic_to, 0, 0 }; FT2GeomData user(path_builder, 1.0/((double)theFace->units_per_EM)); FT_Outline_Decompose (&theFace->glyph->outline, &ft2_outline_funcs, &user); } doAdd=true; } #endif path_builder.finish(); if ( doAdd ) { Geom::PathVector pv = path_builder.peek(); // close all paths for (Geom::PathVector::iterator i = pv.begin(); i != pv.end(); ++i) { i->close(); } if ( !pv.empty() ) { n_g.pathvector = new Geom::PathVector(pv); Geom::OptRect bounds = bounds_exact(*n_g.pathvector); if (bounds) { n_g.bbox[0] = bounds->left(); n_g.bbox[1] = bounds->top(); n_g.bbox[2] = bounds->right(); n_g.bbox[3] = bounds->bottom(); } } glyphs[nbGlyph]=n_g; id_to_no[glyph_id]=nbGlyph; nbGlyph++; } } else { } }
void LPESpiro::doEffect(SPCurve * curve) { using Geom::X; using Geom::Y; // Make copy of old path as it is changed during processing Geom::PathVector const original_pathv = curve->get_pathvector(); guint len = curve->get_segment_count() + 2; curve->reset(); bezctx *bc = new_bezctx_ink(curve); spiro_cp *path = g_new (spiro_cp, len); int ip = 0; for(Geom::PathVector::const_iterator path_it = original_pathv.begin(); path_it != original_pathv.end(); ++path_it) { if (path_it->empty()) continue; // start of path { Geom::Point p = path_it->front().pointAt(0); path[ip].x = p[X]; path[ip].y = p[Y]; path[ip].ty = '{' ; // for closed paths, this is overwritten ip++; } // midpoints Geom::Path::const_iterator curve_it1 = path_it->begin(); // incoming curve Geom::Path::const_iterator curve_it2 = ++(path_it->begin()); // outgoing curve Geom::Path::const_iterator curve_endit = path_it->end_default(); // this determines when the loop has to stop if (path_it->closed()) { // if the path is closed, maybe we have to stop a bit earlier because the closing line segment has zerolength. const Geom::Curve &closingline = path_it->back_closed(); // the closing line segment is always of type Geom::LineSegment. if (are_near(closingline.initialPoint(), closingline.finalPoint())) { // closingline.isDegenerate() did not work, because it only checks for *exact* zero length, which goes wrong for relative coordinates and rounding errors... // the closing line segment has zero-length. So stop before that one! curve_endit = path_it->end_open(); } } while ( curve_it2 != curve_endit ) { /* This deals with the node between curve_it1 and curve_it2. * Loop to end_default (so without last segment), loop ends when curve_it2 hits the end * and then curve_it1 points to end or closing segment */ Geom::Point p = curve_it1->finalPoint(); path[ip].x = p[X]; path[ip].y = p[Y]; // Determine type of spiro node this is, determined by the tangents (angles) of the curves // TODO: see if this can be simplified by using /helpers/geom-nodetype.cpp:get_nodetype bool this_is_line = is_straight_curve(*curve_it1); bool next_is_line = is_straight_curve(*curve_it2); Geom::NodeType nodetype = Geom::get_nodetype(*curve_it1, *curve_it2); if ( nodetype == Geom::NODE_SMOOTH || nodetype == Geom::NODE_SYMM ) { if (this_is_line && !next_is_line) { path[ip].ty = ']'; } else if (next_is_line && !this_is_line) { path[ip].ty = '['; } else { path[ip].ty = 'c'; } } else { path[ip].ty = 'v'; } ++curve_it1; ++curve_it2; ip++; } // add last point to the spiropath Geom::Point p = curve_it1->finalPoint(); path[ip].x = p[X]; path[ip].y = p[Y]; if (path_it->closed()) { // curve_it1 points to the (visually) closing segment. determine the match between first and this last segment (the closing node) Geom::NodeType nodetype = Geom::get_nodetype(*curve_it1, path_it->front()); switch (nodetype) { case Geom::NODE_NONE: // can't happen! but if it does, it means the path isn't closed :-) path[ip].ty = '}'; ip++; break; case Geom::NODE_CUSP: path[0].ty = path[ip].ty = 'v'; break; case Geom::NODE_SMOOTH: case Geom::NODE_SYMM: path[0].ty = path[ip].ty = 'c'; break; } } else { // set type to path closer path[ip].ty = '}'; ip++; } // run subpath through spiro int sp_len = ip; spiro_seg *s = run_spiro(path, sp_len); spiro_to_bpath(s, sp_len, bc); free(s); ip = 0; } g_free (path); }
void LPESimplify::generateHelperPathAndSmooth(Geom::PathVector &result) { if(steps < 1) { return; } Geom::PathVector tmp_path; Geom::CubicBezier const *cubic = NULL; for (Geom::PathVector::iterator path_it = result.begin(); path_it != result.end(); ++path_it) { if (path_it->empty()) { continue; } Geom::Path::iterator curve_it1 = path_it->begin(); // incoming curve Geom::Path::iterator curve_it2 = ++(path_it->begin());// outgoing curve Geom::Path::iterator curve_endit = path_it->end_default(); // this determines when the loop has to stop SPCurve *nCurve = new SPCurve(); if (path_it->closed()) { // if the path is closed, maybe we have to stop a bit earlier because the // closing line segment has zerolength. const Geom::Curve &closingline = path_it->back_closed(); // the closing line segment is always of type // Geom::LineSegment. if (are_near(closingline.initialPoint(), closingline.finalPoint())) { // closingline.isDegenerate() did not work, because it only checks for // *exact* zero length, which goes wrong for relative coordinates and // rounding errors... // the closing line segment has zero-length. So stop before that one! curve_endit = path_it->end_open(); } } if(helper_size > 0) { drawNode(curve_it1->initialPoint()); } nCurve->moveto(curve_it1->initialPoint()); Geom::Point start = Geom::Point(0,0); while (curve_it1 != curve_endit) { cubic = dynamic_cast<Geom::CubicBezier const *>(&*curve_it1); Geom::Point point_at1 = curve_it1->initialPoint(); Geom::Point point_at2 = curve_it1->finalPoint(); Geom::Point point_at3 = curve_it1->finalPoint(); Geom::Point point_at4 = curve_it1->finalPoint(); if(start == Geom::Point(0,0)) { start = point_at1; } if (cubic) { point_at1 = (*cubic)[1]; point_at2 = (*cubic)[2]; } if(path_it->closed() && curve_it2 == curve_endit) { point_at4 = start; } if(curve_it2 != curve_endit) { cubic = dynamic_cast<Geom::CubicBezier const *>(&*curve_it2); if (cubic) { point_at4 = (*cubic)[1]; } } Geom::Ray ray1(point_at2, point_at3); Geom::Ray ray2(point_at3, point_at4); double angle1 = Geom::deg_from_rad(ray1.angle()); double angle2 = Geom::deg_from_rad(ray2.angle()); if((smooth_angles >= std::abs(angle2 - angle1)) && !are_near(point_at4,point_at3) && !are_near(point_at2,point_at3)) { double dist = Geom::distance(point_at2,point_at3); Geom::Angle angleFixed = ray2.angle(); angleFixed -= Geom::Angle::from_degrees(180.0); point_at2 = Geom::Point::polar(angleFixed, dist) + point_at3; } nCurve->curveto(point_at1, point_at2, curve_it1->finalPoint()); cubic = dynamic_cast<Geom::CubicBezier const *>(nCurve->last_segment()); if (cubic) { point_at1 = (*cubic)[1]; point_at2 = (*cubic)[2]; if(helper_size > 0) { if(!are_near((*cubic)[0],(*cubic)[1])) { drawHandle((*cubic)[1]); drawHandleLine((*cubic)[0],(*cubic)[1]); } if(!are_near((*cubic)[3],(*cubic)[2])) { drawHandle((*cubic)[2]); drawHandleLine((*cubic)[3],(*cubic)[2]); } } } if(helper_size > 0) { drawNode(curve_it1->finalPoint()); } ++curve_it1; ++curve_it2; } if (path_it->closed()) { nCurve->closepath_current(); } tmp_path.push_back(nCurve->get_pathvector()[0]); nCurve->reset(); delete nCurve; } result = tmp_path; }
/// @todo investigate why Geom::Point p is passed in but ignored. void Inkscape::ObjectSnapper::_collectPaths(Geom::Point /*p*/, SnapSourceType const source_type, bool const &first_point) const { // Now, let's first collect all paths to snap to. If we have a whole bunch of points to snap, // e.g. when translating an item using the selector tool, then we will only do this for the // first point and store the collection for later use. This significantly improves the performance if (first_point) { _clear_paths(); // Determine the type of bounding box we should snap to SPItem::BBoxType bbox_type = SPItem::GEOMETRIC_BBOX; bool p_is_a_node = source_type & SNAPSOURCE_NODE_CATEGORY; bool p_is_a_bbox = source_type & SNAPSOURCE_BBOX_CATEGORY; bool p_is_other = (source_type & SNAPSOURCE_OTHERS_CATEGORY) || (source_type & SNAPSOURCE_DATUMS_CATEGORY); if (_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_BBOX_EDGE)) { Preferences *prefs = Preferences::get(); int prefs_bbox = prefs->getBool("/tools/bounding_box", 0); bbox_type = !prefs_bbox ? SPItem::VISUAL_BBOX : SPItem::GEOMETRIC_BBOX; } // Consider the page border for snapping if (_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PAGE_BORDER) && _snapmanager->snapprefs.isAnyCategorySnappable()) { Geom::PathVector *border_path = _getBorderPathv(); if (border_path != NULL) { _paths_to_snap_to->push_back(SnapCandidatePath(border_path, SNAPTARGET_PAGE_BORDER, Geom::OptRect())); } } for (std::vector<SnapCandidateItem>::const_iterator i = _candidates->begin(); i != _candidates->end(); ++i) { /* Transform the requested snap point to this item's coordinates */ Geom::Affine i2doc(Geom::identity()); SPItem *root_item = NULL; /* We might have a clone at hand, so make sure we get the root item */ if (SP_IS_USE((*i).item)) { i2doc = SP_USE((*i).item)->get_root_transform(); root_item = SP_USE((*i).item)->root(); g_return_if_fail(root_item); } else { i2doc = (*i).item->i2doc_affine(); root_item = (*i).item; } //Build a list of all paths considered for snapping to //Add the item's path to snap to if (_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PATH, SNAPTARGET_PATH_INTERSECTION, SNAPTARGET_TEXT_BASELINE)) { if (p_is_other || p_is_a_node || (!_snapmanager->snapprefs.getStrictSnapping() && p_is_a_bbox)) { if (SP_IS_TEXT(root_item) || SP_IS_FLOWTEXT(root_item)) { if (_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_TEXT_BASELINE)) { // Snap to the text baseline Text::Layout const *layout = te_get_layout(static_cast<SPItem *>(root_item)); if (layout != NULL && layout->outputExists()) { Geom::PathVector *pv = new Geom::PathVector(); pv->push_back(layout->baseline() * root_item->i2dt_affine() * (*i).additional_affine * _snapmanager->getDesktop()->doc2dt()); _paths_to_snap_to->push_back(SnapCandidatePath(pv, SNAPTARGET_TEXT_BASELINE, Geom::OptRect())); } } } else { // Snapping for example to a traced bitmap is very stressing for // the CPU, so we'll only snap to paths having no more than 500 nodes // This also leads to a lag of approx. 500 msec (in my lousy test set-up). bool very_complex_path = false; if (SP_IS_PATH(root_item)) { very_complex_path = SP_PATH(root_item)->nodesInPath() > 500; } if (!very_complex_path && root_item && _snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PATH, SNAPTARGET_PATH_INTERSECTION)) { SPCurve *curve = NULL; if (SP_IS_SHAPE(root_item)) { curve = SP_SHAPE(root_item)->getCurve(); }/* else if (SP_IS_TEXT(root_item) || SP_IS_FLOWTEXT(root_item)) { curve = te_get_layout(root_item)->convertToCurves(); }*/ if (curve) { // We will get our own copy of the pathvector, which must be freed at some point // Geom::PathVector *pv = pathvector_for_curve(root_item, curve, true, true, Geom::identity(), (*i).additional_affine); Geom::PathVector *pv = new Geom::PathVector(curve->get_pathvector()); (*pv) *= root_item->i2dt_affine() * (*i).additional_affine * _snapmanager->getDesktop()->doc2dt(); // (_edit_transform * _i2d_transform); _paths_to_snap_to->push_back(SnapCandidatePath(pv, SNAPTARGET_PATH, Geom::OptRect())); // Perhaps for speed, get a reference to the Geom::pathvector, and store the transformation besides it. curve->unref(); } } } } } //Add the item's bounding box to snap to if (_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_BBOX_EDGE)) { if (p_is_other || p_is_a_bbox || (!_snapmanager->snapprefs.getStrictSnapping() && p_is_a_node)) { // Discard the bbox of a clipped path / mask, because we don't want to snap to both the bbox // of the item AND the bbox of the clipping path at the same time if (!(*i).clip_or_mask) { Geom::OptRect rect = root_item->bounds(bbox_type, i2doc); if (rect) { Geom::PathVector *path = _getPathvFromRect(*rect); rect = root_item->desktopBounds(bbox_type); _paths_to_snap_to->push_back(SnapCandidatePath(path, SNAPTARGET_BBOX_EDGE, rect)); } } } } } } }
static void spdc_check_for_and_apply_waiting_LPE(FreehandBase *dc, SPItem *item, SPCurve *curve) { using namespace Inkscape::LivePathEffect; Inkscape::Preferences *prefs = Inkscape::Preferences::get(); if (item && SP_IS_LPE_ITEM(item)) { bool simplify = prefs->getInt(tool_name(dc) + "/simplify", 0); if(simplify){ double tol = prefs->getDoubleLimited("/tools/freehand/pencil/tolerance", 10.0, 1.0, 100.0); tol = tol/(100.0*(102.0-tol)); std::ostringstream ss; ss << tol; spdc_apply_simplify(ss.str(), dc, item); sp_lpe_item_update_patheffect(SP_LPE_ITEM(item), false, false); } if (prefs->getInt(tool_name(dc) + "/freehand-mode", 0) == 1) { Effect::createAndApply(SPIRO, dc->desktop->doc(), item); } if (prefs->getInt(tool_name(dc) + "/freehand-mode", 0) == 2) { Effect::createAndApply(BSPLINE, dc->desktop->doc(), item); } SPShape *sp_shape = dynamic_cast<SPShape *>(item); if (sp_shape) { curve = sp_shape->getCurve(); } //Store the clipboard path to apply in the future without the use of clipboard static Geom::PathVector previous_shape_pathv; shapeType shape = (shapeType)prefs->getInt(tool_name(dc) + "/shape", 0); bool shape_applied = false; SPCSSAttr *css_item = sp_css_attr_from_object(item, SP_STYLE_FLAG_ALWAYS); const char *cstroke = sp_repr_css_property(css_item, "stroke", "none"); static SPItem *bend_item; #define SHAPE_LENGTH 10 #define SHAPE_HEIGHT 10 if(shape == LAST_APPLIED){ shape = previous_shape_type; if(shape == CLIPBOARD || shape == BEND_CLIPBOARD){ shape = LAST_APPLIED; } } switch (shape) { case NONE: // don't apply any shape break; case TRIANGLE_IN: { // "triangle in" std::vector<Geom::Point> points(1); points[0] = Geom::Point(0., SHAPE_HEIGHT/2); spdc_apply_powerstroke_shape(points, dc, item); shape_applied = true; break; } case TRIANGLE_OUT: { // "triangle out" guint curve_length = curve->get_segment_count(); std::vector<Geom::Point> points(1); points[0] = Geom::Point((double)curve_length, SHAPE_HEIGHT/2); spdc_apply_powerstroke_shape(points, dc, item); shape_applied = true; break; } case ELLIPSE: { // "ellipse" SPCurve *c = new SPCurve(); const double C1 = 0.552; c->moveto(0, SHAPE_HEIGHT/2); c->curveto(0, (1 - C1) * SHAPE_HEIGHT/2, (1 - C1) * SHAPE_LENGTH/2, 0, SHAPE_LENGTH/2, 0); c->curveto((1 + C1) * SHAPE_LENGTH/2, 0, SHAPE_LENGTH, (1 - C1) * SHAPE_HEIGHT/2, SHAPE_LENGTH, SHAPE_HEIGHT/2); c->curveto(SHAPE_LENGTH, (1 + C1) * SHAPE_HEIGHT/2, (1 + C1) * SHAPE_LENGTH/2, SHAPE_HEIGHT, SHAPE_LENGTH/2, SHAPE_HEIGHT); c->curveto((1 - C1) * SHAPE_LENGTH/2, SHAPE_HEIGHT, 0, (1 + C1) * SHAPE_HEIGHT/2, 0, SHAPE_HEIGHT/2); c->closepath(); spdc_paste_curve_as_freehand_shape(c->get_pathvector(), dc, item); c->unref(); shape_applied = true; break; } case CLIPBOARD: { // take shape from clipboard; Inkscape::UI::ClipboardManager *cm = Inkscape::UI::ClipboardManager::get(); if(cm->paste(SP_ACTIVE_DESKTOP,true) == true){ SPItem * pasted_clipboard = dc->selection->singleItem(); if(pasted_clipboard){ Inkscape::XML::Node *pasted_clipboard_root = pasted_clipboard->getRepr(); Inkscape::XML::Node *path = sp_repr_lookup_name(pasted_clipboard_root, "svg:path", -1); // unlimited search depth if ( path != NULL ) { gchar const *svgd = path->attribute("d"); dc->selection->remove(SP_OBJECT(pasted_clipboard)); previous_shape_pathv = sp_svg_read_pathv(svgd); previous_shape_pathv *= pasted_clipboard->transform; spdc_paste_curve_as_freehand_shape(previous_shape_pathv, dc, item); shape = CLIPBOARD; shape_applied = true; pasted_clipboard->deleteObject(); } else { shape = NONE; } } else { shape = NONE; } } else { shape = NONE; } break; } case BEND_CLIPBOARD: { Inkscape::UI::ClipboardManager *cm = Inkscape::UI::ClipboardManager::get(); if(cm->paste(SP_ACTIVE_DESKTOP,true) == true){ gchar const *svgd = item->getRepr()->attribute("d"); bend_item = dc->selection->singleItem(); if(bend_item){ bend_item->moveTo(item,false); bend_item->transform.setTranslation(Geom::Point()); spdc_apply_bend_shape(svgd, dc, bend_item); dc->selection->add(SP_OBJECT(bend_item)); shape = BEND_CLIPBOARD; } else { shape = NONE; } } else { shape = NONE; } break; } case LAST_APPLIED: { if(previous_shape_type == CLIPBOARD){ if(previous_shape_pathv.size() != 0){ spdc_paste_curve_as_freehand_shape(previous_shape_pathv, dc, item); shape_applied = true; shape = CLIPBOARD; } else{ shape = NONE; } } else { if(bend_item != NULL && bend_item->getRepr() != NULL){ gchar const *svgd = item->getRepr()->attribute("d"); dc->selection->add(SP_OBJECT(bend_item)); sp_selection_duplicate(dc->desktop); dc->selection->remove(SP_OBJECT(bend_item)); bend_item = dc->selection->singleItem(); if(bend_item){ bend_item->moveTo(item,false); Geom::Coord expansion_X = bend_item->transform.expansionX(); Geom::Coord expansion_Y = bend_item->transform.expansionY(); bend_item->transform = Geom::Affine(1,0,0,1,0,0); bend_item->transform.setExpansionX(expansion_X); bend_item->transform.setExpansionY(expansion_Y); spdc_apply_bend_shape(svgd, dc, bend_item); dc->selection->add(SP_OBJECT(bend_item)); shape = BEND_CLIPBOARD; } else { shape = NONE; } } else { shape = NONE; } } break; } default: break; } previous_shape_type = shape; if (shape_applied) { // apply original stroke color as fill and unset stroke; then return SPCSSAttr *css = sp_repr_css_attr_new(); if (!strcmp(cstroke, "none")){ sp_repr_css_set_property (css, "fill", "black"); } else { sp_repr_css_set_property (css, "fill", cstroke); } sp_repr_css_set_property (css, "stroke", "none"); sp_desktop_apply_css_recursive(item, css, true); sp_repr_css_attr_unref(css); return; } if (dc->waiting_LPE_type != INVALID_LPE) { Effect::createAndApply(dc->waiting_LPE_type, dc->desktop->doc(), item); dc->waiting_LPE_type = INVALID_LPE; if (SP_IS_LPETOOL_CONTEXT(dc)) { // since a geometric LPE was applied, we switch back to "inactive" mode lpetool_context_switch_mode(SP_LPETOOL_CONTEXT(dc), INVALID_LPE); } } if (SP_IS_PEN_CONTEXT(dc)) { SP_PEN_CONTEXT(dc)->setPolylineMode(); } } }
void sp_spiro_do_effect(SPCurve *curve){ using Geom::X; using Geom::Y; // Make copy of old path as it is changed during processing Geom::PathVector const original_pathv = curve->get_pathvector(); guint len = curve->get_segment_count() + 2; curve->reset(); Spiro::spiro_cp *path = g_new (Spiro::spiro_cp, len); int ip = 0; for(Geom::PathVector::const_iterator path_it = original_pathv.begin(); path_it != original_pathv.end(); ++path_it) { if (path_it->empty()) continue; // start of path { Geom::Point p = path_it->initialPoint(); path[ip].x = p[X]; path[ip].y = p[Y]; path[ip].ty = '{' ; // for closed paths, this is overwritten ip++; } // midpoints Geom::Path::const_iterator curve_it1 = path_it->begin(); // incoming curve Geom::Path::const_iterator curve_it2 = ++(path_it->begin()); // outgoing curve Geom::Path::const_iterator curve_endit = path_it->end_default(); // this determines when the loop has to stop while ( curve_it2 != curve_endit ) { /* This deals with the node between curve_it1 and curve_it2. * Loop to end_default (so without last segment), loop ends when curve_it2 hits the end * and then curve_it1 points to end or closing segment */ Geom::Point p = curve_it1->finalPoint(); path[ip].x = p[X]; path[ip].y = p[Y]; // Determine type of spiro node this is, determined by the tangents (angles) of the curves // TODO: see if this can be simplified by using /helpers/geom-nodetype.cpp:get_nodetype bool this_is_line = is_straight_curve(*curve_it1); bool next_is_line = is_straight_curve(*curve_it2); Geom::NodeType nodetype = Geom::get_nodetype(*curve_it1, *curve_it2); if ( nodetype == Geom::NODE_SMOOTH || nodetype == Geom::NODE_SYMM ) { if (this_is_line && !next_is_line) { path[ip].ty = ']'; } else if (next_is_line && !this_is_line) { path[ip].ty = '['; } else { path[ip].ty = 'c'; } } else { path[ip].ty = 'v'; } ++curve_it1; ++curve_it2; ip++; } // add last point to the spiropath Geom::Point p = curve_it1->finalPoint(); path[ip].x = p[X]; path[ip].y = p[Y]; if (path_it->closed()) { // curve_it1 points to the (visually) closing segment. determine the match between first and this last segment (the closing node) Geom::NodeType nodetype = Geom::get_nodetype(*curve_it1, path_it->front()); switch (nodetype) { case Geom::NODE_NONE: // can't happen! but if it does, it means the path isn't closed :-) path[ip].ty = '}'; ip++; break; case Geom::NODE_CUSP: path[0].ty = path[ip].ty = 'v'; break; case Geom::NODE_SMOOTH: case Geom::NODE_SYMM: path[0].ty = path[ip].ty = 'c'; break; } } else { // set type to path closer path[ip].ty = '}'; ip++; } // run subpath through spiro int sp_len = ip; Spiro::spiro_run(path, sp_len, *curve); ip = 0; } g_free (path); }