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 "attributes.h"
26 #include "style.h"
27 #include "sp-rect.h"
28 #include <glibmm/i18n.h>
29 #include "xml/repr.h"
31 #define noRECT_VERBOSE
33 static void sp_rect_class_init(SPRectClass *klass);
34 static void sp_rect_init(SPRect *rect);
36 static void sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
37 static void sp_rect_set(SPObject *object, unsigned key, gchar const *value);
38 static void sp_rect_update(SPObject *object, SPCtx *ctx, guint flags);
39 static Inkscape::XML::Node *sp_rect_write(SPObject *object, Inkscape::XML::Node *repr, guint flags);
41 static gchar *sp_rect_description(SPItem *item);
42 static NR::Matrix sp_rect_set_transform(SPItem *item, NR::Matrix const &xform);
44 static void sp_rect_set_shape(SPShape *shape);
46 static SPShapeClass *parent_class;
48 GType
49 sp_rect_get_type(void)
50 {
51 static GType type = 0;
53 if (!type) {
54 GTypeInfo info = {
55 sizeof(SPRectClass),
56 NULL, /* base_init */
57 NULL, /* base_finalize */
58 (GClassInitFunc) sp_rect_class_init,
59 NULL, /* class_finalize */
60 NULL, /* class_data */
61 sizeof(SPRect),
62 16, /* n_preallocs */
63 (GInstanceInitFunc) sp_rect_init,
64 NULL, /* value_table */
65 };
66 type = g_type_register_static(SP_TYPE_SHAPE, "SPRect", &info, (GTypeFlags)0);
67 }
68 return type;
69 }
71 static void
72 sp_rect_class_init(SPRectClass *klass)
73 {
74 SPObjectClass *sp_object_class = (SPObjectClass *) klass;
75 SPItemClass *item_class = (SPItemClass *) klass;
76 SPShapeClass *shape_class = (SPShapeClass *) klass;
78 parent_class = (SPShapeClass *)g_type_class_ref(SP_TYPE_SHAPE);
80 sp_object_class->build = sp_rect_build;
81 sp_object_class->write = sp_rect_write;
82 sp_object_class->set = sp_rect_set;
83 sp_object_class->update = sp_rect_update;
85 item_class->description = sp_rect_description;
86 item_class->set_transform = sp_rect_set_transform;
88 shape_class->set_shape = sp_rect_set_shape;
89 }
91 static void
92 sp_rect_init(SPRect *rect)
93 {
94 /* Initializing to zero is automatic */
95 /* sp_svg_length_unset(&rect->x, SP_SVG_UNIT_NONE, 0.0, 0.0); */
96 /* sp_svg_length_unset(&rect->y, SP_SVG_UNIT_NONE, 0.0, 0.0); */
97 /* sp_svg_length_unset(&rect->width, SP_SVG_UNIT_NONE, 0.0, 0.0); */
98 /* sp_svg_length_unset(&rect->height, SP_SVG_UNIT_NONE, 0.0, 0.0); */
99 /* sp_svg_length_unset(&rect->rx, SP_SVG_UNIT_NONE, 0.0, 0.0); */
100 /* sp_svg_length_unset(&rect->ry, SP_SVG_UNIT_NONE, 0.0, 0.0); */
101 }
103 static void
104 sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
105 {
106 SPRect *rect = SP_RECT(object);
108 if (((SPObjectClass *) parent_class)->build)
109 ((SPObjectClass *) parent_class)->build(object, document, repr);
111 sp_object_read_attr(object, "x");
112 sp_object_read_attr(object, "y");
113 sp_object_read_attr(object, "width");
114 sp_object_read_attr(object, "height");
115 sp_object_read_attr(object, "rx");
116 sp_object_read_attr(object, "ry");
118 Inkscape::Version const version = sp_object_get_sodipodi_version(object);
120 if ( version.major == 0 && version.minor == 29 ) {
121 if (rect->rx._set && rect->ry._set) {
122 /* 0.29 treated 0.0 radius as missing value */
123 if ((rect->rx.value != 0.0) && (rect->ry.value == 0.0)) {
124 repr->setAttribute("ry", NULL);
125 sp_object_read_attr(object, "ry");
126 } else if ((rect->ry.value != 0.0) && (rect->rx.value == 0.0)) {
127 repr->setAttribute("rx", NULL);
128 sp_object_read_attr(object, "rx");
129 }
130 }
131 }
132 }
134 static void
135 sp_rect_set(SPObject *object, unsigned key, gchar const *value)
136 {
137 SPRect *rect = SP_RECT(object);
139 /* fixme: We need real error processing some time */
141 switch (key) {
142 case SP_ATTR_X:
143 rect->x.readOrUnset(value);
144 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
145 break;
146 case SP_ATTR_Y:
147 rect->y.readOrUnset(value);
148 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
149 break;
150 case SP_ATTR_WIDTH:
151 if (!rect->width.read(value) || rect->width.value < 0.0) {
152 rect->width.unset();
153 }
154 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
155 break;
156 case SP_ATTR_HEIGHT:
157 if (!rect->height.read(value) || rect->height.value < 0.0) {
158 rect->height.unset();
159 }
160 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
161 break;
162 case SP_ATTR_RX:
163 if (!rect->rx.read(value) || rect->rx.value < 0.0) {
164 rect->rx.unset();
165 }
166 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
167 break;
168 case SP_ATTR_RY:
169 if (!rect->ry.read(value) || rect->ry.value < 0.0) {
170 rect->ry.unset();
171 }
172 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
173 break;
174 default:
175 if (((SPObjectClass *) parent_class)->set)
176 ((SPObjectClass *) parent_class)->set(object, key, value);
177 break;
178 }
179 }
181 static void
182 sp_rect_update(SPObject *object, SPCtx *ctx, guint flags)
183 {
184 if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
185 SPRect *rect = (SPRect *) object;
186 SPStyle *style = object->style;
187 SPItemCtx const *ictx = (SPItemCtx const *) ctx;
188 double const d = NR::expansion(ictx->i2vp);
189 double const w = (ictx->vp.x1 - ictx->vp.x0) / d;
190 double const h = (ictx->vp.y1 - ictx->vp.y0) / d;
191 double const em = style->font_size.computed;
192 double const ex = 0.5 * em; // fixme: get x height from pango or libnrtype.
193 rect->x.update(em, ex, w);
194 rect->y.update(em, ex, h);
195 rect->width.update(em, ex, w);
196 rect->height.update(em, ex, h);
197 rect->rx.update(em, ex, w);
198 rect->ry.update(em, ex, h);
199 sp_shape_set_shape((SPShape *) object);
200 flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore
201 }
203 if (((SPObjectClass *) parent_class)->update)
204 ((SPObjectClass *) parent_class)->update(object, ctx, flags);
205 }
207 static Inkscape::XML::Node *
208 sp_rect_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
209 {
210 SPRect *rect = SP_RECT(object);
212 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
213 repr = sp_repr_new("svg:rect");
214 }
216 sp_repr_set_svg_double(repr, "width", rect->width.computed);
217 sp_repr_set_svg_double(repr, "height", rect->height.computed);
218 if (rect->rx._set) sp_repr_set_svg_double(repr, "rx", rect->rx.computed);
219 if (rect->ry._set) sp_repr_set_svg_double(repr, "ry", rect->ry.computed);
220 sp_repr_set_svg_double(repr, "x", rect->x.computed);
221 sp_repr_set_svg_double(repr, "y", rect->y.computed);
223 if (((SPObjectClass *) parent_class)->write)
224 ((SPObjectClass *) parent_class)->write(object, repr, flags);
226 return repr;
227 }
229 static gchar *
230 sp_rect_description(SPItem *item)
231 {
232 g_return_val_if_fail(SP_IS_RECT(item), NULL);
234 return g_strdup(_("<b>Rectangle</b>"));
235 }
237 #define C1 0.554
239 static void
240 sp_rect_set_shape(SPShape *shape)
241 {
242 SPRect *rect = (SPRect *) shape;
244 if ((rect->height.computed < 1e-18) || (rect->width.computed < 1e-18)) return;
246 SPCurve *c = sp_curve_new();
248 double const x = rect->x.computed;
249 double const y = rect->y.computed;
250 double const w = rect->width.computed;
251 double const h = rect->height.computed;
252 double const w2 = w / 2;
253 double const h2 = h / 2;
254 double const rx = std::min(( rect->rx._set
255 ? rect->rx.computed
256 : ( rect->ry._set
257 ? rect->ry.computed
258 : 0.0 ) ),
259 .5 * rect->width.computed);
260 double const ry = std::min(( rect->ry._set
261 ? rect->ry.computed
262 : ( rect->rx._set
263 ? rect->rx.computed
264 : 0.0 ) ),
265 .5 * rect->height.computed);
266 /* TODO: Handle negative rx or ry as per
267 * http://www.w3.org/TR/SVG11/shapes.html#RectElementRXAttribute once Inkscape has proper error
268 * handling (see http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing).
269 */
271 /* We don't use proper circular/elliptical arcs, but bezier curves can approximate a 90-degree
272 * arc fairly well.
273 */
274 if ((rx > 1e-18) && (ry > 1e-18)) {
275 sp_curve_moveto(c, x + rx, y);
276 if (rx < w2) sp_curve_lineto(c, x + w - rx, y);
277 sp_curve_curveto(c, x + w - rx * (1 - C1), y, x + w, y + ry * (1 - C1), x + w, y + ry);
278 if (ry < h2) sp_curve_lineto(c, x + w, y + h - ry);
279 sp_curve_curveto(c, x + w, y + h - ry * (1 - C1), x + w - rx * (1 - C1), y + h, x + w - rx, y + h);
280 if (rx < w2) sp_curve_lineto(c, x + rx, y + h);
281 sp_curve_curveto(c, x + rx * (1 - C1), y + h, x, y + h - ry * (1 - C1), x, y + h - ry);
282 if (ry < h2) sp_curve_lineto(c, x, y + ry);
283 sp_curve_curveto(c, x, y + ry * (1 - C1), x + rx * (1 - C1), y, x + rx, y);
284 } else {
285 sp_curve_moveto(c, x + 0.0, y + 0.0);
286 sp_curve_lineto(c, x + w, y + 0.0);
287 sp_curve_lineto(c, x + w, y + h);
288 sp_curve_lineto(c, x + 0.0, y + h);
289 sp_curve_lineto(c, x + 0.0, y + 0.0);
290 }
292 sp_curve_closepath_current(c);
293 sp_shape_set_curve_insync(SP_SHAPE(rect), c, TRUE);
294 sp_curve_unref(c);
295 }
297 /* fixme: Think (Lauris) */
299 void
300 sp_rect_position_set(SPRect *rect, gdouble x, gdouble y, gdouble width, gdouble height)
301 {
302 g_return_if_fail(rect != NULL);
303 g_return_if_fail(SP_IS_RECT(rect));
305 rect->x.computed = x;
306 rect->y.computed = y;
307 rect->width.computed = width;
308 rect->height.computed = height;
310 SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
311 }
313 void
314 sp_rect_set_rx(SPRect *rect, gboolean set, gdouble value)
315 {
316 g_return_if_fail(rect != NULL);
317 g_return_if_fail(SP_IS_RECT(rect));
319 rect->rx._set = set;
320 if (set) rect->rx.computed = value;
322 SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
323 }
325 void
326 sp_rect_set_ry(SPRect *rect, gboolean set, gdouble value)
327 {
328 g_return_if_fail(rect != NULL);
329 g_return_if_fail(SP_IS_RECT(rect));
331 rect->ry._set = set;
332 if (set) rect->ry.computed = value;
334 SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
335 }
337 /*
338 * Initially we'll do:
339 * Transform x, y, set x, y, clear translation
340 */
342 /* fixme: Use preferred units somehow (Lauris) */
343 /* fixme: Alternately preserve whatever units there are (lauris) */
345 static NR::Matrix
346 sp_rect_set_transform(SPItem *item, NR::Matrix const &xform)
347 {
348 SPRect *rect = SP_RECT(item);
350 /* Calculate rect start in parent coords. */
351 NR::Point pos( NR::Point(rect->x.computed, rect->y.computed) * xform );
353 /* This function takes care of translation and scaling, we return whatever parts we can't
354 handle. */
355 NR::Matrix ret(NR::transform(xform));
356 gdouble const sw = hypot(ret[0], ret[1]);
357 gdouble const sh = hypot(ret[2], ret[3]);
358 if (sw > 1e-9) {
359 ret[0] /= sw;
360 ret[1] /= sw;
361 } else {
362 ret[0] = 1.0;
363 ret[1] = 0.0;
364 }
365 if (sh > 1e-9) {
366 ret[2] /= sh;
367 ret[3] /= sh;
368 } else {
369 ret[2] = 0.0;
370 ret[3] = 1.0;
371 }
373 /* fixme: Would be nice to preserve units here */
374 rect->width = rect->width.computed * sw;
375 rect->height = rect->height.computed * sh;
376 if (rect->rx._set) {
377 rect->rx = rect->rx.computed * sw;
378 }
379 if (rect->ry._set) {
380 rect->ry = rect->ry.computed * sh;
381 }
383 /* Find start in item coords */
384 pos = pos * ret.inverse();
385 rect->x = pos[NR::X];
386 rect->y = pos[NR::Y];
388 sp_rect_set_shape(rect);
390 // Adjust stroke width
391 sp_item_adjust_stroke(item, sqrt(fabs(sw * sh)));
393 // Adjust pattern fill
394 sp_item_adjust_pattern(item, xform / ret);
396 // Adjust gradient fill
397 sp_item_adjust_gradient(item, xform / ret);
399 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
401 return ret;
402 }
405 /**
406 Returns the ratio in which the vector from p0 to p1 is stretched by transform
407 */
408 static gdouble
409 vector_stretch(NR::Point p0, NR::Point p1, NR::Matrix xform)
410 {
411 if (p0 == p1)
412 return 0;
413 return (NR::distance(p0 * xform, p1 * xform) / NR::distance(p0, p1));
414 }
416 void
417 sp_rect_set_visible_rx(SPRect *rect, gdouble rx)
418 {
419 if (rx == 0) {
420 rect->rx.computed = 0;
421 rect->rx._set = false;
422 } else {
423 rect->rx.computed = rx / vector_stretch(
424 NR::Point(rect->x.computed + 1, rect->y.computed),
425 NR::Point(rect->x.computed, rect->y.computed),
426 SP_ITEM(rect)->transform);
427 rect->rx._set = true;
428 }
429 SP_OBJECT(rect)->updateRepr();
430 }
432 void
433 sp_rect_set_visible_ry(SPRect *rect, gdouble ry)
434 {
435 if (ry == 0) {
436 rect->ry.computed = 0;
437 rect->ry._set = false;
438 } else {
439 rect->ry.computed = ry / vector_stretch(
440 NR::Point(rect->x.computed, rect->y.computed + 1),
441 NR::Point(rect->x.computed, rect->y.computed),
442 SP_ITEM(rect)->transform);
443 rect->ry._set = true;
444 }
445 SP_OBJECT(rect)->updateRepr();
446 }
448 gdouble
449 sp_rect_get_visible_rx(SPRect *rect)
450 {
451 if (!rect->rx._set)
452 return 0;
453 return rect->rx.computed * vector_stretch(
454 NR::Point(rect->x.computed + 1, rect->y.computed),
455 NR::Point(rect->x.computed, rect->y.computed),
456 SP_ITEM(rect)->transform);
457 }
459 gdouble
460 sp_rect_get_visible_ry(SPRect *rect)
461 {
462 if (!rect->ry._set)
463 return 0;
464 return rect->ry.computed * vector_stretch(
465 NR::Point(rect->x.computed, rect->y.computed + 1),
466 NR::Point(rect->x.computed, rect->y.computed),
467 SP_ITEM(rect)->transform);
468 }
470 void
471 sp_rect_compensate_rxry(SPRect *rect, NR::Matrix xform)
472 {
473 if (rect->rx.computed == 0 && rect->ry.computed == 0)
474 return; // nothing to compensate
476 // test unit vectors to find out compensation:
477 NR::Point c(rect->x.computed, rect->y.computed);
478 NR::Point cx = c + NR::Point(1, 0);
479 NR::Point cy = c + NR::Point(0, 1);
481 // apply previous transform if any
482 c *= SP_ITEM(rect)->transform;
483 cx *= SP_ITEM(rect)->transform;
484 cy *= SP_ITEM(rect)->transform;
486 // find out stretches that we need to compensate
487 gdouble eX = vector_stretch(cx, c, xform);
488 gdouble eY = vector_stretch(cy, c, xform);
490 // If only one of the radii is set, set both radii so they have the same visible length
491 // This is needed because if we just set them the same length in SVG, they might end up unequal because of transform
492 if ((rect->rx._set && !rect->ry._set) || (rect->ry._set && !rect->rx._set)) {
493 gdouble r = MAX(rect->rx.computed, rect->ry.computed);
494 rect->rx.computed = r / eX;
495 rect->ry.computed = r / eY;
496 } else {
497 rect->rx.computed = rect->rx.computed / eX;
498 rect->ry.computed = rect->ry.computed / eY;
499 }
501 // Note that a radius may end up larger than half-side if the rect is scaled down;
502 // that's ok because this preserves the intended radii in case the rect is enlarged again,
503 // and set_shape will take care of trimming too large radii when generating d=
505 rect->rx._set = rect->ry._set = true;
506 }
508 void
509 sp_rect_set_visible_width(SPRect *rect, gdouble width)
510 {
511 rect->width.computed = width / vector_stretch(
512 NR::Point(rect->x.computed + 1, rect->y.computed),
513 NR::Point(rect->x.computed, rect->y.computed),
514 SP_ITEM(rect)->transform);
515 rect->width._set = true;
516 SP_OBJECT(rect)->updateRepr();
517 }
519 void
520 sp_rect_set_visible_height(SPRect *rect, gdouble height)
521 {
522 rect->height.computed = height / vector_stretch(
523 NR::Point(rect->x.computed, rect->y.computed + 1),
524 NR::Point(rect->x.computed, rect->y.computed),
525 SP_ITEM(rect)->transform);
526 rect->height._set = true;
527 SP_OBJECT(rect)->updateRepr();
528 }
530 gdouble
531 sp_rect_get_visible_width(SPRect *rect)
532 {
533 if (!rect->width._set)
534 return 0;
535 return rect->width.computed * vector_stretch(
536 NR::Point(rect->x.computed + 1, rect->y.computed),
537 NR::Point(rect->x.computed, rect->y.computed),
538 SP_ITEM(rect)->transform);
539 }
541 gdouble
542 sp_rect_get_visible_height(SPRect *rect)
543 {
544 if (!rect->height._set)
545 return 0;
546 return rect->height.computed * vector_stretch(
547 NR::Point(rect->x.computed, rect->y.computed + 1),
548 NR::Point(rect->x.computed, rect->y.computed),
549 SP_ITEM(rect)->transform);
550 }
552 /*
553 Local Variables:
554 mode:c++
555 c-file-style:"stroustrup"
556 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
557 indent-tabs-mode:nil
558 fill-column:99
559 End:
560 */
561 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :