1 #define __SP_SVG_PARSE_C__
2 /*
3 svg-path.c: Parse SVG path element data into bezier path.
5 Copyright (C) 2000 Eazel, Inc.
6 Copyright (C) 2000 Lauris Kaplinski
7 Copyright (C) 2001 Ximian, Inc.
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License as
11 published by the Free Software Foundation; either version 2 of the
12 License, or (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 General Public License for more details.
19 You should have received a copy of the GNU General Public
20 License along with this program; if not, write to the
21 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 Boston, MA 02111-1307, USA.
24 Authors:
25 Raph Levien <raph@artofcode.com>
26 Lauris Kaplinski <lauris@ximian.com>
27 */
29 #include <cassert>
30 #include <glib/gmem.h>
31 #include <glib/gmessages.h>
32 #include <glib/gstrfuncs.h>
33 #include <glib.h> // g_assert()
35 #include "libnr/n-art-bpath.h"
36 #include "gnome-canvas-bpath-util.h"
37 #include "svg/path-string.h"
40 /* This module parses an SVG path element into an RsvgBpathDef.
42 At present, there is no support for <marker> or any other contextual
43 information from the SVG file. The API will need to change rather
44 significantly to support these.
46 Reference: SVG working draft 3 March 2000, section 8.
47 */
49 #ifndef M_PI
50 #define M_PI 3.14159265358979323846
51 #endif /* M_PI */
53 /* We are lazy ;-) (Lauris) */
54 #define rsvg_bpath_def_new gnome_canvas_bpath_def_new
55 #define rsvg_bpath_def_moveto gnome_canvas_bpath_def_moveto
56 #define rsvg_bpath_def_lineto gnome_canvas_bpath_def_lineto
57 #define rsvg_bpath_def_curveto gnome_canvas_bpath_def_curveto
58 #define rsvg_bpath_def_closepath gnome_canvas_bpath_def_closepath
60 struct RSVGParsePathCtx {
61 GnomeCanvasBpathDef *bpath;
62 double cpx, cpy; /* current point */
63 double rpx, rpy; /* reflection point (for 's' and 't' commands) */
64 double spx, spy; /* beginning of current subpath point */
65 char cmd; /* current command (lowercase) */
66 int param; /* parameter number */
67 bool rel; /* true if relative coords */
68 double params[7]; /* parameters that have been parsed */
69 };
71 static void rsvg_path_arc_segment(RSVGParsePathCtx *ctx,
72 double xc, double yc,
73 double th0, double th1,
74 double rx, double ry, double x_axis_rotation)
75 {
76 double sin_th, cos_th;
77 double a00, a01, a10, a11;
78 double x1, y1, x2, y2, x3, y3;
79 double t;
80 double th_half;
82 sin_th = sin (x_axis_rotation * (M_PI / 180.0));
83 cos_th = cos (x_axis_rotation * (M_PI / 180.0));
84 /* inverse transform compared with rsvg_path_arc */
85 a00 = cos_th * rx;
86 a01 = -sin_th * ry;
87 a10 = sin_th * rx;
88 a11 = cos_th * ry;
90 th_half = 0.5 * (th1 - th0);
91 t = (8.0 / 3.0) * sin(th_half * 0.5) * sin(th_half * 0.5) / sin(th_half);
92 x1 = xc + cos (th0) - t * sin (th0);
93 y1 = yc + sin (th0) + t * cos (th0);
94 x3 = xc + cos (th1);
95 y3 = yc + sin (th1);
96 x2 = x3 + t * sin (th1);
97 y2 = y3 - t * cos (th1);
98 rsvg_bpath_def_curveto(ctx->bpath,
99 a00 * x1 + a01 * y1, a10 * x1 + a11 * y1,
100 a00 * x2 + a01 * y2, a10 * x2 + a11 * y2,
101 a00 * x3 + a01 * y3, a10 * x3 + a11 * y3);
102 }
104 /**
105 * rsvg_path_arc: Add an RSVG arc to the path context.
106 * @ctx: Path context.
107 * @rx: Radius in x direction (before rotation).
108 * @ry: Radius in y direction (before rotation).
109 * @x_axis_rotation: Rotation angle for axes.
110 * @large_arc_flag: 0 for arc length <= 180, 1 for arc >= 180.
111 * @sweep: 0 for "negative angle", 1 for "positive angle".
112 * @x: New x coordinate.
113 * @y: New y coordinate.
114 *
115 **/
116 static void rsvg_path_arc (RSVGParsePathCtx *ctx,
117 double rx, double ry, double x_axis_rotation,
118 int large_arc_flag, int sweep_flag,
119 double x, double y)
120 {
121 double sin_th, cos_th;
122 double a00, a01, a10, a11;
123 double x0, y0, x1, y1, xc, yc;
124 double d, sfactor, sfactor_sq;
125 double th0, th1, th_arc;
126 double px, py, pl;
127 int i, n_segs;
129 sin_th = sin (x_axis_rotation * (M_PI / 180.0));
130 cos_th = cos (x_axis_rotation * (M_PI / 180.0));
132 /*
133 Correction of out-of-range radii as described in Appendix F.6.6:
135 1. Ensure radii are non-zero (Done?).
136 2. Ensure that radii are positive.
137 3. Ensure that radii are large enough.
138 */
140 if(rx < 0.0) rx = -rx;
141 if(ry < 0.0) ry = -ry;
143 px = cos_th * (ctx->cpx - x) * 0.5 + sin_th * (ctx->cpy - y) * 0.5;
144 py = cos_th * (ctx->cpy - y) * 0.5 - sin_th * (ctx->cpx - x) * 0.5;
145 pl = (px * px) / (rx * rx) + (py * py) / (ry * ry);
147 if(pl > 1.0)
148 {
149 pl = sqrt(pl);
150 rx *= pl;
151 ry *= pl;
152 }
154 /* Proceed with computations as described in Appendix F.6.5 */
156 a00 = cos_th / rx;
157 a01 = sin_th / rx;
158 a10 = -sin_th / ry;
159 a11 = cos_th / ry;
160 x0 = a00 * ctx->cpx + a01 * ctx->cpy;
161 y0 = a10 * ctx->cpx + a11 * ctx->cpy;
162 x1 = a00 * x + a01 * y;
163 y1 = a10 * x + a11 * y;
164 /* (x0, y0) is current point in transformed coordinate space.
165 (x1, y1) is new point in transformed coordinate space.
167 The arc fits a unit-radius circle in this space.
168 */
169 d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0);
170 sfactor_sq = 1.0 / d - 0.25;
171 if (sfactor_sq < 0) sfactor_sq = 0;
172 sfactor = sqrt (sfactor_sq);
173 if (sweep_flag == large_arc_flag) sfactor = -sfactor;
174 xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0);
175 yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0);
176 /* (xc, yc) is center of the circle. */
178 th0 = atan2 (y0 - yc, x0 - xc);
179 th1 = atan2 (y1 - yc, x1 - xc);
181 th_arc = th1 - th0;
182 if (th_arc < 0 && sweep_flag)
183 th_arc += 2 * M_PI;
184 else if (th_arc > 0 && !sweep_flag)
185 th_arc -= 2 * M_PI;
187 n_segs = (int) ceil (fabs (th_arc / (M_PI * 0.5 + 0.001)));
189 for (i = 0; i < n_segs; i++) {
190 rsvg_path_arc_segment(ctx, xc, yc,
191 th0 + i * th_arc / n_segs,
192 th0 + (i + 1) * th_arc / n_segs,
193 rx, ry, x_axis_rotation);
194 }
196 ctx->cpx = x;
197 ctx->cpy = y;
198 }
201 /* supply defaults for missing parameters, assuming relative coordinates
202 are to be interpreted as x,y */
203 static void rsvg_parse_path_default_xy(RSVGParsePathCtx *ctx, int n_params)
204 {
205 int i;
207 if (ctx->rel) {
208 for (i = ctx->param; i < n_params; i++) {
209 if (i > 2)
210 ctx->params[i] = ctx->params[i - 2];
211 else if (i == 1)
212 ctx->params[i] = ctx->cpy;
213 else if (i == 0)
214 /* we shouldn't get here (usually ctx->param > 0 as
215 precondition) */
216 ctx->params[i] = ctx->cpx;
217 }
218 } else {
219 for (i = ctx->param; i < n_params; i++) {
220 ctx->params[i] = 0.0;
221 }
222 }
223 }
225 static void rsvg_parse_path_do_cmd(RSVGParsePathCtx *ctx, bool final)
226 {
227 double x1, y1, x2, y2, x3, y3;
229 #ifdef VERBOSE
230 int i;
232 g_print ("parse_path %c:", ctx->cmd);
233 for (i = 0; i < ctx->param; i++) {
234 g_print(" %f", ctx->params[i]);
235 }
236 g_print (final ? ".\n" : "\n");
237 #endif
239 switch (ctx->cmd) {
240 case 'm':
241 /* moveto */
242 if (ctx->param == 2 || final)
243 {
244 rsvg_parse_path_default_xy (ctx, 2);
245 #ifdef VERBOSE
246 g_print ("'m' moveto %g,%g\n",
247 ctx->params[0], ctx->params[1]);
248 #endif
249 rsvg_bpath_def_moveto (ctx->bpath,
250 ctx->params[0], ctx->params[1]);
251 ctx->cpx = ctx->rpx = ctx->spx = ctx->params[0];
252 ctx->cpy = ctx->rpy = ctx->spy = ctx->params[1];
253 ctx->param = 0;
254 ctx->cmd = 'l';
255 }
256 break;
257 case 'l':
258 /* lineto */
259 if (ctx->param == 2 || final)
260 {
261 rsvg_parse_path_default_xy (ctx, 2);
262 #ifdef VERBOSE
263 g_print ("'l' lineto %g,%g\n",
264 ctx->params[0], ctx->params[1]);
265 #endif
266 rsvg_bpath_def_lineto (ctx->bpath,
267 ctx->params[0], ctx->params[1]);
268 ctx->cpx = ctx->rpx = ctx->params[0];
269 ctx->cpy = ctx->rpy = ctx->params[1];
270 ctx->param = 0;
271 }
272 break;
273 case 'c':
274 /* curveto */
275 if (ctx->param == 6 || final )
276 {
277 rsvg_parse_path_default_xy (ctx, 6);
278 x1 = ctx->params[0];
279 y1 = ctx->params[1];
280 x2 = ctx->params[2];
281 y2 = ctx->params[3];
282 x3 = ctx->params[4];
283 y3 = ctx->params[5];
284 #ifdef VERBOSE
285 g_print ("'c' curveto %g,%g %g,%g, %g,%g\n",
286 x1, y1, x2, y2, x3, y3);
287 #endif
288 rsvg_bpath_def_curveto (ctx->bpath,
289 x1, y1, x2, y2, x3, y3);
290 ctx->rpx = x2;
291 ctx->rpy = y2;
292 ctx->cpx = x3;
293 ctx->cpy = y3;
294 ctx->param = 0;
295 }
296 break;
297 case 's':
298 /* smooth curveto */
299 if (ctx->param == 4 || final)
300 {
301 rsvg_parse_path_default_xy (ctx, 4);
302 x1 = 2 * ctx->cpx - ctx->rpx;
303 y1 = 2 * ctx->cpy - ctx->rpy;
304 x2 = ctx->params[0];
305 y2 = ctx->params[1];
306 x3 = ctx->params[2];
307 y3 = ctx->params[3];
308 #ifdef VERBOSE
309 g_print ("'s' curveto %g,%g %g,%g, %g,%g\n",
310 x1, y1, x2, y2, x3, y3);
311 #endif
312 rsvg_bpath_def_curveto (ctx->bpath,
313 x1, y1, x2, y2, x3, y3);
314 ctx->rpx = x2;
315 ctx->rpy = y2;
316 ctx->cpx = x3;
317 ctx->cpy = y3;
318 ctx->param = 0;
319 }
320 break;
321 case 'h':
322 /* horizontal lineto */
323 if (ctx->param == 1) {
324 #ifdef VERBOSE
325 g_print ("'h' lineto %g,%g\n",
326 ctx->params[0], ctx->cpy);
327 #endif
328 rsvg_bpath_def_lineto (ctx->bpath,
329 ctx->params[0], ctx->cpy);
330 ctx->cpx = ctx->rpx = ctx->params[0];
331 ctx->param = 0;
332 }
333 break;
334 case 'v':
335 /* vertical lineto */
336 if (ctx->param == 1) {
337 #ifdef VERBOSE
338 g_print ("'v' lineto %g,%g\n",
339 ctx->cpx, ctx->params[0]);
340 #endif
341 rsvg_bpath_def_lineto (ctx->bpath,
342 ctx->cpx, ctx->params[0]);
343 ctx->cpy = ctx->rpy = ctx->params[0];
344 ctx->param = 0;
345 }
346 break;
347 case 'q':
348 /* quadratic bezier curveto */
350 /* non-normative reference:
351 http://www.icce.rug.nl/erikjan/bluefuzz/beziers/beziers/beziers.html
352 */
353 if (ctx->param == 4 || final)
354 {
355 rsvg_parse_path_default_xy (ctx, 4);
356 /* raise quadratic bezier to cubic */
357 x1 = (ctx->cpx + 2 * ctx->params[0]) * (1.0 / 3.0);
358 y1 = (ctx->cpy + 2 * ctx->params[1]) * (1.0 / 3.0);
359 x3 = ctx->params[2];
360 y3 = ctx->params[3];
361 x2 = (x3 + 2 * ctx->params[0]) * (1.0 / 3.0);
362 y2 = (y3 + 2 * ctx->params[1]) * (1.0 / 3.0);
363 #ifdef VERBOSE
364 g_print("'q' curveto %g,%g %g,%g, %g,%g\n",
365 x1, y1, x2, y2, x3, y3);
366 #endif
367 rsvg_bpath_def_curveto(ctx->bpath,
368 x1, y1, x2, y2, x3, y3);
369 ctx->rpx = ctx->params[0];
370 ctx->rpy = ctx->params[1];
371 ctx->cpx = x3;
372 ctx->cpy = y3;
373 ctx->param = 0;
374 }
375 break;
376 case 't':
377 /* Truetype quadratic bezier curveto */
378 if (ctx->param == 2 || final) {
379 double xc, yc; /* quadratic control point */
381 xc = 2 * ctx->cpx - ctx->rpx;
382 yc = 2 * ctx->cpy - ctx->rpy;
383 /* generate a quadratic bezier with control point = xc, yc */
384 x1 = (ctx->cpx + 2 * xc) * (1.0 / 3.0);
385 y1 = (ctx->cpy + 2 * yc) * (1.0 / 3.0);
386 x3 = ctx->params[0];
387 y3 = ctx->params[1];
388 x2 = (x3 + 2 * xc) * (1.0 / 3.0);
389 y2 = (y3 + 2 * yc) * (1.0 / 3.0);
390 #ifdef VERBOSE
391 g_print ("'t' curveto %g,%g %g,%g, %g,%g\n",
392 x1, y1, x2, y2, x3, y3);
393 #endif
394 rsvg_bpath_def_curveto (ctx->bpath,
395 x1, y1, x2, y2, x3, y3);
396 ctx->rpx = xc;
397 ctx->rpy = yc;
398 ctx->cpx = x3;
399 ctx->cpy = y3;
400 ctx->param = 0;
401 } else if (final) {
402 if (ctx->param > 2) {
403 rsvg_parse_path_default_xy(ctx, 4);
404 /* raise quadratic bezier to cubic */
405 x1 = (ctx->cpx + 2 * ctx->params[0]) * (1.0 / 3.0);
406 y1 = (ctx->cpy + 2 * ctx->params[1]) * (1.0 / 3.0);
407 x3 = ctx->params[2];
408 y3 = ctx->params[3];
409 x2 = (x3 + 2 * ctx->params[0]) * (1.0 / 3.0);
410 y2 = (y3 + 2 * ctx->params[1]) * (1.0 / 3.0);
411 #ifdef VERBOSE
412 g_print ("'t' curveto %g,%g %g,%g, %g,%g\n",
413 x1, y1, x2, y2, x3, y3);
414 #endif
415 rsvg_bpath_def_curveto (ctx->bpath,
416 x1, y1, x2, y2, x3, y3);
417 ctx->rpx = x2;
418 ctx->rpy = y2;
419 ctx->cpx = x3;
420 ctx->cpy = y3;
421 } else {
422 rsvg_parse_path_default_xy(ctx, 2);
423 #ifdef VERBOSE
424 g_print ("'t' lineto %g,%g\n",
425 ctx->params[0], ctx->params[1]);
426 #endif
427 rsvg_bpath_def_lineto(ctx->bpath,
428 ctx->params[0], ctx->params[1]);
429 ctx->cpx = ctx->rpx = ctx->params[0];
430 ctx->cpy = ctx->rpy = ctx->params[1];
431 }
432 ctx->param = 0;
433 }
434 break;
435 case 'a':
436 if (ctx->param == 7 || final)
437 {
438 rsvg_path_arc(ctx,
439 ctx->params[0], ctx->params[1], ctx->params[2],
440 (int) ctx->params[3], (int) ctx->params[4],
441 ctx->params[5], ctx->params[6]);
442 ctx->param = 0;
443 }
444 break;
445 default:
446 ctx->param = 0;
447 }
448 }
450 static void rsvg_parse_path_data(RSVGParsePathCtx *ctx, const char *data)
451 {
452 int i = 0;
453 double val = 0;
454 char c = 0;
455 bool in_num = false;
456 bool in_frac = false;
457 bool in_exp = false;
458 bool exp_wait_sign = false;
459 int sign = 0;
460 int exp = 0;
461 int exp_sign = 0;
462 double frac = 0.0;
464 /* fixme: Do better error processing: e.g. at least stop parsing as soon as we find an error.
465 * At some point we'll need to do all of
466 * http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing.
467 */
468 for (i = 0; ; i++)
469 {
470 c = data[i];
471 if (c >= '0' && c <= '9')
472 {
473 /* digit */
474 if (in_num)
475 {
476 if (in_exp)
477 {
478 exp = (exp * 10) + c - '0';
479 exp_wait_sign = false;
480 }
481 else if (in_frac)
482 val += (frac *= 0.1) * (c - '0');
483 else
484 val = (val * 10) + c - '0';
485 }
486 else
487 {
488 in_num = true;
489 assert(!in_frac && !in_exp);
490 exp = 0;
491 exp_sign = 1;
492 exp_wait_sign = false;
493 val = c - '0';
494 sign = 1;
495 }
496 }
497 else if (c == '.' && !(in_frac || in_exp))
498 {
499 if (!in_num)
500 {
501 in_num = true;
502 assert(!in_exp);
503 exp = 0;
504 exp_sign = 1;
505 exp_wait_sign = false;
506 val = 0;
507 sign = 1;
508 }
509 in_frac = true;
510 frac = 1;
511 }
512 else if ((c == 'E' || c == 'e') && in_num)
513 {
514 /* fixme: Should we add `&& !in_exp' to the above condition?
515 * It looks like the current code will parse `1e3e4' (as 1e4). */
516 in_exp = true;
517 exp_wait_sign = true;
518 exp = 0;
519 exp_sign = 1;
520 }
521 else if ((c == '+' || c == '-') && in_exp)
522 {
523 exp_sign = c == '+' ? 1 : -1;
524 }
525 else if (in_num)
526 {
527 /* end of number */
529 val *= sign * pow (10, exp_sign * exp);
530 if (ctx->rel)
531 {
532 /* Handle relative coordinates. This switch statement attempts
533 to determine _what_ the coords are relative to. This is
534 underspecified in the 12 Apr working draft. */
535 switch (ctx->cmd)
536 {
537 case 'l':
538 case 'm':
539 case 'c':
540 case 's':
541 case 'q':
542 case 't':
543 if ( ctx->param & 1 ) {
544 val += ctx->cpy; /* odd param, y */
545 } else {
546 val += ctx->cpx; /* even param, x */
547 }
548 break;
549 case 'a':
550 /* rule: sixth and seventh are x and y, rest are not
551 relative */
552 if (ctx->param == 5)
553 val += ctx->cpx;
554 else if (ctx->param == 6)
555 val += ctx->cpy;
556 break;
557 case 'h':
558 /* rule: x-relative */
559 val += ctx->cpx;
560 break;
561 case 'v':
562 /* rule: y-relative */
563 val += ctx->cpy;
564 break;
565 }
566 }
567 ctx->params[ctx->param++] = val;
568 rsvg_parse_path_do_cmd (ctx, false);
569 if (c=='.') {
570 in_num = true;
571 val = 0;
572 in_frac = true;
573 in_exp = false;
574 frac = 1;
575 }
576 else {
577 in_num = false;
578 in_frac = false;
579 in_exp = false;
580 }
581 }
583 if (c == '\0')
584 break;
585 else if ((c == '+' || c == '-') && !exp_wait_sign)
586 {
587 sign = c == '+' ? 1 : -1;;
588 val = 0;
589 in_num = true;
590 in_frac = false;
591 in_exp = false;
592 exp = 0;
593 exp_sign = 1;
594 exp_wait_sign = false;
595 }
596 else if (c == 'z' || c == 'Z')
597 {
598 if (ctx->param)
599 rsvg_parse_path_do_cmd (ctx, true);
600 rsvg_bpath_def_closepath (ctx->bpath);
602 ctx->cmd = 'm';
603 ctx->params[0] = ctx->cpx = ctx->rpx = ctx->spx;
604 ctx->params[1] = ctx->cpy = ctx->rpy = ctx->spy;
605 ctx->param = 2;
606 }
607 else if (c >= 'A' && c <= 'Z' && c != 'E')
608 {
609 if (ctx->param)
610 rsvg_parse_path_do_cmd (ctx, true);
611 ctx->cmd = c + 'a' - 'A';
612 ctx->rel = false;
613 }
614 else if (c >= 'a' && c <= 'z' && c != 'e')
615 {
616 if (ctx->param)
617 rsvg_parse_path_do_cmd (ctx, true);
618 ctx->cmd = c;
619 ctx->rel = true;
620 }
621 /* else c _should_ be whitespace or , */
622 }
623 }
626 NArtBpath *sp_svg_read_path(gchar const *str)
627 {
628 RSVGParsePathCtx ctx;
629 NArtBpath *bpath;
631 ctx.bpath = gnome_canvas_bpath_def_new ();
632 ctx.cpx = 0.0;
633 ctx.cpy = 0.0;
634 ctx.cmd = 0;
635 ctx.param = 0;
637 rsvg_parse_path_data (&ctx, str);
639 if (ctx.param && ctx.cmd != 'm') {
640 rsvg_parse_path_do_cmd (&ctx, TRUE);
641 }
643 gnome_canvas_bpath_def_art_finish (ctx.bpath);
645 bpath = g_new (NArtBpath, ctx.bpath->n_bpath);
646 memcpy (bpath, ctx.bpath->bpath, ctx.bpath->n_bpath * sizeof (NArtBpath));
647 g_assert ((bpath + ctx.bpath->n_bpath - 1)->code == NR_END);
648 gnome_canvas_bpath_def_unref (ctx.bpath);
650 return bpath;
651 }
653 gchar *sp_svg_write_path(NArtBpath const *bpath)
654 {
655 Inkscape::SVGOStringStream os;
656 bool closed=false;
658 g_return_val_if_fail (bpath != NULL, NULL);
660 Inkscape::SVG::PathString str;
662 for (int i = 0; bpath[i].code != NR_END; i++){
663 switch (bpath [i].code){
664 case NR_LINETO:
665 str.lineTo(bpath[i].x3, bpath[i].y3);
666 break;
668 case NR_CURVETO:
669 str.curveTo(bpath[i].x1, bpath[i].y1,
670 bpath[i].x2, bpath[i].y2,
671 bpath[i].x3, bpath[i].y3);
672 break;
674 case NR_MOVETO_OPEN:
675 case NR_MOVETO:
676 if (closed) {
677 str.closePath();
678 }
679 closed = ( bpath[i].code == NR_MOVETO );
680 str.moveTo(bpath[i].x3, bpath[i].y3);
681 break;
683 default:
684 g_assert_not_reached ();
685 }
686 }
687 if (closed) {
688 str.closePath();
689 }
691 return g_strdup(str.c_str());
692 }
694 /*
695 Local Variables:
696 mode:c++
697 c-file-style:"stroustrup"
698 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
699 indent-tabs-mode:nil
700 fill-column:99
701 End:
702 */
703 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :