/* * SVG and related implementations * * Authors: * Lauris Kaplinski * Mitsuru Oka * bulia byak * Abhishek Sharma * * Copyright (C) 1999-2002 Lauris Kaplinski * Copyright (C) 2000-2001 Ximian, Inc. * * Released under GNU GPL, read the file 'COPYING' for more information */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "svg/svg.h" #include "svg/path-string.h" #include "xml/repr.h" #include "attributes.h" #include "style.h" #include "display/curve.h" #include #include <2geom/transforms.h> #include <2geom/pathvector.h> #include "document.h" #include "sp-ellipse.h" #include "preferences.h" #include "snap-candidate.h" /* Common parent class */ #define noELLIPSE_VERBOSE #include "sp-factory.h" namespace { SPObject* createEllipse() { return new SPEllipse(); } SPObject* createCircle() { return new SPCircle(); } SPObject* createArc() { return new SPArc(); } bool ellipseRegistered = SPFactory::instance().registerObject("svg:ellipse", createEllipse); bool circleRegistered = SPFactory::instance().registerObject("svg:circle", createCircle); bool arcRegistered = SPFactory::instance().registerObject("arc", createArc); } #ifndef M_PI #define M_PI 3.14159265358979323846 #endif #define SP_2PI (2 * M_PI) #if 1 /* Hmmm... shouldn't this also qualify */ /* Whether it is faster or not, well, nobody knows */ #define sp_round(v,m) (((v) < 0.0) ? ((ceil((v) / (m) - 0.5)) * (m)) : ((floor((v) / (m) + 0.5)) * (m))) #else /* we do not use C99 round(3) function yet */ static double sp_round(double x, double y) { double remain; g_assert(y > 0.0); /* return round(x/y) * y; */ remain = fmod(x, y); if (remain >= 0.5*y) return x - remain + y; else return x - remain; } #endif static gboolean sp_arc_set_elliptical_path_attribute(SPArc *arc, Inkscape::XML::Node *repr); SPGenericEllipse::SPGenericEllipse() : SPShape() { this->cx.unset(); this->cy.unset(); this->rx.unset(); this->ry.unset(); this->start = 0.0; this->end = SP_2PI; this->closed = TRUE; } SPGenericEllipse::~SPGenericEllipse() { } void SPGenericEllipse::update(SPCtx *ctx, guint flags) { if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { Geom::Rect const &viewbox = ((SPItemCtx const *) ctx)->viewport; double const dx = viewbox.width(); double const dy = viewbox.height(); double const dr = sqrt(dx*dx + dy*dy)/sqrt(2); double const em = this->style->font_size.computed; double const ex = em * 0.5; // fixme: get from pango or libnrtype this->cx.update(em, ex, dx); this->cy.update(em, ex, dy); this->rx.update(em, ex, dr); this->ry.update(em, ex, dr); this->set_shape(); } SPShape::update(ctx, flags); } void SPGenericEllipse::update_patheffect(bool write) { this->set_shape(); if (write) { Inkscape::XML::Node *repr = this->getRepr(); if ( this->_curve != NULL ) { gchar *str = sp_svg_write_path(this->_curve->get_pathvector()); repr->setAttribute("d", str); g_free(str); } else { repr->setAttribute("d", NULL); } } this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); } /* fixme: Think (Lauris) */ /* Can't we use arcto in this method? */ void SPGenericEllipse::set_shape() { if (sp_lpe_item_has_broken_path_effect(this)) { g_warning ("The ellipse shape has unknown LPE on it! Convert to path to make it editable preserving the appearance; editing it as ellipse will remove the bad LPE"); if (this->getRepr()->attribute("d")) { // unconditionally read the curve from d, if any, to preserve appearance Geom::PathVector pv = sp_svg_read_pathv(this->getRepr()->attribute("d")); SPCurve *cold = new SPCurve(pv); this->setCurveInsync( cold, TRUE); cold->unref(); } return; } double rx; double ry; double s; double len; gint slice = FALSE; if ((this->rx.computed < 1e-18) || (this->ry.computed < 1e-18)) { return; } if (fabs(this->end - this->start) < 1e-9) { return; } sp_genericellipse_normalize(this); rx = this->rx.computed; ry = this->ry.computed; // figure out if we have a slice, guarding against rounding errors len = fmod(this->end - this->start, SP_2PI); if (len < 0.0) { len += SP_2PI; } if (fabs(len) < 1e-8 || fabs(len - SP_2PI) < 1e-8) { slice = FALSE; this->end = this->start + SP_2PI; } else { slice = TRUE; } SPCurve * curve = new SPCurve(); curve->moveto(cos(this->start), sin(this->start)); for (s = this->start; s < this->end; s += M_PI_2) { double e = s + M_PI_2; if (e > this->end) { e = this->end; } len = 4*tan((e - s)/4)/3; double x0 = cos(s); double y0 = sin(s); double x1 = x0 + len * cos(s + M_PI_2); double y1 = y0 + len * sin(s + M_PI_2); double x3 = cos(e); double y3 = sin(e); double x2 = x3 + len * cos(e - M_PI_2); double y2 = y3 + len * sin(e - M_PI_2); #ifdef ELLIPSE_VERBOSE g_print("step %d s %f e %f coords %f %f %f %f %f %f\n", i, s, e, x1, y1, x2, y2, x3, y3); #endif curve->curveto(x1,y1, x2,y2, x3,y3); } if (slice && this->closed) { // TODO: is this check for "ellipse->closed" necessary? curve->lineto(0., 0.); } if (this->closed) { curve->closepath(); } Geom::Affine aff = Geom::Scale(rx, ry) * Geom::Translate(this->cx.computed, this->cy.computed); curve->transform(aff); /* Reset the shape's curve to the "original_curve" * This is very important for LPEs to work properly! (the bbox might be recalculated depending on the curve in shape)*/ this->setCurveInsync( curve, TRUE); this->setCurveBeforeLPE(curve); if (sp_lpe_item_has_path_effect(this) && sp_lpe_item_path_effects_enabled(this)) { SPCurve *c_lpe = curve->copy(); bool success = sp_lpe_item_perform_path_effect(this, c_lpe); if (success) { this->setCurveInsync( c_lpe, TRUE); } c_lpe->unref(); } curve->unref(); } void SPGenericEllipse::snappoints(std::vector &p, Inkscape::SnapPreferences const *snapprefs) { sp_genericellipse_normalize(this); Geom::Affine const i2dt = this->i2dt_affine(); // figure out if we have a slice, while guarding against rounding errors bool slice = false; double len = fmod(this->end - this->start, SP_2PI); if (len < 0.0) { len += SP_2PI; } if (fabs(len) < 1e-8 || fabs(len - SP_2PI) < 1e-8) { slice = false; this->end = this->start + SP_2PI; } else { slice = true; } double rx = this->rx.computed; double ry = this->ry.computed; double cx = this->cx.computed; double cy = this->cy.computed; Geom::Point pt; // Snap to the 4 quadrant points of the this, but only if the arc // spans far enough to include them if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_ELLIPSE_QUADRANT_POINT)) { double angle = 0; for (angle = 0; angle < SP_2PI; angle += M_PI_2) { if (angle >= this->start && angle <= this->end) { pt = Geom::Point(cx + cos(angle)*rx, cy + sin(angle)*ry) * i2dt; p.push_back(Inkscape::SnapCandidatePoint(pt, Inkscape::SNAPSOURCE_ELLIPSE_QUADRANT_POINT, Inkscape::SNAPTARGET_ELLIPSE_QUADRANT_POINT)); } } } // Add the centre, if we have a closed slice or when explicitly asked for bool c1 = snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_NODE_CUSP) && slice && this->closed; bool c2 = snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_OBJECT_MIDPOINT); if (c1 || c2) { pt = Geom::Point(cx, cy) * i2dt; if (c1) { p.push_back(Inkscape::SnapCandidatePoint(pt, Inkscape::SNAPSOURCE_NODE_CUSP, Inkscape::SNAPTARGET_NODE_CUSP)); } if (c2) { p.push_back(Inkscape::SnapCandidatePoint(pt, Inkscape::SNAPSOURCE_OBJECT_MIDPOINT, Inkscape::SNAPTARGET_OBJECT_MIDPOINT)); } } // And if we have a slice, also snap to the endpoints if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_NODE_CUSP) && slice) { // Add the start point, if it's not coincident with a quadrant point if (fmod(this->start, M_PI_2) != 0.0 ) { pt = Geom::Point(cx + cos(this->start)*rx, cy + sin(this->start)*ry) * i2dt; p.push_back(Inkscape::SnapCandidatePoint(pt, Inkscape::SNAPSOURCE_NODE_CUSP, Inkscape::SNAPTARGET_NODE_CUSP)); } // Add the end point, if it's not coincident with a quadrant point if (fmod(this->end, M_PI_2) != 0.0 ) { pt = Geom::Point(cx + cos(this->end)*rx, cy + sin(this->end)*ry) * i2dt; p.push_back(Inkscape::SnapCandidatePoint(pt, Inkscape::SNAPSOURCE_NODE_CUSP, Inkscape::SNAPTARGET_NODE_CUSP)); } } } void sp_genericellipse_normalize(SPGenericEllipse *ellipse) { ellipse->start = fmod(ellipse->start, SP_2PI); ellipse->end = fmod(ellipse->end, SP_2PI); if (ellipse->start < 0.0) { ellipse->start += SP_2PI; } double diff = ellipse->start - ellipse->end; if (diff >= 0.0) { ellipse->end += diff - fmod(diff, SP_2PI) + SP_2PI; } /* Now we keep: 0 <= start < end <= 2*PI */ } Inkscape::XML::Node* SPGenericEllipse::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { if (flags & SP_OBJECT_WRITE_EXT) { if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { repr = xml_doc->createElement("svg:path"); } sp_repr_set_svg_double(repr, "sodipodi:cx", this->cx.computed); sp_repr_set_svg_double(repr, "sodipodi:cy", this->cy.computed); sp_repr_set_svg_double(repr, "sodipodi:rx", this->rx.computed); sp_repr_set_svg_double(repr, "sodipodi:ry", this->ry.computed); if (SP_IS_ARC(this)) { sp_arc_set_elliptical_path_attribute(SP_ARC(this), this->getRepr()); } } this->set_shape(); // evaluate SPCurve SPShape::write(xml_doc, repr, flags); return repr; } /* SVG element */ SPEllipse::SPEllipse() : SPGenericEllipse() { } SPEllipse::~SPEllipse() { } void SPEllipse::build(SPDocument *document, Inkscape::XML::Node *repr) { SPGenericEllipse::build(document, repr); this->readAttr( "cx" ); this->readAttr( "cy" ); this->readAttr( "rx" ); this->readAttr( "ry" ); } Inkscape::XML::Node* SPEllipse::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { repr = xml_doc->createElement("svg:ellipse"); } sp_repr_set_svg_double(repr, "cx", this->cx.computed); sp_repr_set_svg_double(repr, "cy", this->cy.computed); sp_repr_set_svg_double(repr, "rx", this->rx.computed); sp_repr_set_svg_double(repr, "ry", this->ry.computed); SPGenericEllipse::write(xml_doc, repr, flags); return repr; } void SPEllipse::set(unsigned int key, gchar const* value) { switch (key) { case SP_ATTR_CX: this->cx.readOrUnset(value); this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_CY: this->cy.readOrUnset(value); this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_RX: if (!this->rx.read(value) || (this->rx.value <= 0.0)) { this->rx.unset(); } this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_RY: if (!this->ry.read(value) || (this->ry.value <= 0.0)) { this->ry.unset(); } this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; default: SPGenericEllipse::set(key, value); break; } } gchar* SPEllipse::description() { return g_strdup(_("Ellipse")); } void sp_ellipse_position_set(SPEllipse *ellipse, gdouble x, gdouble y, gdouble rx, gdouble ry) { SPGenericEllipse *ge; g_return_if_fail(ellipse != NULL); g_return_if_fail(SP_IS_ELLIPSE(ellipse)); ge = SP_GENERICELLIPSE(ellipse); ge->cx.computed = x; ge->cy.computed = y; ge->rx.computed = rx; ge->ry.computed = ry; ((SPObject *)ge)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); } /* SVG element */ SPCircle::SPCircle() : SPGenericEllipse() { } SPCircle::~SPCircle() { } void SPCircle::build(SPDocument *document, Inkscape::XML::Node *repr) { SPGenericEllipse::build(document, repr); this->readAttr( "cx" ); this->readAttr( "cy" ); this->readAttr( "r" ); } Inkscape::XML::Node* SPCircle::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { repr = xml_doc->createElement("svg:circle"); } sp_repr_set_svg_double(repr, "cx", this->cx.computed); sp_repr_set_svg_double(repr, "cy", this->cy.computed); sp_repr_set_svg_double(repr, "r", this->rx.computed); SPGenericEllipse::write(xml_doc, repr, flags); return repr; } void SPCircle::set(unsigned int key, gchar const* value) { switch (key) { case SP_ATTR_CX: this->cx.readOrUnset(value); this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_CY: this->cy.readOrUnset(value); this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_R: if (!this->rx.read(value) || this->rx.value <= 0.0) { this->rx.unset(); } this->ry = this->rx; this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; default: SPGenericEllipse::set(key, value); break; } } gchar* SPCircle::description() { return g_strdup(_("Circle")); } /* element */ SPArc::SPArc() : SPGenericEllipse() { } SPArc::~SPArc() { } void SPArc::build(SPDocument *document, Inkscape::XML::Node *repr) { SPGenericEllipse::build(document, repr); this->readAttr( "sodipodi:cx" ); this->readAttr( "sodipodi:cy" ); this->readAttr( "sodipodi:rx" ); this->readAttr( "sodipodi:ry" ); this->readAttr( "sodipodi:start" ); this->readAttr( "sodipodi:end" ); this->readAttr( "sodipodi:open" ); } /* * sp_arc_set_elliptical_path_attribute: * * Convert center to endpoint parameterization and set it to repr. * * See SVG 1.0 Specification W3C Recommendation * ``F.6 Ellptical arc implementation notes'' for more detail. */ static gboolean sp_arc_set_elliptical_path_attribute(SPArc *arc, Inkscape::XML::Node *repr) { SPGenericEllipse *ge = SP_GENERICELLIPSE(arc); Inkscape::SVG::PathString str; Geom::Point p1 = sp_arc_get_xy(arc, ge->start); Geom::Point p2 = sp_arc_get_xy(arc, ge->end); double rx = ge->rx.computed; double ry = ge->ry.computed; str.moveTo(p1); double dt = fmod(ge->end - ge->start, SP_2PI); if (fabs(dt) < 1e-6) { Geom::Point ph = sp_arc_get_xy(arc, (ge->start + ge->end) / 2.0); str.arcTo(rx, ry, 0, true, true, ph) .arcTo(rx, ry, 0, true, true, p2) .closePath(); } else { bool fa = (fabs(dt) > M_PI); bool fs = (dt > 0); str.arcTo(rx, ry, 0, fa, fs, p2); if (ge->closed) { Geom::Point center = Geom::Point(ge->cx.computed, ge->cy.computed); str.lineTo(center).closePath(); } } repr->setAttribute("d", str.c_str()); return true; } Inkscape::XML::Node* SPArc::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { repr = xml_doc->createElement("svg:path"); } if (flags & SP_OBJECT_WRITE_EXT) { repr->setAttribute("sodipodi:type", "arc"); sp_repr_set_svg_double(repr, "sodipodi:cx", this->cx.computed); sp_repr_set_svg_double(repr, "sodipodi:cy", this->cy.computed); sp_repr_set_svg_double(repr, "sodipodi:rx", this->rx.computed); sp_repr_set_svg_double(repr, "sodipodi:ry", this->ry.computed); // write start and end only if they are non-trivial; otherwise remove gdouble len = fmod(this->end - this->start, SP_2PI); if (len < 0.0) { len += SP_2PI; } if (!(fabs(len) < 1e-8 || fabs(len - SP_2PI) < 1e-8)) { sp_repr_set_svg_double(repr, "sodipodi:start", this->start); sp_repr_set_svg_double(repr, "sodipodi:end", this->end); repr->setAttribute("sodipodi:open", (!this->closed) ? "true" : NULL); } else { repr->setAttribute("sodipodi:end", NULL); repr->setAttribute("sodipodi:start", NULL); repr->setAttribute("sodipodi:open", NULL); } } // write d= sp_arc_set_elliptical_path_attribute(this, repr); SPGenericEllipse::write(xml_doc, repr, flags); return repr; } void SPArc::set(unsigned int key, gchar const* value) { switch (key) { case SP_ATTR_SODIPODI_CX: this->cx.readOrUnset(value); this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_SODIPODI_CY: this->cy.readOrUnset(value); this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_SODIPODI_RX: if (!this->rx.read(value) || this->rx.computed <= 0.0) { this->rx.unset(); } this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_SODIPODI_RY: if (!this->ry.read(value) || this->ry.computed <= 0.0) { this->ry.unset(); } this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_SODIPODI_START: if (value) { sp_svg_number_read_d(value, &this->start); } else { this->start = 0; } this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_SODIPODI_END: if (value) { sp_svg_number_read_d(value, &this->end); } else { this->end = 2 * M_PI; } this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_SODIPODI_OPEN: this->closed = (!value); this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; default: SPGenericEllipse::set(key, value); break; } } void SPArc::modified(guint flags) { if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { this->set_shape(); } SPGenericEllipse::modified(flags); } gchar* SPArc::description() { gdouble len = fmod(this->end - this->start, SP_2PI); if (len < 0.0) { len += SP_2PI; } if (!(fabs(len) < 1e-8 || fabs(len - SP_2PI) < 1e-8)) { if (this->closed) { return g_strdup(_("Segment")); } else { return g_strdup(_("Arc")); } } else { return g_strdup(_("Ellipse")); } } void sp_arc_position_set(SPArc *arc, gdouble x, gdouble y, gdouble rx, gdouble ry) { g_return_if_fail(arc != NULL); g_return_if_fail(SP_IS_ARC(arc)); SPGenericEllipse *ge = SP_GENERICELLIPSE(arc); ge->cx.computed = x; ge->cy.computed = y; ge->rx.computed = rx; ge->ry.computed = ry; Inkscape::Preferences *prefs = Inkscape::Preferences::get(); // those pref values are in degrees, while we want radians if (prefs->getDouble("/tools/shapes/arc/start", 0.0) != 0) ge->start = prefs->getDouble("/tools/shapes/arc/start", 0.0) * M_PI / 180; if (prefs->getDouble("/tools/shapes/arc/end", 0.0) != 0) ge->end = prefs->getDouble("/tools/shapes/arc/end", 0.0) * M_PI / 180; if (!prefs->getBool("/tools/shapes/arc/open")) ge->closed = 1; else ge->closed = 0; ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); } Geom::Point sp_arc_get_xy(SPArc *arc, gdouble arg) { SPGenericEllipse *ge = SP_GENERICELLIPSE(arc); return Geom::Point(ge->rx.computed * cos(arg) + ge->cx.computed, ge->ry.computed * sin(arg) + ge->cy.computed); } /* 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:fileencoding=utf-8:textwidth=99 :