summaryrefslogtreecommitdiffstats
path: root/src/object/sp-rect.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/object/sp-rect.cpp')
-rw-r--r--src/object/sp-rect.cpp619
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 :