Code

continue switching sp_repr_new* over to XML::Document::create*
[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);
47 static SPShapeClass *parent_class;
49 GType
50 sp_rect_get_type(void)
51 {
52     static GType type = 0;
54     if (!type) {
55         GTypeInfo info = {
56             sizeof(SPRectClass),
57             NULL,   /* base_init */
58             NULL,   /* base_finalize */
59             (GClassInitFunc) sp_rect_class_init,
60             NULL,   /* class_finalize */
61             NULL,   /* class_data */
62             sizeof(SPRect),
63             16,     /* n_preallocs */
64             (GInstanceInitFunc) sp_rect_init,
65             NULL,   /* value_table */
66         };
67         type = g_type_register_static(SP_TYPE_SHAPE, "SPRect", &info, (GTypeFlags)0);
68     }
69     return type;
70 }
72 static void
73 sp_rect_class_init(SPRectClass *klass)
74 {
75     SPObjectClass *sp_object_class = (SPObjectClass *) klass;
76     SPItemClass *item_class = (SPItemClass *) klass;
77     SPShapeClass *shape_class = (SPShapeClass *) klass;
79     parent_class = (SPShapeClass *)g_type_class_ref(SP_TYPE_SHAPE);
81     sp_object_class->build = sp_rect_build;
82     sp_object_class->write = sp_rect_write;
83     sp_object_class->set = sp_rect_set;
84     sp_object_class->update = sp_rect_update;
86     item_class->description = sp_rect_description;
87     item_class->set_transform = sp_rect_set_transform;
89     shape_class->set_shape = sp_rect_set_shape;
90 }
92 static void
93 sp_rect_init(SPRect *rect)
94 {
95     /* Initializing to zero is automatic */
96     /* sp_svg_length_unset(&rect->x, SP_SVG_UNIT_NONE, 0.0, 0.0); */
97     /* sp_svg_length_unset(&rect->y, SP_SVG_UNIT_NONE, 0.0, 0.0); */
98     /* sp_svg_length_unset(&rect->width, SP_SVG_UNIT_NONE, 0.0, 0.0); */
99     /* sp_svg_length_unset(&rect->height, SP_SVG_UNIT_NONE, 0.0, 0.0); */
100     /* sp_svg_length_unset(&rect->rx, SP_SVG_UNIT_NONE, 0.0, 0.0); */
101     /* sp_svg_length_unset(&rect->ry, SP_SVG_UNIT_NONE, 0.0, 0.0); */
104 static void
105 sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
107     SPRect *rect = SP_RECT(object);
109     if (((SPObjectClass *) parent_class)->build)
110         ((SPObjectClass *) parent_class)->build(object, document, repr);
112     sp_object_read_attr(object, "x");
113     sp_object_read_attr(object, "y");
114     sp_object_read_attr(object, "width");
115     sp_object_read_attr(object, "height");
116     sp_object_read_attr(object, "rx");
117     sp_object_read_attr(object, "ry");
119     Inkscape::Version const version = sp_object_get_sodipodi_version(object);
121     if ( version.major == 0 && version.minor == 29 ) {
122         if (rect->rx._set && rect->ry._set) {
123             /* 0.29 treated 0.0 radius as missing value */
124             if ((rect->rx.value != 0.0) && (rect->ry.value == 0.0)) {
125                 repr->setAttribute("ry", NULL);
126                 sp_object_read_attr(object, "ry");
127             } else if ((rect->ry.value != 0.0) && (rect->rx.value == 0.0)) {
128                 repr->setAttribute("rx", NULL);
129                 sp_object_read_attr(object, "rx");
130             }
131         }
132     }
135 static void
136 sp_rect_set(SPObject *object, unsigned key, gchar const *value)
138     SPRect *rect = SP_RECT(object);
140     /* fixme: We need real error processing some time */
142     switch (key) {
143         case SP_ATTR_X:
144             rect->x.readOrUnset(value);
145             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
146             break;
147         case SP_ATTR_Y:
148             rect->y.readOrUnset(value);
149             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
150             break;
151         case SP_ATTR_WIDTH:
152             if (!rect->width.read(value) || rect->width.value < 0.0) {
153                 rect->width.unset();
154             }
155             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
156             break;
157         case SP_ATTR_HEIGHT:
158             if (!rect->height.read(value) || rect->height.value < 0.0) {
159                 rect->height.unset();
160             }
161             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
162             break;
163         case SP_ATTR_RX:
164             if (!rect->rx.read(value) || rect->rx.value < 0.0) {
165                 rect->rx.unset();
166             }
167             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
168             break;
169         case SP_ATTR_RY:
170             if (!rect->ry.read(value) || rect->ry.value < 0.0) {
171                 rect->ry.unset();
172             }
173             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
174             break;
175         default:
176             if (((SPObjectClass *) parent_class)->set)
177                 ((SPObjectClass *) parent_class)->set(object, key, value);
178             break;
179     }
182 static void
183 sp_rect_update(SPObject *object, SPCtx *ctx, guint flags)
185     if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
186         SPRect *rect = (SPRect *) object;
187         SPStyle *style = object->style;
188         SPItemCtx const *ictx = (SPItemCtx const *) ctx;
189         double const d = NR::expansion(ictx->i2vp);
190         double const w = (ictx->vp.x1 - ictx->vp.x0) / d;
191         double const h = (ictx->vp.y1 - ictx->vp.y0) / d;
192         double const em = style->font_size.computed;
193         double const ex = 0.5 * em;  // fixme: get x height from pango or libnrtype.
194         rect->x.update(em, ex, w);
195         rect->y.update(em, ex, h);
196         rect->width.update(em, ex, w);
197         rect->height.update(em, ex, h);
198         rect->rx.update(em, ex, w);
199         rect->ry.update(em, ex, h);
200         sp_shape_set_shape((SPShape *) object);
201         flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore
202     }
204     if (((SPObjectClass *) parent_class)->update)
205         ((SPObjectClass *) parent_class)->update(object, ctx, flags);
208 static Inkscape::XML::Node *
209 sp_rect_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
211     SPRect *rect = SP_RECT(object);
213     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
214         Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
215         repr = xml_doc->createElement("svg:rect");
216     }
218     sp_repr_set_svg_double(repr, "width", rect->width.computed);
219     sp_repr_set_svg_double(repr, "height", rect->height.computed);
220     if (rect->rx._set) sp_repr_set_svg_double(repr, "rx", rect->rx.computed);
221     if (rect->ry._set) sp_repr_set_svg_double(repr, "ry", rect->ry.computed);
222     sp_repr_set_svg_double(repr, "x", rect->x.computed);
223     sp_repr_set_svg_double(repr, "y", rect->y.computed);
225     if (((SPObjectClass *) parent_class)->write)
226         ((SPObjectClass *) parent_class)->write(object, repr, flags);
228     return repr;
231 static gchar *
232 sp_rect_description(SPItem *item)
234     g_return_val_if_fail(SP_IS_RECT(item), NULL);
236     return g_strdup(_("<b>Rectangle</b>"));
239 #define C1 0.554
241 static void
242 sp_rect_set_shape(SPShape *shape)
244     SPRect *rect = (SPRect *) shape;
246     if ((rect->height.computed < 1e-18) || (rect->width.computed < 1e-18)) return;
248     SPCurve *c = sp_curve_new();
250     double const x = rect->x.computed;
251     double const y = rect->y.computed;
252     double const w = rect->width.computed;
253     double const h = rect->height.computed;
254     double const w2 = w / 2;
255     double const h2 = h / 2;
256     double const rx = std::min(( rect->rx._set
257                                  ? rect->rx.computed
258                                  : ( rect->ry._set
259                                      ? rect->ry.computed
260                                      : 0.0 ) ),
261                                .5 * rect->width.computed);
262     double const ry = std::min(( rect->ry._set
263                                  ? rect->ry.computed
264                                  : ( rect->rx._set
265                                      ? rect->rx.computed
266                                      : 0.0 ) ),
267                                .5 * rect->height.computed);
268     /* TODO: Handle negative rx or ry as per
269      * http://www.w3.org/TR/SVG11/shapes.html#RectElementRXAttribute once Inkscape has proper error
270      * handling (see http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing).
271      */
273     /* We don't use proper circular/elliptical arcs, but bezier curves can approximate a 90-degree
274      * arc fairly well.
275      */
276     if ((rx > 1e-18) && (ry > 1e-18)) {
277         sp_curve_moveto(c, x + rx, y);
278         if (rx < w2) sp_curve_lineto(c, x + w - rx, y);
279         sp_curve_curveto(c, x + w - rx * (1 - C1), y,     x + w, y + ry * (1 - C1),       x + w, y + ry);
280         if (ry < h2) sp_curve_lineto(c, x + w, y + h - ry);
281         sp_curve_curveto(c, x + w, y + h - ry * (1 - C1),     x + w - rx * (1 - C1), y + h,       x + w - rx, y + h);
282         if (rx < w2) sp_curve_lineto(c, x + rx, y + h);
283         sp_curve_curveto(c, x + rx * (1 - C1), y + h,     x, y + h - ry * (1 - C1),       x, y + h - ry);
284         if (ry < h2) sp_curve_lineto(c, x, y + ry);
285         sp_curve_curveto(c, x, y + ry * (1 - C1),     x + rx * (1 - C1), y,       x + rx, y);
286     } else {
287         sp_curve_moveto(c, x + 0.0, y + 0.0);
288         sp_curve_lineto(c, x + w, y + 0.0);
289         sp_curve_lineto(c, x + w, y + h);
290         sp_curve_lineto(c, x + 0.0, y + h);
291         sp_curve_lineto(c, x + 0.0, y + 0.0);
292     }
294     sp_curve_closepath_current(c);
295     sp_shape_set_curve_insync(SP_SHAPE(rect), c, TRUE);
296     sp_curve_unref(c);
299 /* fixme: Think (Lauris) */
301 void
302 sp_rect_position_set(SPRect *rect, gdouble x, gdouble y, gdouble width, gdouble height)
304     g_return_if_fail(rect != NULL);
305     g_return_if_fail(SP_IS_RECT(rect));
307     rect->x.computed = x;
308     rect->y.computed = y;
309     rect->width.computed = width;
310     rect->height.computed = height;
312     SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
315 void
316 sp_rect_set_rx(SPRect *rect, gboolean set, gdouble value)
318     g_return_if_fail(rect != NULL);
319     g_return_if_fail(SP_IS_RECT(rect));
321     rect->rx._set = set;
322     if (set) rect->rx.computed = value;
324     SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
327 void
328 sp_rect_set_ry(SPRect *rect, gboolean set, gdouble value)
330     g_return_if_fail(rect != NULL);
331     g_return_if_fail(SP_IS_RECT(rect));
333     rect->ry._set = set;
334     if (set) rect->ry.computed = value;
336     SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
339 /*
340  * Initially we'll do:
341  * Transform x, y, set x, y, clear translation
342  */
344 /* fixme: Use preferred units somehow (Lauris) */
345 /* fixme: Alternately preserve whatever units there are (lauris) */
347 static NR::Matrix
348 sp_rect_set_transform(SPItem *item, NR::Matrix const &xform)
350     SPRect *rect = SP_RECT(item);
352     /* Calculate rect start in parent coords. */
353     NR::Point pos( NR::Point(rect->x.computed, rect->y.computed) * xform );
355     /* This function takes care of translation and scaling, we return whatever parts we can't
356        handle. */
357     NR::Matrix ret(NR::transform(xform));
358     gdouble const sw = hypot(ret[0], ret[1]);
359     gdouble const sh = hypot(ret[2], ret[3]);
360     if (sw > 1e-9) {
361         ret[0] /= sw;
362         ret[1] /= sw;
363     } else {
364         ret[0] = 1.0;
365         ret[1] = 0.0;
366     }
367     if (sh > 1e-9) {
368         ret[2] /= sh;
369         ret[3] /= sh;
370     } else {
371         ret[2] = 0.0;
372         ret[3] = 1.0;
373     }
375     /* fixme: Would be nice to preserve units here */
376     rect->width = rect->width.computed * sw;
377     rect->height = rect->height.computed * sh;
378     if (rect->rx._set) {
379         rect->rx = rect->rx.computed * sw;
380     }
381     if (rect->ry._set) {
382         rect->ry = rect->ry.computed * sh;
383     }
385     /* Find start in item coords */
386     pos = pos * ret.inverse();
387     rect->x = pos[NR::X];
388     rect->y = pos[NR::Y];
390     sp_rect_set_shape(rect);
392     // Adjust stroke width
393     sp_item_adjust_stroke(item, sqrt(fabs(sw * sh)));
395     // Adjust pattern fill
396     sp_item_adjust_pattern(item, xform / ret);
398     // Adjust gradient fill
399     sp_item_adjust_gradient(item, xform / ret);
401     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
403     return ret;
407 /**
408 Returns the ratio in which the vector from p0 to p1 is stretched by transform
409  */
410 static gdouble
411 vector_stretch(NR::Point p0, NR::Point p1, NR::Matrix xform)
413     if (p0 == p1)
414         return 0;
415     return (NR::distance(p0 * xform, p1 * xform) / NR::distance(p0, p1));
418 void
419 sp_rect_set_visible_rx(SPRect *rect, gdouble rx)
421     if (rx == 0) {
422         rect->rx.computed = 0;
423         rect->rx._set = false;
424     } else {
425         rect->rx.computed = rx / vector_stretch(
426             NR::Point(rect->x.computed + 1, rect->y.computed),
427             NR::Point(rect->x.computed, rect->y.computed),
428             SP_ITEM(rect)->transform);
429         rect->rx._set = true;
430     }
431     SP_OBJECT(rect)->updateRepr();
434 void
435 sp_rect_set_visible_ry(SPRect *rect, gdouble ry)
437     if (ry == 0) {
438         rect->ry.computed = 0;
439         rect->ry._set = false;
440     } else {
441         rect->ry.computed = ry / vector_stretch(
442             NR::Point(rect->x.computed, rect->y.computed + 1),
443             NR::Point(rect->x.computed, rect->y.computed),
444             SP_ITEM(rect)->transform);
445         rect->ry._set = true;
446     }
447     SP_OBJECT(rect)->updateRepr();
450 gdouble
451 sp_rect_get_visible_rx(SPRect *rect)
453     if (!rect->rx._set)
454         return 0;
455     return rect->rx.computed * vector_stretch(
456         NR::Point(rect->x.computed + 1, rect->y.computed),
457         NR::Point(rect->x.computed, rect->y.computed),
458         SP_ITEM(rect)->transform);
461 gdouble
462 sp_rect_get_visible_ry(SPRect *rect)
464     if (!rect->ry._set)
465         return 0;
466     return rect->ry.computed * vector_stretch(
467         NR::Point(rect->x.computed, rect->y.computed + 1),
468         NR::Point(rect->x.computed, rect->y.computed),
469         SP_ITEM(rect)->transform);
472 void
473 sp_rect_compensate_rxry(SPRect *rect, NR::Matrix xform)
475     if (rect->rx.computed == 0 && rect->ry.computed == 0)
476         return; // nothing to compensate
478     // test unit vectors to find out compensation:
479     NR::Point c(rect->x.computed, rect->y.computed);
480     NR::Point cx = c + NR::Point(1, 0);
481     NR::Point cy = c + NR::Point(0, 1);
483     // apply previous transform if any
484     c *= SP_ITEM(rect)->transform;
485     cx *= SP_ITEM(rect)->transform;
486     cy *= SP_ITEM(rect)->transform;
488     // find out stretches that we need to compensate
489     gdouble eX = vector_stretch(cx, c, xform);
490     gdouble eY = vector_stretch(cy, c, xform);
492     // If only one of the radii is set, set both radii so they have the same visible length
493     // This is needed because if we just set them the same length in SVG, they might end up unequal because of transform
494     if ((rect->rx._set && !rect->ry._set) || (rect->ry._set && !rect->rx._set)) {
495         gdouble r = MAX(rect->rx.computed, rect->ry.computed);
496         rect->rx.computed = r / eX;
497         rect->ry.computed = r / eY;
498     } else {
499         rect->rx.computed = rect->rx.computed / eX;
500         rect->ry.computed = rect->ry.computed / eY;
501     }
503     // Note that a radius may end up larger than half-side if the rect is scaled down;
504     // that's ok because this preserves the intended radii in case the rect is enlarged again,
505     // and set_shape will take care of trimming too large radii when generating d=
507     rect->rx._set = rect->ry._set = true;
510 void
511 sp_rect_set_visible_width(SPRect *rect, gdouble width)
513     rect->width.computed = width / vector_stretch(
514         NR::Point(rect->x.computed + 1, rect->y.computed),
515         NR::Point(rect->x.computed, rect->y.computed),
516         SP_ITEM(rect)->transform);
517     rect->width._set = true;
518     SP_OBJECT(rect)->updateRepr();
521 void
522 sp_rect_set_visible_height(SPRect *rect, gdouble height)
524     rect->height.computed = height / vector_stretch(
525         NR::Point(rect->x.computed, rect->y.computed + 1),
526         NR::Point(rect->x.computed, rect->y.computed),
527         SP_ITEM(rect)->transform);
528     rect->height._set = true;
529     SP_OBJECT(rect)->updateRepr();
532 gdouble
533 sp_rect_get_visible_width(SPRect *rect)
535     if (!rect->width._set)
536         return 0;
537     return rect->width.computed * vector_stretch(
538         NR::Point(rect->x.computed + 1, rect->y.computed),
539         NR::Point(rect->x.computed, rect->y.computed),
540         SP_ITEM(rect)->transform);
543 gdouble
544 sp_rect_get_visible_height(SPRect *rect)
546     if (!rect->height._set)
547         return 0;
548     return rect->height.computed * vector_stretch(
549         NR::Point(rect->x.computed, rect->y.computed + 1),
550         NR::Point(rect->x.computed, rect->y.computed),
551         SP_ITEM(rect)->transform);
554 /*
555   Local Variables:
556   mode:c++
557   c-file-style:"stroustrup"
558   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
559   indent-tabs-mode:nil
560   fill-column:99
561   End:
562 */
563 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :