diff options
Diffstat (limited to '')
-rw-r--r-- | src/object/sp-rect.cpp | 619 |
1 files changed, 619 insertions, 0 deletions
diff --git a/src/object/sp-rect.cpp b/src/object/sp-rect.cpp new file mode 100644 index 0000000..6b03dc7 --- /dev/null +++ b/src/object/sp-rect.cpp @@ -0,0 +1,619 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * SVG <rect> implementation + * + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * bulia byak <buliabyak@users.sf.net> + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "display/curve.h" + +#include "inkscape.h" +#include "document.h" +#include "attributes.h" +#include "style.h" +#include "sp-rect.h" +#include "sp-guide.h" +#include "preferences.h" +#include "svg/svg.h" +#include "snap-candidate.h" +#include "snap-preferences.h" +#include <glibmm/i18n.h> + +#define noRECT_VERBOSE + +//#define OBJECT_TRACE + +SPRect::SPRect() : SPShape() { +} + +SPRect::~SPRect() = default; + +void SPRect::build(SPDocument* doc, Inkscape::XML::Node* repr) { +#ifdef OBJECT_TRACE + objectTrace( "SPRect::build" ); +#endif + + SPShape::build(doc, repr); + + this->readAttr(SPAttr::X); + this->readAttr(SPAttr::Y); + this->readAttr(SPAttr::WIDTH); + this->readAttr(SPAttr::HEIGHT); + this->readAttr(SPAttr::RX); + this->readAttr(SPAttr::RY); + +#ifdef OBJECT_TRACE + objectTrace( "SPRect::build", false ); +#endif +} + +void SPRect::set(SPAttr key, gchar const *value) { + +#ifdef OBJECT_TRACE + std::stringstream temp; + temp << "SPRect::set: " << sp_attribute_name(key) << " " << (value?value:"null"); + objectTrace( temp.str() ); +#endif + + /* fixme: We need real error processing some time */ + + // We must update the SVGLengths immediately or nodes may be misplaced after they are moved. + double const w = viewport.width(); + double const h = viewport.height(); + double const em = style->font_size.computed; + double const ex = em * 0.5; + + switch (key) { + case SPAttr::X: + this->x.readOrUnset(value); + this->x.update( em, ex, w ); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SPAttr::Y: + this->y.readOrUnset(value); + this->y.update( em, ex, h ); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SPAttr::WIDTH: + if (!this->width.read(value) || this->width.value < 0.0) { + this->width.unset(); + } + this->width.update( em, ex, w ); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SPAttr::HEIGHT: + if (!this->height.read(value) || this->height.value < 0.0) { + this->height.unset(); + } + this->height.update( em, ex, h ); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SPAttr::RX: + if (!this->rx.read(value) || this->rx.value <= 0.0) { + this->rx.unset(); + } + this->rx.update( em, ex, w ); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + case SPAttr::RY: + if (!this->ry.read(value) || this->ry.value <= 0.0) { + this->ry.unset(); + } + this->ry.update( em, ex, h ); + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + + default: + SPShape::set(key, value); + break; + } +#ifdef OBJECT_TRACE + objectTrace( "SPRect::set", false ); +#endif +} + +void SPRect::update(SPCtx* ctx, unsigned int flags) { + +#ifdef OBJECT_TRACE + objectTrace( "SPRect::update", true, flags ); +#endif + + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + SPItemCtx const *ictx = reinterpret_cast<SPItemCtx const *>(ctx); + + double const w = ictx->viewport.width(); + double const h = ictx->viewport.height(); + double const em = style->font_size.computed; + double const ex = 0.5 * em; // fixme: get x height from pango or libnrtype. + + this->x.update(em, ex, w); + this->y.update(em, ex, h); + this->width.update(em, ex, w); + this->height.update(em, ex, h); + this->rx.update(em, ex, w); + this->ry.update(em, ex, h); + this->set_shape(); + + flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore + } + + SPShape::update(ctx, flags); +#ifdef OBJECT_TRACE + objectTrace( "SPRect::update", false, flags ); +#endif +} + +Inkscape::XML::Node * SPRect::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + +#ifdef OBJECT_TRACE + objectTrace( "SPRect::write", true, flags ); +#endif + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:rect"); + } + if (this->hasPathEffectOnClipOrMaskRecursive(this) && repr && strcmp(repr->name(), "svg:rect") == 0) { + repr->setCodeUnsafe(g_quark_from_string("svg:path")); + repr->setAttribute("sodipodi:type", "rect"); + } + repr->setAttributeSvgLength("width", this->width); + repr->setAttributeSvgLength("height", this->height); + + if (this->rx._set) { + repr->setAttributeSvgLength("rx", this->rx); + } + + if (this->ry._set) { + repr->setAttributeSvgLength("ry", this->ry); + } + + repr->setAttributeSvgLength("x", this->x); + repr->setAttributeSvgLength("y", this->y); + // write d= + if (strcmp(repr->name(), "svg:rect") != 0) { + set_rect_path_attribute(repr); // include set_shape() + } else { + this->set_shape(); // evaluate SPCurve + } + SPShape::write(xml_doc, repr, flags); + +#ifdef OBJECT_TRACE + objectTrace( "SPRect::write", false, flags ); +#endif + + return repr; +} + +const char* SPRect::typeName() const { + return "rect"; +} + +const char* SPRect::displayName() const { + return _("Rectangle"); +} + +#define C1 0.554 + +void SPRect::set_shape() { + if (checkBrokenPathEffect()) { + return; + } + if ((this->height.computed < 1e-18) || (this->width.computed < 1e-18)) { + this->setCurveInsync(nullptr); + this->setCurveBeforeLPE(nullptr); + return; + } + + auto c = std::make_unique<SPCurve>(); + + double const x = this->x.computed; + double const y = this->y.computed; + double const w = this->width.computed; + double const h = this->height.computed; + double const w2 = w / 2; + double const h2 = h / 2; + double const rx = std::min(( this->rx._set + ? this->rx.computed + : ( this->ry._set + ? this->ry.computed + : 0.0 ) ), + .5 * this->width.computed); + double const ry = std::min(( this->ry._set + ? this->ry.computed + : ( this->rx._set + ? this->rx.computed + : 0.0 ) ), + .5 * this->height.computed); + /* TODO: Handle negative rx or ry as per + * http://www.w3.org/TR/SVG11/shapes.html#RectElementRXAttribute once Inkscape has proper error + * handling (see http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing). + */ + + /* We don't use proper circular/elliptical arcs, but bezier curves can approximate a 90-degree + * arc fairly well. + */ + if ((rx > 1e-18) && (ry > 1e-18)) { + c->moveto(x + rx, y); + + if (rx < w2) { + c->lineto(x + w - rx, y); + } + + c->curveto(x + w - rx * (1 - C1), y, x + w, y + ry * (1 - C1), x + w, y + ry); + + if (ry < h2) { + c->lineto(x + w, y + h - ry); + } + + c->curveto(x + w, y + h - ry * (1 - C1), x + w - rx * (1 - C1), y + h, x + w - rx, y + h); + + if (rx < w2) { + c->lineto(x + rx, y + h); + } + + c->curveto(x + rx * (1 - C1), y + h, x, y + h - ry * (1 - C1), x, y + h - ry); + + if (ry < h2) { + c->lineto(x, y + ry); + } + + c->curveto(x, y + ry * (1 - C1), x + rx * (1 - C1), y, x + rx, y); + } else { + c->moveto(x + 0.0, y + 0.0); + c->lineto(x + w, y + 0.0); + c->lineto(x + w, y + h); + c->lineto(x + 0.0, y + h); + } + + c->closepath(); + + if (prepareShapeForLPE(c.get())) { + return; + } + + // This happends on undo, fix bug:#1791784 + setCurveInsync(std::move(c)); +} + +bool SPRect::set_rect_path_attribute(Inkscape::XML::Node *repr) +{ + // Make sure our pathvector is up to date. + this->set_shape(); + + if (_curve) { + repr->setAttribute("d", sp_svg_write_path(_curve->get_pathvector())); + } else { + repr->removeAttribute("d"); + } + + return true; +} + +void SPRect::modified(guint flags) +{ + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + this->set_shape(); + } + + SPShape::modified(flags); +} + +/* fixme: Think (Lauris) */ + +void SPRect::setPosition(gdouble x, gdouble y, gdouble width, gdouble height) { + this->x = x; + this->y = y; + this->width = width; + this->height = height; + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +void SPRect::setRx(bool set, gdouble value) { + this->rx._set = set; + + if (set) { + this->rx = value; + } + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +void SPRect::setRy(bool set, gdouble value) { + this->ry._set = set; + + if (set) { + this->ry = value; + } + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +void SPRect::update_patheffect(bool write) { + SPShape::update_patheffect(write); +} + +Geom::Affine SPRect::set_transform(Geom::Affine const& xform) { + if (pathEffectsEnabled() && !optimizeTransforms()) { + return xform; + } + /* Calculate rect start in parent coords. */ + Geom::Point pos(Geom::Point(this->x.computed, this->y.computed) * 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 sw = hypot(ret[0], ret[1]); + gdouble const sh = hypot(ret[2], ret[3]); + + if (sw > 1e-9) { + ret[0] /= sw; + ret[1] /= sw; + } else { + ret[0] = 1.0; + ret[1] = 0.0; + } + + if (sh > 1e-9) { + ret[2] /= sh; + ret[3] /= sh; + } else { + ret[2] = 0.0; + ret[3] = 1.0; + } + + /* Preserve units */ + this->width.scale( sw ); + this->height.scale( sh ); + + if (this->rx._set) { + this->rx.scale( sw ); + } + + if (this->ry._set) { + this->ry.scale( sh ); + } + + /* Find start in item coords */ + pos = pos * ret.inverse(); + this->x = pos[Geom::X]; + this->y = pos[Geom::Y]; + + this->set_shape(); + + // Adjust stroke width + this->adjust_stroke(sqrt(fabs(sw * sh))); + + // Adjust pattern fill + this->adjust_pattern(xform * ret.inverse()); + + // Adjust gradient fill + this->adjust_gradient(xform * ret.inverse()); + + return ret; +} + + +/** +Returns the ratio in which the vector from p0 to p1 is stretched by transform + */ +gdouble SPRect::vectorStretch(Geom::Point p0, Geom::Point p1, Geom::Affine xform) { + if (p0 == p1) { + return 0; + } + + return (Geom::distance(p0 * xform, p1 * xform) / Geom::distance(p0, p1)); +} + +void SPRect::setVisibleRx(gdouble rx) { + if (rx == 0) { + this->rx.unset(); + } else { + this->rx = rx / SPRect::vectorStretch( + Geom::Point(this->x.computed + 1, this->y.computed), + Geom::Point(this->x.computed, this->y.computed), + this->i2doc_affine()); + } + + this->updateRepr(); +} + +void SPRect::setVisibleRy(gdouble ry) { + if (ry == 0) { + this->ry.unset(); + } else { + this->ry = ry / SPRect::vectorStretch( + Geom::Point(this->x.computed, this->y.computed + 1), + Geom::Point(this->x.computed, this->y.computed), + this->i2doc_affine()); + } + + this->updateRepr(); +} + +gdouble SPRect::getVisibleRx() const { + if (!this->rx._set) { + return 0; + } + + return this->rx.computed * SPRect::vectorStretch( + Geom::Point(this->x.computed + 1, this->y.computed), + Geom::Point(this->x.computed, this->y.computed), + this->i2doc_affine()); +} + +gdouble SPRect::getVisibleRy() const { + if (!this->ry._set) { + return 0; + } + + return this->ry.computed * SPRect::vectorStretch( + Geom::Point(this->x.computed, this->y.computed + 1), + Geom::Point(this->x.computed, this->y.computed), + this->i2doc_affine()); +} + +Geom::Rect SPRect::getRect() const { + Geom::Point p0 = Geom::Point(this->x.computed, this->y.computed); + Geom::Point p2 = Geom::Point(this->x.computed + this->width.computed, this->y.computed + this->height.computed); + + return Geom::Rect(p0, p2); +} + +void SPRect::compensateRxRy(Geom::Affine xform) { + if (this->rx.computed == 0 && this->ry.computed == 0) { + return; // nothing to compensate + } + + // test unit vectors to find out compensation: + Geom::Point c(this->x.computed, this->y.computed); + Geom::Point cx = c + Geom::Point(1, 0); + Geom::Point cy = c + Geom::Point(0, 1); + + // apply previous transform if any + c *= this->transform; + cx *= this->transform; + cy *= this->transform; + + // find out stretches that we need to compensate + gdouble eX = SPRect::vectorStretch(cx, c, xform); + gdouble eY = SPRect::vectorStretch(cy, c, xform); + + // If only one of the radii is set, set both radii so they have the same visible length + // This is needed because if we just set them the same length in SVG, they might end up unequal because of transform + if ((this->rx._set && !this->ry._set) || (this->ry._set && !this->rx._set)) { + gdouble r = MAX(this->rx.computed, this->ry.computed); + this->rx = r / eX; + this->ry = r / eY; + } else { + this->rx = this->rx.computed / eX; + this->ry = this->ry.computed / eY; + } + + // Note that a radius may end up larger than half-side if the rect is scaled down; + // that's ok because this preserves the intended radii in case the rect is enlarged again, + // and set_shape will take care of trimming too large radii when generating d= +} + +void SPRect::setVisibleWidth(gdouble width) { + this->width = width / SPRect::vectorStretch( + Geom::Point(this->x.computed + 1, this->y.computed), + Geom::Point(this->x.computed, this->y.computed), + this->i2doc_affine()); + + this->updateRepr(); +} + +void SPRect::setVisibleHeight(gdouble height) { + this->height = height / SPRect::vectorStretch( + Geom::Point(this->x.computed, this->y.computed + 1), + Geom::Point(this->x.computed, this->y.computed), + this->i2doc_affine()); + + this->updateRepr(); +} + +gdouble SPRect::getVisibleWidth() const { + if (!this->width._set) { + return 0; + } + + return this->width.computed * SPRect::vectorStretch( + Geom::Point(this->x.computed + 1, this->y.computed), + Geom::Point(this->x.computed, this->y.computed), + this->i2doc_affine()); +} + +gdouble SPRect::getVisibleHeight() const { + if (!this->height._set) { + return 0; + } + + return this->height.computed * SPRect::vectorStretch( + Geom::Point(this->x.computed, this->y.computed + 1), + Geom::Point(this->x.computed, this->y.computed), + this->i2doc_affine()); +} + +void SPRect::snappoints(std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) const { + /* This method overrides sp_shape_snappoints, which is the default for any shape. The default method + returns all eight points along the path of a rounded rectangle, but not the real corners. Snapping + the startpoint and endpoint of each rounded corner is not very useful and really confusing. Instead + we could snap either the real corners, or not snap at all. Bulia Byak opted to snap the real corners, + but it should be noted that this might be confusing in some cases with relatively large radii. With + small radii though the user will easily understand which point is snapping. */ + + Geom::Affine const i2dt (this->i2dt_affine ()); + + Geom::Point p0 = Geom::Point(this->x.computed, this->y.computed) * i2dt; + Geom::Point p1 = Geom::Point(this->x.computed, this->y.computed + this->height.computed) * i2dt; + Geom::Point p2 = Geom::Point(this->x.computed + this->width.computed, this->y.computed + this->height.computed) * i2dt; + Geom::Point p3 = Geom::Point(this->x.computed + this->width.computed, this->y.computed) * i2dt; + + if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_RECT_CORNER)) { + p.emplace_back(p0, Inkscape::SNAPSOURCE_RECT_CORNER, Inkscape::SNAPTARGET_RECT_CORNER); + p.emplace_back(p1, Inkscape::SNAPSOURCE_RECT_CORNER, Inkscape::SNAPTARGET_RECT_CORNER); + p.emplace_back(p2, Inkscape::SNAPSOURCE_RECT_CORNER, Inkscape::SNAPTARGET_RECT_CORNER); + p.emplace_back(p3, Inkscape::SNAPSOURCE_RECT_CORNER, Inkscape::SNAPTARGET_RECT_CORNER); + } + + if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_LINE_MIDPOINT)) { + p.emplace_back((p0 + p1)/2, Inkscape::SNAPSOURCE_LINE_MIDPOINT, Inkscape::SNAPTARGET_LINE_MIDPOINT); + p.emplace_back((p1 + p2)/2, Inkscape::SNAPSOURCE_LINE_MIDPOINT, Inkscape::SNAPTARGET_LINE_MIDPOINT); + p.emplace_back((p2 + p3)/2, Inkscape::SNAPSOURCE_LINE_MIDPOINT, Inkscape::SNAPTARGET_LINE_MIDPOINT); + p.emplace_back((p3 + p0)/2, Inkscape::SNAPSOURCE_LINE_MIDPOINT, Inkscape::SNAPTARGET_LINE_MIDPOINT); + } + + if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_OBJECT_MIDPOINT)) { + p.emplace_back((p0 + p2)/2, Inkscape::SNAPSOURCE_OBJECT_MIDPOINT, Inkscape::SNAPTARGET_OBJECT_MIDPOINT); + } +} + +void SPRect::convert_to_guides() const { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + if (!prefs->getBool("/tools/shapes/rect/convertguides", true)) { + // Use bounding box instead of edges + SPShape::convert_to_guides(); + return; + } + + std::list<std::pair<Geom::Point, Geom::Point> > pts; + + Geom::Affine const i2dt(this->i2dt_affine()); + + Geom::Point A1(Geom::Point(this->x.computed, this->y.computed) * i2dt); + Geom::Point A2(Geom::Point(this->x.computed, this->y.computed + this->height.computed) * i2dt); + Geom::Point A3(Geom::Point(this->x.computed + this->width.computed, this->y.computed + this->height.computed) * i2dt); + Geom::Point A4(Geom::Point(this->x.computed + this->width.computed, this->y.computed) * i2dt); + + pts.emplace_back(A1, A2); + pts.emplace_back(A2, A3); + pts.emplace_back(A3, A4); + pts.emplace_back(A4, A1); + + sp_guide_pt_pairs_to_guides(this->document, pts); +} + +/* + 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 : |