diff options
Diffstat (limited to 'src/sp-rect.cpp')
| -rw-r--r-- | src/sp-rect.cpp | 561 |
1 files changed, 561 insertions, 0 deletions
diff --git a/src/sp-rect.cpp b/src/sp-rect.cpp new file mode 100644 index 000000000..edcea21a2 --- /dev/null +++ b/src/sp-rect.cpp @@ -0,0 +1,561 @@ +#define __SP_RECT_C__ + +/* + * 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, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + + +#include <display/curve.h> +#include <libnr/nr-matrix-div.h> +#include <libnr/nr-matrix-fns.h> + +#include "attributes.h" +#include "style.h" +#include "sp-rect.h" +#include <glibmm/i18n.h> +#include "xml/repr.h" + +#define noRECT_VERBOSE + +static void sp_rect_class_init(SPRectClass *klass); +static void sp_rect_init(SPRect *rect); + +static void sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); +static void sp_rect_set(SPObject *object, unsigned key, gchar const *value); +static void sp_rect_update(SPObject *object, SPCtx *ctx, guint flags); +static Inkscape::XML::Node *sp_rect_write(SPObject *object, Inkscape::XML::Node *repr, guint flags); + +static gchar *sp_rect_description(SPItem *item); +static NR::Matrix sp_rect_set_transform(SPItem *item, NR::Matrix const &xform); + +static void sp_rect_set_shape(SPShape *shape); + +static SPShapeClass *parent_class; + +GType +sp_rect_get_type(void) +{ + static GType type = 0; + + if (!type) { + GTypeInfo info = { + sizeof(SPRectClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_rect_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof(SPRect), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_rect_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_SHAPE, "SPRect", &info, (GTypeFlags)0); + } + return type; +} + +static void +sp_rect_class_init(SPRectClass *klass) +{ + SPObjectClass *sp_object_class = (SPObjectClass *) klass; + SPItemClass *item_class = (SPItemClass *) klass; + SPShapeClass *shape_class = (SPShapeClass *) klass; + + parent_class = (SPShapeClass *)g_type_class_ref(SP_TYPE_SHAPE); + + sp_object_class->build = sp_rect_build; + sp_object_class->write = sp_rect_write; + sp_object_class->set = sp_rect_set; + sp_object_class->update = sp_rect_update; + + item_class->description = sp_rect_description; + item_class->set_transform = sp_rect_set_transform; + + shape_class->set_shape = sp_rect_set_shape; +} + +static void +sp_rect_init(SPRect *rect) +{ + /* Initializing to zero is automatic */ + /* sp_svg_length_unset(&rect->x, SP_SVG_UNIT_NONE, 0.0, 0.0); */ + /* sp_svg_length_unset(&rect->y, SP_SVG_UNIT_NONE, 0.0, 0.0); */ + /* sp_svg_length_unset(&rect->width, SP_SVG_UNIT_NONE, 0.0, 0.0); */ + /* sp_svg_length_unset(&rect->height, SP_SVG_UNIT_NONE, 0.0, 0.0); */ + /* sp_svg_length_unset(&rect->rx, SP_SVG_UNIT_NONE, 0.0, 0.0); */ + /* sp_svg_length_unset(&rect->ry, SP_SVG_UNIT_NONE, 0.0, 0.0); */ +} + +static void +sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + SPRect *rect = SP_RECT(object); + + if (((SPObjectClass *) parent_class)->build) + ((SPObjectClass *) parent_class)->build(object, document, repr); + + sp_object_read_attr(object, "x"); + sp_object_read_attr(object, "y"); + sp_object_read_attr(object, "width"); + sp_object_read_attr(object, "height"); + sp_object_read_attr(object, "rx"); + sp_object_read_attr(object, "ry"); + + Inkscape::Version const version = sp_object_get_sodipodi_version(object); + + if ( version.major == 0 && version.minor == 29 ) { + if (rect->rx._set && rect->ry._set) { + /* 0.29 treated 0.0 radius as missing value */ + if ((rect->rx.value != 0.0) && (rect->ry.value == 0.0)) { + repr->setAttribute("ry", NULL); + sp_object_read_attr(object, "ry"); + } else if ((rect->ry.value != 0.0) && (rect->rx.value == 0.0)) { + repr->setAttribute("rx", NULL); + sp_object_read_attr(object, "rx"); + } + } + } +} + +static void +sp_rect_set(SPObject *object, unsigned key, gchar const *value) +{ + SPRect *rect = SP_RECT(object); + + /* fixme: We need real error processing some time */ + + switch (key) { + case SP_ATTR_X: + rect->x.readOrUnset(value); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_Y: + rect->y.readOrUnset(value); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_WIDTH: + if (!rect->width.read(value) || rect->width.value < 0.0) { + rect->width.unset(); + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_HEIGHT: + if (!rect->height.read(value) || rect->height.value < 0.0) { + rect->height.unset(); + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_RX: + if (!rect->rx.read(value) || rect->rx.value < 0.0) { + rect->rx.unset(); + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_RY: + if (!rect->ry.read(value) || rect->ry.value < 0.0) { + rect->ry.unset(); + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + default: + if (((SPObjectClass *) parent_class)->set) + ((SPObjectClass *) parent_class)->set(object, key, value); + break; + } +} + +static void +sp_rect_update(SPObject *object, SPCtx *ctx, guint flags) +{ + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + SPRect *rect = (SPRect *) object; + SPStyle *style = object->style; + SPItemCtx const *ictx = (SPItemCtx const *) ctx; + double const d = NR::expansion(ictx->i2vp); + double const w = (ictx->vp.x1 - ictx->vp.x0) / d; + double const h = (ictx->vp.y1 - ictx->vp.y0) / d; + double const em = style->font_size.computed; + double const ex = 0.5 * em; // fixme: get x height from pango or libnrtype. + rect->x.update(em, ex, w); + rect->y.update(em, ex, h); + rect->width.update(em, ex, w); + rect->height.update(em, ex, h); + rect->rx.update(em, ex, w); + rect->ry.update(em, ex, h); + sp_shape_set_shape((SPShape *) object); + flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore + } + + if (((SPObjectClass *) parent_class)->update) + ((SPObjectClass *) parent_class)->update(object, ctx, flags); +} + +static Inkscape::XML::Node * +sp_rect_write(SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + SPRect *rect = SP_RECT(object); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = sp_repr_new("svg:rect"); + } + + sp_repr_set_svg_double(repr, "width", rect->width.computed); + sp_repr_set_svg_double(repr, "height", rect->height.computed); + if (rect->rx._set) sp_repr_set_svg_double(repr, "rx", rect->rx.computed); + if (rect->ry._set) sp_repr_set_svg_double(repr, "ry", rect->ry.computed); + sp_repr_set_svg_double(repr, "x", rect->x.computed); + sp_repr_set_svg_double(repr, "y", rect->y.computed); + + if (((SPObjectClass *) parent_class)->write) + ((SPObjectClass *) parent_class)->write(object, repr, flags); + + return repr; +} + +static gchar * +sp_rect_description(SPItem *item) +{ + g_return_val_if_fail(SP_IS_RECT(item), NULL); + + return g_strdup(_("<b>Rectangle</b>")); +} + +#define C1 0.554 + +static void +sp_rect_set_shape(SPShape *shape) +{ + SPRect *rect = (SPRect *) shape; + + if ((rect->height.computed < 1e-18) || (rect->width.computed < 1e-18)) return; + + SPCurve *c = sp_curve_new(); + + double const x = rect->x.computed; + double const y = rect->y.computed; + double const w = rect->width.computed; + double const h = rect->height.computed; + double const w2 = w / 2; + double const h2 = h / 2; + double const rx = std::min(( rect->rx._set + ? rect->rx.computed + : ( rect->ry._set + ? rect->ry.computed + : 0.0 ) ), + .5 * rect->width.computed); + double const ry = std::min(( rect->ry._set + ? rect->ry.computed + : ( rect->rx._set + ? rect->rx.computed + : 0.0 ) ), + .5 * rect->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)) { + sp_curve_moveto(c, x + rx, y); + if (rx < w2) sp_curve_lineto(c, x + w - rx, y); + sp_curve_curveto(c, x + w - rx * (1 - C1), y, x + w, y + ry * (1 - C1), x + w, y + ry); + if (ry < h2) sp_curve_lineto(c, x + w, y + h - ry); + sp_curve_curveto(c, x + w, y + h - ry * (1 - C1), x + w - rx * (1 - C1), y + h, x + w - rx, y + h); + if (rx < w2) sp_curve_lineto(c, x + rx, y + h); + sp_curve_curveto(c, x + rx * (1 - C1), y + h, x, y + h - ry * (1 - C1), x, y + h - ry); + if (ry < h2) sp_curve_lineto(c, x, y + ry); + sp_curve_curveto(c, x, y + ry * (1 - C1), x + rx * (1 - C1), y, x + rx, y); + } else { + sp_curve_moveto(c, x + 0.0, y + 0.0); + sp_curve_lineto(c, x + w, y + 0.0); + sp_curve_lineto(c, x + w, y + h); + sp_curve_lineto(c, x + 0.0, y + h); + sp_curve_lineto(c, x + 0.0, y + 0.0); + } + + sp_curve_closepath_current(c); + sp_shape_set_curve_insync(SP_SHAPE(rect), c, TRUE); + sp_curve_unref(c); +} + +/* fixme: Think (Lauris) */ + +void +sp_rect_position_set(SPRect *rect, gdouble x, gdouble y, gdouble width, gdouble height) +{ + g_return_if_fail(rect != NULL); + g_return_if_fail(SP_IS_RECT(rect)); + + rect->x.computed = x; + rect->y.computed = y; + rect->width.computed = width; + rect->height.computed = height; + + SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +void +sp_rect_set_rx(SPRect *rect, gboolean set, gdouble value) +{ + g_return_if_fail(rect != NULL); + g_return_if_fail(SP_IS_RECT(rect)); + + rect->rx._set = set; + if (set) rect->rx.computed = value; + + SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +void +sp_rect_set_ry(SPRect *rect, gboolean set, gdouble value) +{ + g_return_if_fail(rect != NULL); + g_return_if_fail(SP_IS_RECT(rect)); + + rect->ry._set = set; + if (set) rect->ry.computed = value; + + SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +/* + * Initially we'll do: + * Transform x, y, set x, y, clear translation + */ + +/* fixme: Use preferred units somehow (Lauris) */ +/* fixme: Alternately preserve whatever units there are (lauris) */ + +static NR::Matrix +sp_rect_set_transform(SPItem *item, NR::Matrix const &xform) +{ + SPRect *rect = SP_RECT(item); + + /* Calculate rect start in parent coords. */ + NR::Point pos( NR::Point(rect->x.computed, rect->y.computed) * xform ); + + /* This function takes care of translation and scaling, we return whatever parts we can't + handle. */ + NR::Matrix ret(NR::transform(xform)); + 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; + } + + /* fixme: Would be nice to preserve units here */ + rect->width = rect->width.computed * sw; + rect->height = rect->height.computed * sh; + if (rect->rx._set) { + rect->rx = rect->rx.computed * sw; + } + if (rect->ry._set) { + rect->ry = rect->ry.computed * sh; + } + + /* Find start in item coords */ + pos = pos * ret.inverse(); + rect->x = pos[NR::X]; + rect->y = pos[NR::Y]; + + sp_rect_set_shape(rect); + + // Adjust stroke width + sp_item_adjust_stroke(item, sqrt(fabs(sw * sh))); + + // Adjust pattern fill + sp_item_adjust_pattern(item, xform / ret); + + // Adjust gradient fill + sp_item_adjust_gradient(item, xform / ret); + + item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + + return ret; +} + + +/** +Returns the ratio in which the vector from p0 to p1 is stretched by transform + */ +static gdouble +vector_stretch(NR::Point p0, NR::Point p1, NR::Matrix xform) +{ + if (p0 == p1) + return 0; + return (NR::distance(p0 * xform, p1 * xform) / NR::distance(p0, p1)); +} + +void +sp_rect_set_visible_rx(SPRect *rect, gdouble rx) +{ + if (rx == 0) { + rect->rx.computed = 0; + rect->rx._set = false; + } else { + rect->rx.computed = rx / vector_stretch( + NR::Point(rect->x.computed + 1, rect->y.computed), + NR::Point(rect->x.computed, rect->y.computed), + SP_ITEM(rect)->transform); + rect->rx._set = true; + } + SP_OBJECT(rect)->updateRepr(); +} + +void +sp_rect_set_visible_ry(SPRect *rect, gdouble ry) +{ + if (ry == 0) { + rect->ry.computed = 0; + rect->ry._set = false; + } else { + rect->ry.computed = ry / vector_stretch( + NR::Point(rect->x.computed, rect->y.computed + 1), + NR::Point(rect->x.computed, rect->y.computed), + SP_ITEM(rect)->transform); + rect->ry._set = true; + } + SP_OBJECT(rect)->updateRepr(); +} + +gdouble +sp_rect_get_visible_rx(SPRect *rect) +{ + if (!rect->rx._set) + return 0; + return rect->rx.computed * vector_stretch( + NR::Point(rect->x.computed + 1, rect->y.computed), + NR::Point(rect->x.computed, rect->y.computed), + SP_ITEM(rect)->transform); +} + +gdouble +sp_rect_get_visible_ry(SPRect *rect) +{ + if (!rect->ry._set) + return 0; + return rect->ry.computed * vector_stretch( + NR::Point(rect->x.computed, rect->y.computed + 1), + NR::Point(rect->x.computed, rect->y.computed), + SP_ITEM(rect)->transform); +} + +void +sp_rect_compensate_rxry(SPRect *rect, NR::Matrix xform) +{ + if (rect->rx.computed == 0 && rect->ry.computed == 0) + return; // nothing to compensate + + // test unit vectors to find out compensation: + NR::Point c(rect->x.computed, rect->y.computed); + NR::Point cx = c + NR::Point(1, 0); + NR::Point cy = c + NR::Point(0, 1); + + // apply previous transform if any + c *= SP_ITEM(rect)->transform; + cx *= SP_ITEM(rect)->transform; + cy *= SP_ITEM(rect)->transform; + + // find out stretches that we need to compensate + gdouble eX = vector_stretch(cx, c, xform); + gdouble eY = vector_stretch(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 ((rect->rx._set && !rect->ry._set) || (rect->ry._set && !rect->rx._set)) { + gdouble r = MAX(rect->rx.computed, rect->ry.computed); + rect->rx.computed = r / eX; + rect->ry.computed = r / eY; + } else { + rect->rx.computed = rect->rx.computed / eX; + rect->ry.computed = rect->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= + + rect->rx._set = rect->ry._set = true; +} + +void +sp_rect_set_visible_width(SPRect *rect, gdouble width) +{ + rect->width.computed = width / vector_stretch( + NR::Point(rect->x.computed + 1, rect->y.computed), + NR::Point(rect->x.computed, rect->y.computed), + SP_ITEM(rect)->transform); + rect->width._set = true; + SP_OBJECT(rect)->updateRepr(); +} + +void +sp_rect_set_visible_height(SPRect *rect, gdouble height) +{ + rect->height.computed = height / vector_stretch( + NR::Point(rect->x.computed, rect->y.computed + 1), + NR::Point(rect->x.computed, rect->y.computed), + SP_ITEM(rect)->transform); + rect->height._set = true; + SP_OBJECT(rect)->updateRepr(); +} + +gdouble +sp_rect_get_visible_width(SPRect *rect) +{ + if (!rect->width._set) + return 0; + return rect->width.computed * vector_stretch( + NR::Point(rect->x.computed + 1, rect->y.computed), + NR::Point(rect->x.computed, rect->y.computed), + SP_ITEM(rect)->transform); +} + +gdouble +sp_rect_get_visible_height(SPRect *rect) +{ + if (!rect->height._set) + return 0; + return rect->height.computed * vector_stretch( + NR::Point(rect->x.computed, rect->y.computed + 1), + NR::Point(rect->x.computed, rect->y.computed), + SP_ITEM(rect)->transform); +} + +/* + 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 : |
