2
* SVG <rect> implementation
5
* Lauris Kaplinski <lauris@kaplinski.com>
6
* bulia byak <buliabyak@users.sf.net>
8
* Copyright (C) 1999-2002 Lauris Kaplinski
9
* Copyright (C) 2000-2001 Ximian, Inc.
11
* Released under GNU GPL, read the file 'COPYING' for more information
19
#include <display/curve.h>
20
#include <libnr/nr-matrix-ops.h>
21
#include <libnr/nr-matrix-fns.h>
22
#include <2geom/rect.h>
26
#include "attributes.h"
29
#include <glibmm/i18n.h>
32
#include "preferences.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 Geom::Matrix sp_rect_set_transform(SPItem *item, Geom::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, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const *snapprefs);
51
static SPShapeClass *parent_class;
54
sp_rect_get_type(void)
56
static GType type = 0;
62
NULL, /* base_finalize */
63
(GClassInitFunc) sp_rect_class_init,
64
NULL, /* class_finalize */
65
NULL, /* class_data */
68
(GInstanceInitFunc) sp_rect_init,
69
NULL, /* value_table */
71
type = g_type_register_static(SP_TYPE_SHAPE, "SPRect", &info, (GTypeFlags)0);
77
sp_rect_class_init(SPRectClass *klass)
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;
99
sp_rect_init(SPRect */*rect*/)
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); */
111
sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
113
if (((SPObjectClass *) parent_class)->build)
114
((SPObjectClass *) parent_class)->build(object, document, repr);
116
sp_object_read_attr(object, "x");
117
sp_object_read_attr(object, "y");
118
sp_object_read_attr(object, "width");
119
sp_object_read_attr(object, "height");
120
sp_object_read_attr(object, "rx");
121
sp_object_read_attr(object, "ry");
125
sp_rect_set(SPObject *object, unsigned key, gchar const *value)
127
SPRect *rect = SP_RECT(object);
129
/* fixme: We need real error processing some time */
133
rect->x.readOrUnset(value);
134
object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
137
rect->y.readOrUnset(value);
138
object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
141
if (!rect->width.read(value) || rect->width.value < 0.0) {
144
object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
147
if (!rect->height.read(value) || rect->height.value < 0.0) {
148
rect->height.unset();
150
object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
153
if (!rect->rx.read(value) || rect->rx.value < 0.0) {
156
object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
159
if (!rect->ry.read(value) || rect->ry.value < 0.0) {
162
object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
165
if (((SPObjectClass *) parent_class)->set)
166
((SPObjectClass *) parent_class)->set(object, key, value);
172
sp_rect_update(SPObject *object, SPCtx *ctx, guint flags)
174
if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
175
SPRect *rect = (SPRect *) object;
176
SPStyle *style = object->style;
177
SPItemCtx const *ictx = (SPItemCtx const *) ctx;
178
double const w = (ictx->vp.x1 - ictx->vp.x0);
179
double const h = (ictx->vp.y1 - ictx->vp.y0);
180
double const em = style->font_size.computed;
181
double const ex = 0.5 * em; // fixme: get x height from pango or libnrtype.
182
rect->x.update(em, ex, w);
183
rect->y.update(em, ex, h);
184
rect->width.update(em, ex, w);
185
rect->height.update(em, ex, h);
186
rect->rx.update(em, ex, w);
187
rect->ry.update(em, ex, h);
188
sp_shape_set_shape((SPShape *) object);
189
flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore
192
if (((SPObjectClass *) parent_class)->update)
193
((SPObjectClass *) parent_class)->update(object, ctx, flags);
196
static Inkscape::XML::Node *
197
sp_rect_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
199
SPRect *rect = SP_RECT(object);
201
if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
202
repr = xml_doc->createElement("svg:rect");
205
sp_repr_set_svg_double(repr, "width", rect->width.computed);
206
sp_repr_set_svg_double(repr, "height", rect->height.computed);
207
if (rect->rx._set) sp_repr_set_svg_double(repr, "rx", rect->rx.computed);
208
if (rect->ry._set) sp_repr_set_svg_double(repr, "ry", rect->ry.computed);
209
sp_repr_set_svg_double(repr, "x", rect->x.computed);
210
sp_repr_set_svg_double(repr, "y", rect->y.computed);
212
if (((SPObjectClass *) parent_class)->write)
213
((SPObjectClass *) parent_class)->write(object, xml_doc, repr, flags);
219
sp_rect_description(SPItem *item)
221
g_return_val_if_fail(SP_IS_RECT(item), NULL);
223
return g_strdup(_("<b>Rectangle</b>"));
229
sp_rect_set_shape(SPShape *shape)
231
SPRect *rect = (SPRect *) shape;
233
if ((rect->height.computed < 1e-18) || (rect->width.computed < 1e-18)) {
234
sp_shape_set_curve_insync(SP_SHAPE(rect), NULL, TRUE);
238
SPCurve *c = new SPCurve();
240
double const x = rect->x.computed;
241
double const y = rect->y.computed;
242
double const w = rect->width.computed;
243
double const h = rect->height.computed;
244
double const w2 = w / 2;
245
double const h2 = h / 2;
246
double const rx = std::min(( rect->rx._set
251
.5 * rect->width.computed);
252
double const ry = std::min(( rect->ry._set
257
.5 * rect->height.computed);
258
/* TODO: Handle negative rx or ry as per
259
* http://www.w3.org/TR/SVG11/shapes.html#RectElementRXAttribute once Inkscape has proper error
260
* handling (see http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing).
263
/* We don't use proper circular/elliptical arcs, but bezier curves can approximate a 90-degree
266
if ((rx > 1e-18) && (ry > 1e-18)) {
267
c->moveto(x + rx, y);
268
if (rx < w2) c->lineto(x + w - rx, y);
269
c->curveto(x + w - rx * (1 - C1), y, x + w, y + ry * (1 - C1), x + w, y + ry);
270
if (ry < h2) c->lineto(x + w, y + h - ry);
271
c->curveto(x + w, y + h - ry * (1 - C1), x + w - rx * (1 - C1), y + h, x + w - rx, y + h);
272
if (rx < w2) c->lineto(x + rx, y + h);
273
c->curveto(x + rx * (1 - C1), y + h, x, y + h - ry * (1 - C1), x, y + h - ry);
274
if (ry < h2) c->lineto(x, y + ry);
275
c->curveto(x, y + ry * (1 - C1), x + rx * (1 - C1), y, x + rx, y);
277
c->moveto(x + 0.0, y + 0.0);
278
c->lineto(x + w, y + 0.0);
279
c->lineto(x + w, y + h);
280
c->lineto(x + 0.0, y + h);
281
c->lineto(x + 0.0, y + 0.0);
284
c->closepath_current();
285
sp_shape_set_curve_insync(SP_SHAPE(rect), c, TRUE);
289
/* fixme: Think (Lauris) */
292
sp_rect_position_set(SPRect *rect, gdouble x, gdouble y, gdouble width, gdouble height)
294
g_return_if_fail(rect != NULL);
295
g_return_if_fail(SP_IS_RECT(rect));
297
rect->x.computed = x;
298
rect->y.computed = y;
299
rect->width.computed = width;
300
rect->height.computed = height;
302
SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
306
sp_rect_set_rx(SPRect *rect, gboolean set, gdouble value)
308
g_return_if_fail(rect != NULL);
309
g_return_if_fail(SP_IS_RECT(rect));
312
if (set) rect->rx.computed = value;
314
SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
318
sp_rect_set_ry(SPRect *rect, gboolean set, gdouble value)
320
g_return_if_fail(rect != NULL);
321
g_return_if_fail(SP_IS_RECT(rect));
324
if (set) rect->ry.computed = value;
326
SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
330
* Initially we'll do:
331
* Transform x, y, set x, y, clear translation
334
/* fixme: Use preferred units somehow (Lauris) */
335
/* fixme: Alternately preserve whatever units there are (lauris) */
338
sp_rect_set_transform(SPItem *item, Geom::Matrix const &xform)
340
SPRect *rect = SP_RECT(item);
342
/* Calculate rect start in parent coords. */
343
Geom::Point pos( Geom::Point(rect->x.computed, rect->y.computed) * xform );
345
/* This function takes care of translation and scaling, we return whatever parts we can't
347
Geom::Matrix ret(Geom::Matrix(xform).without_translation());
348
gdouble const sw = hypot(ret[0], ret[1]);
349
gdouble const sh = hypot(ret[2], ret[3]);
365
/* fixme: Would be nice to preserve units here */
366
rect->width = rect->width.computed * sw;
367
rect->height = rect->height.computed * sh;
369
rect->rx = rect->rx.computed * sw;
372
rect->ry = rect->ry.computed * sh;
375
/* Find start in item coords */
376
pos = pos * ret.inverse();
377
rect->x = pos[Geom::X];
378
rect->y = pos[Geom::Y];
380
sp_rect_set_shape(rect);
382
// Adjust stroke width
383
sp_item_adjust_stroke(item, sqrt(fabs(sw * sh)));
385
// Adjust pattern fill
386
sp_item_adjust_pattern(item, xform * ret.inverse());
388
// Adjust gradient fill
389
sp_item_adjust_gradient(item, xform * ret.inverse());
391
item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
398
Returns the ratio in which the vector from p0 to p1 is stretched by transform
401
vector_stretch(Geom::Point p0, Geom::Point p1, Geom::Matrix xform)
405
return (Geom::distance(p0 * xform, p1 * xform) / Geom::distance(p0, p1));
409
sp_rect_set_visible_rx(SPRect *rect, gdouble rx)
412
rect->rx.computed = 0;
413
rect->rx._set = false;
415
rect->rx.computed = rx / vector_stretch(
416
Geom::Point(rect->x.computed + 1, rect->y.computed),
417
Geom::Point(rect->x.computed, rect->y.computed),
418
SP_ITEM(rect)->transform);
419
rect->rx._set = true;
421
SP_OBJECT(rect)->updateRepr();
425
sp_rect_set_visible_ry(SPRect *rect, gdouble ry)
428
rect->ry.computed = 0;
429
rect->ry._set = false;
431
rect->ry.computed = ry / vector_stretch(
432
Geom::Point(rect->x.computed, rect->y.computed + 1),
433
Geom::Point(rect->x.computed, rect->y.computed),
434
SP_ITEM(rect)->transform);
435
rect->ry._set = true;
437
SP_OBJECT(rect)->updateRepr();
441
sp_rect_get_visible_rx(SPRect *rect)
445
return rect->rx.computed * vector_stretch(
446
Geom::Point(rect->x.computed + 1, rect->y.computed),
447
Geom::Point(rect->x.computed, rect->y.computed),
448
SP_ITEM(rect)->transform);
452
sp_rect_get_visible_ry(SPRect *rect)
456
return rect->ry.computed * vector_stretch(
457
Geom::Point(rect->x.computed, rect->y.computed + 1),
458
Geom::Point(rect->x.computed, rect->y.computed),
459
SP_ITEM(rect)->transform);
463
sp_rect_get_rect (SPRect *rect)
465
Geom::Point p0 = Geom::Point(rect->x.computed, rect->y.computed);
466
Geom::Point p2 = Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
467
return Geom::Rect(p0, p2);
471
sp_rect_compensate_rxry(SPRect *rect, Geom::Matrix xform)
473
if (rect->rx.computed == 0 && rect->ry.computed == 0)
474
return; // nothing to compensate
476
// test unit vectors to find out compensation:
477
Geom::Point c(rect->x.computed, rect->y.computed);
478
Geom::Point cx = c + Geom::Point(1, 0);
479
Geom::Point cy = c + Geom::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;
497
rect->rx.computed = rect->rx.computed / eX;
498
rect->ry.computed = rect->ry.computed / eY;
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;
509
sp_rect_set_visible_width(SPRect *rect, gdouble width)
511
rect->width.computed = width / vector_stretch(
512
Geom::Point(rect->x.computed + 1, rect->y.computed),
513
Geom::Point(rect->x.computed, rect->y.computed),
514
SP_ITEM(rect)->transform);
515
rect->width._set = true;
516
SP_OBJECT(rect)->updateRepr();
520
sp_rect_set_visible_height(SPRect *rect, gdouble height)
522
rect->height.computed = height / vector_stretch(
523
Geom::Point(rect->x.computed, rect->y.computed + 1),
524
Geom::Point(rect->x.computed, rect->y.computed),
525
SP_ITEM(rect)->transform);
526
rect->height._set = true;
527
SP_OBJECT(rect)->updateRepr();
531
sp_rect_get_visible_width(SPRect *rect)
533
if (!rect->width._set)
535
return rect->width.computed * vector_stretch(
536
Geom::Point(rect->x.computed + 1, rect->y.computed),
537
Geom::Point(rect->x.computed, rect->y.computed),
538
SP_ITEM(rect)->transform);
542
sp_rect_get_visible_height(SPRect *rect)
544
if (!rect->height._set)
546
return rect->height.computed * vector_stretch(
547
Geom::Point(rect->x.computed, rect->y.computed + 1),
548
Geom::Point(rect->x.computed, rect->y.computed),
549
SP_ITEM(rect)->transform);
553
* Sets the snappoint p to the unrounded corners of the rectangle
555
static void sp_rect_snappoints(SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const *snapprefs)
557
/* This method overrides sp_shape_snappoints, which is the default for any shape. The default method
558
returns all eight points along the path of a rounded rectangle, but not the real corners. Snapping
559
the startpoint and endpoint of each rounded corner is not very useful and really confusing. Instead
560
we could snap either the real corners, or not snap at all. Bulia Byak opted to snap the real corners,
561
but it should be noted that this might be confusing in some cases with relatively large radii. With
562
small radii though the user will easily understand which point is snapping. */
564
g_assert(item != NULL);
565
g_assert(SP_IS_RECT(item));
567
// Help enforcing strict snapping, i.e. only return nodes when we're snapping nodes to nodes or a guide to nodes
568
if (!(snapprefs->getSnapModeNode() || snapprefs->getSnapModeGuide())) {
572
SPRect *rect = SP_RECT(item);
574
Geom::Matrix const i2d (sp_item_i2d_affine (item));
576
Geom::Point p0 = Geom::Point(rect->x.computed, rect->y.computed) * i2d;
577
Geom::Point p1 = Geom::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d;
578
Geom::Point p2 = Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d;
579
Geom::Point p3 = Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d;
583
if (snapprefs->getSnapToItemNode()) {
584
type = target ? int(Inkscape::SNAPTARGET_CORNER) : int(Inkscape::SNAPSOURCE_CORNER);
585
p.push_back(std::make_pair(p0, type));
586
p.push_back(std::make_pair(p1, type));
587
p.push_back(std::make_pair(p2, type));
588
p.push_back(std::make_pair(p3, type));
591
if (snapprefs->getSnapLineMidpoints()) { // only do this when we're snapping nodes (enforce strict snapping)
592
type = target ? int(Inkscape::SNAPTARGET_LINE_MIDPOINT) : int(Inkscape::SNAPSOURCE_LINE_MIDPOINT);
593
p.push_back(std::make_pair((p0 + p1)/2, type));
594
p.push_back(std::make_pair((p1 + p2)/2, type));
595
p.push_back(std::make_pair((p2 + p3)/2, type));
596
p.push_back(std::make_pair((p3 + p0)/2, type));
599
if (snapprefs->getSnapObjectMidpoints()) { // only do this when we're snapping nodes (enforce strict snapping)
600
type = target ? int(Inkscape::SNAPTARGET_OBJECT_MIDPOINT) : int(Inkscape::SNAPSOURCE_OBJECT_MIDPOINT);
601
p.push_back(std::make_pair((p0 + p2)/2, type));
607
sp_rect_convert_to_guides(SPItem *item) {
608
SPRect *rect = SP_RECT(item);
610
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
611
if (!prefs->getBool("/tools/shapes/rect/convertguides", true)) {
612
sp_item_convert_to_guides(SP_ITEM(rect));
616
std::list<std::pair<Geom::Point, Geom::Point> > pts;
618
Geom::Matrix const i2d (sp_item_i2d_affine(SP_ITEM(rect)));
620
Geom::Point A1(Geom::Point(rect->x.computed, rect->y.computed) * i2d);
621
Geom::Point A2(Geom::Point(rect->x.computed, rect->y.computed + rect->height.computed) * i2d);
622
Geom::Point A3(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed) * i2d);
623
Geom::Point A4(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed) * i2d);
625
pts.push_back(std::make_pair(A1, A2));
626
pts.push_back(std::make_pair(A2, A3));
627
pts.push_back(std::make_pair(A3, A4));
628
pts.push_back(std::make_pair(A4, A1));
630
sp_guide_pt_pairs_to_guides(inkscape_active_desktop(), pts);
636
c-file-style:"stroustrup"
637
c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
642
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :