Code

e5f1da05d24eb735e527ef04bfee4e60da0b57e0
[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-div.h>
23 #include <libnr/nr-matrix-fns.h>
25 #include "document.h"
26 #include "attributes.h"
27 #include "style.h"
28 #include "sp-rect.h"
29 #include <glibmm/i18n.h>
30 #include "xml/repr.h"
32 #define noRECT_VERBOSE
34 static void sp_rect_class_init(SPRectClass *klass);
35 static void sp_rect_init(SPRect *rect);
37 static void sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
38 static void sp_rect_set(SPObject *object, unsigned key, gchar const *value);
39 static void sp_rect_update(SPObject *object, SPCtx *ctx, guint flags);
40 static Inkscape::XML::Node *sp_rect_write(SPObject *object, Inkscape::XML::Node *repr, guint flags);
42 static gchar *sp_rect_description(SPItem *item);
43 static NR::Matrix sp_rect_set_transform(SPItem *item, NR::Matrix const &xform);
45 static void sp_rect_set_shape(SPShape *shape);
46 static void sp_rect_snappoints(SPItem const *item, SnapPointsIter p);
48 static SPShapeClass *parent_class;
50 GType
51 sp_rect_get_type(void)
52 {
53     static GType type = 0;
55     if (!type) {
56         GTypeInfo info = {
57             sizeof(SPRectClass),
58             NULL,   /* base_init */
59             NULL,   /* base_finalize */
60             (GClassInitFunc) sp_rect_class_init,
61             NULL,   /* class_finalize */
62             NULL,   /* class_data */
63             sizeof(SPRect),
64             16,     /* n_preallocs */
65             (GInstanceInitFunc) sp_rect_init,
66             NULL,   /* value_table */
67         };
68         type = g_type_register_static(SP_TYPE_SHAPE, "SPRect", &info, (GTypeFlags)0);
69     }
70     return type;
71 }
73 static void
74 sp_rect_class_init(SPRectClass *klass)
75 {
76     SPObjectClass *sp_object_class = (SPObjectClass *) klass;
77     SPItemClass *item_class = (SPItemClass *) klass;
78     SPShapeClass *shape_class = (SPShapeClass *) klass;
80     parent_class = (SPShapeClass *)g_type_class_ref(SP_TYPE_SHAPE);
82     sp_object_class->build = sp_rect_build;
83     sp_object_class->write = sp_rect_write;
84     sp_object_class->set = sp_rect_set;
85     sp_object_class->update = sp_rect_update;
87     item_class->description = sp_rect_description;
88     item_class->set_transform = sp_rect_set_transform;
89     item_class->snappoints = sp_rect_snappoints; //override the default sp_shape_snappoints; see sp_rect_snappoints for details
91     shape_class->set_shape = sp_rect_set_shape;
92 }
94 static void
95 sp_rect_init(SPRect */*rect*/)
96 {
97     /* Initializing to zero is automatic */
98     /* sp_svg_length_unset(&rect->x, SP_SVG_UNIT_NONE, 0.0, 0.0); */
99     /* sp_svg_length_unset(&rect->y, SP_SVG_UNIT_NONE, 0.0, 0.0); */
100     /* sp_svg_length_unset(&rect->width, SP_SVG_UNIT_NONE, 0.0, 0.0); */
101     /* sp_svg_length_unset(&rect->height, SP_SVG_UNIT_NONE, 0.0, 0.0); */
102     /* sp_svg_length_unset(&rect->rx, SP_SVG_UNIT_NONE, 0.0, 0.0); */
103     /* sp_svg_length_unset(&rect->ry, SP_SVG_UNIT_NONE, 0.0, 0.0); */
106 static void
107 sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
109     SPRect *rect = SP_RECT(object);
111     if (((SPObjectClass *) parent_class)->build)
112         ((SPObjectClass *) parent_class)->build(object, document, repr);
114     sp_object_read_attr(object, "x");
115     sp_object_read_attr(object, "y");
116     sp_object_read_attr(object, "width");
117     sp_object_read_attr(object, "height");
118     sp_object_read_attr(object, "rx");
119     sp_object_read_attr(object, "ry");
121     Inkscape::Version const version = sp_object_get_sodipodi_version(object);
123     if ( version.major == 0 && version.minor == 29 ) {
124         if (rect->rx._set && rect->ry._set) {
125             /* 0.29 treated 0.0 radius as missing value */
126             if ((rect->rx.value != 0.0) && (rect->ry.value == 0.0)) {
127                 repr->setAttribute("ry", NULL);
128                 sp_object_read_attr(object, "ry");
129             } else if ((rect->ry.value != 0.0) && (rect->rx.value == 0.0)) {
130                 repr->setAttribute("rx", NULL);
131                 sp_object_read_attr(object, "rx");
132             }
133         }
134     }
137 static void
138 sp_rect_set(SPObject *object, unsigned key, gchar const *value)
140     SPRect *rect = SP_RECT(object);
142     /* fixme: We need real error processing some time */
144     switch (key) {
145         case SP_ATTR_X:
146             rect->x.readOrUnset(value);
147             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
148             break;
149         case SP_ATTR_Y:
150             rect->y.readOrUnset(value);
151             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
152             break;
153         case SP_ATTR_WIDTH:
154             if (!rect->width.read(value) || rect->width.value < 0.0) {
155                 rect->width.unset();
156             }
157             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
158             break;
159         case SP_ATTR_HEIGHT:
160             if (!rect->height.read(value) || rect->height.value < 0.0) {
161                 rect->height.unset();
162             }
163             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
164             break;
165         case SP_ATTR_RX:
166             if (!rect->rx.read(value) || rect->rx.value < 0.0) {
167                 rect->rx.unset();
168             }
169             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
170             break;
171         case SP_ATTR_RY:
172             if (!rect->ry.read(value) || rect->ry.value < 0.0) {
173                 rect->ry.unset();
174             }
175             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
176             break;
177         default:
178             if (((SPObjectClass *) parent_class)->set)
179                 ((SPObjectClass *) parent_class)->set(object, key, value);
180             break;
181     }
184 static void
185 sp_rect_update(SPObject *object, SPCtx *ctx, guint flags)
187     if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
188         SPRect *rect = (SPRect *) object;
189         SPStyle *style = object->style;
190         SPItemCtx const *ictx = (SPItemCtx const *) ctx;
191         double const d = NR::expansion(ictx->i2vp);
192         double const w = (ictx->vp.x1 - ictx->vp.x0) / d;
193         double const h = (ictx->vp.y1 - ictx->vp.y0) / d;
194         double const em = style->font_size.computed;
195         double const ex = 0.5 * em;  // fixme: get x height from pango or libnrtype.
196         rect->x.update(em, ex, w);
197         rect->y.update(em, ex, h);
198         rect->width.update(em, ex, w);
199         rect->height.update(em, ex, h);
200         rect->rx.update(em, ex, w);
201         rect->ry.update(em, ex, h);
202         sp_shape_set_shape((SPShape *) object);
203         flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore
204     }
206     if (((SPObjectClass *) parent_class)->update)
207         ((SPObjectClass *) parent_class)->update(object, ctx, flags);
210 static Inkscape::XML::Node *
211 sp_rect_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
213     SPRect *rect = SP_RECT(object);
215     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
216         Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
217         repr = xml_doc->createElement("svg:rect");
218     }
220     sp_repr_set_svg_double(repr, "width", rect->width.computed);
221     sp_repr_set_svg_double(repr, "height", rect->height.computed);
222     if (rect->rx._set) sp_repr_set_svg_double(repr, "rx", rect->rx.computed);
223     if (rect->ry._set) sp_repr_set_svg_double(repr, "ry", rect->ry.computed);
224     sp_repr_set_svg_double(repr, "x", rect->x.computed);
225     sp_repr_set_svg_double(repr, "y", rect->y.computed);
227     if (((SPObjectClass *) parent_class)->write)
228         ((SPObjectClass *) parent_class)->write(object, repr, flags);
230     return repr;
233 static gchar *
234 sp_rect_description(SPItem *item)
236     g_return_val_if_fail(SP_IS_RECT(item), NULL);
238     return g_strdup(_("<b>Rectangle</b>"));
241 #define C1 0.554
243 static void
244 sp_rect_set_shape(SPShape *shape)
246     SPRect *rect = (SPRect *) shape;
248     if ((rect->height.computed < 1e-18) || (rect->width.computed < 1e-18)) return;
250     SPCurve *c = sp_curve_new();
252     double const x = rect->x.computed;
253     double const y = rect->y.computed;
254     double const w = rect->width.computed;
255     double const h = rect->height.computed;
256     double const w2 = w / 2;
257     double const h2 = h / 2;
258     double const rx = std::min(( rect->rx._set
259                                  ? rect->rx.computed
260                                  : ( rect->ry._set
261                                      ? rect->ry.computed
262                                      : 0.0 ) ),
263                                .5 * rect->width.computed);
264     double const ry = std::min(( rect->ry._set
265                                  ? rect->ry.computed
266                                  : ( rect->rx._set
267                                      ? rect->rx.computed
268                                      : 0.0 ) ),
269                                .5 * rect->height.computed);
270     /* TODO: Handle negative rx or ry as per
271      * http://www.w3.org/TR/SVG11/shapes.html#RectElementRXAttribute once Inkscape has proper error
272      * handling (see http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing).
273      */
275     /* We don't use proper circular/elliptical arcs, but bezier curves can approximate a 90-degree
276      * arc fairly well.
277      */
278     if ((rx > 1e-18) && (ry > 1e-18)) {
279         sp_curve_moveto(c, x + rx, y);
280         if (rx < w2) sp_curve_lineto(c, x + w - rx, y);
281         sp_curve_curveto(c, x + w - rx * (1 - C1), y,     x + w, y + ry * (1 - C1),       x + w, y + ry);
282         if (ry < h2) sp_curve_lineto(c, x + w, y + h - ry);
283         sp_curve_curveto(c, x + w, y + h - ry * (1 - C1),     x + w - rx * (1 - C1), y + h,       x + w - rx, y + h);
284         if (rx < w2) sp_curve_lineto(c, x + rx, y + h);
285         sp_curve_curveto(c, x + rx * (1 - C1), y + h,     x, y + h - ry * (1 - C1),       x, y + h - ry);
286         if (ry < h2) sp_curve_lineto(c, x, y + ry);
287         sp_curve_curveto(c, x, y + ry * (1 - C1),     x + rx * (1 - C1), y,       x + rx, y);
288     } else {
289         sp_curve_moveto(c, x + 0.0, y + 0.0);
290         sp_curve_lineto(c, x + w, y + 0.0);
291         sp_curve_lineto(c, x + w, y + h);
292         sp_curve_lineto(c, x + 0.0, y + h);
293         sp_curve_lineto(c, x + 0.0, y + 0.0);
294     }
296     sp_curve_closepath_current(c);
297     sp_shape_set_curve_insync(SP_SHAPE(rect), c, TRUE);
298     sp_curve_unref(c);
301 /* fixme: Think (Lauris) */
303 void
304 sp_rect_position_set(SPRect *rect, gdouble x, gdouble y, gdouble width, gdouble height)
306     g_return_if_fail(rect != NULL);
307     g_return_if_fail(SP_IS_RECT(rect));
309     rect->x.computed = x;
310     rect->y.computed = y;
311     rect->width.computed = width;
312     rect->height.computed = height;
314     SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
317 void
318 sp_rect_set_rx(SPRect *rect, gboolean set, gdouble value)
320     g_return_if_fail(rect != NULL);
321     g_return_if_fail(SP_IS_RECT(rect));
323     rect->rx._set = set;
324     if (set) rect->rx.computed = value;
326     SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
329 void
330 sp_rect_set_ry(SPRect *rect, gboolean set, gdouble value)
332     g_return_if_fail(rect != NULL);
333     g_return_if_fail(SP_IS_RECT(rect));
335     rect->ry._set = set;
336     if (set) rect->ry.computed = value;
338     SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
341 /*
342  * Initially we'll do:
343  * Transform x, y, set x, y, clear translation
344  */
346 /* fixme: Use preferred units somehow (Lauris) */
347 /* fixme: Alternately preserve whatever units there are (lauris) */
349 static NR::Matrix
350 sp_rect_set_transform(SPItem *item, NR::Matrix const &xform)
352     g_print ("sp_rect_set_transform\n");
353     SPRect *rect = SP_RECT(item);
355     /* Calculate rect start in parent coords. */
356     NR::Point pos( NR::Point(rect->x.computed, rect->y.computed) * xform );
358     /* This function takes care of translation and scaling, we return whatever parts we can't
359        handle. */
360     NR::Matrix ret(NR::transform(xform));
361     gdouble const sw = hypot(ret[0], ret[1]);
362     gdouble const sh = hypot(ret[2], ret[3]);
363     if (sw > 1e-9) {
364         ret[0] /= sw;
365         ret[1] /= sw;
366     } else {
367         ret[0] = 1.0;
368         ret[1] = 0.0;
369     }
370     if (sh > 1e-9) {
371         ret[2] /= sh;
372         ret[3] /= sh;
373     } else {
374         ret[2] = 0.0;
375         ret[3] = 1.0;
376     }
378     /* fixme: Would be nice to preserve units here */
379     rect->width = rect->width.computed * sw;
380     rect->height = rect->height.computed * sh;
381     if (rect->rx._set) {
382         rect->rx = rect->rx.computed * sw;
383     }
384     if (rect->ry._set) {
385         rect->ry = rect->ry.computed * sh;
386     }
388     /* Find start in item coords */
389     pos = pos * ret.inverse();
390     rect->x = pos[NR::X];
391     rect->y = pos[NR::Y];
393     sp_rect_set_shape(rect);
395     // Adjust stroke width
396     sp_item_adjust_stroke(item, sqrt(fabs(sw * sh)));
398     // Adjust pattern fill
399     sp_item_adjust_pattern(item, xform / ret);
401     // Adjust gradient fill
402     sp_item_adjust_gradient(item, xform / ret);
404     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
406     return ret;
410 /**
411 Returns the ratio in which the vector from p0 to p1 is stretched by transform
412  */
413 static gdouble
414 vector_stretch(NR::Point p0, NR::Point p1, NR::Matrix xform)
416     if (p0 == p1)
417         return 0;
418     return (NR::distance(p0 * xform, p1 * xform) / NR::distance(p0, p1));
421 void
422 sp_rect_set_visible_rx(SPRect *rect, gdouble rx)
424     if (rx == 0) {
425         rect->rx.computed = 0;
426         rect->rx._set = false;
427     } else {
428         rect->rx.computed = rx / vector_stretch(
429             NR::Point(rect->x.computed + 1, rect->y.computed),
430             NR::Point(rect->x.computed, rect->y.computed),
431             SP_ITEM(rect)->transform);
432         rect->rx._set = true;
433     }
434     SP_OBJECT(rect)->updateRepr();
437 void
438 sp_rect_set_visible_ry(SPRect *rect, gdouble ry)
440     if (ry == 0) {
441         rect->ry.computed = 0;
442         rect->ry._set = false;
443     } else {
444         rect->ry.computed = ry / vector_stretch(
445             NR::Point(rect->x.computed, rect->y.computed + 1),
446             NR::Point(rect->x.computed, rect->y.computed),
447             SP_ITEM(rect)->transform);
448         rect->ry._set = true;
449     }
450     SP_OBJECT(rect)->updateRepr();
453 gdouble
454 sp_rect_get_visible_rx(SPRect *rect)
456     if (!rect->rx._set)
457         return 0;
458     return rect->rx.computed * vector_stretch(
459         NR::Point(rect->x.computed + 1, rect->y.computed),
460         NR::Point(rect->x.computed, rect->y.computed),
461         SP_ITEM(rect)->transform);
464 gdouble
465 sp_rect_get_visible_ry(SPRect *rect)
467     if (!rect->ry._set)
468         return 0;
469     return rect->ry.computed * vector_stretch(
470         NR::Point(rect->x.computed, rect->y.computed + 1),
471         NR::Point(rect->x.computed, rect->y.computed),
472         SP_ITEM(rect)->transform);
475 void
476 sp_rect_compensate_rxry(SPRect *rect, NR::Matrix xform)
478     if (rect->rx.computed == 0 && rect->ry.computed == 0)
479         return; // nothing to compensate
481     // test unit vectors to find out compensation:
482     NR::Point c(rect->x.computed, rect->y.computed);
483     NR::Point cx = c + NR::Point(1, 0);
484     NR::Point cy = c + NR::Point(0, 1);
486     // apply previous transform if any
487     c *= SP_ITEM(rect)->transform;
488     cx *= SP_ITEM(rect)->transform;
489     cy *= SP_ITEM(rect)->transform;
491     // find out stretches that we need to compensate
492     gdouble eX = vector_stretch(cx, c, xform);
493     gdouble eY = vector_stretch(cy, c, xform);
495     // If only one of the radii is set, set both radii so they have the same visible length
496     // This is needed because if we just set them the same length in SVG, they might end up unequal because of transform
497     if ((rect->rx._set && !rect->ry._set) || (rect->ry._set && !rect->rx._set)) {
498         gdouble r = MAX(rect->rx.computed, rect->ry.computed);
499         rect->rx.computed = r / eX;
500         rect->ry.computed = r / eY;
501     } else {
502         rect->rx.computed = rect->rx.computed / eX;
503         rect->ry.computed = rect->ry.computed / eY;
504     }
506     // Note that a radius may end up larger than half-side if the rect is scaled down;
507     // that's ok because this preserves the intended radii in case the rect is enlarged again,
508     // and set_shape will take care of trimming too large radii when generating d=
510     rect->rx._set = rect->ry._set = true;
513 void
514 sp_rect_set_visible_width(SPRect *rect, gdouble width)
516     rect->width.computed = width / vector_stretch(
517         NR::Point(rect->x.computed + 1, rect->y.computed),
518         NR::Point(rect->x.computed, rect->y.computed),
519         SP_ITEM(rect)->transform);
520     rect->width._set = true;
521     SP_OBJECT(rect)->updateRepr();
524 void
525 sp_rect_set_visible_height(SPRect *rect, gdouble height)
527     rect->height.computed = height / vector_stretch(
528         NR::Point(rect->x.computed, rect->y.computed + 1),
529         NR::Point(rect->x.computed, rect->y.computed),
530         SP_ITEM(rect)->transform);
531     rect->height._set = true;
532     SP_OBJECT(rect)->updateRepr();
535 gdouble
536 sp_rect_get_visible_width(SPRect *rect)
538     if (!rect->width._set)
539         return 0;
540     return rect->width.computed * vector_stretch(
541         NR::Point(rect->x.computed + 1, rect->y.computed),
542         NR::Point(rect->x.computed, rect->y.computed),
543         SP_ITEM(rect)->transform);
546 gdouble
547 sp_rect_get_visible_height(SPRect *rect)
549     if (!rect->height._set)
550         return 0;
551     return rect->height.computed * vector_stretch(
552         NR::Point(rect->x.computed, rect->y.computed + 1),
553         NR::Point(rect->x.computed, rect->y.computed),
554         SP_ITEM(rect)->transform);
557 /**
558  * Sets the snappoint p to the unrounded corners of the rectangle
559  */
560 static void sp_rect_snappoints(SPItem const *item, SnapPointsIter p)
562     /* This method overrides sp_shape_snappoints, which is the default for any shape. The default method
563     returns all eight points along the path of a rounded rectangle, but not the real corners. Snapping
564     the startpoint and endpoint of each rounded corner is not very usefull and really confusing. Instead 
565     we could snap either the real corners, or not snap at all. Bulia Byak opted to snap the real corners,
566     but it should be noted that this might be confusing in some cases with relatively large radii. With 
567     small radii though the user will easily understand which point is snapping. */
568     
569     g_assert(item != NULL);
570     g_assert(SP_IS_RECT(item));
572     SPRect *rect = SP_RECT(item);
574     NR::Matrix const i2d (sp_item_i2d_affine (item));
576     *p = NR::Point(rect->x.computed, rect->y.computed) * i2d;
577     *p = NR::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d;
578     *p = NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d;
579     *p = NR::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d;
582 /*
583   Local Variables:
584   mode:c++
585   c-file-style:"stroustrup"
586   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
587   indent-tabs-mode:nil
588   fill-column:99
589   End:
590 */
591 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :