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 "inkscape.h"
26 #include "document.h"
27 #include "attributes.h"
28 #include "style.h"
29 #include "sp-rect.h"
30 #include <glibmm/i18n.h>
31 #include "xml/repr.h"
32 #include "sp-guide.h"
33 #include "prefs-utils.h"
35 #define noRECT_VERBOSE
37 static void sp_rect_class_init(SPRectClass *klass);
38 static void sp_rect_init(SPRect *rect);
40 static void sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
41 static void sp_rect_set(SPObject *object, unsigned key, gchar const *value);
42 static void sp_rect_update(SPObject *object, SPCtx *ctx, guint flags);
43 static Inkscape::XML::Node *sp_rect_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags);
45 static gchar *sp_rect_description(SPItem *item);
46 static NR::Matrix sp_rect_set_transform(SPItem *item, NR::Matrix const &xform);
47 static void sp_rect_convert_to_guides(SPItem *item);
49 static void sp_rect_set_shape(SPShape *shape);
50 static void sp_rect_snappoints(SPItem const *item, SnapPointsIter p);
52 static SPShapeClass *parent_class;
54 GType
55 sp_rect_get_type(void)
56 {
57 static GType type = 0;
59 if (!type) {
60 GTypeInfo info = {
61 sizeof(SPRectClass),
62 NULL, /* base_init */
63 NULL, /* base_finalize */
64 (GClassInitFunc) sp_rect_class_init,
65 NULL, /* class_finalize */
66 NULL, /* class_data */
67 sizeof(SPRect),
68 16, /* n_preallocs */
69 (GInstanceInitFunc) sp_rect_init,
70 NULL, /* value_table */
71 };
72 type = g_type_register_static(SP_TYPE_SHAPE, "SPRect", &info, (GTypeFlags)0);
73 }
74 return type;
75 }
77 static void
78 sp_rect_class_init(SPRectClass *klass)
79 {
80 SPObjectClass *sp_object_class = (SPObjectClass *) klass;
81 SPItemClass *item_class = (SPItemClass *) klass;
82 SPShapeClass *shape_class = (SPShapeClass *) klass;
84 parent_class = (SPShapeClass *)g_type_class_ref(SP_TYPE_SHAPE);
86 sp_object_class->build = sp_rect_build;
87 sp_object_class->write = sp_rect_write;
88 sp_object_class->set = sp_rect_set;
89 sp_object_class->update = sp_rect_update;
91 item_class->description = sp_rect_description;
92 item_class->set_transform = sp_rect_set_transform;
93 item_class->convert_to_guides = sp_rect_convert_to_guides;
94 item_class->snappoints = sp_rect_snappoints; //override the default sp_shape_snappoints; see sp_rect_snappoints for details
96 shape_class->set_shape = sp_rect_set_shape;
97 }
99 static void
100 sp_rect_init(SPRect */*rect*/)
101 {
102 /* Initializing to zero is automatic */
103 /* sp_svg_length_unset(&rect->x, SP_SVG_UNIT_NONE, 0.0, 0.0); */
104 /* sp_svg_length_unset(&rect->y, SP_SVG_UNIT_NONE, 0.0, 0.0); */
105 /* sp_svg_length_unset(&rect->width, SP_SVG_UNIT_NONE, 0.0, 0.0); */
106 /* sp_svg_length_unset(&rect->height, SP_SVG_UNIT_NONE, 0.0, 0.0); */
107 /* sp_svg_length_unset(&rect->rx, SP_SVG_UNIT_NONE, 0.0, 0.0); */
108 /* sp_svg_length_unset(&rect->ry, SP_SVG_UNIT_NONE, 0.0, 0.0); */
109 }
111 static void
112 sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
113 {
114 if (((SPObjectClass *) parent_class)->build)
115 ((SPObjectClass *) parent_class)->build(object, document, repr);
117 sp_object_read_attr(object, "x");
118 sp_object_read_attr(object, "y");
119 sp_object_read_attr(object, "width");
120 sp_object_read_attr(object, "height");
121 sp_object_read_attr(object, "rx");
122 sp_object_read_attr(object, "ry");
123 }
125 static void
126 sp_rect_set(SPObject *object, unsigned key, gchar const *value)
127 {
128 SPRect *rect = SP_RECT(object);
130 /* fixme: We need real error processing some time */
132 switch (key) {
133 case SP_ATTR_X:
134 rect->x.readOrUnset(value);
135 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
136 break;
137 case SP_ATTR_Y:
138 rect->y.readOrUnset(value);
139 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
140 break;
141 case SP_ATTR_WIDTH:
142 if (!rect->width.read(value) || rect->width.value < 0.0) {
143 rect->width.unset();
144 }
145 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
146 break;
147 case SP_ATTR_HEIGHT:
148 if (!rect->height.read(value) || rect->height.value < 0.0) {
149 rect->height.unset();
150 }
151 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
152 break;
153 case SP_ATTR_RX:
154 if (!rect->rx.read(value) || rect->rx.value < 0.0) {
155 rect->rx.unset();
156 }
157 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
158 break;
159 case SP_ATTR_RY:
160 if (!rect->ry.read(value) || rect->ry.value < 0.0) {
161 rect->ry.unset();
162 }
163 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
164 break;
165 default:
166 if (((SPObjectClass *) parent_class)->set)
167 ((SPObjectClass *) parent_class)->set(object, key, value);
168 break;
169 }
170 }
172 static void
173 sp_rect_update(SPObject *object, SPCtx *ctx, guint flags)
174 {
175 if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
176 SPRect *rect = (SPRect *) object;
177 SPStyle *style = object->style;
178 SPItemCtx const *ictx = (SPItemCtx const *) ctx;
179 double const d = NR::expansion(ictx->i2vp);
180 double const w = (ictx->vp.x1 - ictx->vp.x0) / d;
181 double const h = (ictx->vp.y1 - ictx->vp.y0) / d;
182 double const em = style->font_size.computed;
183 double const ex = 0.5 * em; // fixme: get x height from pango or libnrtype.
184 rect->x.update(em, ex, w);
185 rect->y.update(em, ex, h);
186 rect->width.update(em, ex, w);
187 rect->height.update(em, ex, h);
188 rect->rx.update(em, ex, w);
189 rect->ry.update(em, ex, h);
190 sp_shape_set_shape((SPShape *) object);
191 flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore
192 }
194 if (((SPObjectClass *) parent_class)->update)
195 ((SPObjectClass *) parent_class)->update(object, ctx, flags);
196 }
198 static Inkscape::XML::Node *
199 sp_rect_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
200 {
201 SPRect *rect = SP_RECT(object);
203 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
204 repr = xml_doc->createElement("svg:rect");
205 }
207 sp_repr_set_svg_double(repr, "width", rect->width.computed);
208 sp_repr_set_svg_double(repr, "height", rect->height.computed);
209 if (rect->rx._set) sp_repr_set_svg_double(repr, "rx", rect->rx.computed);
210 if (rect->ry._set) sp_repr_set_svg_double(repr, "ry", rect->ry.computed);
211 sp_repr_set_svg_double(repr, "x", rect->x.computed);
212 sp_repr_set_svg_double(repr, "y", rect->y.computed);
214 if (((SPObjectClass *) parent_class)->write)
215 ((SPObjectClass *) parent_class)->write(object, xml_doc, repr, flags);
217 return repr;
218 }
220 static gchar *
221 sp_rect_description(SPItem *item)
222 {
223 g_return_val_if_fail(SP_IS_RECT(item), NULL);
225 return g_strdup(_("<b>Rectangle</b>"));
226 }
228 #define C1 0.554
230 static void
231 sp_rect_set_shape(SPShape *shape)
232 {
233 SPRect *rect = (SPRect *) shape;
235 if ((rect->height.computed < 1e-18) || (rect->width.computed < 1e-18)) {
236 sp_shape_set_curve_insync(SP_SHAPE(rect), NULL, TRUE);
237 return;
238 }
240 SPCurve *c = new SPCurve();
242 double const x = rect->x.computed;
243 double const y = rect->y.computed;
244 double const w = rect->width.computed;
245 double const h = rect->height.computed;
246 double const w2 = w / 2;
247 double const h2 = h / 2;
248 double const rx = std::min(( rect->rx._set
249 ? rect->rx.computed
250 : ( rect->ry._set
251 ? rect->ry.computed
252 : 0.0 ) ),
253 .5 * rect->width.computed);
254 double const ry = std::min(( rect->ry._set
255 ? rect->ry.computed
256 : ( rect->rx._set
257 ? rect->rx.computed
258 : 0.0 ) ),
259 .5 * rect->height.computed);
260 /* TODO: Handle negative rx or ry as per
261 * http://www.w3.org/TR/SVG11/shapes.html#RectElementRXAttribute once Inkscape has proper error
262 * handling (see http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing).
263 */
265 /* We don't use proper circular/elliptical arcs, but bezier curves can approximate a 90-degree
266 * arc fairly well.
267 */
268 if ((rx > 1e-18) && (ry > 1e-18)) {
269 c->moveto(x + rx, y);
270 if (rx < w2) c->lineto(x + w - rx, y);
271 c->curveto(x + w - rx * (1 - C1), y, x + w, y + ry * (1 - C1), x + w, y + ry);
272 if (ry < h2) c->lineto(x + w, y + h - ry);
273 c->curveto(x + w, y + h - ry * (1 - C1), x + w - rx * (1 - C1), y + h, x + w - rx, y + h);
274 if (rx < w2) c->lineto(x + rx, y + h);
275 c->curveto(x + rx * (1 - C1), y + h, x, y + h - ry * (1 - C1), x, y + h - ry);
276 if (ry < h2) c->lineto(x, y + ry);
277 c->curveto(x, y + ry * (1 - C1), x + rx * (1 - C1), y, x + rx, y);
278 } else {
279 c->moveto(x + 0.0, y + 0.0);
280 c->lineto(x + w, y + 0.0);
281 c->lineto(x + w, y + h);
282 c->lineto(x + 0.0, y + h);
283 c->lineto(x + 0.0, y + 0.0);
284 }
286 c->closepath_current();
287 sp_shape_set_curve_insync(SP_SHAPE(rect), c, TRUE);
288 c->unref();
289 }
291 /* fixme: Think (Lauris) */
293 void
294 sp_rect_position_set(SPRect *rect, gdouble x, gdouble y, gdouble width, gdouble height)
295 {
296 g_return_if_fail(rect != NULL);
297 g_return_if_fail(SP_IS_RECT(rect));
299 rect->x.computed = x;
300 rect->y.computed = y;
301 rect->width.computed = width;
302 rect->height.computed = height;
304 SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
305 }
307 void
308 sp_rect_set_rx(SPRect *rect, gboolean set, gdouble value)
309 {
310 g_return_if_fail(rect != NULL);
311 g_return_if_fail(SP_IS_RECT(rect));
313 rect->rx._set = set;
314 if (set) rect->rx.computed = value;
316 SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
317 }
319 void
320 sp_rect_set_ry(SPRect *rect, gboolean set, gdouble value)
321 {
322 g_return_if_fail(rect != NULL);
323 g_return_if_fail(SP_IS_RECT(rect));
325 rect->ry._set = set;
326 if (set) rect->ry.computed = value;
328 SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
329 }
331 /*
332 * Initially we'll do:
333 * Transform x, y, set x, y, clear translation
334 */
336 /* fixme: Use preferred units somehow (Lauris) */
337 /* fixme: Alternately preserve whatever units there are (lauris) */
339 static NR::Matrix
340 sp_rect_set_transform(SPItem *item, NR::Matrix const &xform)
341 {
342 SPRect *rect = SP_RECT(item);
344 /* Calculate rect start in parent coords. */
345 NR::Point pos( NR::Point(rect->x.computed, rect->y.computed) * xform );
347 /* This function takes care of translation and scaling, we return whatever parts we can't
348 handle. */
349 NR::Matrix ret(NR::transform(xform));
350 gdouble const sw = hypot(ret[0], ret[1]);
351 gdouble const sh = hypot(ret[2], ret[3]);
352 if (sw > 1e-9) {
353 ret[0] /= sw;
354 ret[1] /= sw;
355 } else {
356 ret[0] = 1.0;
357 ret[1] = 0.0;
358 }
359 if (sh > 1e-9) {
360 ret[2] /= sh;
361 ret[3] /= sh;
362 } else {
363 ret[2] = 0.0;
364 ret[3] = 1.0;
365 }
367 /* fixme: Would be nice to preserve units here */
368 rect->width = rect->width.computed * sw;
369 rect->height = rect->height.computed * sh;
370 if (rect->rx._set) {
371 rect->rx = rect->rx.computed * sw;
372 }
373 if (rect->ry._set) {
374 rect->ry = rect->ry.computed * sh;
375 }
377 /* Find start in item coords */
378 pos = pos * ret.inverse();
379 rect->x = pos[NR::X];
380 rect->y = pos[NR::Y];
382 sp_rect_set_shape(rect);
384 // Adjust stroke width
385 sp_item_adjust_stroke(item, sqrt(fabs(sw * sh)));
387 // Adjust pattern fill
388 sp_item_adjust_pattern(item, xform / ret);
390 // Adjust gradient fill
391 sp_item_adjust_gradient(item, xform / ret);
393 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
395 return ret;
396 }
399 /**
400 Returns the ratio in which the vector from p0 to p1 is stretched by transform
401 */
402 static gdouble
403 vector_stretch(NR::Point p0, NR::Point p1, NR::Matrix xform)
404 {
405 if (p0 == p1)
406 return 0;
407 return (NR::distance(p0 * xform, p1 * xform) / NR::distance(p0, p1));
408 }
410 void
411 sp_rect_set_visible_rx(SPRect *rect, gdouble rx)
412 {
413 if (rx == 0) {
414 rect->rx.computed = 0;
415 rect->rx._set = false;
416 } else {
417 rect->rx.computed = rx / vector_stretch(
418 NR::Point(rect->x.computed + 1, rect->y.computed),
419 NR::Point(rect->x.computed, rect->y.computed),
420 SP_ITEM(rect)->transform);
421 rect->rx._set = true;
422 }
423 SP_OBJECT(rect)->updateRepr();
424 }
426 void
427 sp_rect_set_visible_ry(SPRect *rect, gdouble ry)
428 {
429 if (ry == 0) {
430 rect->ry.computed = 0;
431 rect->ry._set = false;
432 } else {
433 rect->ry.computed = ry / vector_stretch(
434 NR::Point(rect->x.computed, rect->y.computed + 1),
435 NR::Point(rect->x.computed, rect->y.computed),
436 SP_ITEM(rect)->transform);
437 rect->ry._set = true;
438 }
439 SP_OBJECT(rect)->updateRepr();
440 }
442 gdouble
443 sp_rect_get_visible_rx(SPRect *rect)
444 {
445 if (!rect->rx._set)
446 return 0;
447 return rect->rx.computed * vector_stretch(
448 NR::Point(rect->x.computed + 1, rect->y.computed),
449 NR::Point(rect->x.computed, rect->y.computed),
450 SP_ITEM(rect)->transform);
451 }
453 gdouble
454 sp_rect_get_visible_ry(SPRect *rect)
455 {
456 if (!rect->ry._set)
457 return 0;
458 return rect->ry.computed * vector_stretch(
459 NR::Point(rect->x.computed, rect->y.computed + 1),
460 NR::Point(rect->x.computed, rect->y.computed),
461 SP_ITEM(rect)->transform);
462 }
464 void
465 sp_rect_compensate_rxry(SPRect *rect, NR::Matrix xform)
466 {
467 if (rect->rx.computed == 0 && rect->ry.computed == 0)
468 return; // nothing to compensate
470 // test unit vectors to find out compensation:
471 NR::Point c(rect->x.computed, rect->y.computed);
472 NR::Point cx = c + NR::Point(1, 0);
473 NR::Point cy = c + NR::Point(0, 1);
475 // apply previous transform if any
476 c *= SP_ITEM(rect)->transform;
477 cx *= SP_ITEM(rect)->transform;
478 cy *= SP_ITEM(rect)->transform;
480 // find out stretches that we need to compensate
481 gdouble eX = vector_stretch(cx, c, xform);
482 gdouble eY = vector_stretch(cy, c, xform);
484 // If only one of the radii is set, set both radii so they have the same visible length
485 // This is needed because if we just set them the same length in SVG, they might end up unequal because of transform
486 if ((rect->rx._set && !rect->ry._set) || (rect->ry._set && !rect->rx._set)) {
487 gdouble r = MAX(rect->rx.computed, rect->ry.computed);
488 rect->rx.computed = r / eX;
489 rect->ry.computed = r / eY;
490 } else {
491 rect->rx.computed = rect->rx.computed / eX;
492 rect->ry.computed = rect->ry.computed / eY;
493 }
495 // Note that a radius may end up larger than half-side if the rect is scaled down;
496 // that's ok because this preserves the intended radii in case the rect is enlarged again,
497 // and set_shape will take care of trimming too large radii when generating d=
499 rect->rx._set = rect->ry._set = true;
500 }
502 void
503 sp_rect_set_visible_width(SPRect *rect, gdouble width)
504 {
505 rect->width.computed = width / vector_stretch(
506 NR::Point(rect->x.computed + 1, rect->y.computed),
507 NR::Point(rect->x.computed, rect->y.computed),
508 SP_ITEM(rect)->transform);
509 rect->width._set = true;
510 SP_OBJECT(rect)->updateRepr();
511 }
513 void
514 sp_rect_set_visible_height(SPRect *rect, gdouble height)
515 {
516 rect->height.computed = height / vector_stretch(
517 NR::Point(rect->x.computed, rect->y.computed + 1),
518 NR::Point(rect->x.computed, rect->y.computed),
519 SP_ITEM(rect)->transform);
520 rect->height._set = true;
521 SP_OBJECT(rect)->updateRepr();
522 }
524 gdouble
525 sp_rect_get_visible_width(SPRect *rect)
526 {
527 if (!rect->width._set)
528 return 0;
529 return rect->width.computed * vector_stretch(
530 NR::Point(rect->x.computed + 1, rect->y.computed),
531 NR::Point(rect->x.computed, rect->y.computed),
532 SP_ITEM(rect)->transform);
533 }
535 gdouble
536 sp_rect_get_visible_height(SPRect *rect)
537 {
538 if (!rect->height._set)
539 return 0;
540 return rect->height.computed * vector_stretch(
541 NR::Point(rect->x.computed, rect->y.computed + 1),
542 NR::Point(rect->x.computed, rect->y.computed),
543 SP_ITEM(rect)->transform);
544 }
546 /**
547 * Sets the snappoint p to the unrounded corners of the rectangle
548 */
549 static void sp_rect_snappoints(SPItem const *item, SnapPointsIter p)
550 {
551 /* This method overrides sp_shape_snappoints, which is the default for any shape. The default method
552 returns all eight points along the path of a rounded rectangle, but not the real corners. Snapping
553 the startpoint and endpoint of each rounded corner is not very usefull and really confusing. Instead
554 we could snap either the real corners, or not snap at all. Bulia Byak opted to snap the real corners,
555 but it should be noted that this might be confusing in some cases with relatively large radii. With
556 small radii though the user will easily understand which point is snapping. */
558 g_assert(item != NULL);
559 g_assert(SP_IS_RECT(item));
561 SPRect *rect = SP_RECT(item);
563 NR::Matrix const i2d (from_2geom(sp_item_i2d_affine (item)));
565 *p = NR::Point(rect->x.computed, rect->y.computed) * i2d;
566 *p = NR::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d;
567 *p = NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d;
568 *p = NR::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d;
569 }
571 void
572 sp_rect_convert_to_guides(SPItem *item) {
573 SPRect *rect = SP_RECT(item);
575 if (prefs_get_int_attribute("tools.shapes.rect", "convertguides", 1) == 0) {
576 sp_item_convert_to_guides(SP_ITEM(rect));
577 return;
578 }
580 std::list<std::pair<Geom::Point, Geom::Point> > pts;
582 NR::Matrix const i2d (from_2geom(sp_item_i2d_affine(SP_ITEM(rect))));
584 NR::Point A1(NR::Point(rect->x.computed, rect->y.computed) * i2d);
585 NR::Point A2(NR::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d);
586 NR::Point A3(NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d);
587 NR::Point A4(NR::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d);
589 pts.push_back(std::make_pair(A1.to_2geom(), A2.to_2geom()));
590 pts.push_back(std::make_pair(A2.to_2geom(), A3.to_2geom()));
591 pts.push_back(std::make_pair(A3.to_2geom(), A4.to_2geom()));
592 pts.push_back(std::make_pair(A4.to_2geom(), A1.to_2geom()));
594 sp_guide_pt_pairs_to_guides(inkscape_active_desktop(), pts);
595 }
597 /*
598 Local Variables:
599 mode:c++
600 c-file-style:"stroustrup"
601 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
602 indent-tabs-mode:nil
603 fill-column:99
604 End:
605 */
606 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :