1 /*
2 * SVG <rect> implementation
3 *
4 * Authors:
5 * Lauris Kaplinski <lauris@kaplinski.com>
6 * bulia byak <buliabyak@users.sf.net>
7 *
8 * Copyright (C) 1999-2002 Lauris Kaplinski
9 * Copyright (C) 2000-2001 Ximian, Inc.
10 *
11 * Released under GNU GPL, read the file 'COPYING' for more information
12 */
14 #ifdef HAVE_CONFIG_H
15 # include "config.h"
16 #endif
19 #include <display/curve.h>
20 #include <libnr/nr-matrix-ops.h>
21 #include <libnr/nr-matrix-fns.h>
23 #include "inkscape.h"
24 #include "document.h"
25 #include "attributes.h"
26 #include "style.h"
27 #include "sp-rect.h"
28 #include <glibmm/i18n.h>
29 #include "xml/repr.h"
30 #include "sp-guide.h"
31 #include "preferences.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::Document *doc, Inkscape::XML::Node *repr, guint flags);
43 static gchar *sp_rect_description(SPItem *item);
44 static Geom::Matrix sp_rect_set_transform(SPItem *item, Geom::Matrix const &xform);
45 static void sp_rect_convert_to_guides(SPItem *item);
47 static void sp_rect_set_shape(SPShape *shape);
48 static void sp_rect_snappoints(SPItem const *item, SnapPointsIter p, Inkscape::SnapPreferences const *snapprefs);
50 static SPShapeClass *parent_class;
52 GType
53 sp_rect_get_type(void)
54 {
55 static GType type = 0;
57 if (!type) {
58 GTypeInfo info = {
59 sizeof(SPRectClass),
60 NULL, /* base_init */
61 NULL, /* base_finalize */
62 (GClassInitFunc) sp_rect_class_init,
63 NULL, /* class_finalize */
64 NULL, /* class_data */
65 sizeof(SPRect),
66 16, /* n_preallocs */
67 (GInstanceInitFunc) sp_rect_init,
68 NULL, /* value_table */
69 };
70 type = g_type_register_static(SP_TYPE_SHAPE, "SPRect", &info, (GTypeFlags)0);
71 }
72 return type;
73 }
75 static void
76 sp_rect_class_init(SPRectClass *klass)
77 {
78 SPObjectClass *sp_object_class = (SPObjectClass *) klass;
79 SPItemClass *item_class = (SPItemClass *) klass;
80 SPShapeClass *shape_class = (SPShapeClass *) klass;
82 parent_class = (SPShapeClass *)g_type_class_ref(SP_TYPE_SHAPE);
84 sp_object_class->build = sp_rect_build;
85 sp_object_class->write = sp_rect_write;
86 sp_object_class->set = sp_rect_set;
87 sp_object_class->update = sp_rect_update;
89 item_class->description = sp_rect_description;
90 item_class->set_transform = sp_rect_set_transform;
91 item_class->convert_to_guides = sp_rect_convert_to_guides;
92 item_class->snappoints = sp_rect_snappoints; //override the default sp_shape_snappoints; see sp_rect_snappoints for details
94 shape_class->set_shape = sp_rect_set_shape;
95 }
97 static void
98 sp_rect_init(SPRect */*rect*/)
99 {
100 /* Initializing to zero is automatic */
101 /* sp_svg_length_unset(&rect->x, SP_SVG_UNIT_NONE, 0.0, 0.0); */
102 /* sp_svg_length_unset(&rect->y, SP_SVG_UNIT_NONE, 0.0, 0.0); */
103 /* sp_svg_length_unset(&rect->width, SP_SVG_UNIT_NONE, 0.0, 0.0); */
104 /* sp_svg_length_unset(&rect->height, SP_SVG_UNIT_NONE, 0.0, 0.0); */
105 /* sp_svg_length_unset(&rect->rx, SP_SVG_UNIT_NONE, 0.0, 0.0); */
106 /* sp_svg_length_unset(&rect->ry, SP_SVG_UNIT_NONE, 0.0, 0.0); */
107 }
109 static void
110 sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
111 {
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");
121 }
123 static void
124 sp_rect_set(SPObject *object, unsigned key, gchar const *value)
125 {
126 SPRect *rect = SP_RECT(object);
128 /* fixme: We need real error processing some time */
130 switch (key) {
131 case SP_ATTR_X:
132 rect->x.readOrUnset(value);
133 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
134 break;
135 case SP_ATTR_Y:
136 rect->y.readOrUnset(value);
137 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
138 break;
139 case SP_ATTR_WIDTH:
140 if (!rect->width.read(value) || rect->width.value < 0.0) {
141 rect->width.unset();
142 }
143 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
144 break;
145 case SP_ATTR_HEIGHT:
146 if (!rect->height.read(value) || rect->height.value < 0.0) {
147 rect->height.unset();
148 }
149 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
150 break;
151 case SP_ATTR_RX:
152 if (!rect->rx.read(value) || rect->rx.value < 0.0) {
153 rect->rx.unset();
154 }
155 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
156 break;
157 case SP_ATTR_RY:
158 if (!rect->ry.read(value) || rect->ry.value < 0.0) {
159 rect->ry.unset();
160 }
161 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
162 break;
163 default:
164 if (((SPObjectClass *) parent_class)->set)
165 ((SPObjectClass *) parent_class)->set(object, key, value);
166 break;
167 }
168 }
170 static void
171 sp_rect_update(SPObject *object, SPCtx *ctx, guint flags)
172 {
173 if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
174 SPRect *rect = (SPRect *) object;
175 SPStyle *style = object->style;
176 SPItemCtx const *ictx = (SPItemCtx const *) ctx;
177 double const d = ictx->i2vp.descrim();
178 double const w = (ictx->vp.x1 - ictx->vp.x0) / d;
179 double const h = (ictx->vp.y1 - ictx->vp.y0) / d;
180 double const em = style->font_size.computed;
181 double const ex = 0.5 * em; // fixme: get x height from pango or libnrtype.
182 rect->x.update(em, ex, w);
183 rect->y.update(em, ex, h);
184 rect->width.update(em, ex, w);
185 rect->height.update(em, ex, h);
186 rect->rx.update(em, ex, w);
187 rect->ry.update(em, ex, h);
188 sp_shape_set_shape((SPShape *) object);
189 flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore
190 }
192 if (((SPObjectClass *) parent_class)->update)
193 ((SPObjectClass *) parent_class)->update(object, ctx, flags);
194 }
196 static Inkscape::XML::Node *
197 sp_rect_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
198 {
199 SPRect *rect = SP_RECT(object);
201 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
202 repr = xml_doc->createElement("svg:rect");
203 }
205 sp_repr_set_svg_double(repr, "width", rect->width.computed);
206 sp_repr_set_svg_double(repr, "height", rect->height.computed);
207 if (rect->rx._set) sp_repr_set_svg_double(repr, "rx", rect->rx.computed);
208 if (rect->ry._set) sp_repr_set_svg_double(repr, "ry", rect->ry.computed);
209 sp_repr_set_svg_double(repr, "x", rect->x.computed);
210 sp_repr_set_svg_double(repr, "y", rect->y.computed);
212 if (((SPObjectClass *) parent_class)->write)
213 ((SPObjectClass *) parent_class)->write(object, xml_doc, repr, flags);
215 return repr;
216 }
218 static gchar *
219 sp_rect_description(SPItem *item)
220 {
221 g_return_val_if_fail(SP_IS_RECT(item), NULL);
223 return g_strdup(_("<b>Rectangle</b>"));
224 }
226 #define C1 0.554
228 static void
229 sp_rect_set_shape(SPShape *shape)
230 {
231 SPRect *rect = (SPRect *) shape;
233 if ((rect->height.computed < 1e-18) || (rect->width.computed < 1e-18)) {
234 sp_shape_set_curve_insync(SP_SHAPE(rect), NULL, TRUE);
235 return;
236 }
238 SPCurve *c = new SPCurve();
240 double const x = rect->x.computed;
241 double const y = rect->y.computed;
242 double const w = rect->width.computed;
243 double const h = rect->height.computed;
244 double const w2 = w / 2;
245 double const h2 = h / 2;
246 double const rx = std::min(( rect->rx._set
247 ? rect->rx.computed
248 : ( rect->ry._set
249 ? rect->ry.computed
250 : 0.0 ) ),
251 .5 * rect->width.computed);
252 double const ry = std::min(( rect->ry._set
253 ? rect->ry.computed
254 : ( rect->rx._set
255 ? rect->rx.computed
256 : 0.0 ) ),
257 .5 * rect->height.computed);
258 /* TODO: Handle negative rx or ry as per
259 * http://www.w3.org/TR/SVG11/shapes.html#RectElementRXAttribute once Inkscape has proper error
260 * handling (see http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing).
261 */
263 /* We don't use proper circular/elliptical arcs, but bezier curves can approximate a 90-degree
264 * arc fairly well.
265 */
266 if ((rx > 1e-18) && (ry > 1e-18)) {
267 c->moveto(x + rx, y);
268 if (rx < w2) c->lineto(x + w - rx, y);
269 c->curveto(x + w - rx * (1 - C1), y, x + w, y + ry * (1 - C1), x + w, y + ry);
270 if (ry < h2) c->lineto(x + w, y + h - ry);
271 c->curveto(x + w, y + h - ry * (1 - C1), x + w - rx * (1 - C1), y + h, x + w - rx, y + h);
272 if (rx < w2) c->lineto(x + rx, y + h);
273 c->curveto(x + rx * (1 - C1), y + h, x, y + h - ry * (1 - C1), x, y + h - ry);
274 if (ry < h2) c->lineto(x, y + ry);
275 c->curveto(x, y + ry * (1 - C1), x + rx * (1 - C1), y, x + rx, y);
276 } else {
277 c->moveto(x + 0.0, y + 0.0);
278 c->lineto(x + w, y + 0.0);
279 c->lineto(x + w, y + h);
280 c->lineto(x + 0.0, y + h);
281 c->lineto(x + 0.0, y + 0.0);
282 }
284 c->closepath_current();
285 sp_shape_set_curve_insync(SP_SHAPE(rect), c, TRUE);
286 c->unref();
287 }
289 /* fixme: Think (Lauris) */
291 void
292 sp_rect_position_set(SPRect *rect, gdouble x, gdouble y, gdouble width, gdouble height)
293 {
294 g_return_if_fail(rect != NULL);
295 g_return_if_fail(SP_IS_RECT(rect));
297 rect->x.computed = x;
298 rect->y.computed = y;
299 rect->width.computed = width;
300 rect->height.computed = height;
302 SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
303 }
305 void
306 sp_rect_set_rx(SPRect *rect, gboolean set, gdouble value)
307 {
308 g_return_if_fail(rect != NULL);
309 g_return_if_fail(SP_IS_RECT(rect));
311 rect->rx._set = set;
312 if (set) rect->rx.computed = value;
314 SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
315 }
317 void
318 sp_rect_set_ry(SPRect *rect, gboolean set, gdouble value)
319 {
320 g_return_if_fail(rect != NULL);
321 g_return_if_fail(SP_IS_RECT(rect));
323 rect->ry._set = set;
324 if (set) rect->ry.computed = value;
326 SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
327 }
329 /*
330 * Initially we'll do:
331 * Transform x, y, set x, y, clear translation
332 */
334 /* fixme: Use preferred units somehow (Lauris) */
335 /* fixme: Alternately preserve whatever units there are (lauris) */
337 static Geom::Matrix
338 sp_rect_set_transform(SPItem *item, Geom::Matrix const &xform)
339 {
340 SPRect *rect = SP_RECT(item);
342 /* Calculate rect start in parent coords. */
343 Geom::Point pos( Geom::Point(rect->x.computed, rect->y.computed) * xform );
345 /* This function takes care of translation and scaling, we return whatever parts we can't
346 handle. */
347 Geom::Matrix ret(Geom::Matrix(xform).without_translation());
348 gdouble const sw = hypot(ret[0], ret[1]);
349 gdouble const sh = hypot(ret[2], ret[3]);
350 if (sw > 1e-9) {
351 ret[0] /= sw;
352 ret[1] /= sw;
353 } else {
354 ret[0] = 1.0;
355 ret[1] = 0.0;
356 }
357 if (sh > 1e-9) {
358 ret[2] /= sh;
359 ret[3] /= sh;
360 } else {
361 ret[2] = 0.0;
362 ret[3] = 1.0;
363 }
365 /* fixme: Would be nice to preserve units here */
366 rect->width = rect->width.computed * sw;
367 rect->height = rect->height.computed * sh;
368 if (rect->rx._set) {
369 rect->rx = rect->rx.computed * sw;
370 }
371 if (rect->ry._set) {
372 rect->ry = rect->ry.computed * sh;
373 }
375 /* Find start in item coords */
376 pos = pos * ret.inverse();
377 rect->x = pos[Geom::X];
378 rect->y = pos[Geom::Y];
380 sp_rect_set_shape(rect);
382 // Adjust stroke width
383 sp_item_adjust_stroke(item, sqrt(fabs(sw * sh)));
385 // Adjust pattern fill
386 sp_item_adjust_pattern(item, xform * ret.inverse());
388 // Adjust gradient fill
389 sp_item_adjust_gradient(item, xform * ret.inverse());
391 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
393 return ret;
394 }
397 /**
398 Returns the ratio in which the vector from p0 to p1 is stretched by transform
399 */
400 static gdouble
401 vector_stretch(Geom::Point p0, Geom::Point p1, Geom::Matrix xform)
402 {
403 if (p0 == p1)
404 return 0;
405 return (Geom::distance(p0 * xform, p1 * xform) / Geom::distance(p0, p1));
406 }
408 void
409 sp_rect_set_visible_rx(SPRect *rect, gdouble rx)
410 {
411 if (rx == 0) {
412 rect->rx.computed = 0;
413 rect->rx._set = false;
414 } else {
415 rect->rx.computed = rx / vector_stretch(
416 Geom::Point(rect->x.computed + 1, rect->y.computed),
417 Geom::Point(rect->x.computed, rect->y.computed),
418 SP_ITEM(rect)->transform);
419 rect->rx._set = true;
420 }
421 SP_OBJECT(rect)->updateRepr();
422 }
424 void
425 sp_rect_set_visible_ry(SPRect *rect, gdouble ry)
426 {
427 if (ry == 0) {
428 rect->ry.computed = 0;
429 rect->ry._set = false;
430 } else {
431 rect->ry.computed = ry / vector_stretch(
432 Geom::Point(rect->x.computed, rect->y.computed + 1),
433 Geom::Point(rect->x.computed, rect->y.computed),
434 SP_ITEM(rect)->transform);
435 rect->ry._set = true;
436 }
437 SP_OBJECT(rect)->updateRepr();
438 }
440 gdouble
441 sp_rect_get_visible_rx(SPRect *rect)
442 {
443 if (!rect->rx._set)
444 return 0;
445 return rect->rx.computed * vector_stretch(
446 Geom::Point(rect->x.computed + 1, rect->y.computed),
447 Geom::Point(rect->x.computed, rect->y.computed),
448 SP_ITEM(rect)->transform);
449 }
451 gdouble
452 sp_rect_get_visible_ry(SPRect *rect)
453 {
454 if (!rect->ry._set)
455 return 0;
456 return rect->ry.computed * vector_stretch(
457 Geom::Point(rect->x.computed, rect->y.computed + 1),
458 Geom::Point(rect->x.computed, rect->y.computed),
459 SP_ITEM(rect)->transform);
460 }
462 void
463 sp_rect_compensate_rxry(SPRect *rect, Geom::Matrix xform)
464 {
465 if (rect->rx.computed == 0 && rect->ry.computed == 0)
466 return; // nothing to compensate
468 // test unit vectors to find out compensation:
469 Geom::Point c(rect->x.computed, rect->y.computed);
470 Geom::Point cx = c + Geom::Point(1, 0);
471 Geom::Point cy = c + Geom::Point(0, 1);
473 // apply previous transform if any
474 c *= SP_ITEM(rect)->transform;
475 cx *= SP_ITEM(rect)->transform;
476 cy *= SP_ITEM(rect)->transform;
478 // find out stretches that we need to compensate
479 gdouble eX = vector_stretch(cx, c, xform);
480 gdouble eY = vector_stretch(cy, c, xform);
482 // If only one of the radii is set, set both radii so they have the same visible length
483 // This is needed because if we just set them the same length in SVG, they might end up unequal because of transform
484 if ((rect->rx._set && !rect->ry._set) || (rect->ry._set && !rect->rx._set)) {
485 gdouble r = MAX(rect->rx.computed, rect->ry.computed);
486 rect->rx.computed = r / eX;
487 rect->ry.computed = r / eY;
488 } else {
489 rect->rx.computed = rect->rx.computed / eX;
490 rect->ry.computed = rect->ry.computed / eY;
491 }
493 // Note that a radius may end up larger than half-side if the rect is scaled down;
494 // that's ok because this preserves the intended radii in case the rect is enlarged again,
495 // and set_shape will take care of trimming too large radii when generating d=
497 rect->rx._set = rect->ry._set = true;
498 }
500 void
501 sp_rect_set_visible_width(SPRect *rect, gdouble width)
502 {
503 rect->width.computed = width / vector_stretch(
504 Geom::Point(rect->x.computed + 1, rect->y.computed),
505 Geom::Point(rect->x.computed, rect->y.computed),
506 SP_ITEM(rect)->transform);
507 rect->width._set = true;
508 SP_OBJECT(rect)->updateRepr();
509 }
511 void
512 sp_rect_set_visible_height(SPRect *rect, gdouble height)
513 {
514 rect->height.computed = height / vector_stretch(
515 Geom::Point(rect->x.computed, rect->y.computed + 1),
516 Geom::Point(rect->x.computed, rect->y.computed),
517 SP_ITEM(rect)->transform);
518 rect->height._set = true;
519 SP_OBJECT(rect)->updateRepr();
520 }
522 gdouble
523 sp_rect_get_visible_width(SPRect *rect)
524 {
525 if (!rect->width._set)
526 return 0;
527 return rect->width.computed * vector_stretch(
528 Geom::Point(rect->x.computed + 1, rect->y.computed),
529 Geom::Point(rect->x.computed, rect->y.computed),
530 SP_ITEM(rect)->transform);
531 }
533 gdouble
534 sp_rect_get_visible_height(SPRect *rect)
535 {
536 if (!rect->height._set)
537 return 0;
538 return rect->height.computed * vector_stretch(
539 Geom::Point(rect->x.computed, rect->y.computed + 1),
540 Geom::Point(rect->x.computed, rect->y.computed),
541 SP_ITEM(rect)->transform);
542 }
544 /**
545 * Sets the snappoint p to the unrounded corners of the rectangle
546 */
547 static void sp_rect_snappoints(SPItem const *item, SnapPointsIter p, Inkscape::SnapPreferences const *snapprefs)
548 {
549 /* This method overrides sp_shape_snappoints, which is the default for any shape. The default method
550 returns all eight points along the path of a rounded rectangle, but not the real corners. Snapping
551 the startpoint and endpoint of each rounded corner is not very useful and really confusing. Instead
552 we could snap either the real corners, or not snap at all. Bulia Byak opted to snap the real corners,
553 but it should be noted that this might be confusing in some cases with relatively large radii. With
554 small radii though the user will easily understand which point is snapping. */
556 g_assert(item != NULL);
557 g_assert(SP_IS_RECT(item));
559 SPRect *rect = SP_RECT(item);
561 Geom::Matrix const i2d (sp_item_i2d_affine (item));
563 Geom::Point p0 = Geom::Point(rect->x.computed, rect->y.computed) * i2d;
564 Geom::Point p1 = Geom::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d;
565 Geom::Point p2 = Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d;
566 Geom::Point p3 = Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d;
568 *p = p0;
569 *p = p1;
570 *p = p2;
571 *p = p3;
573 if (snapprefs->getSnapMidpoints()) {
574 *p = (p0 + p1)/2;
575 *p = (p1 + p2)/2;
576 *p = (p2 + p3)/2;
577 *p = (p3 + p0)/2;
578 }
579 }
581 void
582 sp_rect_convert_to_guides(SPItem *item) {
583 SPRect *rect = SP_RECT(item);
585 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
586 if (!prefs->getBool("/tools/shapes/rect/convertguides", true)) {
587 sp_item_convert_to_guides(SP_ITEM(rect));
588 return;
589 }
591 std::list<std::pair<Geom::Point, Geom::Point> > pts;
593 Geom::Matrix const i2d (sp_item_i2d_affine(SP_ITEM(rect)));
595 Geom::Point A1(Geom::Point(rect->x.computed, rect->y.computed) * i2d);
596 Geom::Point A2(Geom::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d);
597 Geom::Point A3(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d);
598 Geom::Point A4(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d);
600 pts.push_back(std::make_pair(A1, A2));
601 pts.push_back(std::make_pair(A2, A3));
602 pts.push_back(std::make_pair(A3, A4));
603 pts.push_back(std::make_pair(A4, A1));
605 sp_guide_pt_pairs_to_guides(inkscape_active_desktop(), pts);
606 }
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 :