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"
32 #define noRECT_VERBOSE
34 static void sp_rect_class_init(SPRectClass *klass);
35 static void sp_rect_init(SPRect *rect);
37 static void sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
38 static void sp_rect_set(SPObject *object, unsigned key, gchar const *value);
39 static void sp_rect_update(SPObject *object, SPCtx *ctx, guint flags);
40 static Inkscape::XML::Node *sp_rect_write(SPObject *object, Inkscape::XML::Node *repr, guint flags);
42 static gchar *sp_rect_description(SPItem *item);
43 static NR::Matrix sp_rect_set_transform(SPItem *item, NR::Matrix const &xform);
45 static void sp_rect_set_shape(SPShape *shape);
46 static void sp_rect_snappoints(SPItem const *item, SnapPointsIter p);
48 static SPShapeClass *parent_class;
50 GType
51 sp_rect_get_type(void)
52 {
53 static GType type = 0;
55 if (!type) {
56 GTypeInfo info = {
57 sizeof(SPRectClass),
58 NULL, /* base_init */
59 NULL, /* base_finalize */
60 (GClassInitFunc) sp_rect_class_init,
61 NULL, /* class_finalize */
62 NULL, /* class_data */
63 sizeof(SPRect),
64 16, /* n_preallocs */
65 (GInstanceInitFunc) sp_rect_init,
66 NULL, /* value_table */
67 };
68 type = g_type_register_static(SP_TYPE_SHAPE, "SPRect", &info, (GTypeFlags)0);
69 }
70 return type;
71 }
73 static void
74 sp_rect_class_init(SPRectClass *klass)
75 {
76 SPObjectClass *sp_object_class = (SPObjectClass *) klass;
77 SPItemClass *item_class = (SPItemClass *) klass;
78 SPShapeClass *shape_class = (SPShapeClass *) klass;
80 parent_class = (SPShapeClass *)g_type_class_ref(SP_TYPE_SHAPE);
82 sp_object_class->build = sp_rect_build;
83 sp_object_class->write = sp_rect_write;
84 sp_object_class->set = sp_rect_set;
85 sp_object_class->update = sp_rect_update;
87 item_class->description = sp_rect_description;
88 item_class->set_transform = sp_rect_set_transform;
89 item_class->snappoints = sp_rect_snappoints; //override the default sp_shape_snappoints; see sp_rect_snappoints for details
91 shape_class->set_shape = sp_rect_set_shape;
92 }
94 static void
95 sp_rect_init(SPRect *rect)
96 {
97 /* Initializing to zero is automatic */
98 /* sp_svg_length_unset(&rect->x, SP_SVG_UNIT_NONE, 0.0, 0.0); */
99 /* sp_svg_length_unset(&rect->y, SP_SVG_UNIT_NONE, 0.0, 0.0); */
100 /* sp_svg_length_unset(&rect->width, SP_SVG_UNIT_NONE, 0.0, 0.0); */
101 /* sp_svg_length_unset(&rect->height, SP_SVG_UNIT_NONE, 0.0, 0.0); */
102 /* sp_svg_length_unset(&rect->rx, SP_SVG_UNIT_NONE, 0.0, 0.0); */
103 /* sp_svg_length_unset(&rect->ry, SP_SVG_UNIT_NONE, 0.0, 0.0); */
104 }
106 static void
107 sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
108 {
109 SPRect *rect = SP_RECT(object);
111 if (((SPObjectClass *) parent_class)->build)
112 ((SPObjectClass *) parent_class)->build(object, document, repr);
114 sp_object_read_attr(object, "x");
115 sp_object_read_attr(object, "y");
116 sp_object_read_attr(object, "width");
117 sp_object_read_attr(object, "height");
118 sp_object_read_attr(object, "rx");
119 sp_object_read_attr(object, "ry");
121 Inkscape::Version const version = sp_object_get_sodipodi_version(object);
123 if ( version.major == 0 && version.minor == 29 ) {
124 if (rect->rx._set && rect->ry._set) {
125 /* 0.29 treated 0.0 radius as missing value */
126 if ((rect->rx.value != 0.0) && (rect->ry.value == 0.0)) {
127 repr->setAttribute("ry", NULL);
128 sp_object_read_attr(object, "ry");
129 } else if ((rect->ry.value != 0.0) && (rect->rx.value == 0.0)) {
130 repr->setAttribute("rx", NULL);
131 sp_object_read_attr(object, "rx");
132 }
133 }
134 }
135 }
137 static void
138 sp_rect_set(SPObject *object, unsigned key, gchar const *value)
139 {
140 SPRect *rect = SP_RECT(object);
142 /* fixme: We need real error processing some time */
144 switch (key) {
145 case SP_ATTR_X:
146 rect->x.readOrUnset(value);
147 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
148 break;
149 case SP_ATTR_Y:
150 rect->y.readOrUnset(value);
151 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
152 break;
153 case SP_ATTR_WIDTH:
154 if (!rect->width.read(value) || rect->width.value < 0.0) {
155 rect->width.unset();
156 }
157 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
158 break;
159 case SP_ATTR_HEIGHT:
160 if (!rect->height.read(value) || rect->height.value < 0.0) {
161 rect->height.unset();
162 }
163 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
164 break;
165 case SP_ATTR_RX:
166 if (!rect->rx.read(value) || rect->rx.value < 0.0) {
167 rect->rx.unset();
168 }
169 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
170 break;
171 case SP_ATTR_RY:
172 if (!rect->ry.read(value) || rect->ry.value < 0.0) {
173 rect->ry.unset();
174 }
175 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
176 break;
177 default:
178 if (((SPObjectClass *) parent_class)->set)
179 ((SPObjectClass *) parent_class)->set(object, key, value);
180 break;
181 }
182 }
184 static void
185 sp_rect_update(SPObject *object, SPCtx *ctx, guint flags)
186 {
187 if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
188 SPRect *rect = (SPRect *) object;
189 SPStyle *style = object->style;
190 SPItemCtx const *ictx = (SPItemCtx const *) ctx;
191 double const d = NR::expansion(ictx->i2vp);
192 double const w = (ictx->vp.x1 - ictx->vp.x0) / d;
193 double const h = (ictx->vp.y1 - ictx->vp.y0) / d;
194 double const em = style->font_size.computed;
195 double const ex = 0.5 * em; // fixme: get x height from pango or libnrtype.
196 rect->x.update(em, ex, w);
197 rect->y.update(em, ex, h);
198 rect->width.update(em, ex, w);
199 rect->height.update(em, ex, h);
200 rect->rx.update(em, ex, w);
201 rect->ry.update(em, ex, h);
202 sp_shape_set_shape((SPShape *) object);
203 flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore
204 }
206 if (((SPObjectClass *) parent_class)->update)
207 ((SPObjectClass *) parent_class)->update(object, ctx, flags);
208 }
210 static Inkscape::XML::Node *
211 sp_rect_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
212 {
213 SPRect *rect = SP_RECT(object);
215 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
216 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
217 repr = xml_doc->createElement("svg:rect");
218 }
220 sp_repr_set_svg_double(repr, "width", rect->width.computed);
221 sp_repr_set_svg_double(repr, "height", rect->height.computed);
222 if (rect->rx._set) sp_repr_set_svg_double(repr, "rx", rect->rx.computed);
223 if (rect->ry._set) sp_repr_set_svg_double(repr, "ry", rect->ry.computed);
224 sp_repr_set_svg_double(repr, "x", rect->x.computed);
225 sp_repr_set_svg_double(repr, "y", rect->y.computed);
227 if (((SPObjectClass *) parent_class)->write)
228 ((SPObjectClass *) parent_class)->write(object, repr, flags);
230 return repr;
231 }
233 static gchar *
234 sp_rect_description(SPItem *item)
235 {
236 g_return_val_if_fail(SP_IS_RECT(item), NULL);
238 return g_strdup(_("<b>Rectangle</b>"));
239 }
241 #define C1 0.554
243 static void
244 sp_rect_set_shape(SPShape *shape)
245 {
246 SPRect *rect = (SPRect *) shape;
248 if ((rect->height.computed < 1e-18) || (rect->width.computed < 1e-18)) return;
250 SPCurve *c = sp_curve_new();
252 double const x = rect->x.computed;
253 double const y = rect->y.computed;
254 double const w = rect->width.computed;
255 double const h = rect->height.computed;
256 double const w2 = w / 2;
257 double const h2 = h / 2;
258 double const rx = std::min(( rect->rx._set
259 ? rect->rx.computed
260 : ( rect->ry._set
261 ? rect->ry.computed
262 : 0.0 ) ),
263 .5 * rect->width.computed);
264 double const ry = std::min(( rect->ry._set
265 ? rect->ry.computed
266 : ( rect->rx._set
267 ? rect->rx.computed
268 : 0.0 ) ),
269 .5 * rect->height.computed);
270 /* TODO: Handle negative rx or ry as per
271 * http://www.w3.org/TR/SVG11/shapes.html#RectElementRXAttribute once Inkscape has proper error
272 * handling (see http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing).
273 */
275 /* We don't use proper circular/elliptical arcs, but bezier curves can approximate a 90-degree
276 * arc fairly well.
277 */
278 if ((rx > 1e-18) && (ry > 1e-18)) {
279 sp_curve_moveto(c, x + rx, y);
280 if (rx < w2) sp_curve_lineto(c, x + w - rx, y);
281 sp_curve_curveto(c, x + w - rx * (1 - C1), y, x + w, y + ry * (1 - C1), x + w, y + ry);
282 if (ry < h2) sp_curve_lineto(c, x + w, y + h - ry);
283 sp_curve_curveto(c, x + w, y + h - ry * (1 - C1), x + w - rx * (1 - C1), y + h, x + w - rx, y + h);
284 if (rx < w2) sp_curve_lineto(c, x + rx, y + h);
285 sp_curve_curveto(c, x + rx * (1 - C1), y + h, x, y + h - ry * (1 - C1), x, y + h - ry);
286 if (ry < h2) sp_curve_lineto(c, x, y + ry);
287 sp_curve_curveto(c, x, y + ry * (1 - C1), x + rx * (1 - C1), y, x + rx, y);
288 } else {
289 sp_curve_moveto(c, x + 0.0, y + 0.0);
290 sp_curve_lineto(c, x + w, y + 0.0);
291 sp_curve_lineto(c, x + w, y + h);
292 sp_curve_lineto(c, x + 0.0, y + h);
293 sp_curve_lineto(c, x + 0.0, y + 0.0);
294 }
296 sp_curve_closepath_current(c);
297 sp_shape_set_curve_insync(SP_SHAPE(rect), c, TRUE);
298 sp_curve_unref(c);
299 }
301 /* fixme: Think (Lauris) */
303 void
304 sp_rect_position_set(SPRect *rect, gdouble x, gdouble y, gdouble width, gdouble height)
305 {
306 g_return_if_fail(rect != NULL);
307 g_return_if_fail(SP_IS_RECT(rect));
309 rect->x.computed = x;
310 rect->y.computed = y;
311 rect->width.computed = width;
312 rect->height.computed = height;
314 SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
315 }
317 void
318 sp_rect_set_rx(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->rx._set = set;
324 if (set) rect->rx.computed = value;
326 SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
327 }
329 void
330 sp_rect_set_ry(SPRect *rect, gboolean set, gdouble value)
331 {
332 g_return_if_fail(rect != NULL);
333 g_return_if_fail(SP_IS_RECT(rect));
335 rect->ry._set = set;
336 if (set) rect->ry.computed = value;
338 SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
339 }
341 /*
342 * Initially we'll do:
343 * Transform x, y, set x, y, clear translation
344 */
346 /* fixme: Use preferred units somehow (Lauris) */
347 /* fixme: Alternately preserve whatever units there are (lauris) */
349 static NR::Matrix
350 sp_rect_set_transform(SPItem *item, NR::Matrix const &xform)
351 {
352 SPRect *rect = SP_RECT(item);
354 /* Calculate rect start in parent coords. */
355 NR::Point pos( NR::Point(rect->x.computed, rect->y.computed) * xform );
357 /* This function takes care of translation and scaling, we return whatever parts we can't
358 handle. */
359 NR::Matrix ret(NR::transform(xform));
360 gdouble const sw = hypot(ret[0], ret[1]);
361 gdouble const sh = hypot(ret[2], ret[3]);
362 if (sw > 1e-9) {
363 ret[0] /= sw;
364 ret[1] /= sw;
365 } else {
366 ret[0] = 1.0;
367 ret[1] = 0.0;
368 }
369 if (sh > 1e-9) {
370 ret[2] /= sh;
371 ret[3] /= sh;
372 } else {
373 ret[2] = 0.0;
374 ret[3] = 1.0;
375 }
377 /* fixme: Would be nice to preserve units here */
378 rect->width = rect->width.computed * sw;
379 rect->height = rect->height.computed * sh;
380 if (rect->rx._set) {
381 rect->rx = rect->rx.computed * sw;
382 }
383 if (rect->ry._set) {
384 rect->ry = rect->ry.computed * sh;
385 }
387 /* Find start in item coords */
388 pos = pos * ret.inverse();
389 rect->x = pos[NR::X];
390 rect->y = pos[NR::Y];
392 sp_rect_set_shape(rect);
394 // Adjust stroke width
395 sp_item_adjust_stroke(item, sqrt(fabs(sw * sh)));
397 // Adjust pattern fill
398 sp_item_adjust_pattern(item, xform / ret);
400 // Adjust gradient fill
401 sp_item_adjust_gradient(item, xform / ret);
403 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
405 return ret;
406 }
409 /**
410 Returns the ratio in which the vector from p0 to p1 is stretched by transform
411 */
412 static gdouble
413 vector_stretch(NR::Point p0, NR::Point p1, NR::Matrix xform)
414 {
415 if (p0 == p1)
416 return 0;
417 return (NR::distance(p0 * xform, p1 * xform) / NR::distance(p0, p1));
418 }
420 void
421 sp_rect_set_visible_rx(SPRect *rect, gdouble rx)
422 {
423 if (rx == 0) {
424 rect->rx.computed = 0;
425 rect->rx._set = false;
426 } else {
427 rect->rx.computed = rx / vector_stretch(
428 NR::Point(rect->x.computed + 1, rect->y.computed),
429 NR::Point(rect->x.computed, rect->y.computed),
430 SP_ITEM(rect)->transform);
431 rect->rx._set = true;
432 }
433 SP_OBJECT(rect)->updateRepr();
434 }
436 void
437 sp_rect_set_visible_ry(SPRect *rect, gdouble ry)
438 {
439 if (ry == 0) {
440 rect->ry.computed = 0;
441 rect->ry._set = false;
442 } else {
443 rect->ry.computed = ry / vector_stretch(
444 NR::Point(rect->x.computed, rect->y.computed + 1),
445 NR::Point(rect->x.computed, rect->y.computed),
446 SP_ITEM(rect)->transform);
447 rect->ry._set = true;
448 }
449 SP_OBJECT(rect)->updateRepr();
450 }
452 gdouble
453 sp_rect_get_visible_rx(SPRect *rect)
454 {
455 if (!rect->rx._set)
456 return 0;
457 return rect->rx.computed * vector_stretch(
458 NR::Point(rect->x.computed + 1, rect->y.computed),
459 NR::Point(rect->x.computed, rect->y.computed),
460 SP_ITEM(rect)->transform);
461 }
463 gdouble
464 sp_rect_get_visible_ry(SPRect *rect)
465 {
466 if (!rect->ry._set)
467 return 0;
468 return rect->ry.computed * vector_stretch(
469 NR::Point(rect->x.computed, rect->y.computed + 1),
470 NR::Point(rect->x.computed, rect->y.computed),
471 SP_ITEM(rect)->transform);
472 }
474 void
475 sp_rect_compensate_rxry(SPRect *rect, NR::Matrix xform)
476 {
477 if (rect->rx.computed == 0 && rect->ry.computed == 0)
478 return; // nothing to compensate
480 // test unit vectors to find out compensation:
481 NR::Point c(rect->x.computed, rect->y.computed);
482 NR::Point cx = c + NR::Point(1, 0);
483 NR::Point cy = c + NR::Point(0, 1);
485 // apply previous transform if any
486 c *= SP_ITEM(rect)->transform;
487 cx *= SP_ITEM(rect)->transform;
488 cy *= SP_ITEM(rect)->transform;
490 // find out stretches that we need to compensate
491 gdouble eX = vector_stretch(cx, c, xform);
492 gdouble eY = vector_stretch(cy, c, xform);
494 // If only one of the radii is set, set both radii so they have the same visible length
495 // This is needed because if we just set them the same length in SVG, they might end up unequal because of transform
496 if ((rect->rx._set && !rect->ry._set) || (rect->ry._set && !rect->rx._set)) {
497 gdouble r = MAX(rect->rx.computed, rect->ry.computed);
498 rect->rx.computed = r / eX;
499 rect->ry.computed = r / eY;
500 } else {
501 rect->rx.computed = rect->rx.computed / eX;
502 rect->ry.computed = rect->ry.computed / eY;
503 }
505 // Note that a radius may end up larger than half-side if the rect is scaled down;
506 // that's ok because this preserves the intended radii in case the rect is enlarged again,
507 // and set_shape will take care of trimming too large radii when generating d=
509 rect->rx._set = rect->ry._set = true;
510 }
512 void
513 sp_rect_set_visible_width(SPRect *rect, gdouble width)
514 {
515 rect->width.computed = width / vector_stretch(
516 NR::Point(rect->x.computed + 1, rect->y.computed),
517 NR::Point(rect->x.computed, rect->y.computed),
518 SP_ITEM(rect)->transform);
519 rect->width._set = true;
520 SP_OBJECT(rect)->updateRepr();
521 }
523 void
524 sp_rect_set_visible_height(SPRect *rect, gdouble height)
525 {
526 rect->height.computed = height / vector_stretch(
527 NR::Point(rect->x.computed, rect->y.computed + 1),
528 NR::Point(rect->x.computed, rect->y.computed),
529 SP_ITEM(rect)->transform);
530 rect->height._set = true;
531 SP_OBJECT(rect)->updateRepr();
532 }
534 gdouble
535 sp_rect_get_visible_width(SPRect *rect)
536 {
537 if (!rect->width._set)
538 return 0;
539 return rect->width.computed * vector_stretch(
540 NR::Point(rect->x.computed + 1, rect->y.computed),
541 NR::Point(rect->x.computed, rect->y.computed),
542 SP_ITEM(rect)->transform);
543 }
545 gdouble
546 sp_rect_get_visible_height(SPRect *rect)
547 {
548 if (!rect->height._set)
549 return 0;
550 return rect->height.computed * vector_stretch(
551 NR::Point(rect->x.computed, rect->y.computed + 1),
552 NR::Point(rect->x.computed, rect->y.computed),
553 SP_ITEM(rect)->transform);
554 }
556 /**
557 * Sets the snappoint p to the unrounded corners of the rectangle
558 */
559 static void sp_rect_snappoints(SPItem const *item, SnapPointsIter p)
560 {
561 /* This method overrides sp_shape_snappoints, which is the default for any shape. The default method
562 returns all eight points along the path of a rounded rectangle, but not the real corners. Snapping
563 the startpoint and endpoint of each rounded corner is not very usefull and really confusing. Instead
564 we could snap either the real corners, or not snap at all. Bulia Byak opted to snap the real corners,
565 but it should be noted that this might be confusing in some cases with relatively large radii. With
566 small radii though the user will easily understand which point is snapping. */
568 g_assert(item != NULL);
569 g_assert(SP_IS_RECT(item));
571 SPRect *rect = SP_RECT(item);
573 NR::Matrix const i2d (sp_item_i2d_affine (item));
575 *p = NR::Point(rect->x.computed, rect->y.computed) * i2d;
576 *p = NR::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d;
577 *p = NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d;
578 *p = NR::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d;
579 }
581 /*
582 Local Variables:
583 mode:c++
584 c-file-style:"stroustrup"
585 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
586 indent-tabs-mode:nil
587 fill-column:99
588 End:
589 */
590 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :