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)) 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);
303 }
305 /* fixme: Think (Lauris) */
307 void
308 sp_rect_position_set(SPRect *rect, gdouble x, gdouble y, gdouble width, gdouble height)
309 {
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);
319 }
321 void
322 sp_rect_set_rx(SPRect *rect, gboolean set, gdouble value)
323 {
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);
331 }
333 void
334 sp_rect_set_ry(SPRect *rect, gboolean set, gdouble value)
335 {
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);
343 }
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)
355 {
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;
410 }
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)
418 {
419 if (p0 == p1)
420 return 0;
421 return (NR::distance(p0 * xform, p1 * xform) / NR::distance(p0, p1));
422 }
424 void
425 sp_rect_set_visible_rx(SPRect *rect, gdouble rx)
426 {
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();
438 }
440 void
441 sp_rect_set_visible_ry(SPRect *rect, gdouble ry)
442 {
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();
454 }
456 gdouble
457 sp_rect_get_visible_rx(SPRect *rect)
458 {
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);
465 }
467 gdouble
468 sp_rect_get_visible_ry(SPRect *rect)
469 {
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);
476 }
478 void
479 sp_rect_compensate_rxry(SPRect *rect, NR::Matrix xform)
480 {
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;
514 }
516 void
517 sp_rect_set_visible_width(SPRect *rect, gdouble width)
518 {
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();
525 }
527 void
528 sp_rect_set_visible_height(SPRect *rect, gdouble height)
529 {
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();
536 }
538 gdouble
539 sp_rect_get_visible_width(SPRect *rect)
540 {
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);
547 }
549 gdouble
550 sp_rect_get_visible_height(SPRect *rect)
551 {
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);
558 }
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)
564 {
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. */
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;
583 }
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);
612 }
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 :