1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * SVG <rect> implementation
4 *
5 * Authors:
6 * Lauris Kaplinski <lauris@kaplinski.com>
7 * bulia byak <buliabyak@users.sf.net>
8 *
9 * Copyright (C) 1999-2002 Lauris Kaplinski
10 * Copyright (C) 2000-2001 Ximian, Inc.
11 *
12 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
13 */
14
15 #include "display/curve.h"
16
17 #include "inkscape.h"
18 #include "document.h"
19 #include "attributes.h"
20 #include "style.h"
21 #include "sp-rect.h"
22 #include "sp-guide.h"
23 #include "preferences.h"
24 #include "svg/svg.h"
25 #include <glibmm/i18n.h>
26
27 #define noRECT_VERBOSE
28
29 //#define OBJECT_TRACE
30
SPRect()31 SPRect::SPRect() : SPShape() {
32 }
33
34 SPRect::~SPRect() = default;
35
build(SPDocument * doc,Inkscape::XML::Node * repr)36 void SPRect::build(SPDocument* doc, Inkscape::XML::Node* repr) {
37 #ifdef OBJECT_TRACE
38 objectTrace( "SPRect::build" );
39 #endif
40
41 SPShape::build(doc, repr);
42
43 this->readAttr(SPAttr::X);
44 this->readAttr(SPAttr::Y);
45 this->readAttr(SPAttr::WIDTH);
46 this->readAttr(SPAttr::HEIGHT);
47 this->readAttr(SPAttr::RX);
48 this->readAttr(SPAttr::RY);
49
50 #ifdef OBJECT_TRACE
51 objectTrace( "SPRect::build", false );
52 #endif
53 }
54
set(SPAttr key,gchar const * value)55 void SPRect::set(SPAttr key, gchar const *value) {
56
57 #ifdef OBJECT_TRACE
58 std::stringstream temp;
59 temp << "SPRect::set: " << key << " " << (value?value:"null");
60 objectTrace( temp.str() );
61 #endif
62
63 /* fixme: We need real error processing some time */
64
65 // We must update the SVGLengths immediately or nodes may be misplaced after they are moved.
66 double const w = viewport.width();
67 double const h = viewport.height();
68 double const em = style->font_size.computed;
69 double const ex = em * 0.5;
70
71 switch (key) {
72 case SPAttr::X:
73 this->x.readOrUnset(value);
74 this->x.update( em, ex, w );
75 this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
76 break;
77
78 case SPAttr::Y:
79 this->y.readOrUnset(value);
80 this->y.update( em, ex, h );
81 this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
82 break;
83
84 case SPAttr::WIDTH:
85 if (!this->width.read(value) || this->width.value < 0.0) {
86 this->width.unset();
87 }
88 this->width.update( em, ex, w );
89 this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
90 break;
91
92 case SPAttr::HEIGHT:
93 if (!this->height.read(value) || this->height.value < 0.0) {
94 this->height.unset();
95 }
96 this->height.update( em, ex, h );
97 this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
98 break;
99
100 case SPAttr::RX:
101 if (!this->rx.read(value) || this->rx.value <= 0.0) {
102 this->rx.unset();
103 }
104 this->rx.update( em, ex, w );
105 this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
106 break;
107
108 case SPAttr::RY:
109 if (!this->ry.read(value) || this->ry.value <= 0.0) {
110 this->ry.unset();
111 }
112 this->ry.update( em, ex, h );
113 this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
114 break;
115
116 default:
117 SPShape::set(key, value);
118 break;
119 }
120 #ifdef OBJECT_TRACE
121 objectTrace( "SPRect::set", false );
122 #endif
123 }
124
update(SPCtx * ctx,unsigned int flags)125 void SPRect::update(SPCtx* ctx, unsigned int flags) {
126
127 #ifdef OBJECT_TRACE
128 objectTrace( "SPRect::update", true, flags );
129 #endif
130
131 if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
132 SPItemCtx const *ictx = reinterpret_cast<SPItemCtx const *>(ctx);
133
134 double const w = ictx->viewport.width();
135 double const h = ictx->viewport.height();
136 double const em = style->font_size.computed;
137 double const ex = 0.5 * em; // fixme: get x height from pango or libnrtype.
138
139 this->x.update(em, ex, w);
140 this->y.update(em, ex, h);
141 this->width.update(em, ex, w);
142 this->height.update(em, ex, h);
143 this->rx.update(em, ex, w);
144 this->ry.update(em, ex, h);
145 this->set_shape();
146
147 flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore
148 }
149
150 SPShape::update(ctx, flags);
151 #ifdef OBJECT_TRACE
152 objectTrace( "SPRect::update", false, flags );
153 #endif
154 }
155
write(Inkscape::XML::Document * xml_doc,Inkscape::XML::Node * repr,guint flags)156 Inkscape::XML::Node * SPRect::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) {
157
158 #ifdef OBJECT_TRACE
159 objectTrace( "SPRect::write", true, flags );
160 #endif
161
162 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
163 repr = xml_doc->createElement("svg:rect");
164 }
165 if (this->hasPathEffectOnClipOrMaskRecursive(this) && repr && strcmp(repr->name(), "svg:rect") == 0) {
166 repr->setCodeUnsafe(g_quark_from_string("svg:path"));
167 repr->setAttribute("sodipodi:type", "rect");
168 }
169 sp_repr_set_svg_length(repr, "width", this->width);
170 sp_repr_set_svg_length(repr, "height", this->height);
171
172 if (this->rx._set) {
173 sp_repr_set_svg_length(repr, "rx", this->rx);
174 }
175
176 if (this->ry._set) {
177 sp_repr_set_svg_length(repr, "ry", this->ry);
178 }
179
180 sp_repr_set_svg_length(repr, "x", this->x);
181 sp_repr_set_svg_length(repr, "y", this->y);
182 // write d=
183 if (strcmp(repr->name(), "svg:rect") != 0) {
184 set_rect_path_attribute(repr); // include set_shape()
185 } else {
186 this->set_shape(); // evaluate SPCurve
187 }
188 SPShape::write(xml_doc, repr, flags);
189
190 #ifdef OBJECT_TRACE
191 objectTrace( "SPRect::write", false, flags );
192 #endif
193
194 return repr;
195 }
196
displayName() const197 const char* SPRect::displayName() const {
198 return _("Rectangle");
199 }
200
201 #define C1 0.554
202
set_shape()203 void SPRect::set_shape() {
204 if (hasBrokenPathEffect()) {
205 g_warning("The rect shape has unknown LPE on it!");
206
207 if (this->getRepr()->attribute("d")) {
208 // unconditionally read the curve from d, if any, to preserve appearance
209 Geom::PathVector pv = sp_svg_read_pathv(this->getRepr()->attribute("d"));
210 setCurveInsync(std::make_unique<SPCurve>(pv));
211 setCurveBeforeLPE(curve());
212 }
213
214 return;
215 }
216 if ((this->height.computed < 1e-18) || (this->width.computed < 1e-18)) {
217 this->setCurveInsync(nullptr);
218 this->setCurveBeforeLPE(nullptr);
219 return;
220 }
221
222 auto c = std::make_unique<SPCurve>();
223
224 double const x = this->x.computed;
225 double const y = this->y.computed;
226 double const w = this->width.computed;
227 double const h = this->height.computed;
228 double const w2 = w / 2;
229 double const h2 = h / 2;
230 double const rx = std::min(( this->rx._set
231 ? this->rx.computed
232 : ( this->ry._set
233 ? this->ry.computed
234 : 0.0 ) ),
235 .5 * this->width.computed);
236 double const ry = std::min(( this->ry._set
237 ? this->ry.computed
238 : ( this->rx._set
239 ? this->rx.computed
240 : 0.0 ) ),
241 .5 * this->height.computed);
242 /* TODO: Handle negative rx or ry as per
243 * http://www.w3.org/TR/SVG11/shapes.html#RectElementRXAttribute once Inkscape has proper error
244 * handling (see http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing).
245 */
246
247 /* We don't use proper circular/elliptical arcs, but bezier curves can approximate a 90-degree
248 * arc fairly well.
249 */
250 if ((rx > 1e-18) && (ry > 1e-18)) {
251 c->moveto(x + rx, y);
252
253 if (rx < w2) {
254 c->lineto(x + w - rx, y);
255 }
256
257 c->curveto(x + w - rx * (1 - C1), y, x + w, y + ry * (1 - C1), x + w, y + ry);
258
259 if (ry < h2) {
260 c->lineto(x + w, y + h - ry);
261 }
262
263 c->curveto(x + w, y + h - ry * (1 - C1), x + w - rx * (1 - C1), y + h, x + w - rx, y + h);
264
265 if (rx < w2) {
266 c->lineto(x + rx, y + h);
267 }
268
269 c->curveto(x + rx * (1 - C1), y + h, x, y + h - ry * (1 - C1), x, y + h - ry);
270
271 if (ry < h2) {
272 c->lineto(x, y + ry);
273 }
274
275 c->curveto(x, y + ry * (1 - C1), x + rx * (1 - C1), y, x + rx, y);
276 } else {
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 }
282
283 c->closepath();
284
285
286 /* Reset the shape's curve to the "original_curve"
287 * This is very important for LPEs to work properly! (the bbox might be recalculated depending on the curve in shape)*/
288
289 auto const before = this->curveBeforeLPE();
290 if (before && before->get_pathvector() != c->get_pathvector()) {
291 setCurveBeforeLPE(std::move(c));
292 sp_lpe_item_update_patheffect(this, true, false);
293 return;
294 }
295 if (this->hasPathEffectOnClipOrMaskRecursive(this)) {
296 setCurveBeforeLPE(std::move(c));
297
298 Inkscape::XML::Node *rectrepr = this->getRepr();
299 if (strcmp(rectrepr->name(), "svg:rect") == 0) {
300 sp_lpe_item_update_patheffect(this, true, false);
301 this->write(rectrepr->document(), rectrepr, SP_OBJECT_MODIFIED_FLAG);
302 }
303
304 return;
305 }
306
307 // This happends on undo, fix bug:#1791784
308 setCurveInsync(std::move(c));
309 }
310
set_rect_path_attribute(Inkscape::XML::Node * repr)311 bool SPRect::set_rect_path_attribute(Inkscape::XML::Node *repr)
312 {
313 // Make sure our pathvector is up to date.
314 this->set_shape();
315
316 if (_curve) {
317 repr->setAttribute("d", sp_svg_write_path(_curve->get_pathvector()));
318 } else {
319 repr->removeAttribute("d");
320 }
321
322 return true;
323 }
324
modified(guint flags)325 void SPRect::modified(guint flags)
326 {
327 if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
328 this->set_shape();
329 }
330
331 SPShape::modified(flags);
332 }
333
334 /* fixme: Think (Lauris) */
335
setPosition(gdouble x,gdouble y,gdouble width,gdouble height)336 void SPRect::setPosition(gdouble x, gdouble y, gdouble width, gdouble height) {
337 this->x = x;
338 this->y = y;
339 this->width = width;
340 this->height = height;
341
342 this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
343 }
344
setRx(bool set,gdouble value)345 void SPRect::setRx(bool set, gdouble value) {
346 this->rx._set = set;
347
348 if (set) {
349 this->rx = value;
350 }
351
352 this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
353 }
354
setRy(bool set,gdouble value)355 void SPRect::setRy(bool set, gdouble value) {
356 this->ry._set = set;
357
358 if (set) {
359 this->ry = value;
360 }
361
362 this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
363 }
364
update_patheffect(bool write)365 void SPRect::update_patheffect(bool write) {
366 SPShape::update_patheffect(write);
367 }
368
set_transform(Geom::Affine const & xform)369 Geom::Affine SPRect::set_transform(Geom::Affine const& xform) {
370 if (pathEffectsEnabled() && !optimizeTransforms()) {
371 return xform;
372 }
373 /* Calculate rect start in parent coords. */
374 Geom::Point pos(Geom::Point(this->x.computed, this->y.computed) * xform);
375
376 /* This function takes care of translation and scaling, we return whatever parts we can't
377 handle. */
378 Geom::Affine ret(Geom::Affine(xform).withoutTranslation());
379 gdouble const sw = hypot(ret[0], ret[1]);
380 gdouble const sh = hypot(ret[2], ret[3]);
381
382 if (sw > 1e-9) {
383 ret[0] /= sw;
384 ret[1] /= sw;
385 } else {
386 ret[0] = 1.0;
387 ret[1] = 0.0;
388 }
389
390 if (sh > 1e-9) {
391 ret[2] /= sh;
392 ret[3] /= sh;
393 } else {
394 ret[2] = 0.0;
395 ret[3] = 1.0;
396 }
397
398 /* Preserve units */
399 this->width.scale( sw );
400 this->height.scale( sh );
401
402 if (this->rx._set) {
403 this->rx.scale( sw );
404 }
405
406 if (this->ry._set) {
407 this->ry.scale( sh );
408 }
409
410 /* Find start in item coords */
411 pos = pos * ret.inverse();
412 this->x = pos[Geom::X];
413 this->y = pos[Geom::Y];
414
415 this->set_shape();
416
417 // Adjust stroke width
418 this->adjust_stroke(sqrt(fabs(sw * sh)));
419
420 // Adjust pattern fill
421 this->adjust_pattern(xform * ret.inverse());
422
423 // Adjust gradient fill
424 this->adjust_gradient(xform * ret.inverse());
425
426 return ret;
427 }
428
429
430 /**
431 Returns the ratio in which the vector from p0 to p1 is stretched by transform
432 */
vectorStretch(Geom::Point p0,Geom::Point p1,Geom::Affine xform)433 gdouble SPRect::vectorStretch(Geom::Point p0, Geom::Point p1, Geom::Affine xform) {
434 if (p0 == p1) {
435 return 0;
436 }
437
438 return (Geom::distance(p0 * xform, p1 * xform) / Geom::distance(p0, p1));
439 }
440
setVisibleRx(gdouble rx)441 void SPRect::setVisibleRx(gdouble rx) {
442 if (rx == 0) {
443 this->rx.unset();
444 } else {
445 this->rx = rx / SPRect::vectorStretch(
446 Geom::Point(this->x.computed + 1, this->y.computed),
447 Geom::Point(this->x.computed, this->y.computed),
448 this->i2doc_affine());
449 }
450
451 this->updateRepr();
452 }
453
setVisibleRy(gdouble ry)454 void SPRect::setVisibleRy(gdouble ry) {
455 if (ry == 0) {
456 this->ry.unset();
457 } else {
458 this->ry = ry / SPRect::vectorStretch(
459 Geom::Point(this->x.computed, this->y.computed + 1),
460 Geom::Point(this->x.computed, this->y.computed),
461 this->i2doc_affine());
462 }
463
464 this->updateRepr();
465 }
466
getVisibleRx() const467 gdouble SPRect::getVisibleRx() const {
468 if (!this->rx._set) {
469 return 0;
470 }
471
472 return this->rx.computed * SPRect::vectorStretch(
473 Geom::Point(this->x.computed + 1, this->y.computed),
474 Geom::Point(this->x.computed, this->y.computed),
475 this->i2doc_affine());
476 }
477
getVisibleRy() const478 gdouble SPRect::getVisibleRy() const {
479 if (!this->ry._set) {
480 return 0;
481 }
482
483 return this->ry.computed * SPRect::vectorStretch(
484 Geom::Point(this->x.computed, this->y.computed + 1),
485 Geom::Point(this->x.computed, this->y.computed),
486 this->i2doc_affine());
487 }
488
getRect() const489 Geom::Rect SPRect::getRect() const {
490 Geom::Point p0 = Geom::Point(this->x.computed, this->y.computed);
491 Geom::Point p2 = Geom::Point(this->x.computed + this->width.computed, this->y.computed + this->height.computed);
492
493 return Geom::Rect(p0, p2);
494 }
495
compensateRxRy(Geom::Affine xform)496 void SPRect::compensateRxRy(Geom::Affine xform) {
497 if (this->rx.computed == 0 && this->ry.computed == 0) {
498 return; // nothing to compensate
499 }
500
501 // test unit vectors to find out compensation:
502 Geom::Point c(this->x.computed, this->y.computed);
503 Geom::Point cx = c + Geom::Point(1, 0);
504 Geom::Point cy = c + Geom::Point(0, 1);
505
506 // apply previous transform if any
507 c *= this->transform;
508 cx *= this->transform;
509 cy *= this->transform;
510
511 // find out stretches that we need to compensate
512 gdouble eX = SPRect::vectorStretch(cx, c, xform);
513 gdouble eY = SPRect::vectorStretch(cy, c, xform);
514
515 // If only one of the radii is set, set both radii so they have the same visible length
516 // This is needed because if we just set them the same length in SVG, they might end up unequal because of transform
517 if ((this->rx._set && !this->ry._set) || (this->ry._set && !this->rx._set)) {
518 gdouble r = MAX(this->rx.computed, this->ry.computed);
519 this->rx = r / eX;
520 this->ry = r / eY;
521 } else {
522 this->rx = this->rx.computed / eX;
523 this->ry = this->ry.computed / eY;
524 }
525
526 // Note that a radius may end up larger than half-side if the rect is scaled down;
527 // that's ok because this preserves the intended radii in case the rect is enlarged again,
528 // and set_shape will take care of trimming too large radii when generating d=
529 }
530
setVisibleWidth(gdouble width)531 void SPRect::setVisibleWidth(gdouble width) {
532 this->width = width / SPRect::vectorStretch(
533 Geom::Point(this->x.computed + 1, this->y.computed),
534 Geom::Point(this->x.computed, this->y.computed),
535 this->i2doc_affine());
536
537 this->updateRepr();
538 }
539
setVisibleHeight(gdouble height)540 void SPRect::setVisibleHeight(gdouble height) {
541 this->height = height / SPRect::vectorStretch(
542 Geom::Point(this->x.computed, this->y.computed + 1),
543 Geom::Point(this->x.computed, this->y.computed),
544 this->i2doc_affine());
545
546 this->updateRepr();
547 }
548
getVisibleWidth() const549 gdouble SPRect::getVisibleWidth() const {
550 if (!this->width._set) {
551 return 0;
552 }
553
554 return this->width.computed * SPRect::vectorStretch(
555 Geom::Point(this->x.computed + 1, this->y.computed),
556 Geom::Point(this->x.computed, this->y.computed),
557 this->i2doc_affine());
558 }
559
getVisibleHeight() const560 gdouble SPRect::getVisibleHeight() const {
561 if (!this->height._set) {
562 return 0;
563 }
564
565 return this->height.computed * SPRect::vectorStretch(
566 Geom::Point(this->x.computed, this->y.computed + 1),
567 Geom::Point(this->x.computed, this->y.computed),
568 this->i2doc_affine());
569 }
570
snappoints(std::vector<Inkscape::SnapCandidatePoint> & p,Inkscape::SnapPreferences const * snapprefs) const571 void SPRect::snappoints(std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) const {
572 /* This method overrides sp_shape_snappoints, which is the default for any shape. The default method
573 returns all eight points along the path of a rounded rectangle, but not the real corners. Snapping
574 the startpoint and endpoint of each rounded corner is not very useful and really confusing. Instead
575 we could snap either the real corners, or not snap at all. Bulia Byak opted to snap the real corners,
576 but it should be noted that this might be confusing in some cases with relatively large radii. With
577 small radii though the user will easily understand which point is snapping. */
578
579 Geom::Affine const i2dt (this->i2dt_affine ());
580
581 Geom::Point p0 = Geom::Point(this->x.computed, this->y.computed) * i2dt;
582 Geom::Point p1 = Geom::Point(this->x.computed, this->y.computed + this->height.computed) * i2dt;
583 Geom::Point p2 = Geom::Point(this->x.computed + this->width.computed, this->y.computed + this->height.computed) * i2dt;
584 Geom::Point p3 = Geom::Point(this->x.computed + this->width.computed, this->y.computed) * i2dt;
585
586 if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_RECT_CORNER)) {
587 p.emplace_back(p0, Inkscape::SNAPSOURCE_RECT_CORNER, Inkscape::SNAPTARGET_RECT_CORNER);
588 p.emplace_back(p1, Inkscape::SNAPSOURCE_RECT_CORNER, Inkscape::SNAPTARGET_RECT_CORNER);
589 p.emplace_back(p2, Inkscape::SNAPSOURCE_RECT_CORNER, Inkscape::SNAPTARGET_RECT_CORNER);
590 p.emplace_back(p3, Inkscape::SNAPSOURCE_RECT_CORNER, Inkscape::SNAPTARGET_RECT_CORNER);
591 }
592
593 if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_LINE_MIDPOINT)) {
594 p.emplace_back((p0 + p1)/2, Inkscape::SNAPSOURCE_LINE_MIDPOINT, Inkscape::SNAPTARGET_LINE_MIDPOINT);
595 p.emplace_back((p1 + p2)/2, Inkscape::SNAPSOURCE_LINE_MIDPOINT, Inkscape::SNAPTARGET_LINE_MIDPOINT);
596 p.emplace_back((p2 + p3)/2, Inkscape::SNAPSOURCE_LINE_MIDPOINT, Inkscape::SNAPTARGET_LINE_MIDPOINT);
597 p.emplace_back((p3 + p0)/2, Inkscape::SNAPSOURCE_LINE_MIDPOINT, Inkscape::SNAPTARGET_LINE_MIDPOINT);
598 }
599
600 if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_OBJECT_MIDPOINT)) {
601 p.emplace_back((p0 + p2)/2, Inkscape::SNAPSOURCE_OBJECT_MIDPOINT, Inkscape::SNAPTARGET_OBJECT_MIDPOINT);
602 }
603 }
604
convert_to_guides() const605 void SPRect::convert_to_guides() const {
606 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
607
608 if (!prefs->getBool("/tools/shapes/rect/convertguides", true)) {
609 // Use bounding box instead of edges
610 SPShape::convert_to_guides();
611 return;
612 }
613
614 std::list<std::pair<Geom::Point, Geom::Point> > pts;
615
616 Geom::Affine const i2dt(this->i2dt_affine());
617
618 Geom::Point A1(Geom::Point(this->x.computed, this->y.computed) * i2dt);
619 Geom::Point A2(Geom::Point(this->x.computed, this->y.computed + this->height.computed) * i2dt);
620 Geom::Point A3(Geom::Point(this->x.computed + this->width.computed, this->y.computed + this->height.computed) * i2dt);
621 Geom::Point A4(Geom::Point(this->x.computed + this->width.computed, this->y.computed) * i2dt);
622
623 pts.emplace_back(A1, A2);
624 pts.emplace_back(A2, A3);
625 pts.emplace_back(A3, A4);
626 pts.emplace_back(A4, A1);
627
628 sp_guide_pt_pairs_to_guides(this->document, pts);
629 }
630
631 /*
632 Local Variables:
633 mode:c++
634 c-file-style:"stroustrup"
635 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
636 indent-tabs-mode:nil
637 fill-column:99
638 End:
639 */
640 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
641