Code

remove antediluvian cruft
[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");
126 static void
127 sp_rect_set(SPObject *object, unsigned key, gchar const *value)
129     SPRect *rect = SP_RECT(object);
131     /* fixme: We need real error processing some time */
133     switch (key) {
134         case SP_ATTR_X:
135             rect->x.readOrUnset(value);
136             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
137             break;
138         case SP_ATTR_Y:
139             rect->y.readOrUnset(value);
140             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
141             break;
142         case SP_ATTR_WIDTH:
143             if (!rect->width.read(value) || rect->width.value < 0.0) {
144                 rect->width.unset();
145             }
146             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
147             break;
148         case SP_ATTR_HEIGHT:
149             if (!rect->height.read(value) || rect->height.value < 0.0) {
150                 rect->height.unset();
151             }
152             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
153             break;
154         case SP_ATTR_RX:
155             if (!rect->rx.read(value) || rect->rx.value < 0.0) {
156                 rect->rx.unset();
157             }
158             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
159             break;
160         case SP_ATTR_RY:
161             if (!rect->ry.read(value) || rect->ry.value < 0.0) {
162                 rect->ry.unset();
163             }
164             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
165             break;
166         default:
167             if (((SPObjectClass *) parent_class)->set)
168                 ((SPObjectClass *) parent_class)->set(object, key, value);
169             break;
170     }
173 static void
174 sp_rect_update(SPObject *object, SPCtx *ctx, guint flags)
176     if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
177         SPRect *rect = (SPRect *) object;
178         SPStyle *style = object->style;
179         SPItemCtx const *ictx = (SPItemCtx const *) ctx;
180         double const d = NR::expansion(ictx->i2vp);
181         double const w = (ictx->vp.x1 - ictx->vp.x0) / d;
182         double const h = (ictx->vp.y1 - ictx->vp.y0) / d;
183         double const em = style->font_size.computed;
184         double const ex = 0.5 * em;  // fixme: get x height from pango or libnrtype.
185         rect->x.update(em, ex, w);
186         rect->y.update(em, ex, h);
187         rect->width.update(em, ex, w);
188         rect->height.update(em, ex, h);
189         rect->rx.update(em, ex, w);
190         rect->ry.update(em, ex, h);
191         sp_shape_set_shape((SPShape *) object);
192         flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore
193     }
195     if (((SPObjectClass *) parent_class)->update)
196         ((SPObjectClass *) parent_class)->update(object, ctx, flags);
199 static Inkscape::XML::Node *
200 sp_rect_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
202     SPRect *rect = SP_RECT(object);
204     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
205         Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
206         repr = xml_doc->createElement("svg:rect");
207     }
209     sp_repr_set_svg_double(repr, "width", rect->width.computed);
210     sp_repr_set_svg_double(repr, "height", rect->height.computed);
211     if (rect->rx._set) sp_repr_set_svg_double(repr, "rx", rect->rx.computed);
212     if (rect->ry._set) sp_repr_set_svg_double(repr, "ry", rect->ry.computed);
213     sp_repr_set_svg_double(repr, "x", rect->x.computed);
214     sp_repr_set_svg_double(repr, "y", rect->y.computed);
216     if (((SPObjectClass *) parent_class)->write)
217         ((SPObjectClass *) parent_class)->write(object, repr, flags);
219     return repr;
222 static gchar *
223 sp_rect_description(SPItem *item)
225     g_return_val_if_fail(SP_IS_RECT(item), NULL);
227     return g_strdup(_("<b>Rectangle</b>"));
230 #define C1 0.554
232 static void
233 sp_rect_set_shape(SPShape *shape)
235     SPRect *rect = (SPRect *) shape;
237     if ((rect->height.computed < 1e-18) || (rect->width.computed < 1e-18)) {
238         sp_shape_set_curve_insync(SP_SHAPE(rect), NULL, TRUE);
239         return;
240     }
242     SPCurve *c = sp_curve_new();
244     double const x = rect->x.computed;
245     double const y = rect->y.computed;
246     double const w = rect->width.computed;
247     double const h = rect->height.computed;
248     double const w2 = w / 2;
249     double const h2 = h / 2;
250     double const rx = std::min(( rect->rx._set
251                                  ? rect->rx.computed
252                                  : ( rect->ry._set
253                                      ? rect->ry.computed
254                                      : 0.0 ) ),
255                                .5 * rect->width.computed);
256     double const ry = std::min(( rect->ry._set
257                                  ? rect->ry.computed
258                                  : ( rect->rx._set
259                                      ? rect->rx.computed
260                                      : 0.0 ) ),
261                                .5 * rect->height.computed);
262     /* TODO: Handle negative rx or ry as per
263      * http://www.w3.org/TR/SVG11/shapes.html#RectElementRXAttribute once Inkscape has proper error
264      * handling (see http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing).
265      */
267     /* We don't use proper circular/elliptical arcs, but bezier curves can approximate a 90-degree
268      * arc fairly well.
269      */
270     if ((rx > 1e-18) && (ry > 1e-18)) {
271         sp_curve_moveto(c, x + rx, y);
272         if (rx < w2) sp_curve_lineto(c, x + w - rx, y);
273         sp_curve_curveto(c, x + w - rx * (1 - C1), y,     x + w, y + ry * (1 - C1),       x + w, y + ry);
274         if (ry < h2) sp_curve_lineto(c, x + w, y + h - ry);
275         sp_curve_curveto(c, x + w, y + h - ry * (1 - C1),     x + w - rx * (1 - C1), y + h,       x + w - rx, y + h);
276         if (rx < w2) sp_curve_lineto(c, x + rx, y + h);
277         sp_curve_curveto(c, x + rx * (1 - C1), y + h,     x, y + h - ry * (1 - C1),       x, y + h - ry);
278         if (ry < h2) sp_curve_lineto(c, x, y + ry);
279         sp_curve_curveto(c, x, y + ry * (1 - C1),     x + rx * (1 - C1), y,       x + rx, y);
280     } else {
281         sp_curve_moveto(c, x + 0.0, y + 0.0);
282         sp_curve_lineto(c, x + w, y + 0.0);
283         sp_curve_lineto(c, x + w, y + h);
284         sp_curve_lineto(c, x + 0.0, y + h);
285         sp_curve_lineto(c, x + 0.0, y + 0.0);
286     }
288     sp_curve_closepath_current(c);
289     sp_shape_set_curve_insync(SP_SHAPE(rect), c, TRUE);
290     sp_curve_unref(c);
293 /* fixme: Think (Lauris) */
295 void
296 sp_rect_position_set(SPRect *rect, gdouble x, gdouble y, gdouble width, gdouble height)
298     g_return_if_fail(rect != NULL);
299     g_return_if_fail(SP_IS_RECT(rect));
301     rect->x.computed = x;
302     rect->y.computed = y;
303     rect->width.computed = width;
304     rect->height.computed = height;
306     SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
309 void
310 sp_rect_set_rx(SPRect *rect, gboolean set, gdouble value)
312     g_return_if_fail(rect != NULL);
313     g_return_if_fail(SP_IS_RECT(rect));
315     rect->rx._set = set;
316     if (set) rect->rx.computed = value;
318     SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
321 void
322 sp_rect_set_ry(SPRect *rect, gboolean set, gdouble value)
324     g_return_if_fail(rect != NULL);
325     g_return_if_fail(SP_IS_RECT(rect));
327     rect->ry._set = set;
328     if (set) rect->ry.computed = value;
330     SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
333 /*
334  * Initially we'll do:
335  * Transform x, y, set x, y, clear translation
336  */
338 /* fixme: Use preferred units somehow (Lauris) */
339 /* fixme: Alternately preserve whatever units there are (lauris) */
341 static NR::Matrix
342 sp_rect_set_transform(SPItem *item, NR::Matrix const &xform)
344     SPRect *rect = SP_RECT(item);
346     /* Calculate rect start in parent coords. */
347     NR::Point pos( NR::Point(rect->x.computed, rect->y.computed) * xform );
349     /* This function takes care of translation and scaling, we return whatever parts we can't
350        handle. */
351     NR::Matrix ret(NR::transform(xform));
352     gdouble const sw = hypot(ret[0], ret[1]);
353     gdouble const sh = hypot(ret[2], ret[3]);
354     if (sw > 1e-9) {
355         ret[0] /= sw;
356         ret[1] /= sw;
357     } else {
358         ret[0] = 1.0;
359         ret[1] = 0.0;
360     }
361     if (sh > 1e-9) {
362         ret[2] /= sh;
363         ret[3] /= sh;
364     } else {
365         ret[2] = 0.0;
366         ret[3] = 1.0;
367     }
369     /* fixme: Would be nice to preserve units here */
370     rect->width = rect->width.computed * sw;
371     rect->height = rect->height.computed * sh;
372     if (rect->rx._set) {
373         rect->rx = rect->rx.computed * sw;
374     }
375     if (rect->ry._set) {
376         rect->ry = rect->ry.computed * sh;
377     }
379     /* Find start in item coords */
380     pos = pos * ret.inverse();
381     rect->x = pos[NR::X];
382     rect->y = pos[NR::Y];
384     sp_rect_set_shape(rect);
386     // Adjust stroke width
387     sp_item_adjust_stroke(item, sqrt(fabs(sw * sh)));
389     // Adjust pattern fill
390     sp_item_adjust_pattern(item, xform / ret);
392     // Adjust gradient fill
393     sp_item_adjust_gradient(item, xform / ret);
395     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
397     return ret;
401 /**
402 Returns the ratio in which the vector from p0 to p1 is stretched by transform
403  */
404 static gdouble
405 vector_stretch(NR::Point p0, NR::Point p1, NR::Matrix xform)
407     if (p0 == p1)
408         return 0;
409     return (NR::distance(p0 * xform, p1 * xform) / NR::distance(p0, p1));
412 void
413 sp_rect_set_visible_rx(SPRect *rect, gdouble rx)
415     if (rx == 0) {
416         rect->rx.computed = 0;
417         rect->rx._set = false;
418     } else {
419         rect->rx.computed = rx / vector_stretch(
420             NR::Point(rect->x.computed + 1, rect->y.computed),
421             NR::Point(rect->x.computed, rect->y.computed),
422             SP_ITEM(rect)->transform);
423         rect->rx._set = true;
424     }
425     SP_OBJECT(rect)->updateRepr();
428 void
429 sp_rect_set_visible_ry(SPRect *rect, gdouble ry)
431     if (ry == 0) {
432         rect->ry.computed = 0;
433         rect->ry._set = false;
434     } else {
435         rect->ry.computed = ry / vector_stretch(
436             NR::Point(rect->x.computed, rect->y.computed + 1),
437             NR::Point(rect->x.computed, rect->y.computed),
438             SP_ITEM(rect)->transform);
439         rect->ry._set = true;
440     }
441     SP_OBJECT(rect)->updateRepr();
444 gdouble
445 sp_rect_get_visible_rx(SPRect *rect)
447     if (!rect->rx._set)
448         return 0;
449     return rect->rx.computed * vector_stretch(
450         NR::Point(rect->x.computed + 1, rect->y.computed),
451         NR::Point(rect->x.computed, rect->y.computed),
452         SP_ITEM(rect)->transform);
455 gdouble
456 sp_rect_get_visible_ry(SPRect *rect)
458     if (!rect->ry._set)
459         return 0;
460     return rect->ry.computed * vector_stretch(
461         NR::Point(rect->x.computed, rect->y.computed + 1),
462         NR::Point(rect->x.computed, rect->y.computed),
463         SP_ITEM(rect)->transform);
466 void
467 sp_rect_compensate_rxry(SPRect *rect, NR::Matrix xform)
469     if (rect->rx.computed == 0 && rect->ry.computed == 0)
470         return; // nothing to compensate
472     // test unit vectors to find out compensation:
473     NR::Point c(rect->x.computed, rect->y.computed);
474     NR::Point cx = c + NR::Point(1, 0);
475     NR::Point cy = c + NR::Point(0, 1);
477     // apply previous transform if any
478     c *= SP_ITEM(rect)->transform;
479     cx *= SP_ITEM(rect)->transform;
480     cy *= SP_ITEM(rect)->transform;
482     // find out stretches that we need to compensate
483     gdouble eX = vector_stretch(cx, c, xform);
484     gdouble eY = vector_stretch(cy, c, xform);
486     // If only one of the radii is set, set both radii so they have the same visible length
487     // This is needed because if we just set them the same length in SVG, they might end up unequal because of transform
488     if ((rect->rx._set && !rect->ry._set) || (rect->ry._set && !rect->rx._set)) {
489         gdouble r = MAX(rect->rx.computed, rect->ry.computed);
490         rect->rx.computed = r / eX;
491         rect->ry.computed = r / eY;
492     } else {
493         rect->rx.computed = rect->rx.computed / eX;
494         rect->ry.computed = rect->ry.computed / eY;
495     }
497     // Note that a radius may end up larger than half-side if the rect is scaled down;
498     // that's ok because this preserves the intended radii in case the rect is enlarged again,
499     // and set_shape will take care of trimming too large radii when generating d=
501     rect->rx._set = rect->ry._set = true;
504 void
505 sp_rect_set_visible_width(SPRect *rect, gdouble width)
507     rect->width.computed = width / vector_stretch(
508         NR::Point(rect->x.computed + 1, rect->y.computed),
509         NR::Point(rect->x.computed, rect->y.computed),
510         SP_ITEM(rect)->transform);
511     rect->width._set = true;
512     SP_OBJECT(rect)->updateRepr();
515 void
516 sp_rect_set_visible_height(SPRect *rect, gdouble height)
518     rect->height.computed = height / vector_stretch(
519         NR::Point(rect->x.computed, rect->y.computed + 1),
520         NR::Point(rect->x.computed, rect->y.computed),
521         SP_ITEM(rect)->transform);
522     rect->height._set = true;
523     SP_OBJECT(rect)->updateRepr();
526 gdouble
527 sp_rect_get_visible_width(SPRect *rect)
529     if (!rect->width._set)
530         return 0;
531     return rect->width.computed * vector_stretch(
532         NR::Point(rect->x.computed + 1, rect->y.computed),
533         NR::Point(rect->x.computed, rect->y.computed),
534         SP_ITEM(rect)->transform);
537 gdouble
538 sp_rect_get_visible_height(SPRect *rect)
540     if (!rect->height._set)
541         return 0;
542     return rect->height.computed * vector_stretch(
543         NR::Point(rect->x.computed, rect->y.computed + 1),
544         NR::Point(rect->x.computed, rect->y.computed),
545         SP_ITEM(rect)->transform);
548 /**
549  * Sets the snappoint p to the unrounded corners of the rectangle
550  */
551 static void sp_rect_snappoints(SPItem const *item, SnapPointsIter p)
553     /* This method overrides sp_shape_snappoints, which is the default for any shape. The default method
554     returns all eight points along the path of a rounded rectangle, but not the real corners. Snapping
555     the startpoint and endpoint of each rounded corner is not very usefull and really confusing. Instead 
556     we could snap either the real corners, or not snap at all. Bulia Byak opted to snap the real corners,
557     but it should be noted that this might be confusing in some cases with relatively large radii. With 
558     small radii though the user will easily understand which point is snapping. */
559     
560     g_assert(item != NULL);
561     g_assert(SP_IS_RECT(item));
563     SPRect *rect = SP_RECT(item);
565     NR::Matrix const i2d (sp_item_i2d_affine (item));
567     *p = NR::Point(rect->x.computed, rect->y.computed) * i2d;
568     *p = NR::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d;
569     *p = NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d;
570     *p = NR::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d;
573 void
574 sp_rect_convert_to_guides(SPItem *item) {
575     SPRect *rect = SP_RECT(item);
577     if (prefs_get_int_attribute("tools.shapes.rect", "convertguides", 1) == 0) {
578         sp_item_convert_to_guides(SP_ITEM(rect));
579         return;
580     }
582     SPDocument *doc = SP_OBJECT_DOCUMENT(rect);
583     std::list<std::pair<Geom::Point, Geom::Point> > pts;
585     NR::Matrix const i2d (sp_item_i2d_affine(SP_ITEM(rect)));
587     NR::Point A1(NR::Point(rect->x.computed, rect->y.computed) * i2d);
588     NR::Point A2(NR::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d);
589     NR::Point A3(NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d);
590     NR::Point A4(NR::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d);
592     pts.push_back(std::make_pair(A1.to_2geom(), A2.to_2geom()));
593     pts.push_back(std::make_pair(A2.to_2geom(), A3.to_2geom()));
594     pts.push_back(std::make_pair(A3.to_2geom(), A4.to_2geom()));
595     pts.push_back(std::make_pair(A4.to_2geom(), A1.to_2geom()));
597     sp_guide_pt_pairs_to_guides(doc, pts);
600 /*
601   Local Variables:
602   mode:c++
603   c-file-style:"stroustrup"
604   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
605   indent-tabs-mode:nil
606   fill-column:99
607   End:
608 */
609 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :