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::Document *doc, 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::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
201 {
202 SPRect *rect = SP_RECT(object);
204 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
205 repr = xml_doc->createElement("svg:rect");
206 }
208 sp_repr_set_svg_double(repr, "width", rect->width.computed);
209 sp_repr_set_svg_double(repr, "height", rect->height.computed);
210 if (rect->rx._set) sp_repr_set_svg_double(repr, "rx", rect->rx.computed);
211 if (rect->ry._set) sp_repr_set_svg_double(repr, "ry", rect->ry.computed);
212 sp_repr_set_svg_double(repr, "x", rect->x.computed);
213 sp_repr_set_svg_double(repr, "y", rect->y.computed);
215 if (((SPObjectClass *) parent_class)->write)
216 ((SPObjectClass *) parent_class)->write(object, xml_doc, repr, flags);
218 return repr;
219 }
221 static gchar *
222 sp_rect_description(SPItem *item)
223 {
224 g_return_val_if_fail(SP_IS_RECT(item), NULL);
226 return g_strdup(_("<b>Rectangle</b>"));
227 }
229 #define C1 0.554
231 static void
232 sp_rect_set_shape(SPShape *shape)
233 {
234 SPRect *rect = (SPRect *) shape;
236 if ((rect->height.computed < 1e-18) || (rect->width.computed < 1e-18)) {
237 sp_shape_set_curve_insync(SP_SHAPE(rect), NULL, TRUE);
238 return;
239 }
241 SPCurve *c = new SPCurve();
243 double const x = rect->x.computed;
244 double const y = rect->y.computed;
245 double const w = rect->width.computed;
246 double const h = rect->height.computed;
247 double const w2 = w / 2;
248 double const h2 = h / 2;
249 double const rx = std::min(( rect->rx._set
250 ? rect->rx.computed
251 : ( rect->ry._set
252 ? rect->ry.computed
253 : 0.0 ) ),
254 .5 * rect->width.computed);
255 double const ry = std::min(( rect->ry._set
256 ? rect->ry.computed
257 : ( rect->rx._set
258 ? rect->rx.computed
259 : 0.0 ) ),
260 .5 * rect->height.computed);
261 /* TODO: Handle negative rx or ry as per
262 * http://www.w3.org/TR/SVG11/shapes.html#RectElementRXAttribute once Inkscape has proper error
263 * handling (see http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing).
264 */
266 /* We don't use proper circular/elliptical arcs, but bezier curves can approximate a 90-degree
267 * arc fairly well.
268 */
269 if ((rx > 1e-18) && (ry > 1e-18)) {
270 c->moveto(x + rx, y);
271 if (rx < w2) c->lineto(x + w - rx, y);
272 c->curveto(x + w - rx * (1 - C1), y, x + w, y + ry * (1 - C1), x + w, y + ry);
273 if (ry < h2) c->lineto(x + w, y + h - ry);
274 c->curveto(x + w, y + h - ry * (1 - C1), x + w - rx * (1 - C1), y + h, x + w - rx, y + h);
275 if (rx < w2) c->lineto(x + rx, y + h);
276 c->curveto(x + rx * (1 - C1), y + h, x, y + h - ry * (1 - C1), x, y + h - ry);
277 if (ry < h2) c->lineto(x, y + ry);
278 c->curveto(x, y + ry * (1 - C1), x + rx * (1 - C1), y, x + rx, y);
279 } else {
280 c->moveto(x + 0.0, y + 0.0);
281 c->lineto(x + w, y + 0.0);
282 c->lineto(x + w, y + h);
283 c->lineto(x + 0.0, y + h);
284 c->lineto(x + 0.0, y + 0.0);
285 }
287 c->closepath_current();
288 sp_shape_set_curve_insync(SP_SHAPE(rect), c, TRUE);
289 c->unref();
290 }
292 /* fixme: Think (Lauris) */
294 void
295 sp_rect_position_set(SPRect *rect, gdouble x, gdouble y, gdouble width, gdouble height)
296 {
297 g_return_if_fail(rect != NULL);
298 g_return_if_fail(SP_IS_RECT(rect));
300 rect->x.computed = x;
301 rect->y.computed = y;
302 rect->width.computed = width;
303 rect->height.computed = height;
305 SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
306 }
308 void
309 sp_rect_set_rx(SPRect *rect, gboolean set, gdouble value)
310 {
311 g_return_if_fail(rect != NULL);
312 g_return_if_fail(SP_IS_RECT(rect));
314 rect->rx._set = set;
315 if (set) rect->rx.computed = value;
317 SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
318 }
320 void
321 sp_rect_set_ry(SPRect *rect, gboolean set, gdouble value)
322 {
323 g_return_if_fail(rect != NULL);
324 g_return_if_fail(SP_IS_RECT(rect));
326 rect->ry._set = set;
327 if (set) rect->ry.computed = value;
329 SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
330 }
332 /*
333 * Initially we'll do:
334 * Transform x, y, set x, y, clear translation
335 */
337 /* fixme: Use preferred units somehow (Lauris) */
338 /* fixme: Alternately preserve whatever units there are (lauris) */
340 static NR::Matrix
341 sp_rect_set_transform(SPItem *item, NR::Matrix const &xform)
342 {
343 SPRect *rect = SP_RECT(item);
345 /* Calculate rect start in parent coords. */
346 NR::Point pos( NR::Point(rect->x.computed, rect->y.computed) * xform );
348 /* This function takes care of translation and scaling, we return whatever parts we can't
349 handle. */
350 NR::Matrix ret(NR::transform(xform));
351 gdouble const sw = hypot(ret[0], ret[1]);
352 gdouble const sh = hypot(ret[2], ret[3]);
353 if (sw > 1e-9) {
354 ret[0] /= sw;
355 ret[1] /= sw;
356 } else {
357 ret[0] = 1.0;
358 ret[1] = 0.0;
359 }
360 if (sh > 1e-9) {
361 ret[2] /= sh;
362 ret[3] /= sh;
363 } else {
364 ret[2] = 0.0;
365 ret[3] = 1.0;
366 }
368 /* fixme: Would be nice to preserve units here */
369 rect->width = rect->width.computed * sw;
370 rect->height = rect->height.computed * sh;
371 if (rect->rx._set) {
372 rect->rx = rect->rx.computed * sw;
373 }
374 if (rect->ry._set) {
375 rect->ry = rect->ry.computed * sh;
376 }
378 /* Find start in item coords */
379 pos = pos * ret.inverse();
380 rect->x = pos[NR::X];
381 rect->y = pos[NR::Y];
383 sp_rect_set_shape(rect);
385 // Adjust stroke width
386 sp_item_adjust_stroke(item, sqrt(fabs(sw * sh)));
388 // Adjust pattern fill
389 sp_item_adjust_pattern(item, xform / ret);
391 // Adjust gradient fill
392 sp_item_adjust_gradient(item, xform / ret);
394 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
396 return ret;
397 }
400 /**
401 Returns the ratio in which the vector from p0 to p1 is stretched by transform
402 */
403 static gdouble
404 vector_stretch(NR::Point p0, NR::Point p1, NR::Matrix xform)
405 {
406 if (p0 == p1)
407 return 0;
408 return (NR::distance(p0 * xform, p1 * xform) / NR::distance(p0, p1));
409 }
411 void
412 sp_rect_set_visible_rx(SPRect *rect, gdouble rx)
413 {
414 if (rx == 0) {
415 rect->rx.computed = 0;
416 rect->rx._set = false;
417 } else {
418 rect->rx.computed = rx / vector_stretch(
419 NR::Point(rect->x.computed + 1, rect->y.computed),
420 NR::Point(rect->x.computed, rect->y.computed),
421 SP_ITEM(rect)->transform);
422 rect->rx._set = true;
423 }
424 SP_OBJECT(rect)->updateRepr();
425 }
427 void
428 sp_rect_set_visible_ry(SPRect *rect, gdouble ry)
429 {
430 if (ry == 0) {
431 rect->ry.computed = 0;
432 rect->ry._set = false;
433 } else {
434 rect->ry.computed = ry / vector_stretch(
435 NR::Point(rect->x.computed, rect->y.computed + 1),
436 NR::Point(rect->x.computed, rect->y.computed),
437 SP_ITEM(rect)->transform);
438 rect->ry._set = true;
439 }
440 SP_OBJECT(rect)->updateRepr();
441 }
443 gdouble
444 sp_rect_get_visible_rx(SPRect *rect)
445 {
446 if (!rect->rx._set)
447 return 0;
448 return rect->rx.computed * vector_stretch(
449 NR::Point(rect->x.computed + 1, rect->y.computed),
450 NR::Point(rect->x.computed, rect->y.computed),
451 SP_ITEM(rect)->transform);
452 }
454 gdouble
455 sp_rect_get_visible_ry(SPRect *rect)
456 {
457 if (!rect->ry._set)
458 return 0;
459 return rect->ry.computed * vector_stretch(
460 NR::Point(rect->x.computed, rect->y.computed + 1),
461 NR::Point(rect->x.computed, rect->y.computed),
462 SP_ITEM(rect)->transform);
463 }
465 void
466 sp_rect_compensate_rxry(SPRect *rect, NR::Matrix xform)
467 {
468 if (rect->rx.computed == 0 && rect->ry.computed == 0)
469 return; // nothing to compensate
471 // test unit vectors to find out compensation:
472 NR::Point c(rect->x.computed, rect->y.computed);
473 NR::Point cx = c + NR::Point(1, 0);
474 NR::Point cy = c + NR::Point(0, 1);
476 // apply previous transform if any
477 c *= SP_ITEM(rect)->transform;
478 cx *= SP_ITEM(rect)->transform;
479 cy *= SP_ITEM(rect)->transform;
481 // find out stretches that we need to compensate
482 gdouble eX = vector_stretch(cx, c, xform);
483 gdouble eY = vector_stretch(cy, c, xform);
485 // If only one of the radii is set, set both radii so they have the same visible length
486 // This is needed because if we just set them the same length in SVG, they might end up unequal because of transform
487 if ((rect->rx._set && !rect->ry._set) || (rect->ry._set && !rect->rx._set)) {
488 gdouble r = MAX(rect->rx.computed, rect->ry.computed);
489 rect->rx.computed = r / eX;
490 rect->ry.computed = r / eY;
491 } else {
492 rect->rx.computed = rect->rx.computed / eX;
493 rect->ry.computed = rect->ry.computed / eY;
494 }
496 // Note that a radius may end up larger than half-side if the rect is scaled down;
497 // that's ok because this preserves the intended radii in case the rect is enlarged again,
498 // and set_shape will take care of trimming too large radii when generating d=
500 rect->rx._set = rect->ry._set = true;
501 }
503 void
504 sp_rect_set_visible_width(SPRect *rect, gdouble width)
505 {
506 rect->width.computed = width / vector_stretch(
507 NR::Point(rect->x.computed + 1, rect->y.computed),
508 NR::Point(rect->x.computed, rect->y.computed),
509 SP_ITEM(rect)->transform);
510 rect->width._set = true;
511 SP_OBJECT(rect)->updateRepr();
512 }
514 void
515 sp_rect_set_visible_height(SPRect *rect, gdouble height)
516 {
517 rect->height.computed = height / vector_stretch(
518 NR::Point(rect->x.computed, rect->y.computed + 1),
519 NR::Point(rect->x.computed, rect->y.computed),
520 SP_ITEM(rect)->transform);
521 rect->height._set = true;
522 SP_OBJECT(rect)->updateRepr();
523 }
525 gdouble
526 sp_rect_get_visible_width(SPRect *rect)
527 {
528 if (!rect->width._set)
529 return 0;
530 return rect->width.computed * vector_stretch(
531 NR::Point(rect->x.computed + 1, rect->y.computed),
532 NR::Point(rect->x.computed, rect->y.computed),
533 SP_ITEM(rect)->transform);
534 }
536 gdouble
537 sp_rect_get_visible_height(SPRect *rect)
538 {
539 if (!rect->height._set)
540 return 0;
541 return rect->height.computed * vector_stretch(
542 NR::Point(rect->x.computed, rect->y.computed + 1),
543 NR::Point(rect->x.computed, rect->y.computed),
544 SP_ITEM(rect)->transform);
545 }
547 /**
548 * Sets the snappoint p to the unrounded corners of the rectangle
549 */
550 static void sp_rect_snappoints(SPItem const *item, SnapPointsIter p)
551 {
552 /* This method overrides sp_shape_snappoints, which is the default for any shape. The default method
553 returns all eight points along the path of a rounded rectangle, but not the real corners. Snapping
554 the startpoint and endpoint of each rounded corner is not very usefull and really confusing. Instead
555 we could snap either the real corners, or not snap at all. Bulia Byak opted to snap the real corners,
556 but it should be noted that this might be confusing in some cases with relatively large radii. With
557 small radii though the user will easily understand which point is snapping. */
559 g_assert(item != NULL);
560 g_assert(SP_IS_RECT(item));
562 SPRect *rect = SP_RECT(item);
564 NR::Matrix const i2d (from_2geom(sp_item_i2d_affine (item)));
566 *p = NR::Point(rect->x.computed, rect->y.computed) * i2d;
567 *p = NR::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d;
568 *p = NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d;
569 *p = NR::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d;
570 }
572 void
573 sp_rect_convert_to_guides(SPItem *item) {
574 SPRect *rect = SP_RECT(item);
576 if (prefs_get_int_attribute("tools.shapes.rect", "convertguides", 1) == 0) {
577 sp_item_convert_to_guides(SP_ITEM(rect));
578 return;
579 }
581 SPDocument *doc = SP_OBJECT_DOCUMENT(rect);
582 std::list<std::pair<Geom::Point, Geom::Point> > pts;
584 NR::Matrix const i2d (from_2geom(sp_item_i2d_affine(SP_ITEM(rect))));
586 NR::Point A1(NR::Point(rect->x.computed, rect->y.computed) * i2d);
587 NR::Point A2(NR::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d);
588 NR::Point A3(NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d);
589 NR::Point A4(NR::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d);
591 pts.push_back(std::make_pair(A1.to_2geom(), A2.to_2geom()));
592 pts.push_back(std::make_pair(A2.to_2geom(), A3.to_2geom()));
593 pts.push_back(std::make_pair(A3.to_2geom(), A4.to_2geom()));
594 pts.push_back(std::make_pair(A4.to_2geom(), A1.to_2geom()));
596 sp_guide_pt_pairs_to_guides(doc, pts);
597 }
599 /*
600 Local Variables:
601 mode:c++
602 c-file-style:"stroustrup"
603 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
604 indent-tabs-mode:nil
605 fill-column:99
606 End:
607 */
608 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :