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 w = (ictx->vp.x1 - ictx->vp.x0);
178 double const h = (ictx->vp.y1 - ictx->vp.y0);
179 double const em = style->font_size.computed;
180 double const ex = 0.5 * em; // fixme: get x height from pango or libnrtype.
181 rect->x.update(em, ex, w);
182 rect->y.update(em, ex, h);
183 rect->width.update(em, ex, w);
184 rect->height.update(em, ex, h);
185 rect->rx.update(em, ex, w);
186 rect->ry.update(em, ex, h);
187 sp_shape_set_shape((SPShape *) object);
188 flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore
189 }
191 if (((SPObjectClass *) parent_class)->update)
192 ((SPObjectClass *) parent_class)->update(object, ctx, flags);
193 }
195 static Inkscape::XML::Node *
196 sp_rect_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
197 {
198 SPRect *rect = SP_RECT(object);
200 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
201 repr = xml_doc->createElement("svg:rect");
202 }
204 sp_repr_set_svg_double(repr, "width", rect->width.computed);
205 sp_repr_set_svg_double(repr, "height", rect->height.computed);
206 if (rect->rx._set) sp_repr_set_svg_double(repr, "rx", rect->rx.computed);
207 if (rect->ry._set) sp_repr_set_svg_double(repr, "ry", rect->ry.computed);
208 sp_repr_set_svg_double(repr, "x", rect->x.computed);
209 sp_repr_set_svg_double(repr, "y", rect->y.computed);
211 if (((SPObjectClass *) parent_class)->write)
212 ((SPObjectClass *) parent_class)->write(object, xml_doc, repr, flags);
214 return repr;
215 }
217 static gchar *
218 sp_rect_description(SPItem *item)
219 {
220 g_return_val_if_fail(SP_IS_RECT(item), NULL);
222 return g_strdup(_("<b>Rectangle</b>"));
223 }
225 #define C1 0.554
227 static void
228 sp_rect_set_shape(SPShape *shape)
229 {
230 SPRect *rect = (SPRect *) shape;
232 if ((rect->height.computed < 1e-18) || (rect->width.computed < 1e-18)) {
233 sp_shape_set_curve_insync(SP_SHAPE(rect), NULL, TRUE);
234 return;
235 }
237 SPCurve *c = new SPCurve();
239 double const x = rect->x.computed;
240 double const y = rect->y.computed;
241 double const w = rect->width.computed;
242 double const h = rect->height.computed;
243 double const w2 = w / 2;
244 double const h2 = h / 2;
245 double const rx = std::min(( rect->rx._set
246 ? rect->rx.computed
247 : ( rect->ry._set
248 ? rect->ry.computed
249 : 0.0 ) ),
250 .5 * rect->width.computed);
251 double const ry = std::min(( rect->ry._set
252 ? rect->ry.computed
253 : ( rect->rx._set
254 ? rect->rx.computed
255 : 0.0 ) ),
256 .5 * rect->height.computed);
257 /* TODO: Handle negative rx or ry as per
258 * http://www.w3.org/TR/SVG11/shapes.html#RectElementRXAttribute once Inkscape has proper error
259 * handling (see http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing).
260 */
262 /* We don't use proper circular/elliptical arcs, but bezier curves can approximate a 90-degree
263 * arc fairly well.
264 */
265 if ((rx > 1e-18) && (ry > 1e-18)) {
266 c->moveto(x + rx, y);
267 if (rx < w2) c->lineto(x + w - rx, y);
268 c->curveto(x + w - rx * (1 - C1), y, x + w, y + ry * (1 - C1), x + w, y + ry);
269 if (ry < h2) c->lineto(x + w, y + h - ry);
270 c->curveto(x + w, y + h - ry * (1 - C1), x + w - rx * (1 - C1), y + h, x + w - rx, y + h);
271 if (rx < w2) c->lineto(x + rx, y + h);
272 c->curveto(x + rx * (1 - C1), y + h, x, y + h - ry * (1 - C1), x, y + h - ry);
273 if (ry < h2) c->lineto(x, y + ry);
274 c->curveto(x, y + ry * (1 - C1), x + rx * (1 - C1), y, x + rx, y);
275 } else {
276 c->moveto(x + 0.0, y + 0.0);
277 c->lineto(x + w, y + 0.0);
278 c->lineto(x + w, y + h);
279 c->lineto(x + 0.0, y + h);
280 c->lineto(x + 0.0, y + 0.0);
281 }
283 c->closepath_current();
284 sp_shape_set_curve_insync(SP_SHAPE(rect), c, TRUE);
285 c->unref();
286 }
288 /* fixme: Think (Lauris) */
290 void
291 sp_rect_position_set(SPRect *rect, gdouble x, gdouble y, gdouble width, gdouble height)
292 {
293 g_return_if_fail(rect != NULL);
294 g_return_if_fail(SP_IS_RECT(rect));
296 rect->x.computed = x;
297 rect->y.computed = y;
298 rect->width.computed = width;
299 rect->height.computed = height;
301 SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
302 }
304 void
305 sp_rect_set_rx(SPRect *rect, gboolean set, gdouble value)
306 {
307 g_return_if_fail(rect != NULL);
308 g_return_if_fail(SP_IS_RECT(rect));
310 rect->rx._set = set;
311 if (set) rect->rx.computed = value;
313 SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
314 }
316 void
317 sp_rect_set_ry(SPRect *rect, gboolean set, gdouble value)
318 {
319 g_return_if_fail(rect != NULL);
320 g_return_if_fail(SP_IS_RECT(rect));
322 rect->ry._set = set;
323 if (set) rect->ry.computed = value;
325 SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
326 }
328 /*
329 * Initially we'll do:
330 * Transform x, y, set x, y, clear translation
331 */
333 /* fixme: Use preferred units somehow (Lauris) */
334 /* fixme: Alternately preserve whatever units there are (lauris) */
336 static Geom::Matrix
337 sp_rect_set_transform(SPItem *item, Geom::Matrix const &xform)
338 {
339 SPRect *rect = SP_RECT(item);
341 /* Calculate rect start in parent coords. */
342 Geom::Point pos( Geom::Point(rect->x.computed, rect->y.computed) * xform );
344 /* This function takes care of translation and scaling, we return whatever parts we can't
345 handle. */
346 Geom::Matrix ret(Geom::Matrix(xform).without_translation());
347 gdouble const sw = hypot(ret[0], ret[1]);
348 gdouble const sh = hypot(ret[2], ret[3]);
349 if (sw > 1e-9) {
350 ret[0] /= sw;
351 ret[1] /= sw;
352 } else {
353 ret[0] = 1.0;
354 ret[1] = 0.0;
355 }
356 if (sh > 1e-9) {
357 ret[2] /= sh;
358 ret[3] /= sh;
359 } else {
360 ret[2] = 0.0;
361 ret[3] = 1.0;
362 }
364 /* fixme: Would be nice to preserve units here */
365 rect->width = rect->width.computed * sw;
366 rect->height = rect->height.computed * sh;
367 if (rect->rx._set) {
368 rect->rx = rect->rx.computed * sw;
369 }
370 if (rect->ry._set) {
371 rect->ry = rect->ry.computed * sh;
372 }
374 /* Find start in item coords */
375 pos = pos * ret.inverse();
376 rect->x = pos[Geom::X];
377 rect->y = pos[Geom::Y];
379 sp_rect_set_shape(rect);
381 // Adjust stroke width
382 sp_item_adjust_stroke(item, sqrt(fabs(sw * sh)));
384 // Adjust pattern fill
385 sp_item_adjust_pattern(item, xform * ret.inverse());
387 // Adjust gradient fill
388 sp_item_adjust_gradient(item, xform * ret.inverse());
390 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
392 return ret;
393 }
396 /**
397 Returns the ratio in which the vector from p0 to p1 is stretched by transform
398 */
399 static gdouble
400 vector_stretch(Geom::Point p0, Geom::Point p1, Geom::Matrix xform)
401 {
402 if (p0 == p1)
403 return 0;
404 return (Geom::distance(p0 * xform, p1 * xform) / Geom::distance(p0, p1));
405 }
407 void
408 sp_rect_set_visible_rx(SPRect *rect, gdouble rx)
409 {
410 if (rx == 0) {
411 rect->rx.computed = 0;
412 rect->rx._set = false;
413 } else {
414 rect->rx.computed = rx / vector_stretch(
415 Geom::Point(rect->x.computed + 1, rect->y.computed),
416 Geom::Point(rect->x.computed, rect->y.computed),
417 SP_ITEM(rect)->transform);
418 rect->rx._set = true;
419 }
420 SP_OBJECT(rect)->updateRepr();
421 }
423 void
424 sp_rect_set_visible_ry(SPRect *rect, gdouble ry)
425 {
426 if (ry == 0) {
427 rect->ry.computed = 0;
428 rect->ry._set = false;
429 } else {
430 rect->ry.computed = ry / vector_stretch(
431 Geom::Point(rect->x.computed, rect->y.computed + 1),
432 Geom::Point(rect->x.computed, rect->y.computed),
433 SP_ITEM(rect)->transform);
434 rect->ry._set = true;
435 }
436 SP_OBJECT(rect)->updateRepr();
437 }
439 gdouble
440 sp_rect_get_visible_rx(SPRect *rect)
441 {
442 if (!rect->rx._set)
443 return 0;
444 return rect->rx.computed * vector_stretch(
445 Geom::Point(rect->x.computed + 1, rect->y.computed),
446 Geom::Point(rect->x.computed, rect->y.computed),
447 SP_ITEM(rect)->transform);
448 }
450 gdouble
451 sp_rect_get_visible_ry(SPRect *rect)
452 {
453 if (!rect->ry._set)
454 return 0;
455 return rect->ry.computed * vector_stretch(
456 Geom::Point(rect->x.computed, rect->y.computed + 1),
457 Geom::Point(rect->x.computed, rect->y.computed),
458 SP_ITEM(rect)->transform);
459 }
461 void
462 sp_rect_compensate_rxry(SPRect *rect, Geom::Matrix xform)
463 {
464 if (rect->rx.computed == 0 && rect->ry.computed == 0)
465 return; // nothing to compensate
467 // test unit vectors to find out compensation:
468 Geom::Point c(rect->x.computed, rect->y.computed);
469 Geom::Point cx = c + Geom::Point(1, 0);
470 Geom::Point cy = c + Geom::Point(0, 1);
472 // apply previous transform if any
473 c *= SP_ITEM(rect)->transform;
474 cx *= SP_ITEM(rect)->transform;
475 cy *= SP_ITEM(rect)->transform;
477 // find out stretches that we need to compensate
478 gdouble eX = vector_stretch(cx, c, xform);
479 gdouble eY = vector_stretch(cy, c, xform);
481 // If only one of the radii is set, set both radii so they have the same visible length
482 // This is needed because if we just set them the same length in SVG, they might end up unequal because of transform
483 if ((rect->rx._set && !rect->ry._set) || (rect->ry._set && !rect->rx._set)) {
484 gdouble r = MAX(rect->rx.computed, rect->ry.computed);
485 rect->rx.computed = r / eX;
486 rect->ry.computed = r / eY;
487 } else {
488 rect->rx.computed = rect->rx.computed / eX;
489 rect->ry.computed = rect->ry.computed / eY;
490 }
492 // Note that a radius may end up larger than half-side if the rect is scaled down;
493 // that's ok because this preserves the intended radii in case the rect is enlarged again,
494 // and set_shape will take care of trimming too large radii when generating d=
496 rect->rx._set = rect->ry._set = true;
497 }
499 void
500 sp_rect_set_visible_width(SPRect *rect, gdouble width)
501 {
502 rect->width.computed = width / vector_stretch(
503 Geom::Point(rect->x.computed + 1, rect->y.computed),
504 Geom::Point(rect->x.computed, rect->y.computed),
505 SP_ITEM(rect)->transform);
506 rect->width._set = true;
507 SP_OBJECT(rect)->updateRepr();
508 }
510 void
511 sp_rect_set_visible_height(SPRect *rect, gdouble height)
512 {
513 rect->height.computed = height / vector_stretch(
514 Geom::Point(rect->x.computed, rect->y.computed + 1),
515 Geom::Point(rect->x.computed, rect->y.computed),
516 SP_ITEM(rect)->transform);
517 rect->height._set = true;
518 SP_OBJECT(rect)->updateRepr();
519 }
521 gdouble
522 sp_rect_get_visible_width(SPRect *rect)
523 {
524 if (!rect->width._set)
525 return 0;
526 return rect->width.computed * vector_stretch(
527 Geom::Point(rect->x.computed + 1, rect->y.computed),
528 Geom::Point(rect->x.computed, rect->y.computed),
529 SP_ITEM(rect)->transform);
530 }
532 gdouble
533 sp_rect_get_visible_height(SPRect *rect)
534 {
535 if (!rect->height._set)
536 return 0;
537 return rect->height.computed * vector_stretch(
538 Geom::Point(rect->x.computed, rect->y.computed + 1),
539 Geom::Point(rect->x.computed, rect->y.computed),
540 SP_ITEM(rect)->transform);
541 }
543 /**
544 * Sets the snappoint p to the unrounded corners of the rectangle
545 */
546 static void sp_rect_snappoints(SPItem const *item, SnapPointsIter p, Inkscape::SnapPreferences const *snapprefs)
547 {
548 /* This method overrides sp_shape_snappoints, which is the default for any shape. The default method
549 returns all eight points along the path of a rounded rectangle, but not the real corners. Snapping
550 the startpoint and endpoint of each rounded corner is not very useful and really confusing. Instead
551 we could snap either the real corners, or not snap at all. Bulia Byak opted to snap the real corners,
552 but it should be noted that this might be confusing in some cases with relatively large radii. With
553 small radii though the user will easily understand which point is snapping. */
555 g_assert(item != NULL);
556 g_assert(SP_IS_RECT(item));
558 // Help enforcing strict snapping, i.e. only return nodes when we're snapping nodes to nodes or a guide to nodes
559 if (!(snapprefs->getSnapModeNode() || snapprefs->getSnapModeGuide())) {
560 return;
561 }
563 SPRect *rect = SP_RECT(item);
565 Geom::Matrix const i2d (sp_item_i2d_affine (item));
567 Geom::Point p0 = Geom::Point(rect->x.computed, rect->y.computed) * i2d;
568 Geom::Point p1 = Geom::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d;
569 Geom::Point p2 = Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d;
570 Geom::Point p3 = Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d;
572 if (snapprefs->getSnapToItemNode()) {
573 *p = p0;
574 *p = p1;
575 *p = p2;
576 *p = p3;
577 }
579 if (snapprefs->getSnapLineMidpoints()) { // only do this when we're snapping nodes (enforce strict snapping)
580 *p = (p0 + p1)/2;
581 *p = (p1 + p2)/2;
582 *p = (p2 + p3)/2;
583 *p = (p3 + p0)/2;
584 }
586 if (snapprefs->getSnapObjectMidpoints()) { // only do this when we're snapping nodes (enforce strict snapping)
587 *p = (p0 + p2)/2;
588 }
590 }
592 void
593 sp_rect_convert_to_guides(SPItem *item) {
594 SPRect *rect = SP_RECT(item);
596 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
597 if (!prefs->getBool("/tools/shapes/rect/convertguides", true)) {
598 sp_item_convert_to_guides(SP_ITEM(rect));
599 return;
600 }
602 std::list<std::pair<Geom::Point, Geom::Point> > pts;
604 Geom::Matrix const i2d (sp_item_i2d_affine(SP_ITEM(rect)));
606 Geom::Point A1(Geom::Point(rect->x.computed, rect->y.computed) * i2d);
607 Geom::Point A2(Geom::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d);
608 Geom::Point A3(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d);
609 Geom::Point A4(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d);
611 pts.push_back(std::make_pair(A1, A2));
612 pts.push_back(std::make_pair(A2, A3));
613 pts.push_back(std::make_pair(A3, A4));
614 pts.push_back(std::make_pair(A4, A1));
616 sp_guide_pt_pairs_to_guides(inkscape_active_desktop(), pts);
617 }
619 /*
620 Local Variables:
621 mode:c++
622 c-file-style:"stroustrup"
623 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
624 indent-tabs-mode:nil
625 fill-column:99
626 End:
627 */
628 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :