e5f1da05d24eb735e527ef04bfee4e60da0b57e0
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 g_print ("sp_rect_set_transform\n");
353 SPRect *rect = SP_RECT(item);
355 /* Calculate rect start in parent coords. */
356 NR::Point pos( NR::Point(rect->x.computed, rect->y.computed) * xform );
358 /* This function takes care of translation and scaling, we return whatever parts we can't
359 handle. */
360 NR::Matrix ret(NR::transform(xform));
361 gdouble const sw = hypot(ret[0], ret[1]);
362 gdouble const sh = hypot(ret[2], ret[3]);
363 if (sw > 1e-9) {
364 ret[0] /= sw;
365 ret[1] /= sw;
366 } else {
367 ret[0] = 1.0;
368 ret[1] = 0.0;
369 }
370 if (sh > 1e-9) {
371 ret[2] /= sh;
372 ret[3] /= sh;
373 } else {
374 ret[2] = 0.0;
375 ret[3] = 1.0;
376 }
378 /* fixme: Would be nice to preserve units here */
379 rect->width = rect->width.computed * sw;
380 rect->height = rect->height.computed * sh;
381 if (rect->rx._set) {
382 rect->rx = rect->rx.computed * sw;
383 }
384 if (rect->ry._set) {
385 rect->ry = rect->ry.computed * sh;
386 }
388 /* Find start in item coords */
389 pos = pos * ret.inverse();
390 rect->x = pos[NR::X];
391 rect->y = pos[NR::Y];
393 sp_rect_set_shape(rect);
395 // Adjust stroke width
396 sp_item_adjust_stroke(item, sqrt(fabs(sw * sh)));
398 // Adjust pattern fill
399 sp_item_adjust_pattern(item, xform / ret);
401 // Adjust gradient fill
402 sp_item_adjust_gradient(item, xform / ret);
404 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
406 return ret;
407 }
410 /**
411 Returns the ratio in which the vector from p0 to p1 is stretched by transform
412 */
413 static gdouble
414 vector_stretch(NR::Point p0, NR::Point p1, NR::Matrix xform)
415 {
416 if (p0 == p1)
417 return 0;
418 return (NR::distance(p0 * xform, p1 * xform) / NR::distance(p0, p1));
419 }
421 void
422 sp_rect_set_visible_rx(SPRect *rect, gdouble rx)
423 {
424 if (rx == 0) {
425 rect->rx.computed = 0;
426 rect->rx._set = false;
427 } else {
428 rect->rx.computed = rx / vector_stretch(
429 NR::Point(rect->x.computed + 1, rect->y.computed),
430 NR::Point(rect->x.computed, rect->y.computed),
431 SP_ITEM(rect)->transform);
432 rect->rx._set = true;
433 }
434 SP_OBJECT(rect)->updateRepr();
435 }
437 void
438 sp_rect_set_visible_ry(SPRect *rect, gdouble ry)
439 {
440 if (ry == 0) {
441 rect->ry.computed = 0;
442 rect->ry._set = false;
443 } else {
444 rect->ry.computed = ry / vector_stretch(
445 NR::Point(rect->x.computed, rect->y.computed + 1),
446 NR::Point(rect->x.computed, rect->y.computed),
447 SP_ITEM(rect)->transform);
448 rect->ry._set = true;
449 }
450 SP_OBJECT(rect)->updateRepr();
451 }
453 gdouble
454 sp_rect_get_visible_rx(SPRect *rect)
455 {
456 if (!rect->rx._set)
457 return 0;
458 return rect->rx.computed * vector_stretch(
459 NR::Point(rect->x.computed + 1, rect->y.computed),
460 NR::Point(rect->x.computed, rect->y.computed),
461 SP_ITEM(rect)->transform);
462 }
464 gdouble
465 sp_rect_get_visible_ry(SPRect *rect)
466 {
467 if (!rect->ry._set)
468 return 0;
469 return rect->ry.computed * vector_stretch(
470 NR::Point(rect->x.computed, rect->y.computed + 1),
471 NR::Point(rect->x.computed, rect->y.computed),
472 SP_ITEM(rect)->transform);
473 }
475 void
476 sp_rect_compensate_rxry(SPRect *rect, NR::Matrix xform)
477 {
478 if (rect->rx.computed == 0 && rect->ry.computed == 0)
479 return; // nothing to compensate
481 // test unit vectors to find out compensation:
482 NR::Point c(rect->x.computed, rect->y.computed);
483 NR::Point cx = c + NR::Point(1, 0);
484 NR::Point cy = c + NR::Point(0, 1);
486 // apply previous transform if any
487 c *= SP_ITEM(rect)->transform;
488 cx *= SP_ITEM(rect)->transform;
489 cy *= SP_ITEM(rect)->transform;
491 // find out stretches that we need to compensate
492 gdouble eX = vector_stretch(cx, c, xform);
493 gdouble eY = vector_stretch(cy, c, xform);
495 // If only one of the radii is set, set both radii so they have the same visible length
496 // This is needed because if we just set them the same length in SVG, they might end up unequal because of transform
497 if ((rect->rx._set && !rect->ry._set) || (rect->ry._set && !rect->rx._set)) {
498 gdouble r = MAX(rect->rx.computed, rect->ry.computed);
499 rect->rx.computed = r / eX;
500 rect->ry.computed = r / eY;
501 } else {
502 rect->rx.computed = rect->rx.computed / eX;
503 rect->ry.computed = rect->ry.computed / eY;
504 }
506 // Note that a radius may end up larger than half-side if the rect is scaled down;
507 // that's ok because this preserves the intended radii in case the rect is enlarged again,
508 // and set_shape will take care of trimming too large radii when generating d=
510 rect->rx._set = rect->ry._set = true;
511 }
513 void
514 sp_rect_set_visible_width(SPRect *rect, gdouble width)
515 {
516 rect->width.computed = width / vector_stretch(
517 NR::Point(rect->x.computed + 1, rect->y.computed),
518 NR::Point(rect->x.computed, rect->y.computed),
519 SP_ITEM(rect)->transform);
520 rect->width._set = true;
521 SP_OBJECT(rect)->updateRepr();
522 }
524 void
525 sp_rect_set_visible_height(SPRect *rect, gdouble height)
526 {
527 rect->height.computed = height / vector_stretch(
528 NR::Point(rect->x.computed, rect->y.computed + 1),
529 NR::Point(rect->x.computed, rect->y.computed),
530 SP_ITEM(rect)->transform);
531 rect->height._set = true;
532 SP_OBJECT(rect)->updateRepr();
533 }
535 gdouble
536 sp_rect_get_visible_width(SPRect *rect)
537 {
538 if (!rect->width._set)
539 return 0;
540 return rect->width.computed * vector_stretch(
541 NR::Point(rect->x.computed + 1, rect->y.computed),
542 NR::Point(rect->x.computed, rect->y.computed),
543 SP_ITEM(rect)->transform);
544 }
546 gdouble
547 sp_rect_get_visible_height(SPRect *rect)
548 {
549 if (!rect->height._set)
550 return 0;
551 return rect->height.computed * vector_stretch(
552 NR::Point(rect->x.computed, rect->y.computed + 1),
553 NR::Point(rect->x.computed, rect->y.computed),
554 SP_ITEM(rect)->transform);
555 }
557 /**
558 * Sets the snappoint p to the unrounded corners of the rectangle
559 */
560 static void sp_rect_snappoints(SPItem const *item, SnapPointsIter p)
561 {
562 /* This method overrides sp_shape_snappoints, which is the default for any shape. The default method
563 returns all eight points along the path of a rounded rectangle, but not the real corners. Snapping
564 the startpoint and endpoint of each rounded corner is not very usefull and really confusing. Instead
565 we could snap either the real corners, or not snap at all. Bulia Byak opted to snap the real corners,
566 but it should be noted that this might be confusing in some cases with relatively large radii. With
567 small radii though the user will easily understand which point is snapping. */
569 g_assert(item != NULL);
570 g_assert(SP_IS_RECT(item));
572 SPRect *rect = SP_RECT(item);
574 NR::Matrix const i2d (sp_item_i2d_affine (item));
576 *p = NR::Point(rect->x.computed, rect->y.computed) * i2d;
577 *p = NR::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d;
578 *p = NR::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d;
579 *p = NR::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d;
580 }
582 /*
583 Local Variables:
584 mode:c++
585 c-file-style:"stroustrup"
586 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
587 indent-tabs-mode:nil
588 fill-column:99
589 End:
590 */
591 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :