Code

Add possibility to convert objects (only rectangles and 3D boxes currently) to guidel...
[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"
33 #define noRECT_VERBOSE
35 static void sp_rect_class_init(SPRectClass *klass);
36 static void sp_rect_init(SPRect *rect);
38 static void sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
39 static void sp_rect_set(SPObject *object, unsigned key, gchar const *value);
40 static void sp_rect_update(SPObject *object, SPCtx *ctx, guint flags);
41 static Inkscape::XML::Node *sp_rect_write(SPObject *object, Inkscape::XML::Node *repr, guint flags);
43 static gchar *sp_rect_description(SPItem *item);
44 static NR::Matrix sp_rect_set_transform(SPItem *item, NR::Matrix const &xform);
46 static void sp_rect_set_shape(SPShape *shape);
47 static void sp_rect_snappoints(SPItem const *item, SnapPointsIter p);
49 static SPShapeClass *parent_class;
51 GType
52 sp_rect_get_type(void)
53 {
54     static GType type = 0;
56     if (!type) {
57         GTypeInfo info = {
58             sizeof(SPRectClass),
59             NULL,   /* base_init */
60             NULL,   /* base_finalize */
61             (GClassInitFunc) sp_rect_class_init,
62             NULL,   /* class_finalize */
63             NULL,   /* class_data */
64             sizeof(SPRect),
65             16,     /* n_preallocs */
66             (GInstanceInitFunc) sp_rect_init,
67             NULL,   /* value_table */
68         };
69         type = g_type_register_static(SP_TYPE_SHAPE, "SPRect", &info, (GTypeFlags)0);
70     }
71     return type;
72 }
74 static void
75 sp_rect_class_init(SPRectClass *klass)
76 {
77     SPObjectClass *sp_object_class = (SPObjectClass *) klass;
78     SPItemClass *item_class = (SPItemClass *) klass;
79     SPShapeClass *shape_class = (SPShapeClass *) klass;
81     parent_class = (SPShapeClass *)g_type_class_ref(SP_TYPE_SHAPE);
83     sp_object_class->build = sp_rect_build;
84     sp_object_class->write = sp_rect_write;
85     sp_object_class->set = sp_rect_set;
86     sp_object_class->update = sp_rect_update;
88     item_class->description = sp_rect_description;
89     item_class->set_transform = sp_rect_set_transform;
90     item_class->snappoints = sp_rect_snappoints; //override the default sp_shape_snappoints; see sp_rect_snappoints for details
92     shape_class->set_shape = sp_rect_set_shape;
93 }
95 static void
96 sp_rect_init(SPRect */*rect*/)
97 {
98     /* Initializing to zero is automatic */
99     /* sp_svg_length_unset(&rect->x, SP_SVG_UNIT_NONE, 0.0, 0.0); */
100     /* sp_svg_length_unset(&rect->y, SP_SVG_UNIT_NONE, 0.0, 0.0); */
101     /* sp_svg_length_unset(&rect->width, SP_SVG_UNIT_NONE, 0.0, 0.0); */
102     /* sp_svg_length_unset(&rect->height, SP_SVG_UNIT_NONE, 0.0, 0.0); */
103     /* sp_svg_length_unset(&rect->rx, SP_SVG_UNIT_NONE, 0.0, 0.0); */
104     /* sp_svg_length_unset(&rect->ry, SP_SVG_UNIT_NONE, 0.0, 0.0); */
107 static void
108 sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
110     SPRect *rect = SP_RECT(object);
112     if (((SPObjectClass *) parent_class)->build)
113         ((SPObjectClass *) parent_class)->build(object, document, repr);
115     sp_object_read_attr(object, "x");
116     sp_object_read_attr(object, "y");
117     sp_object_read_attr(object, "width");
118     sp_object_read_attr(object, "height");
119     sp_object_read_attr(object, "rx");
120     sp_object_read_attr(object, "ry");
122     Inkscape::Version const version = sp_object_get_sodipodi_version(object);
124     if ( version.major == 0 && version.minor == 29 ) {
125         if (rect->rx._set && rect->ry._set) {
126             /* 0.29 treated 0.0 radius as missing value */
127             if ((rect->rx.value != 0.0) && (rect->ry.value == 0.0)) {
128                 repr->setAttribute("ry", NULL);
129                 sp_object_read_attr(object, "ry");
130             } else if ((rect->ry.value != 0.0) && (rect->rx.value == 0.0)) {
131                 repr->setAttribute("rx", NULL);
132                 sp_object_read_attr(object, "rx");
133             }
134         }
135     }
138 static void
139 sp_rect_set(SPObject *object, unsigned key, gchar const *value)
141     SPRect *rect = SP_RECT(object);
143     /* fixme: We need real error processing some time */
145     switch (key) {
146         case SP_ATTR_X:
147             rect->x.readOrUnset(value);
148             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
149             break;
150         case SP_ATTR_Y:
151             rect->y.readOrUnset(value);
152             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
153             break;
154         case SP_ATTR_WIDTH:
155             if (!rect->width.read(value) || rect->width.value < 0.0) {
156                 rect->width.unset();
157             }
158             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
159             break;
160         case SP_ATTR_HEIGHT:
161             if (!rect->height.read(value) || rect->height.value < 0.0) {
162                 rect->height.unset();
163             }
164             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
165             break;
166         case SP_ATTR_RX:
167             if (!rect->rx.read(value) || rect->rx.value < 0.0) {
168                 rect->rx.unset();
169             }
170             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
171             break;
172         case SP_ATTR_RY:
173             if (!rect->ry.read(value) || rect->ry.value < 0.0) {
174                 rect->ry.unset();
175             }
176             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
177             break;
178         default:
179             if (((SPObjectClass *) parent_class)->set)
180                 ((SPObjectClass *) parent_class)->set(object, key, value);
181             break;
182     }
185 static void
186 sp_rect_update(SPObject *object, SPCtx *ctx, guint flags)
188     if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
189         SPRect *rect = (SPRect *) object;
190         SPStyle *style = object->style;
191         SPItemCtx const *ictx = (SPItemCtx const *) ctx;
192         double const d = NR::expansion(ictx->i2vp);
193         double const w = (ictx->vp.x1 - ictx->vp.x0) / d;
194         double const h = (ictx->vp.y1 - ictx->vp.y0) / d;
195         double const em = style->font_size.computed;
196         double const ex = 0.5 * em;  // fixme: get x height from pango or libnrtype.
197         rect->x.update(em, ex, w);
198         rect->y.update(em, ex, h);
199         rect->width.update(em, ex, w);
200         rect->height.update(em, ex, h);
201         rect->rx.update(em, ex, w);
202         rect->ry.update(em, ex, h);
203         sp_shape_set_shape((SPShape *) object);
204         flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore
205     }
207     if (((SPObjectClass *) parent_class)->update)
208         ((SPObjectClass *) parent_class)->update(object, ctx, flags);
211 static Inkscape::XML::Node *
212 sp_rect_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
214     SPRect *rect = SP_RECT(object);
216     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
217         Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
218         repr = xml_doc->createElement("svg:rect");
219     }
221     sp_repr_set_svg_double(repr, "width", rect->width.computed);
222     sp_repr_set_svg_double(repr, "height", rect->height.computed);
223     if (rect->rx._set) sp_repr_set_svg_double(repr, "rx", rect->rx.computed);
224     if (rect->ry._set) sp_repr_set_svg_double(repr, "ry", rect->ry.computed);
225     sp_repr_set_svg_double(repr, "x", rect->x.computed);
226     sp_repr_set_svg_double(repr, "y", rect->y.computed);
228     if (((SPObjectClass *) parent_class)->write)
229         ((SPObjectClass *) parent_class)->write(object, repr, flags);
231     return repr;
234 static gchar *
235 sp_rect_description(SPItem *item)
237     g_return_val_if_fail(SP_IS_RECT(item), NULL);
239     return g_strdup(_("<b>Rectangle</b>"));
242 #define C1 0.554
244 static void
245 sp_rect_set_shape(SPShape *shape)
247     SPRect *rect = (SPRect *) shape;
249     if ((rect->height.computed < 1e-18) || (rect->width.computed < 1e-18)) return;
251     SPCurve *c = sp_curve_new();
253     double const x = rect->x.computed;
254     double const y = rect->y.computed;
255     double const w = rect->width.computed;
256     double const h = rect->height.computed;
257     double const w2 = w / 2;
258     double const h2 = h / 2;
259     double const rx = std::min(( rect->rx._set
260                                  ? rect->rx.computed
261                                  : ( rect->ry._set
262                                      ? rect->ry.computed
263                                      : 0.0 ) ),
264                                .5 * rect->width.computed);
265     double const ry = std::min(( rect->ry._set
266                                  ? rect->ry.computed
267                                  : ( rect->rx._set
268                                      ? rect->rx.computed
269                                      : 0.0 ) ),
270                                .5 * rect->height.computed);
271     /* TODO: Handle negative rx or ry as per
272      * http://www.w3.org/TR/SVG11/shapes.html#RectElementRXAttribute once Inkscape has proper error
273      * handling (see http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing).
274      */
276     /* We don't use proper circular/elliptical arcs, but bezier curves can approximate a 90-degree
277      * arc fairly well.
278      */
279     if ((rx > 1e-18) && (ry > 1e-18)) {
280         sp_curve_moveto(c, x + rx, y);
281         if (rx < w2) sp_curve_lineto(c, x + w - rx, y);
282         sp_curve_curveto(c, x + w - rx * (1 - C1), y,     x + w, y + ry * (1 - C1),       x + w, y + ry);
283         if (ry < h2) sp_curve_lineto(c, x + w, y + h - ry);
284         sp_curve_curveto(c, x + w, y + h - ry * (1 - C1),     x + w - rx * (1 - C1), y + h,       x + w - rx, y + h);
285         if (rx < w2) sp_curve_lineto(c, x + rx, y + h);
286         sp_curve_curveto(c, x + rx * (1 - C1), y + h,     x, y + h - ry * (1 - C1),       x, y + h - ry);
287         if (ry < h2) sp_curve_lineto(c, x, y + ry);
288         sp_curve_curveto(c, x, y + ry * (1 - C1),     x + rx * (1 - C1), y,       x + rx, y);
289     } else {
290         sp_curve_moveto(c, x + 0.0, y + 0.0);
291         sp_curve_lineto(c, x + w, y + 0.0);
292         sp_curve_lineto(c, x + w, y + h);
293         sp_curve_lineto(c, x + 0.0, y + h);
294         sp_curve_lineto(c, x + 0.0, y + 0.0);
295     }
297     sp_curve_closepath_current(c);
298     sp_shape_set_curve_insync(SP_SHAPE(rect), c, TRUE);
299     sp_curve_unref(c);
302 /* fixme: Think (Lauris) */
304 void
305 sp_rect_position_set(SPRect *rect, gdouble x, gdouble y, gdouble width, gdouble height)
307     g_return_if_fail(rect != NULL);
308     g_return_if_fail(SP_IS_RECT(rect));
310     rect->x.computed = x;
311     rect->y.computed = y;
312     rect->width.computed = width;
313     rect->height.computed = height;
315     SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
318 void
319 sp_rect_set_rx(SPRect *rect, gboolean set, gdouble value)
321     g_return_if_fail(rect != NULL);
322     g_return_if_fail(SP_IS_RECT(rect));
324     rect->rx._set = set;
325     if (set) rect->rx.computed = value;
327     SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
330 void
331 sp_rect_set_ry(SPRect *rect, gboolean set, gdouble value)
333     g_return_if_fail(rect != NULL);
334     g_return_if_fail(SP_IS_RECT(rect));
336     rect->ry._set = set;
337     if (set) rect->ry.computed = value;
339     SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
342 /*
343  * Initially we'll do:
344  * Transform x, y, set x, y, clear translation
345  */
347 /* fixme: Use preferred units somehow (Lauris) */
348 /* fixme: Alternately preserve whatever units there are (lauris) */
350 static NR::Matrix
351 sp_rect_set_transform(SPItem *item, NR::Matrix const &xform)
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 void
583 sp_rect_convert_to_guides(SPRect *rect, bool write_undo) {
584     SPDocument *doc = SP_OBJECT_DOCUMENT(rect);
585     std::list<std::pair<Geom::Point, Geom::Point> > pts;
587     NR::Matrix const i2d (sp_item_i2d_affine(SP_ITEM(rect)));
589     NR::Point A1(NR::Point(rect->x.computed, rect->y.computed) * i2d);
590     NR::Point A2(NR::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d);
591     NR::Point A3(NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d);
592     NR::Point A4(NR::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d);
594     pts.push_back(std::make_pair(A1.to_2geom(), A2.to_2geom()));
595     pts.push_back(std::make_pair(A2.to_2geom(), A3.to_2geom()));
596     pts.push_back(std::make_pair(A3.to_2geom(), A4.to_2geom()));
597     pts.push_back(std::make_pair(A4.to_2geom(), A1.to_2geom()));
599     sp_guide_pt_pairs_to_guides(doc, pts);
601     SP_OBJECT(rect)->deleteObject(true);
603     if (write_undo) {
604         sp_document_done(doc, SP_VERB_CONTEXT_RECT, _("Convert to guides"));
605     }
608 /*
609   Local Variables:
610   mode:c++
611   c-file-style:"stroustrup"
612   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
613   indent-tabs-mode:nil
614   fill-column:99
615   End:
616 */
617 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :