Code

warning cleanup
[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     SPRect *rect = SP_RECT(item);
354     /* Calculate rect start in parent coords. */
355     NR::Point pos( NR::Point(rect->x.computed, rect->y.computed) * xform );
357     /* This function takes care of translation and scaling, we return whatever parts we can't
358        handle. */
359     NR::Matrix ret(NR::transform(xform));
360     gdouble const sw = hypot(ret[0], ret[1]);
361     gdouble const sh = hypot(ret[2], ret[3]);
362     if (sw > 1e-9) {
363         ret[0] /= sw;
364         ret[1] /= sw;
365     } else {
366         ret[0] = 1.0;
367         ret[1] = 0.0;
368     }
369     if (sh > 1e-9) {
370         ret[2] /= sh;
371         ret[3] /= sh;
372     } else {
373         ret[2] = 0.0;
374         ret[3] = 1.0;
375     }
377     /* fixme: Would be nice to preserve units here */
378     rect->width = rect->width.computed * sw;
379     rect->height = rect->height.computed * sh;
380     if (rect->rx._set) {
381         rect->rx = rect->rx.computed * sw;
382     }
383     if (rect->ry._set) {
384         rect->ry = rect->ry.computed * sh;
385     }
387     /* Find start in item coords */
388     pos = pos * ret.inverse();
389     rect->x = pos[NR::X];
390     rect->y = pos[NR::Y];
392     sp_rect_set_shape(rect);
394     // Adjust stroke width
395     sp_item_adjust_stroke(item, sqrt(fabs(sw * sh)));
397     // Adjust pattern fill
398     sp_item_adjust_pattern(item, xform / ret);
400     // Adjust gradient fill
401     sp_item_adjust_gradient(item, xform / ret);
403     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
405     return ret;
409 /**
410 Returns the ratio in which the vector from p0 to p1 is stretched by transform
411  */
412 static gdouble
413 vector_stretch(NR::Point p0, NR::Point p1, NR::Matrix xform)
415     if (p0 == p1)
416         return 0;
417     return (NR::distance(p0 * xform, p1 * xform) / NR::distance(p0, p1));
420 void
421 sp_rect_set_visible_rx(SPRect *rect, gdouble rx)
423     if (rx == 0) {
424         rect->rx.computed = 0;
425         rect->rx._set = false;
426     } else {
427         rect->rx.computed = rx / vector_stretch(
428             NR::Point(rect->x.computed + 1, rect->y.computed),
429             NR::Point(rect->x.computed, rect->y.computed),
430             SP_ITEM(rect)->transform);
431         rect->rx._set = true;
432     }
433     SP_OBJECT(rect)->updateRepr();
436 void
437 sp_rect_set_visible_ry(SPRect *rect, gdouble ry)
439     if (ry == 0) {
440         rect->ry.computed = 0;
441         rect->ry._set = false;
442     } else {
443         rect->ry.computed = ry / vector_stretch(
444             NR::Point(rect->x.computed, rect->y.computed + 1),
445             NR::Point(rect->x.computed, rect->y.computed),
446             SP_ITEM(rect)->transform);
447         rect->ry._set = true;
448     }
449     SP_OBJECT(rect)->updateRepr();
452 gdouble
453 sp_rect_get_visible_rx(SPRect *rect)
455     if (!rect->rx._set)
456         return 0;
457     return rect->rx.computed * vector_stretch(
458         NR::Point(rect->x.computed + 1, rect->y.computed),
459         NR::Point(rect->x.computed, rect->y.computed),
460         SP_ITEM(rect)->transform);
463 gdouble
464 sp_rect_get_visible_ry(SPRect *rect)
466     if (!rect->ry._set)
467         return 0;
468     return rect->ry.computed * vector_stretch(
469         NR::Point(rect->x.computed, rect->y.computed + 1),
470         NR::Point(rect->x.computed, rect->y.computed),
471         SP_ITEM(rect)->transform);
474 void
475 sp_rect_compensate_rxry(SPRect *rect, NR::Matrix xform)
477     if (rect->rx.computed == 0 && rect->ry.computed == 0)
478         return; // nothing to compensate
480     // test unit vectors to find out compensation:
481     NR::Point c(rect->x.computed, rect->y.computed);
482     NR::Point cx = c + NR::Point(1, 0);
483     NR::Point cy = c + NR::Point(0, 1);
485     // apply previous transform if any
486     c *= SP_ITEM(rect)->transform;
487     cx *= SP_ITEM(rect)->transform;
488     cy *= SP_ITEM(rect)->transform;
490     // find out stretches that we need to compensate
491     gdouble eX = vector_stretch(cx, c, xform);
492     gdouble eY = vector_stretch(cy, c, xform);
494     // If only one of the radii is set, set both radii so they have the same visible length
495     // This is needed because if we just set them the same length in SVG, they might end up unequal because of transform
496     if ((rect->rx._set && !rect->ry._set) || (rect->ry._set && !rect->rx._set)) {
497         gdouble r = MAX(rect->rx.computed, rect->ry.computed);
498         rect->rx.computed = r / eX;
499         rect->ry.computed = r / eY;
500     } else {
501         rect->rx.computed = rect->rx.computed / eX;
502         rect->ry.computed = rect->ry.computed / eY;
503     }
505     // Note that a radius may end up larger than half-side if the rect is scaled down;
506     // that's ok because this preserves the intended radii in case the rect is enlarged again,
507     // and set_shape will take care of trimming too large radii when generating d=
509     rect->rx._set = rect->ry._set = true;
512 void
513 sp_rect_set_visible_width(SPRect *rect, gdouble width)
515     rect->width.computed = width / vector_stretch(
516         NR::Point(rect->x.computed + 1, rect->y.computed),
517         NR::Point(rect->x.computed, rect->y.computed),
518         SP_ITEM(rect)->transform);
519     rect->width._set = true;
520     SP_OBJECT(rect)->updateRepr();
523 void
524 sp_rect_set_visible_height(SPRect *rect, gdouble height)
526     rect->height.computed = height / vector_stretch(
527         NR::Point(rect->x.computed, rect->y.computed + 1),
528         NR::Point(rect->x.computed, rect->y.computed),
529         SP_ITEM(rect)->transform);
530     rect->height._set = true;
531     SP_OBJECT(rect)->updateRepr();
534 gdouble
535 sp_rect_get_visible_width(SPRect *rect)
537     if (!rect->width._set)
538         return 0;
539     return rect->width.computed * vector_stretch(
540         NR::Point(rect->x.computed + 1, rect->y.computed),
541         NR::Point(rect->x.computed, rect->y.computed),
542         SP_ITEM(rect)->transform);
545 gdouble
546 sp_rect_get_visible_height(SPRect *rect)
548     if (!rect->height._set)
549         return 0;
550     return rect->height.computed * vector_stretch(
551         NR::Point(rect->x.computed, rect->y.computed + 1),
552         NR::Point(rect->x.computed, rect->y.computed),
553         SP_ITEM(rect)->transform);
556 /**
557  * Sets the snappoint p to the unrounded corners of the rectangle
558  */
559 static void sp_rect_snappoints(SPItem const *item, SnapPointsIter p)
561     /* This method overrides sp_shape_snappoints, which is the default for any shape. The default method
562     returns all eight points along the path of a rounded rectangle, but not the real corners. Snapping
563     the startpoint and endpoint of each rounded corner is not very usefull and really confusing. Instead 
564     we could snap either the real corners, or not snap at all. Bulia Byak opted to snap the real corners,
565     but it should be noted that this might be confusing in some cases with relatively large radii. With 
566     small radii though the user will easily understand which point is snapping. */
567     
568     g_assert(item != NULL);
569     g_assert(SP_IS_RECT(item));
571     SPRect *rect = SP_RECT(item);
573     NR::Matrix const i2d (sp_item_i2d_affine (item));
575     *p = NR::Point(rect->x.computed, rect->y.computed) * i2d;
576     *p = NR::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d;
577     *p = NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d;
578     *p = NR::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d;
581 /*
582   Local Variables:
583   mode:c++
584   c-file-style:"stroustrup"
585   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
586   indent-tabs-mode:nil
587   fill-column:99
588   End:
589 */
590 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :