Code

08d30b4f4be5a8db104f2c1c4ae2f6abe9ba8bee
[inkscape.git] / src / sp-rect.cpp
1 #define __SP_RECT_C__
3 /*
4  * SVG <rect> implementation
5  *
6  * Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   bulia byak <buliabyak@users.sf.net>
9  *
10  * Copyright (C) 1999-2002 Lauris Kaplinski
11  * Copyright (C) 2000-2001 Ximian, Inc.
12  *
13  * Released under GNU GPL, read the file 'COPYING' for more information
14  */
16 #ifdef HAVE_CONFIG_H
17 # include "config.h"
18 #endif
21 #include <display/curve.h>
22 #include <libnr/nr-matrix-ops.h>
23 #include <libnr/nr-matrix-fns.h>
25 #include "inkscape.h"
26 #include "document.h"
27 #include "attributes.h"
28 #include "style.h"
29 #include "sp-rect.h"
30 #include <glibmm/i18n.h>
31 #include "xml/repr.h"
32 #include "sp-guide.h"
33 #include "prefs-utils.h"
35 #define noRECT_VERBOSE
37 static void sp_rect_class_init(SPRectClass *klass);
38 static void sp_rect_init(SPRect *rect);
40 static void sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
41 static void sp_rect_set(SPObject *object, unsigned key, gchar const *value);
42 static void sp_rect_update(SPObject *object, SPCtx *ctx, guint flags);
43 static Inkscape::XML::Node *sp_rect_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags);
45 static gchar *sp_rect_description(SPItem *item);
46 static NR::Matrix sp_rect_set_transform(SPItem *item, NR::Matrix const &xform);
47 static void sp_rect_convert_to_guides(SPItem *item);
49 static void sp_rect_set_shape(SPShape *shape);
50 static void sp_rect_snappoints(SPItem const *item, SnapPointsIter p);
52 static SPShapeClass *parent_class;
54 GType
55 sp_rect_get_type(void)
56 {
57     static GType type = 0;
59     if (!type) {
60         GTypeInfo info = {
61             sizeof(SPRectClass),
62             NULL,   /* base_init */
63             NULL,   /* base_finalize */
64             (GClassInitFunc) sp_rect_class_init,
65             NULL,   /* class_finalize */
66             NULL,   /* class_data */
67             sizeof(SPRect),
68             16,     /* n_preallocs */
69             (GInstanceInitFunc) sp_rect_init,
70             NULL,   /* value_table */
71         };
72         type = g_type_register_static(SP_TYPE_SHAPE, "SPRect", &info, (GTypeFlags)0);
73     }
74     return type;
75 }
77 static void
78 sp_rect_class_init(SPRectClass *klass)
79 {
80     SPObjectClass *sp_object_class = (SPObjectClass *) klass;
81     SPItemClass *item_class = (SPItemClass *) klass;
82     SPShapeClass *shape_class = (SPShapeClass *) klass;
84     parent_class = (SPShapeClass *)g_type_class_ref(SP_TYPE_SHAPE);
86     sp_object_class->build = sp_rect_build;
87     sp_object_class->write = sp_rect_write;
88     sp_object_class->set = sp_rect_set;
89     sp_object_class->update = sp_rect_update;
91     item_class->description = sp_rect_description;
92     item_class->set_transform = sp_rect_set_transform;
93     item_class->convert_to_guides = sp_rect_convert_to_guides;
94     item_class->snappoints = sp_rect_snappoints; //override the default sp_shape_snappoints; see sp_rect_snappoints for details
96     shape_class->set_shape = sp_rect_set_shape;
97 }
99 static void
100 sp_rect_init(SPRect */*rect*/)
102     /* Initializing to zero is automatic */
103     /* sp_svg_length_unset(&rect->x, SP_SVG_UNIT_NONE, 0.0, 0.0); */
104     /* sp_svg_length_unset(&rect->y, SP_SVG_UNIT_NONE, 0.0, 0.0); */
105     /* sp_svg_length_unset(&rect->width, SP_SVG_UNIT_NONE, 0.0, 0.0); */
106     /* sp_svg_length_unset(&rect->height, SP_SVG_UNIT_NONE, 0.0, 0.0); */
107     /* sp_svg_length_unset(&rect->rx, SP_SVG_UNIT_NONE, 0.0, 0.0); */
108     /* sp_svg_length_unset(&rect->ry, SP_SVG_UNIT_NONE, 0.0, 0.0); */
111 static void
112 sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
114     if (((SPObjectClass *) parent_class)->build)
115         ((SPObjectClass *) parent_class)->build(object, document, repr);
117     sp_object_read_attr(object, "x");
118     sp_object_read_attr(object, "y");
119     sp_object_read_attr(object, "width");
120     sp_object_read_attr(object, "height");
121     sp_object_read_attr(object, "rx");
122     sp_object_read_attr(object, "ry");
125 static void
126 sp_rect_set(SPObject *object, unsigned key, gchar const *value)
128     SPRect *rect = SP_RECT(object);
130     /* fixme: We need real error processing some time */
132     switch (key) {
133         case SP_ATTR_X:
134             rect->x.readOrUnset(value);
135             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
136             break;
137         case SP_ATTR_Y:
138             rect->y.readOrUnset(value);
139             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
140             break;
141         case SP_ATTR_WIDTH:
142             if (!rect->width.read(value) || rect->width.value < 0.0) {
143                 rect->width.unset();
144             }
145             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
146             break;
147         case SP_ATTR_HEIGHT:
148             if (!rect->height.read(value) || rect->height.value < 0.0) {
149                 rect->height.unset();
150             }
151             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
152             break;
153         case SP_ATTR_RX:
154             if (!rect->rx.read(value) || rect->rx.value < 0.0) {
155                 rect->rx.unset();
156             }
157             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
158             break;
159         case SP_ATTR_RY:
160             if (!rect->ry.read(value) || rect->ry.value < 0.0) {
161                 rect->ry.unset();
162             }
163             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
164             break;
165         default:
166             if (((SPObjectClass *) parent_class)->set)
167                 ((SPObjectClass *) parent_class)->set(object, key, value);
168             break;
169     }
172 static void
173 sp_rect_update(SPObject *object, SPCtx *ctx, guint flags)
175     if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
176         SPRect *rect = (SPRect *) object;
177         SPStyle *style = object->style;
178         SPItemCtx const *ictx = (SPItemCtx const *) ctx;
179         double const d = NR::expansion(ictx->i2vp);
180         double const w = (ictx->vp.x1 - ictx->vp.x0) / d;
181         double const h = (ictx->vp.y1 - ictx->vp.y0) / d;
182         double const em = style->font_size.computed;
183         double const ex = 0.5 * em;  // fixme: get x height from pango or libnrtype.
184         rect->x.update(em, ex, w);
185         rect->y.update(em, ex, h);
186         rect->width.update(em, ex, w);
187         rect->height.update(em, ex, h);
188         rect->rx.update(em, ex, w);
189         rect->ry.update(em, ex, h);
190         sp_shape_set_shape((SPShape *) object);
191         flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore
192     }
194     if (((SPObjectClass *) parent_class)->update)
195         ((SPObjectClass *) parent_class)->update(object, ctx, flags);
198 static Inkscape::XML::Node *
199 sp_rect_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
201     SPRect *rect = SP_RECT(object);
203     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
204         repr = xml_doc->createElement("svg:rect");
205     }
207     sp_repr_set_svg_double(repr, "width", rect->width.computed);
208     sp_repr_set_svg_double(repr, "height", rect->height.computed);
209     if (rect->rx._set) sp_repr_set_svg_double(repr, "rx", rect->rx.computed);
210     if (rect->ry._set) sp_repr_set_svg_double(repr, "ry", rect->ry.computed);
211     sp_repr_set_svg_double(repr, "x", rect->x.computed);
212     sp_repr_set_svg_double(repr, "y", rect->y.computed);
214     if (((SPObjectClass *) parent_class)->write)
215         ((SPObjectClass *) parent_class)->write(object, xml_doc, repr, flags);
217     return repr;
220 static gchar *
221 sp_rect_description(SPItem *item)
223     g_return_val_if_fail(SP_IS_RECT(item), NULL);
225     return g_strdup(_("<b>Rectangle</b>"));
228 #define C1 0.554
230 static void
231 sp_rect_set_shape(SPShape *shape)
233     SPRect *rect = (SPRect *) shape;
235     if ((rect->height.computed < 1e-18) || (rect->width.computed < 1e-18)) {
236         sp_shape_set_curve_insync(SP_SHAPE(rect), NULL, TRUE);
237         return;
238     }
240     SPCurve *c = new SPCurve();
242     double const x = rect->x.computed;
243     double const y = rect->y.computed;
244     double const w = rect->width.computed;
245     double const h = rect->height.computed;
246     double const w2 = w / 2;
247     double const h2 = h / 2;
248     double const rx = std::min(( rect->rx._set
249                                  ? rect->rx.computed
250                                  : ( rect->ry._set
251                                      ? rect->ry.computed
252                                      : 0.0 ) ),
253                                .5 * rect->width.computed);
254     double const ry = std::min(( rect->ry._set
255                                  ? rect->ry.computed
256                                  : ( rect->rx._set
257                                      ? rect->rx.computed
258                                      : 0.0 ) ),
259                                .5 * rect->height.computed);
260     /* TODO: Handle negative rx or ry as per
261      * http://www.w3.org/TR/SVG11/shapes.html#RectElementRXAttribute once Inkscape has proper error
262      * handling (see http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing).
263      */
265     /* We don't use proper circular/elliptical arcs, but bezier curves can approximate a 90-degree
266      * arc fairly well.
267      */
268     if ((rx > 1e-18) && (ry > 1e-18)) {
269         c->moveto(x + rx, y);
270         if (rx < w2) c->lineto(x + w - rx, y);
271         c->curveto(x + w - rx * (1 - C1), y,     x + w, y + ry * (1 - C1),       x + w, y + ry);
272         if (ry < h2) c->lineto(x + w, y + h - ry);
273         c->curveto(x + w, y + h - ry * (1 - C1),     x + w - rx * (1 - C1), y + h,       x + w - rx, y + h);
274         if (rx < w2) c->lineto(x + rx, y + h);
275         c->curveto(x + rx * (1 - C1), y + h,     x, y + h - ry * (1 - C1),       x, y + h - ry);
276         if (ry < h2) c->lineto(x, y + ry);
277         c->curveto(x, y + ry * (1 - C1),     x + rx * (1 - C1), y,       x + rx, y);
278     } else {
279         c->moveto(x + 0.0, y + 0.0);
280         c->lineto(x + w, y + 0.0);
281         c->lineto(x + w, y + h);
282         c->lineto(x + 0.0, y + h);
283         c->lineto(x + 0.0, y + 0.0);
284     }
286     c->closepath_current();
287     sp_shape_set_curve_insync(SP_SHAPE(rect), c, TRUE);
288     c->unref();
291 /* fixme: Think (Lauris) */
293 void
294 sp_rect_position_set(SPRect *rect, gdouble x, gdouble y, gdouble width, gdouble height)
296     g_return_if_fail(rect != NULL);
297     g_return_if_fail(SP_IS_RECT(rect));
299     rect->x.computed = x;
300     rect->y.computed = y;
301     rect->width.computed = width;
302     rect->height.computed = height;
304     SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
307 void
308 sp_rect_set_rx(SPRect *rect, gboolean set, gdouble value)
310     g_return_if_fail(rect != NULL);
311     g_return_if_fail(SP_IS_RECT(rect));
313     rect->rx._set = set;
314     if (set) rect->rx.computed = value;
316     SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
319 void
320 sp_rect_set_ry(SPRect *rect, gboolean set, gdouble value)
322     g_return_if_fail(rect != NULL);
323     g_return_if_fail(SP_IS_RECT(rect));
325     rect->ry._set = set;
326     if (set) rect->ry.computed = value;
328     SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
331 /*
332  * Initially we'll do:
333  * Transform x, y, set x, y, clear translation
334  */
336 /* fixme: Use preferred units somehow (Lauris) */
337 /* fixme: Alternately preserve whatever units there are (lauris) */
339 static NR::Matrix
340 sp_rect_set_transform(SPItem *item, NR::Matrix const &xform)
342     SPRect *rect = SP_RECT(item);
344     /* Calculate rect start in parent coords. */
345     NR::Point pos( NR::Point(rect->x.computed, rect->y.computed) * xform );
347     /* This function takes care of translation and scaling, we return whatever parts we can't
348        handle. */
349     NR::Matrix ret(NR::transform(xform));
350     gdouble const sw = hypot(ret[0], ret[1]);
351     gdouble const sh = hypot(ret[2], ret[3]);
352     if (sw > 1e-9) {
353         ret[0] /= sw;
354         ret[1] /= sw;
355     } else {
356         ret[0] = 1.0;
357         ret[1] = 0.0;
358     }
359     if (sh > 1e-9) {
360         ret[2] /= sh;
361         ret[3] /= sh;
362     } else {
363         ret[2] = 0.0;
364         ret[3] = 1.0;
365     }
367     /* fixme: Would be nice to preserve units here */
368     rect->width = rect->width.computed * sw;
369     rect->height = rect->height.computed * sh;
370     if (rect->rx._set) {
371         rect->rx = rect->rx.computed * sw;
372     }
373     if (rect->ry._set) {
374         rect->ry = rect->ry.computed * sh;
375     }
377     /* Find start in item coords */
378     pos = pos * ret.inverse();
379     rect->x = pos[NR::X];
380     rect->y = pos[NR::Y];
382     sp_rect_set_shape(rect);
384     // Adjust stroke width
385     sp_item_adjust_stroke(item, sqrt(fabs(sw * sh)));
387     // Adjust pattern fill
388     sp_item_adjust_pattern(item, xform * ret.inverse());
390     // Adjust gradient fill
391     sp_item_adjust_gradient(item, xform * ret.inverse());
393     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
395     return ret;
399 /**
400 Returns the ratio in which the vector from p0 to p1 is stretched by transform
401  */
402 static gdouble
403 vector_stretch(NR::Point p0, NR::Point p1, NR::Matrix xform)
405     if (p0 == p1)
406         return 0;
407     return (NR::distance(p0 * xform, p1 * xform) / NR::distance(p0, p1));
410 void
411 sp_rect_set_visible_rx(SPRect *rect, gdouble rx)
413     if (rx == 0) {
414         rect->rx.computed = 0;
415         rect->rx._set = false;
416     } else {
417         rect->rx.computed = rx / vector_stretch(
418             NR::Point(rect->x.computed + 1, rect->y.computed),
419             NR::Point(rect->x.computed, rect->y.computed),
420             SP_ITEM(rect)->transform);
421         rect->rx._set = true;
422     }
423     SP_OBJECT(rect)->updateRepr();
426 void
427 sp_rect_set_visible_ry(SPRect *rect, gdouble ry)
429     if (ry == 0) {
430         rect->ry.computed = 0;
431         rect->ry._set = false;
432     } else {
433         rect->ry.computed = ry / vector_stretch(
434             NR::Point(rect->x.computed, rect->y.computed + 1),
435             NR::Point(rect->x.computed, rect->y.computed),
436             SP_ITEM(rect)->transform);
437         rect->ry._set = true;
438     }
439     SP_OBJECT(rect)->updateRepr();
442 gdouble
443 sp_rect_get_visible_rx(SPRect *rect)
445     if (!rect->rx._set)
446         return 0;
447     return rect->rx.computed * vector_stretch(
448         NR::Point(rect->x.computed + 1, rect->y.computed),
449         NR::Point(rect->x.computed, rect->y.computed),
450         SP_ITEM(rect)->transform);
453 gdouble
454 sp_rect_get_visible_ry(SPRect *rect)
456     if (!rect->ry._set)
457         return 0;
458     return rect->ry.computed * vector_stretch(
459         NR::Point(rect->x.computed, rect->y.computed + 1),
460         NR::Point(rect->x.computed, rect->y.computed),
461         SP_ITEM(rect)->transform);
464 void
465 sp_rect_compensate_rxry(SPRect *rect, NR::Matrix xform)
467     if (rect->rx.computed == 0 && rect->ry.computed == 0)
468         return; // nothing to compensate
470     // test unit vectors to find out compensation:
471     NR::Point c(rect->x.computed, rect->y.computed);
472     NR::Point cx = c + NR::Point(1, 0);
473     NR::Point cy = c + NR::Point(0, 1);
475     // apply previous transform if any
476     c *= SP_ITEM(rect)->transform;
477     cx *= SP_ITEM(rect)->transform;
478     cy *= SP_ITEM(rect)->transform;
480     // find out stretches that we need to compensate
481     gdouble eX = vector_stretch(cx, c, xform);
482     gdouble eY = vector_stretch(cy, c, xform);
484     // If only one of the radii is set, set both radii so they have the same visible length
485     // This is needed because if we just set them the same length in SVG, they might end up unequal because of transform
486     if ((rect->rx._set && !rect->ry._set) || (rect->ry._set && !rect->rx._set)) {
487         gdouble r = MAX(rect->rx.computed, rect->ry.computed);
488         rect->rx.computed = r / eX;
489         rect->ry.computed = r / eY;
490     } else {
491         rect->rx.computed = rect->rx.computed / eX;
492         rect->ry.computed = rect->ry.computed / eY;
493     }
495     // Note that a radius may end up larger than half-side if the rect is scaled down;
496     // that's ok because this preserves the intended radii in case the rect is enlarged again,
497     // and set_shape will take care of trimming too large radii when generating d=
499     rect->rx._set = rect->ry._set = true;
502 void
503 sp_rect_set_visible_width(SPRect *rect, gdouble width)
505     rect->width.computed = width / vector_stretch(
506         NR::Point(rect->x.computed + 1, rect->y.computed),
507         NR::Point(rect->x.computed, rect->y.computed),
508         SP_ITEM(rect)->transform);
509     rect->width._set = true;
510     SP_OBJECT(rect)->updateRepr();
513 void
514 sp_rect_set_visible_height(SPRect *rect, gdouble height)
516     rect->height.computed = height / vector_stretch(
517         NR::Point(rect->x.computed, rect->y.computed + 1),
518         NR::Point(rect->x.computed, rect->y.computed),
519         SP_ITEM(rect)->transform);
520     rect->height._set = true;
521     SP_OBJECT(rect)->updateRepr();
524 gdouble
525 sp_rect_get_visible_width(SPRect *rect)
527     if (!rect->width._set)
528         return 0;
529     return rect->width.computed * vector_stretch(
530         NR::Point(rect->x.computed + 1, rect->y.computed),
531         NR::Point(rect->x.computed, rect->y.computed),
532         SP_ITEM(rect)->transform);
535 gdouble
536 sp_rect_get_visible_height(SPRect *rect)
538     if (!rect->height._set)
539         return 0;
540     return rect->height.computed * vector_stretch(
541         NR::Point(rect->x.computed, rect->y.computed + 1),
542         NR::Point(rect->x.computed, rect->y.computed),
543         SP_ITEM(rect)->transform);
546 /**
547  * Sets the snappoint p to the unrounded corners of the rectangle
548  */
549 static void sp_rect_snappoints(SPItem const *item, SnapPointsIter p)
551     /* This method overrides sp_shape_snappoints, which is the default for any shape. The default method
552     returns all eight points along the path of a rounded rectangle, but not the real corners. Snapping
553     the startpoint and endpoint of each rounded corner is not very usefull and really confusing. Instead 
554     we could snap either the real corners, or not snap at all. Bulia Byak opted to snap the real corners,
555     but it should be noted that this might be confusing in some cases with relatively large radii. With 
556     small radii though the user will easily understand which point is snapping. */
557     
558     g_assert(item != NULL);
559     g_assert(SP_IS_RECT(item));
561     SPRect *rect = SP_RECT(item);
563     NR::Matrix const i2d (from_2geom(sp_item_i2d_affine (item)));
565     *p = NR::Point(rect->x.computed, rect->y.computed) * i2d;
566     *p = NR::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d;
567     *p = NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d;
568     *p = NR::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d;
571 void
572 sp_rect_convert_to_guides(SPItem *item) {
573     SPRect *rect = SP_RECT(item);
575     if (prefs_get_int_attribute("tools.shapes.rect", "convertguides", 1) == 0) {
576         sp_item_convert_to_guides(SP_ITEM(rect));
577         return;
578     }
580     std::list<std::pair<Geom::Point, Geom::Point> > pts;
582     NR::Matrix const i2d (from_2geom(sp_item_i2d_affine(SP_ITEM(rect))));
584     NR::Point A1(NR::Point(rect->x.computed, rect->y.computed) * i2d);
585     NR::Point A2(NR::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d);
586     NR::Point A3(NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d);
587     NR::Point A4(NR::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d);
589     pts.push_back(std::make_pair(A1, A2));
590     pts.push_back(std::make_pair(A2, A3));
591     pts.push_back(std::make_pair(A3, A4));
592     pts.push_back(std::make_pair(A4, A1));
594     sp_guide_pt_pairs_to_guides(inkscape_active_desktop(), pts);
597 /*
598   Local Variables:
599   mode:c++
600   c-file-style:"stroustrup"
601   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
602   indent-tabs-mode:nil
603   fill-column:99
604   End:
605 */
606 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :