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");
124 }
126 static void
127 sp_rect_set(SPObject *object, unsigned key, gchar const *value)
128 {
129 SPRect *rect = SP_RECT(object);
131 /* fixme: We need real error processing some time */
133 switch (key) {
134 case SP_ATTR_X:
135 rect->x.readOrUnset(value);
136 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
137 break;
138 case SP_ATTR_Y:
139 rect->y.readOrUnset(value);
140 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
141 break;
142 case SP_ATTR_WIDTH:
143 if (!rect->width.read(value) || rect->width.value < 0.0) {
144 rect->width.unset();
145 }
146 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
147 break;
148 case SP_ATTR_HEIGHT:
149 if (!rect->height.read(value) || rect->height.value < 0.0) {
150 rect->height.unset();
151 }
152 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
153 break;
154 case SP_ATTR_RX:
155 if (!rect->rx.read(value) || rect->rx.value < 0.0) {
156 rect->rx.unset();
157 }
158 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
159 break;
160 case SP_ATTR_RY:
161 if (!rect->ry.read(value) || rect->ry.value < 0.0) {
162 rect->ry.unset();
163 }
164 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
165 break;
166 default:
167 if (((SPObjectClass *) parent_class)->set)
168 ((SPObjectClass *) parent_class)->set(object, key, value);
169 break;
170 }
171 }
173 static void
174 sp_rect_update(SPObject *object, SPCtx *ctx, guint flags)
175 {
176 if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
177 SPRect *rect = (SPRect *) object;
178 SPStyle *style = object->style;
179 SPItemCtx const *ictx = (SPItemCtx const *) ctx;
180 double const d = NR::expansion(ictx->i2vp);
181 double const w = (ictx->vp.x1 - ictx->vp.x0) / d;
182 double const h = (ictx->vp.y1 - ictx->vp.y0) / d;
183 double const em = style->font_size.computed;
184 double const ex = 0.5 * em; // fixme: get x height from pango or libnrtype.
185 rect->x.update(em, ex, w);
186 rect->y.update(em, ex, h);
187 rect->width.update(em, ex, w);
188 rect->height.update(em, ex, h);
189 rect->rx.update(em, ex, w);
190 rect->ry.update(em, ex, h);
191 sp_shape_set_shape((SPShape *) object);
192 flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore
193 }
195 if (((SPObjectClass *) parent_class)->update)
196 ((SPObjectClass *) parent_class)->update(object, ctx, flags);
197 }
199 static Inkscape::XML::Node *
200 sp_rect_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
201 {
202 SPRect *rect = SP_RECT(object);
204 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
205 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
206 repr = xml_doc->createElement("svg:rect");
207 }
209 sp_repr_set_svg_double(repr, "width", rect->width.computed);
210 sp_repr_set_svg_double(repr, "height", rect->height.computed);
211 if (rect->rx._set) sp_repr_set_svg_double(repr, "rx", rect->rx.computed);
212 if (rect->ry._set) sp_repr_set_svg_double(repr, "ry", rect->ry.computed);
213 sp_repr_set_svg_double(repr, "x", rect->x.computed);
214 sp_repr_set_svg_double(repr, "y", rect->y.computed);
216 if (((SPObjectClass *) parent_class)->write)
217 ((SPObjectClass *) parent_class)->write(object, repr, flags);
219 return repr;
220 }
222 static gchar *
223 sp_rect_description(SPItem *item)
224 {
225 g_return_val_if_fail(SP_IS_RECT(item), NULL);
227 return g_strdup(_("<b>Rectangle</b>"));
228 }
230 #define C1 0.554
232 static void
233 sp_rect_set_shape(SPShape *shape)
234 {
235 SPRect *rect = (SPRect *) shape;
237 if ((rect->height.computed < 1e-18) || (rect->width.computed < 1e-18)) {
238 sp_shape_set_curve_insync(SP_SHAPE(rect), NULL, TRUE);
239 return;
240 }
242 SPCurve *c = new SPCurve();
244 double const x = rect->x.computed;
245 double const y = rect->y.computed;
246 double const w = rect->width.computed;
247 double const h = rect->height.computed;
248 double const w2 = w / 2;
249 double const h2 = h / 2;
250 double const rx = std::min(( rect->rx._set
251 ? rect->rx.computed
252 : ( rect->ry._set
253 ? rect->ry.computed
254 : 0.0 ) ),
255 .5 * rect->width.computed);
256 double const ry = std::min(( rect->ry._set
257 ? rect->ry.computed
258 : ( rect->rx._set
259 ? rect->rx.computed
260 : 0.0 ) ),
261 .5 * rect->height.computed);
262 /* TODO: Handle negative rx or ry as per
263 * http://www.w3.org/TR/SVG11/shapes.html#RectElementRXAttribute once Inkscape has proper error
264 * handling (see http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing).
265 */
267 /* We don't use proper circular/elliptical arcs, but bezier curves can approximate a 90-degree
268 * arc fairly well.
269 */
270 if ((rx > 1e-18) && (ry > 1e-18)) {
271 c->moveto(x + rx, y);
272 if (rx < w2) c->lineto(x + w - rx, y);
273 c->curveto(x + w - rx * (1 - C1), y, x + w, y + ry * (1 - C1), x + w, y + ry);
274 if (ry < h2) c->lineto(x + w, y + h - ry);
275 c->curveto(x + w, y + h - ry * (1 - C1), x + w - rx * (1 - C1), y + h, x + w - rx, y + h);
276 if (rx < w2) c->lineto(x + rx, y + h);
277 c->curveto(x + rx * (1 - C1), y + h, x, y + h - ry * (1 - C1), x, y + h - ry);
278 if (ry < h2) c->lineto(x, y + ry);
279 c->curveto(x, y + ry * (1 - C1), x + rx * (1 - C1), y, x + rx, y);
280 } else {
281 c->moveto(x + 0.0, y + 0.0);
282 c->lineto(x + w, y + 0.0);
283 c->lineto(x + w, y + h);
284 c->lineto(x + 0.0, y + h);
285 c->lineto(x + 0.0, y + 0.0);
286 }
288 c->closepath_current();
289 sp_shape_set_curve_insync(SP_SHAPE(rect), c, TRUE);
290 c->unref();
291 }
293 /* fixme: Think (Lauris) */
295 void
296 sp_rect_position_set(SPRect *rect, gdouble x, gdouble y, gdouble width, gdouble height)
297 {
298 g_return_if_fail(rect != NULL);
299 g_return_if_fail(SP_IS_RECT(rect));
301 rect->x.computed = x;
302 rect->y.computed = y;
303 rect->width.computed = width;
304 rect->height.computed = height;
306 SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
307 }
309 void
310 sp_rect_set_rx(SPRect *rect, gboolean set, gdouble value)
311 {
312 g_return_if_fail(rect != NULL);
313 g_return_if_fail(SP_IS_RECT(rect));
315 rect->rx._set = set;
316 if (set) rect->rx.computed = value;
318 SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
319 }
321 void
322 sp_rect_set_ry(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->ry._set = set;
328 if (set) rect->ry.computed = value;
330 SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
331 }
333 /*
334 * Initially we'll do:
335 * Transform x, y, set x, y, clear translation
336 */
338 /* fixme: Use preferred units somehow (Lauris) */
339 /* fixme: Alternately preserve whatever units there are (lauris) */
341 static NR::Matrix
342 sp_rect_set_transform(SPItem *item, NR::Matrix const &xform)
343 {
344 SPRect *rect = SP_RECT(item);
346 /* Calculate rect start in parent coords. */
347 NR::Point pos( NR::Point(rect->x.computed, rect->y.computed) * xform );
349 /* This function takes care of translation and scaling, we return whatever parts we can't
350 handle. */
351 NR::Matrix ret(NR::transform(xform));
352 gdouble const sw = hypot(ret[0], ret[1]);
353 gdouble const sh = hypot(ret[2], ret[3]);
354 if (sw > 1e-9) {
355 ret[0] /= sw;
356 ret[1] /= sw;
357 } else {
358 ret[0] = 1.0;
359 ret[1] = 0.0;
360 }
361 if (sh > 1e-9) {
362 ret[2] /= sh;
363 ret[3] /= sh;
364 } else {
365 ret[2] = 0.0;
366 ret[3] = 1.0;
367 }
369 /* fixme: Would be nice to preserve units here */
370 rect->width = rect->width.computed * sw;
371 rect->height = rect->height.computed * sh;
372 if (rect->rx._set) {
373 rect->rx = rect->rx.computed * sw;
374 }
375 if (rect->ry._set) {
376 rect->ry = rect->ry.computed * sh;
377 }
379 /* Find start in item coords */
380 pos = pos * ret.inverse();
381 rect->x = pos[NR::X];
382 rect->y = pos[NR::Y];
384 sp_rect_set_shape(rect);
386 // Adjust stroke width
387 sp_item_adjust_stroke(item, sqrt(fabs(sw * sh)));
389 // Adjust pattern fill
390 sp_item_adjust_pattern(item, xform / ret);
392 // Adjust gradient fill
393 sp_item_adjust_gradient(item, xform / ret);
395 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
397 return ret;
398 }
401 /**
402 Returns the ratio in which the vector from p0 to p1 is stretched by transform
403 */
404 static gdouble
405 vector_stretch(NR::Point p0, NR::Point p1, NR::Matrix xform)
406 {
407 if (p0 == p1)
408 return 0;
409 return (NR::distance(p0 * xform, p1 * xform) / NR::distance(p0, p1));
410 }
412 void
413 sp_rect_set_visible_rx(SPRect *rect, gdouble rx)
414 {
415 if (rx == 0) {
416 rect->rx.computed = 0;
417 rect->rx._set = false;
418 } else {
419 rect->rx.computed = rx / vector_stretch(
420 NR::Point(rect->x.computed + 1, rect->y.computed),
421 NR::Point(rect->x.computed, rect->y.computed),
422 SP_ITEM(rect)->transform);
423 rect->rx._set = true;
424 }
425 SP_OBJECT(rect)->updateRepr();
426 }
428 void
429 sp_rect_set_visible_ry(SPRect *rect, gdouble ry)
430 {
431 if (ry == 0) {
432 rect->ry.computed = 0;
433 rect->ry._set = false;
434 } else {
435 rect->ry.computed = ry / vector_stretch(
436 NR::Point(rect->x.computed, rect->y.computed + 1),
437 NR::Point(rect->x.computed, rect->y.computed),
438 SP_ITEM(rect)->transform);
439 rect->ry._set = true;
440 }
441 SP_OBJECT(rect)->updateRepr();
442 }
444 gdouble
445 sp_rect_get_visible_rx(SPRect *rect)
446 {
447 if (!rect->rx._set)
448 return 0;
449 return rect->rx.computed * vector_stretch(
450 NR::Point(rect->x.computed + 1, rect->y.computed),
451 NR::Point(rect->x.computed, rect->y.computed),
452 SP_ITEM(rect)->transform);
453 }
455 gdouble
456 sp_rect_get_visible_ry(SPRect *rect)
457 {
458 if (!rect->ry._set)
459 return 0;
460 return rect->ry.computed * vector_stretch(
461 NR::Point(rect->x.computed, rect->y.computed + 1),
462 NR::Point(rect->x.computed, rect->y.computed),
463 SP_ITEM(rect)->transform);
464 }
466 void
467 sp_rect_compensate_rxry(SPRect *rect, NR::Matrix xform)
468 {
469 if (rect->rx.computed == 0 && rect->ry.computed == 0)
470 return; // nothing to compensate
472 // test unit vectors to find out compensation:
473 NR::Point c(rect->x.computed, rect->y.computed);
474 NR::Point cx = c + NR::Point(1, 0);
475 NR::Point cy = c + NR::Point(0, 1);
477 // apply previous transform if any
478 c *= SP_ITEM(rect)->transform;
479 cx *= SP_ITEM(rect)->transform;
480 cy *= SP_ITEM(rect)->transform;
482 // find out stretches that we need to compensate
483 gdouble eX = vector_stretch(cx, c, xform);
484 gdouble eY = vector_stretch(cy, c, xform);
486 // If only one of the radii is set, set both radii so they have the same visible length
487 // This is needed because if we just set them the same length in SVG, they might end up unequal because of transform
488 if ((rect->rx._set && !rect->ry._set) || (rect->ry._set && !rect->rx._set)) {
489 gdouble r = MAX(rect->rx.computed, rect->ry.computed);
490 rect->rx.computed = r / eX;
491 rect->ry.computed = r / eY;
492 } else {
493 rect->rx.computed = rect->rx.computed / eX;
494 rect->ry.computed = rect->ry.computed / eY;
495 }
497 // Note that a radius may end up larger than half-side if the rect is scaled down;
498 // that's ok because this preserves the intended radii in case the rect is enlarged again,
499 // and set_shape will take care of trimming too large radii when generating d=
501 rect->rx._set = rect->ry._set = true;
502 }
504 void
505 sp_rect_set_visible_width(SPRect *rect, gdouble width)
506 {
507 rect->width.computed = width / vector_stretch(
508 NR::Point(rect->x.computed + 1, rect->y.computed),
509 NR::Point(rect->x.computed, rect->y.computed),
510 SP_ITEM(rect)->transform);
511 rect->width._set = true;
512 SP_OBJECT(rect)->updateRepr();
513 }
515 void
516 sp_rect_set_visible_height(SPRect *rect, gdouble height)
517 {
518 rect->height.computed = height / vector_stretch(
519 NR::Point(rect->x.computed, rect->y.computed + 1),
520 NR::Point(rect->x.computed, rect->y.computed),
521 SP_ITEM(rect)->transform);
522 rect->height._set = true;
523 SP_OBJECT(rect)->updateRepr();
524 }
526 gdouble
527 sp_rect_get_visible_width(SPRect *rect)
528 {
529 if (!rect->width._set)
530 return 0;
531 return rect->width.computed * vector_stretch(
532 NR::Point(rect->x.computed + 1, rect->y.computed),
533 NR::Point(rect->x.computed, rect->y.computed),
534 SP_ITEM(rect)->transform);
535 }
537 gdouble
538 sp_rect_get_visible_height(SPRect *rect)
539 {
540 if (!rect->height._set)
541 return 0;
542 return rect->height.computed * vector_stretch(
543 NR::Point(rect->x.computed, rect->y.computed + 1),
544 NR::Point(rect->x.computed, rect->y.computed),
545 SP_ITEM(rect)->transform);
546 }
548 /**
549 * Sets the snappoint p to the unrounded corners of the rectangle
550 */
551 static void sp_rect_snappoints(SPItem const *item, SnapPointsIter p)
552 {
553 /* This method overrides sp_shape_snappoints, which is the default for any shape. The default method
554 returns all eight points along the path of a rounded rectangle, but not the real corners. Snapping
555 the startpoint and endpoint of each rounded corner is not very usefull and really confusing. Instead
556 we could snap either the real corners, or not snap at all. Bulia Byak opted to snap the real corners,
557 but it should be noted that this might be confusing in some cases with relatively large radii. With
558 small radii though the user will easily understand which point is snapping. */
560 g_assert(item != NULL);
561 g_assert(SP_IS_RECT(item));
563 SPRect *rect = SP_RECT(item);
565 NR::Matrix const i2d (sp_item_i2d_affine (item));
567 *p = NR::Point(rect->x.computed, rect->y.computed) * i2d;
568 *p = NR::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d;
569 *p = NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d;
570 *p = NR::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d;
571 }
573 void
574 sp_rect_convert_to_guides(SPItem *item) {
575 SPRect *rect = SP_RECT(item);
577 if (prefs_get_int_attribute("tools.shapes.rect", "convertguides", 1) == 0) {
578 sp_item_convert_to_guides(SP_ITEM(rect));
579 return;
580 }
582 SPDocument *doc = SP_OBJECT_DOCUMENT(rect);
583 std::list<std::pair<Geom::Point, Geom::Point> > pts;
585 NR::Matrix const i2d (sp_item_i2d_affine(SP_ITEM(rect)));
587 NR::Point A1(NR::Point(rect->x.computed, rect->y.computed) * i2d);
588 NR::Point A2(NR::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d);
589 NR::Point A3(NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d);
590 NR::Point A4(NR::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d);
592 pts.push_back(std::make_pair(A1.to_2geom(), A2.to_2geom()));
593 pts.push_back(std::make_pair(A2.to_2geom(), A3.to_2geom()));
594 pts.push_back(std::make_pair(A3.to_2geom(), A4.to_2geom()));
595 pts.push_back(std::make_pair(A4.to_2geom(), A1.to_2geom()));
597 sp_guide_pt_pairs_to_guides(doc, pts);
598 }
600 /*
601 Local Variables:
602 mode:c++
603 c-file-style:"stroustrup"
604 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
605 indent-tabs-mode:nil
606 fill-column:99
607 End:
608 */
609 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :