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