#define __SP_RECT_C__ /* * SVG implementation * * Authors: * Lauris Kaplinski * bulia byak * * 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 #include #include #include "attributes.h" #include "style.h" #include "sp-rect.h" #include #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(_("Rectangle")); } #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 :