Code

fix-render-of-zero-dimension-rects
[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"
31 #include "sp-guide.h"
32 #include "prefs-utils.h"
34 #define noRECT_VERBOSE
36 static void sp_rect_class_init(SPRectClass *klass);
37 static void sp_rect_init(SPRect *rect);
39 static void sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
40 static void sp_rect_set(SPObject *object, unsigned key, gchar const *value);
41 static void sp_rect_update(SPObject *object, SPCtx *ctx, guint flags);
42 static Inkscape::XML::Node *sp_rect_write(SPObject *object, Inkscape::XML::Node *repr, guint flags);
44 static gchar *sp_rect_description(SPItem *item);
45 static NR::Matrix sp_rect_set_transform(SPItem *item, NR::Matrix const &xform);
46 static void sp_rect_convert_to_guides(SPItem *item);
48 static void sp_rect_set_shape(SPShape *shape);
49 static void sp_rect_snappoints(SPItem const *item, SnapPointsIter p);
51 static SPShapeClass *parent_class;
53 GType
54 sp_rect_get_type(void)
55 {
56     static GType type = 0;
58     if (!type) {
59         GTypeInfo info = {
60             sizeof(SPRectClass),
61             NULL,   /* base_init */
62             NULL,   /* base_finalize */
63             (GClassInitFunc) sp_rect_class_init,
64             NULL,   /* class_finalize */
65             NULL,   /* class_data */
66             sizeof(SPRect),
67             16,     /* n_preallocs */
68             (GInstanceInitFunc) sp_rect_init,
69             NULL,   /* value_table */
70         };
71         type = g_type_register_static(SP_TYPE_SHAPE, "SPRect", &info, (GTypeFlags)0);
72     }
73     return type;
74 }
76 static void
77 sp_rect_class_init(SPRectClass *klass)
78 {
79     SPObjectClass *sp_object_class = (SPObjectClass *) klass;
80     SPItemClass *item_class = (SPItemClass *) klass;
81     SPShapeClass *shape_class = (SPShapeClass *) klass;
83     parent_class = (SPShapeClass *)g_type_class_ref(SP_TYPE_SHAPE);
85     sp_object_class->build = sp_rect_build;
86     sp_object_class->write = sp_rect_write;
87     sp_object_class->set = sp_rect_set;
88     sp_object_class->update = sp_rect_update;
90     item_class->description = sp_rect_description;
91     item_class->set_transform = sp_rect_set_transform;
92     item_class->convert_to_guides = sp_rect_convert_to_guides;
93     item_class->snappoints = sp_rect_snappoints; //override the default sp_shape_snappoints; see sp_rect_snappoints for details
95     shape_class->set_shape = sp_rect_set_shape;
96 }
98 static void
99 sp_rect_init(SPRect */*rect*/)
101     /* Initializing to zero is automatic */
102     /* sp_svg_length_unset(&rect->x, SP_SVG_UNIT_NONE, 0.0, 0.0); */
103     /* sp_svg_length_unset(&rect->y, SP_SVG_UNIT_NONE, 0.0, 0.0); */
104     /* sp_svg_length_unset(&rect->width, SP_SVG_UNIT_NONE, 0.0, 0.0); */
105     /* sp_svg_length_unset(&rect->height, SP_SVG_UNIT_NONE, 0.0, 0.0); */
106     /* sp_svg_length_unset(&rect->rx, SP_SVG_UNIT_NONE, 0.0, 0.0); */
107     /* sp_svg_length_unset(&rect->ry, SP_SVG_UNIT_NONE, 0.0, 0.0); */
110 static void
111 sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
113     SPRect *rect = SP_RECT(object);
115     if (((SPObjectClass *) parent_class)->build)
116         ((SPObjectClass *) parent_class)->build(object, document, repr);
118     sp_object_read_attr(object, "x");
119     sp_object_read_attr(object, "y");
120     sp_object_read_attr(object, "width");
121     sp_object_read_attr(object, "height");
122     sp_object_read_attr(object, "rx");
123     sp_object_read_attr(object, "ry");
125     Inkscape::Version const version = sp_object_get_sodipodi_version(object);
127     if ( version.major == 0 && version.minor == 29 ) {
128         if (rect->rx._set && rect->ry._set) {
129             /* 0.29 treated 0.0 radius as missing value */
130             if ((rect->rx.value != 0.0) && (rect->ry.value == 0.0)) {
131                 repr->setAttribute("ry", NULL);
132                 sp_object_read_attr(object, "ry");
133             } else if ((rect->ry.value != 0.0) && (rect->rx.value == 0.0)) {
134                 repr->setAttribute("rx", NULL);
135                 sp_object_read_attr(object, "rx");
136             }
137         }
138     }
141 static void
142 sp_rect_set(SPObject *object, unsigned key, gchar const *value)
144     SPRect *rect = SP_RECT(object);
146     /* fixme: We need real error processing some time */
148     switch (key) {
149         case SP_ATTR_X:
150             rect->x.readOrUnset(value);
151             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
152             break;
153         case SP_ATTR_Y:
154             rect->y.readOrUnset(value);
155             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
156             break;
157         case SP_ATTR_WIDTH:
158             if (!rect->width.read(value) || rect->width.value < 0.0) {
159                 rect->width.unset();
160             }
161             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
162             break;
163         case SP_ATTR_HEIGHT:
164             if (!rect->height.read(value) || rect->height.value < 0.0) {
165                 rect->height.unset();
166             }
167             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
168             break;
169         case SP_ATTR_RX:
170             if (!rect->rx.read(value) || rect->rx.value < 0.0) {
171                 rect->rx.unset();
172             }
173             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
174             break;
175         case SP_ATTR_RY:
176             if (!rect->ry.read(value) || rect->ry.value < 0.0) {
177                 rect->ry.unset();
178             }
179             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
180             break;
181         default:
182             if (((SPObjectClass *) parent_class)->set)
183                 ((SPObjectClass *) parent_class)->set(object, key, value);
184             break;
185     }
188 static void
189 sp_rect_update(SPObject *object, SPCtx *ctx, guint flags)
191     if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
192         SPRect *rect = (SPRect *) object;
193         SPStyle *style = object->style;
194         SPItemCtx const *ictx = (SPItemCtx const *) ctx;
195         double const d = NR::expansion(ictx->i2vp);
196         double const w = (ictx->vp.x1 - ictx->vp.x0) / d;
197         double const h = (ictx->vp.y1 - ictx->vp.y0) / d;
198         double const em = style->font_size.computed;
199         double const ex = 0.5 * em;  // fixme: get x height from pango or libnrtype.
200         rect->x.update(em, ex, w);
201         rect->y.update(em, ex, h);
202         rect->width.update(em, ex, w);
203         rect->height.update(em, ex, h);
204         rect->rx.update(em, ex, w);
205         rect->ry.update(em, ex, h);
206         sp_shape_set_shape((SPShape *) object);
207         flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore
208     }
210     if (((SPObjectClass *) parent_class)->update)
211         ((SPObjectClass *) parent_class)->update(object, ctx, flags);
214 static Inkscape::XML::Node *
215 sp_rect_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
217     SPRect *rect = SP_RECT(object);
219     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
220         Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
221         repr = xml_doc->createElement("svg:rect");
222     }
224     sp_repr_set_svg_double(repr, "width", rect->width.computed);
225     sp_repr_set_svg_double(repr, "height", rect->height.computed);
226     if (rect->rx._set) sp_repr_set_svg_double(repr, "rx", rect->rx.computed);
227     if (rect->ry._set) sp_repr_set_svg_double(repr, "ry", rect->ry.computed);
228     sp_repr_set_svg_double(repr, "x", rect->x.computed);
229     sp_repr_set_svg_double(repr, "y", rect->y.computed);
231     if (((SPObjectClass *) parent_class)->write)
232         ((SPObjectClass *) parent_class)->write(object, repr, flags);
234     return repr;
237 static gchar *
238 sp_rect_description(SPItem *item)
240     g_return_val_if_fail(SP_IS_RECT(item), NULL);
242     return g_strdup(_("<b>Rectangle</b>"));
245 #define C1 0.554
247 static void
248 sp_rect_set_shape(SPShape *shape)
250     SPRect *rect = (SPRect *) shape;
252     if ((rect->height.computed < 1e-18) || (rect->width.computed < 1e-18)) {
253         sp_shape_set_curve_insync(SP_SHAPE(rect), NULL, TRUE);
254         return;
255     }
257     SPCurve *c = sp_curve_new();
259     double const x = rect->x.computed;
260     double const y = rect->y.computed;
261     double const w = rect->width.computed;
262     double const h = rect->height.computed;
263     double const w2 = w / 2;
264     double const h2 = h / 2;
265     double const rx = std::min(( rect->rx._set
266                                  ? rect->rx.computed
267                                  : ( rect->ry._set
268                                      ? rect->ry.computed
269                                      : 0.0 ) ),
270                                .5 * rect->width.computed);
271     double const ry = std::min(( rect->ry._set
272                                  ? rect->ry.computed
273                                  : ( rect->rx._set
274                                      ? rect->rx.computed
275                                      : 0.0 ) ),
276                                .5 * rect->height.computed);
277     /* TODO: Handle negative rx or ry as per
278      * http://www.w3.org/TR/SVG11/shapes.html#RectElementRXAttribute once Inkscape has proper error
279      * handling (see http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing).
280      */
282     /* We don't use proper circular/elliptical arcs, but bezier curves can approximate a 90-degree
283      * arc fairly well.
284      */
285     if ((rx > 1e-18) && (ry > 1e-18)) {
286         sp_curve_moveto(c, x + rx, y);
287         if (rx < w2) sp_curve_lineto(c, x + w - rx, y);
288         sp_curve_curveto(c, x + w - rx * (1 - C1), y,     x + w, y + ry * (1 - C1),       x + w, y + ry);
289         if (ry < h2) sp_curve_lineto(c, x + w, y + h - ry);
290         sp_curve_curveto(c, x + w, y + h - ry * (1 - C1),     x + w - rx * (1 - C1), y + h,       x + w - rx, y + h);
291         if (rx < w2) sp_curve_lineto(c, x + rx, y + h);
292         sp_curve_curveto(c, x + rx * (1 - C1), y + h,     x, y + h - ry * (1 - C1),       x, y + h - ry);
293         if (ry < h2) sp_curve_lineto(c, x, y + ry);
294         sp_curve_curveto(c, x, y + ry * (1 - C1),     x + rx * (1 - C1), y,       x + rx, y);
295     } else {
296         sp_curve_moveto(c, x + 0.0, y + 0.0);
297         sp_curve_lineto(c, x + w, y + 0.0);
298         sp_curve_lineto(c, x + w, y + h);
299         sp_curve_lineto(c, x + 0.0, y + h);
300         sp_curve_lineto(c, x + 0.0, y + 0.0);
301     }
303     sp_curve_closepath_current(c);
304     sp_shape_set_curve_insync(SP_SHAPE(rect), c, TRUE);
305     sp_curve_unref(c);
308 /* fixme: Think (Lauris) */
310 void
311 sp_rect_position_set(SPRect *rect, gdouble x, gdouble y, gdouble width, gdouble height)
313     g_return_if_fail(rect != NULL);
314     g_return_if_fail(SP_IS_RECT(rect));
316     rect->x.computed = x;
317     rect->y.computed = y;
318     rect->width.computed = width;
319     rect->height.computed = height;
321     SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
324 void
325 sp_rect_set_rx(SPRect *rect, gboolean set, gdouble value)
327     g_return_if_fail(rect != NULL);
328     g_return_if_fail(SP_IS_RECT(rect));
330     rect->rx._set = set;
331     if (set) rect->rx.computed = value;
333     SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
336 void
337 sp_rect_set_ry(SPRect *rect, gboolean set, gdouble value)
339     g_return_if_fail(rect != NULL);
340     g_return_if_fail(SP_IS_RECT(rect));
342     rect->ry._set = set;
343     if (set) rect->ry.computed = value;
345     SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
348 /*
349  * Initially we'll do:
350  * Transform x, y, set x, y, clear translation
351  */
353 /* fixme: Use preferred units somehow (Lauris) */
354 /* fixme: Alternately preserve whatever units there are (lauris) */
356 static NR::Matrix
357 sp_rect_set_transform(SPItem *item, NR::Matrix const &xform)
359     SPRect *rect = SP_RECT(item);
361     /* Calculate rect start in parent coords. */
362     NR::Point pos( NR::Point(rect->x.computed, rect->y.computed) * xform );
364     /* This function takes care of translation and scaling, we return whatever parts we can't
365        handle. */
366     NR::Matrix ret(NR::transform(xform));
367     gdouble const sw = hypot(ret[0], ret[1]);
368     gdouble const sh = hypot(ret[2], ret[3]);
369     if (sw > 1e-9) {
370         ret[0] /= sw;
371         ret[1] /= sw;
372     } else {
373         ret[0] = 1.0;
374         ret[1] = 0.0;
375     }
376     if (sh > 1e-9) {
377         ret[2] /= sh;
378         ret[3] /= sh;
379     } else {
380         ret[2] = 0.0;
381         ret[3] = 1.0;
382     }
384     /* fixme: Would be nice to preserve units here */
385     rect->width = rect->width.computed * sw;
386     rect->height = rect->height.computed * sh;
387     if (rect->rx._set) {
388         rect->rx = rect->rx.computed * sw;
389     }
390     if (rect->ry._set) {
391         rect->ry = rect->ry.computed * sh;
392     }
394     /* Find start in item coords */
395     pos = pos * ret.inverse();
396     rect->x = pos[NR::X];
397     rect->y = pos[NR::Y];
399     sp_rect_set_shape(rect);
401     // Adjust stroke width
402     sp_item_adjust_stroke(item, sqrt(fabs(sw * sh)));
404     // Adjust pattern fill
405     sp_item_adjust_pattern(item, xform / ret);
407     // Adjust gradient fill
408     sp_item_adjust_gradient(item, xform / ret);
410     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
412     return ret;
416 /**
417 Returns the ratio in which the vector from p0 to p1 is stretched by transform
418  */
419 static gdouble
420 vector_stretch(NR::Point p0, NR::Point p1, NR::Matrix xform)
422     if (p0 == p1)
423         return 0;
424     return (NR::distance(p0 * xform, p1 * xform) / NR::distance(p0, p1));
427 void
428 sp_rect_set_visible_rx(SPRect *rect, gdouble rx)
430     if (rx == 0) {
431         rect->rx.computed = 0;
432         rect->rx._set = false;
433     } else {
434         rect->rx.computed = rx / vector_stretch(
435             NR::Point(rect->x.computed + 1, rect->y.computed),
436             NR::Point(rect->x.computed, rect->y.computed),
437             SP_ITEM(rect)->transform);
438         rect->rx._set = true;
439     }
440     SP_OBJECT(rect)->updateRepr();
443 void
444 sp_rect_set_visible_ry(SPRect *rect, gdouble ry)
446     if (ry == 0) {
447         rect->ry.computed = 0;
448         rect->ry._set = false;
449     } else {
450         rect->ry.computed = ry / vector_stretch(
451             NR::Point(rect->x.computed, rect->y.computed + 1),
452             NR::Point(rect->x.computed, rect->y.computed),
453             SP_ITEM(rect)->transform);
454         rect->ry._set = true;
455     }
456     SP_OBJECT(rect)->updateRepr();
459 gdouble
460 sp_rect_get_visible_rx(SPRect *rect)
462     if (!rect->rx._set)
463         return 0;
464     return rect->rx.computed * vector_stretch(
465         NR::Point(rect->x.computed + 1, rect->y.computed),
466         NR::Point(rect->x.computed, rect->y.computed),
467         SP_ITEM(rect)->transform);
470 gdouble
471 sp_rect_get_visible_ry(SPRect *rect)
473     if (!rect->ry._set)
474         return 0;
475     return rect->ry.computed * vector_stretch(
476         NR::Point(rect->x.computed, rect->y.computed + 1),
477         NR::Point(rect->x.computed, rect->y.computed),
478         SP_ITEM(rect)->transform);
481 void
482 sp_rect_compensate_rxry(SPRect *rect, NR::Matrix xform)
484     if (rect->rx.computed == 0 && rect->ry.computed == 0)
485         return; // nothing to compensate
487     // test unit vectors to find out compensation:
488     NR::Point c(rect->x.computed, rect->y.computed);
489     NR::Point cx = c + NR::Point(1, 0);
490     NR::Point cy = c + NR::Point(0, 1);
492     // apply previous transform if any
493     c *= SP_ITEM(rect)->transform;
494     cx *= SP_ITEM(rect)->transform;
495     cy *= SP_ITEM(rect)->transform;
497     // find out stretches that we need to compensate
498     gdouble eX = vector_stretch(cx, c, xform);
499     gdouble eY = vector_stretch(cy, c, xform);
501     // If only one of the radii is set, set both radii so they have the same visible length
502     // This is needed because if we just set them the same length in SVG, they might end up unequal because of transform
503     if ((rect->rx._set && !rect->ry._set) || (rect->ry._set && !rect->rx._set)) {
504         gdouble r = MAX(rect->rx.computed, rect->ry.computed);
505         rect->rx.computed = r / eX;
506         rect->ry.computed = r / eY;
507     } else {
508         rect->rx.computed = rect->rx.computed / eX;
509         rect->ry.computed = rect->ry.computed / eY;
510     }
512     // Note that a radius may end up larger than half-side if the rect is scaled down;
513     // that's ok because this preserves the intended radii in case the rect is enlarged again,
514     // and set_shape will take care of trimming too large radii when generating d=
516     rect->rx._set = rect->ry._set = true;
519 void
520 sp_rect_set_visible_width(SPRect *rect, gdouble width)
522     rect->width.computed = width / vector_stretch(
523         NR::Point(rect->x.computed + 1, rect->y.computed),
524         NR::Point(rect->x.computed, rect->y.computed),
525         SP_ITEM(rect)->transform);
526     rect->width._set = true;
527     SP_OBJECT(rect)->updateRepr();
530 void
531 sp_rect_set_visible_height(SPRect *rect, gdouble height)
533     rect->height.computed = height / vector_stretch(
534         NR::Point(rect->x.computed, rect->y.computed + 1),
535         NR::Point(rect->x.computed, rect->y.computed),
536         SP_ITEM(rect)->transform);
537     rect->height._set = true;
538     SP_OBJECT(rect)->updateRepr();
541 gdouble
542 sp_rect_get_visible_width(SPRect *rect)
544     if (!rect->width._set)
545         return 0;
546     return rect->width.computed * vector_stretch(
547         NR::Point(rect->x.computed + 1, rect->y.computed),
548         NR::Point(rect->x.computed, rect->y.computed),
549         SP_ITEM(rect)->transform);
552 gdouble
553 sp_rect_get_visible_height(SPRect *rect)
555     if (!rect->height._set)
556         return 0;
557     return rect->height.computed * vector_stretch(
558         NR::Point(rect->x.computed, rect->y.computed + 1),
559         NR::Point(rect->x.computed, rect->y.computed),
560         SP_ITEM(rect)->transform);
563 /**
564  * Sets the snappoint p to the unrounded corners of the rectangle
565  */
566 static void sp_rect_snappoints(SPItem const *item, SnapPointsIter p)
568     /* This method overrides sp_shape_snappoints, which is the default for any shape. The default method
569     returns all eight points along the path of a rounded rectangle, but not the real corners. Snapping
570     the startpoint and endpoint of each rounded corner is not very usefull and really confusing. Instead 
571     we could snap either the real corners, or not snap at all. Bulia Byak opted to snap the real corners,
572     but it should be noted that this might be confusing in some cases with relatively large radii. With 
573     small radii though the user will easily understand which point is snapping. */
574     
575     g_assert(item != NULL);
576     g_assert(SP_IS_RECT(item));
578     SPRect *rect = SP_RECT(item);
580     NR::Matrix const i2d (sp_item_i2d_affine (item));
582     *p = NR::Point(rect->x.computed, rect->y.computed) * i2d;
583     *p = NR::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d;
584     *p = NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d;
585     *p = NR::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d;
588 void
589 sp_rect_convert_to_guides(SPItem *item) {
590     SPRect *rect = SP_RECT(item);
592     if (prefs_get_int_attribute("tools.shapes.rect", "convertguides", 1) == 0) {
593         sp_item_convert_to_guides(SP_ITEM(rect));
594         return;
595     }
597     SPDocument *doc = SP_OBJECT_DOCUMENT(rect);
598     std::list<std::pair<Geom::Point, Geom::Point> > pts;
600     NR::Matrix const i2d (sp_item_i2d_affine(SP_ITEM(rect)));
602     NR::Point A1(NR::Point(rect->x.computed, rect->y.computed) * i2d);
603     NR::Point A2(NR::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d);
604     NR::Point A3(NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d);
605     NR::Point A4(NR::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d);
607     pts.push_back(std::make_pair(A1.to_2geom(), A2.to_2geom()));
608     pts.push_back(std::make_pair(A2.to_2geom(), A3.to_2geom()));
609     pts.push_back(std::make_pair(A3.to_2geom(), A4.to_2geom()));
610     pts.push_back(std::make_pair(A4.to_2geom(), A1.to_2geom()));
612     sp_guide_pt_pairs_to_guides(doc, pts);
615 /*
616   Local Variables:
617   mode:c++
618   c-file-style:"stroustrup"
619   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
620   indent-tabs-mode:nil
621   fill-column:99
622   End:
623 */
624 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :