Code

Slightly more 'object-oriented' way to invoke item-specific conversion-to-guides...
[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)) return;
254     SPCurve *c = sp_curve_new();
256     double const x = rect->x.computed;
257     double const y = rect->y.computed;
258     double const w = rect->width.computed;
259     double const h = rect->height.computed;
260     double const w2 = w / 2;
261     double const h2 = h / 2;
262     double const rx = std::min(( rect->rx._set
263                                  ? rect->rx.computed
264                                  : ( rect->ry._set
265                                      ? rect->ry.computed
266                                      : 0.0 ) ),
267                                .5 * rect->width.computed);
268     double const ry = std::min(( rect->ry._set
269                                  ? rect->ry.computed
270                                  : ( rect->rx._set
271                                      ? rect->rx.computed
272                                      : 0.0 ) ),
273                                .5 * rect->height.computed);
274     /* TODO: Handle negative rx or ry as per
275      * http://www.w3.org/TR/SVG11/shapes.html#RectElementRXAttribute once Inkscape has proper error
276      * handling (see http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing).
277      */
279     /* We don't use proper circular/elliptical arcs, but bezier curves can approximate a 90-degree
280      * arc fairly well.
281      */
282     if ((rx > 1e-18) && (ry > 1e-18)) {
283         sp_curve_moveto(c, x + rx, y);
284         if (rx < w2) sp_curve_lineto(c, x + w - rx, y);
285         sp_curve_curveto(c, x + w - rx * (1 - C1), y,     x + w, y + ry * (1 - C1),       x + w, y + ry);
286         if (ry < h2) sp_curve_lineto(c, x + w, y + h - ry);
287         sp_curve_curveto(c, x + w, y + h - ry * (1 - C1),     x + w - rx * (1 - C1), y + h,       x + w - rx, y + h);
288         if (rx < w2) sp_curve_lineto(c, x + rx, y + h);
289         sp_curve_curveto(c, x + rx * (1 - C1), y + h,     x, y + h - ry * (1 - C1),       x, y + h - ry);
290         if (ry < h2) sp_curve_lineto(c, x, y + ry);
291         sp_curve_curveto(c, x, y + ry * (1 - C1),     x + rx * (1 - C1), y,       x + rx, y);
292     } else {
293         sp_curve_moveto(c, x + 0.0, y + 0.0);
294         sp_curve_lineto(c, x + w, y + 0.0);
295         sp_curve_lineto(c, x + w, y + h);
296         sp_curve_lineto(c, x + 0.0, y + h);
297         sp_curve_lineto(c, x + 0.0, y + 0.0);
298     }
300     sp_curve_closepath_current(c);
301     sp_shape_set_curve_insync(SP_SHAPE(rect), c, TRUE);
302     sp_curve_unref(c);
305 /* fixme: Think (Lauris) */
307 void
308 sp_rect_position_set(SPRect *rect, gdouble x, gdouble y, gdouble width, gdouble height)
310     g_return_if_fail(rect != NULL);
311     g_return_if_fail(SP_IS_RECT(rect));
313     rect->x.computed = x;
314     rect->y.computed = y;
315     rect->width.computed = width;
316     rect->height.computed = height;
318     SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
321 void
322 sp_rect_set_rx(SPRect *rect, gboolean set, gdouble value)
324     g_return_if_fail(rect != NULL);
325     g_return_if_fail(SP_IS_RECT(rect));
327     rect->rx._set = set;
328     if (set) rect->rx.computed = value;
330     SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
333 void
334 sp_rect_set_ry(SPRect *rect, gboolean set, gdouble value)
336     g_return_if_fail(rect != NULL);
337     g_return_if_fail(SP_IS_RECT(rect));
339     rect->ry._set = set;
340     if (set) rect->ry.computed = value;
342     SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
345 /*
346  * Initially we'll do:
347  * Transform x, y, set x, y, clear translation
348  */
350 /* fixme: Use preferred units somehow (Lauris) */
351 /* fixme: Alternately preserve whatever units there are (lauris) */
353 static NR::Matrix
354 sp_rect_set_transform(SPItem *item, NR::Matrix const &xform)
356     SPRect *rect = SP_RECT(item);
358     /* Calculate rect start in parent coords. */
359     NR::Point pos( NR::Point(rect->x.computed, rect->y.computed) * xform );
361     /* This function takes care of translation and scaling, we return whatever parts we can't
362        handle. */
363     NR::Matrix ret(NR::transform(xform));
364     gdouble const sw = hypot(ret[0], ret[1]);
365     gdouble const sh = hypot(ret[2], ret[3]);
366     if (sw > 1e-9) {
367         ret[0] /= sw;
368         ret[1] /= sw;
369     } else {
370         ret[0] = 1.0;
371         ret[1] = 0.0;
372     }
373     if (sh > 1e-9) {
374         ret[2] /= sh;
375         ret[3] /= sh;
376     } else {
377         ret[2] = 0.0;
378         ret[3] = 1.0;
379     }
381     /* fixme: Would be nice to preserve units here */
382     rect->width = rect->width.computed * sw;
383     rect->height = rect->height.computed * sh;
384     if (rect->rx._set) {
385         rect->rx = rect->rx.computed * sw;
386     }
387     if (rect->ry._set) {
388         rect->ry = rect->ry.computed * sh;
389     }
391     /* Find start in item coords */
392     pos = pos * ret.inverse();
393     rect->x = pos[NR::X];
394     rect->y = pos[NR::Y];
396     sp_rect_set_shape(rect);
398     // Adjust stroke width
399     sp_item_adjust_stroke(item, sqrt(fabs(sw * sh)));
401     // Adjust pattern fill
402     sp_item_adjust_pattern(item, xform / ret);
404     // Adjust gradient fill
405     sp_item_adjust_gradient(item, xform / ret);
407     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
409     return ret;
413 /**
414 Returns the ratio in which the vector from p0 to p1 is stretched by transform
415  */
416 static gdouble
417 vector_stretch(NR::Point p0, NR::Point p1, NR::Matrix xform)
419     if (p0 == p1)
420         return 0;
421     return (NR::distance(p0 * xform, p1 * xform) / NR::distance(p0, p1));
424 void
425 sp_rect_set_visible_rx(SPRect *rect, gdouble rx)
427     if (rx == 0) {
428         rect->rx.computed = 0;
429         rect->rx._set = false;
430     } else {
431         rect->rx.computed = rx / vector_stretch(
432             NR::Point(rect->x.computed + 1, rect->y.computed),
433             NR::Point(rect->x.computed, rect->y.computed),
434             SP_ITEM(rect)->transform);
435         rect->rx._set = true;
436     }
437     SP_OBJECT(rect)->updateRepr();
440 void
441 sp_rect_set_visible_ry(SPRect *rect, gdouble ry)
443     if (ry == 0) {
444         rect->ry.computed = 0;
445         rect->ry._set = false;
446     } else {
447         rect->ry.computed = ry / vector_stretch(
448             NR::Point(rect->x.computed, rect->y.computed + 1),
449             NR::Point(rect->x.computed, rect->y.computed),
450             SP_ITEM(rect)->transform);
451         rect->ry._set = true;
452     }
453     SP_OBJECT(rect)->updateRepr();
456 gdouble
457 sp_rect_get_visible_rx(SPRect *rect)
459     if (!rect->rx._set)
460         return 0;
461     return rect->rx.computed * vector_stretch(
462         NR::Point(rect->x.computed + 1, rect->y.computed),
463         NR::Point(rect->x.computed, rect->y.computed),
464         SP_ITEM(rect)->transform);
467 gdouble
468 sp_rect_get_visible_ry(SPRect *rect)
470     if (!rect->ry._set)
471         return 0;
472     return rect->ry.computed * vector_stretch(
473         NR::Point(rect->x.computed, rect->y.computed + 1),
474         NR::Point(rect->x.computed, rect->y.computed),
475         SP_ITEM(rect)->transform);
478 void
479 sp_rect_compensate_rxry(SPRect *rect, NR::Matrix xform)
481     if (rect->rx.computed == 0 && rect->ry.computed == 0)
482         return; // nothing to compensate
484     // test unit vectors to find out compensation:
485     NR::Point c(rect->x.computed, rect->y.computed);
486     NR::Point cx = c + NR::Point(1, 0);
487     NR::Point cy = c + NR::Point(0, 1);
489     // apply previous transform if any
490     c *= SP_ITEM(rect)->transform;
491     cx *= SP_ITEM(rect)->transform;
492     cy *= SP_ITEM(rect)->transform;
494     // find out stretches that we need to compensate
495     gdouble eX = vector_stretch(cx, c, xform);
496     gdouble eY = vector_stretch(cy, c, xform);
498     // If only one of the radii is set, set both radii so they have the same visible length
499     // This is needed because if we just set them the same length in SVG, they might end up unequal because of transform
500     if ((rect->rx._set && !rect->ry._set) || (rect->ry._set && !rect->rx._set)) {
501         gdouble r = MAX(rect->rx.computed, rect->ry.computed);
502         rect->rx.computed = r / eX;
503         rect->ry.computed = r / eY;
504     } else {
505         rect->rx.computed = rect->rx.computed / eX;
506         rect->ry.computed = rect->ry.computed / eY;
507     }
509     // Note that a radius may end up larger than half-side if the rect is scaled down;
510     // that's ok because this preserves the intended radii in case the rect is enlarged again,
511     // and set_shape will take care of trimming too large radii when generating d=
513     rect->rx._set = rect->ry._set = true;
516 void
517 sp_rect_set_visible_width(SPRect *rect, gdouble width)
519     rect->width.computed = width / vector_stretch(
520         NR::Point(rect->x.computed + 1, rect->y.computed),
521         NR::Point(rect->x.computed, rect->y.computed),
522         SP_ITEM(rect)->transform);
523     rect->width._set = true;
524     SP_OBJECT(rect)->updateRepr();
527 void
528 sp_rect_set_visible_height(SPRect *rect, gdouble height)
530     rect->height.computed = height / vector_stretch(
531         NR::Point(rect->x.computed, rect->y.computed + 1),
532         NR::Point(rect->x.computed, rect->y.computed),
533         SP_ITEM(rect)->transform);
534     rect->height._set = true;
535     SP_OBJECT(rect)->updateRepr();
538 gdouble
539 sp_rect_get_visible_width(SPRect *rect)
541     if (!rect->width._set)
542         return 0;
543     return rect->width.computed * vector_stretch(
544         NR::Point(rect->x.computed + 1, rect->y.computed),
545         NR::Point(rect->x.computed, rect->y.computed),
546         SP_ITEM(rect)->transform);
549 gdouble
550 sp_rect_get_visible_height(SPRect *rect)
552     if (!rect->height._set)
553         return 0;
554     return rect->height.computed * vector_stretch(
555         NR::Point(rect->x.computed, rect->y.computed + 1),
556         NR::Point(rect->x.computed, rect->y.computed),
557         SP_ITEM(rect)->transform);
560 /**
561  * Sets the snappoint p to the unrounded corners of the rectangle
562  */
563 static void sp_rect_snappoints(SPItem const *item, SnapPointsIter p)
565     /* This method overrides sp_shape_snappoints, which is the default for any shape. The default method
566     returns all eight points along the path of a rounded rectangle, but not the real corners. Snapping
567     the startpoint and endpoint of each rounded corner is not very usefull and really confusing. Instead 
568     we could snap either the real corners, or not snap at all. Bulia Byak opted to snap the real corners,
569     but it should be noted that this might be confusing in some cases with relatively large radii. With 
570     small radii though the user will easily understand which point is snapping. */
571     
572     g_assert(item != NULL);
573     g_assert(SP_IS_RECT(item));
575     SPRect *rect = SP_RECT(item);
577     NR::Matrix const i2d (sp_item_i2d_affine (item));
579     *p = NR::Point(rect->x.computed, rect->y.computed) * i2d;
580     *p = NR::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d;
581     *p = NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d;
582     *p = NR::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d;
585 void
586 sp_rect_convert_to_guides(SPItem *item) {
587     SPRect *rect = SP_RECT(item);
589     if (prefs_get_int_attribute("tools.shapes.rect", "convertguides", 1) == 0) {
590         sp_item_convert_to_guides(SP_ITEM(rect));
591         return;
592     }
594     SPDocument *doc = SP_OBJECT_DOCUMENT(rect);
595     std::list<std::pair<Geom::Point, Geom::Point> > pts;
597     NR::Matrix const i2d (sp_item_i2d_affine(SP_ITEM(rect)));
599     NR::Point A1(NR::Point(rect->x.computed, rect->y.computed) * i2d);
600     NR::Point A2(NR::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d);
601     NR::Point A3(NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d);
602     NR::Point A4(NR::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d);
604     pts.push_back(std::make_pair(A1.to_2geom(), A2.to_2geom()));
605     pts.push_back(std::make_pair(A2.to_2geom(), A3.to_2geom()));
606     pts.push_back(std::make_pair(A3.to_2geom(), A4.to_2geom()));
607     pts.push_back(std::make_pair(A4.to_2geom(), A1.to_2geom()));
609     sp_guide_pt_pairs_to_guides(doc, pts);
611     SP_OBJECT(rect)->deleteObject(true);
614 /*
615   Local Variables:
616   mode:c++
617   c-file-style:"stroustrup"
618   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
619   indent-tabs-mode:nil
620   fill-column:99
621   End:
622 */
623 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :