summaryrefslogtreecommitdiffstats
path: root/src/2geom/svg-path-parser.rl
diff options
context:
space:
mode:
Diffstat (limited to 'src/2geom/svg-path-parser.rl')
-rw-r--r--src/2geom/svg-path-parser.rl487
1 files changed, 487 insertions, 0 deletions
diff --git a/src/2geom/svg-path-parser.rl b/src/2geom/svg-path-parser.rl
new file mode 100644
index 0000000..7b3eb5a
--- /dev/null
+++ b/src/2geom/svg-path-parser.rl
@@ -0,0 +1,487 @@
+/**
+ * \file
+ * \brief parse SVG path specifications
+ *
+ * Copyright 2007 MenTaLguY <mental@rydia.net>
+ * Copyright 2007 Aaron Spike <aaron@ekips.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ */
+
+#include <cstdio>
+#include <cmath>
+#include <vector>
+#include <glib.h>
+
+#include <2geom/point.h>
+#include <2geom/svg-path-parser.h>
+#include <2geom/angle.h>
+
+namespace Geom {
+
+%%{
+ machine svg_path;
+ write data noerror;
+}%%
+
+SVGPathParser::SVGPathParser(PathSink &sink)
+ : _absolute(false)
+ , _sink(sink)
+ , _z_snap_threshold(0)
+ , _curve(NULL)
+{
+ reset();
+}
+
+SVGPathParser::~SVGPathParser()
+{
+ delete _curve;
+}
+
+void SVGPathParser::reset() {
+ _absolute = false;
+ _current = _initial = Point(0, 0);
+ _quad_tangent = _cubic_tangent = Point(0, 0);
+ _params.clear();
+ delete _curve;
+ _curve = NULL;
+
+ %%{
+ write init;
+ }%%
+}
+
+void SVGPathParser::parse(char const *str, int len)
+{
+ if (len < 0) {
+ len = std::strlen(str);
+ }
+ _parse(str, str + len, true);
+}
+
+void SVGPathParser::parse(std::string const &s)
+{
+ _parse(s.c_str(), s.c_str() + s.size(), true);
+}
+
+void SVGPathParser::feed(char const *str, int len)
+{
+ if (len < 0) {
+ len = std::strlen(str);
+ }
+ _parse(str, str + len, false);
+}
+
+void SVGPathParser::feed(std::string const &s)
+{
+ _parse(s.c_str(), s.c_str() + s.size(), false);
+}
+
+void SVGPathParser::finish()
+{
+ char const *empty = "";
+ _parse(empty, empty, true);
+}
+
+void SVGPathParser::_push(Coord value)
+{
+ _params.push_back(value);
+}
+
+Coord SVGPathParser::_pop()
+{
+ Coord value = _params.back();
+ _params.pop_back();
+ return value;
+}
+
+bool SVGPathParser::_pop_flag()
+{
+ return _pop() != 0.0;
+}
+
+Coord SVGPathParser::_pop_coord(Dim2 axis)
+{
+ if (_absolute) {
+ return _pop();
+ } else {
+ return _pop() + _current[axis];
+ }
+}
+
+Point SVGPathParser::_pop_point()
+{
+ Coord y = _pop_coord(Y);
+ Coord x = _pop_coord(X);
+ return Point(x, y);
+}
+
+void SVGPathParser::_moveTo(Point const &p)
+{
+ _pushCurve(NULL); // flush
+ _sink.moveTo(p);
+ _quad_tangent = _cubic_tangent = _current = _initial = p;
+}
+
+void SVGPathParser::_lineTo(Point const &p)
+{
+ _pushCurve(new LineSegment(_current, p));
+ _quad_tangent = _cubic_tangent = _current = p;
+}
+
+void SVGPathParser::_curveTo(Point const &c0, Point const &c1, Point const &p)
+{
+ _pushCurve(new CubicBezier(_current, c0, c1, p));
+ _quad_tangent = _current = p;
+ _cubic_tangent = p + ( p - c1 );
+}
+
+void SVGPathParser::_quadTo(Point const &c, Point const &p)
+{
+ _pushCurve(new QuadraticBezier(_current, c, p));
+ _cubic_tangent = _current = p;
+ _quad_tangent = p + ( p - c );
+}
+
+void SVGPathParser::_arcTo(Coord rx, Coord ry, Coord angle,
+ bool large_arc, bool sweep, Point const &p)
+{
+ if (_current == p) {
+ return; // ignore invalid (ambiguous) arc segments where start and end point are the same (per SVG spec)
+ }
+
+ _pushCurve(new EllipticalArc(_current, fabs(rx), fabs(ry), angle, large_arc, sweep, p));
+ _quad_tangent = _cubic_tangent = _current = p;
+}
+
+void SVGPathParser::_closePath()
+{
+ if (_curve && (!_absolute || !_moveto_was_absolute) &&
+ are_near(_initial, _current, _z_snap_threshold))
+ {
+ _curve->setFinal(_initial);
+ }
+
+ _pushCurve(NULL); // flush
+ _sink.closePath();
+ _quad_tangent = _cubic_tangent = _current = _initial;
+}
+
+void SVGPathParser::_pushCurve(Curve *c)
+{
+ if (_curve) {
+ _sink.feed(*_curve, false);
+ delete _curve;
+ }
+ _curve = c;
+}
+
+void SVGPathParser::_parse(char const *str, char const *strend, bool finish)
+{
+ char const *p = str;
+ char const *pe = strend;
+ char const *eof = finish ? pe : NULL;
+ char const *start = NULL;
+
+ %%{
+ action start_number {
+ start = p;
+ }
+
+ action push_number {
+ if (start) {
+ std::string buf(start, p);
+ _push(g_ascii_strtod(buf.c_str(), NULL));
+ start = NULL;
+ } else {
+ std::string buf(str, p);
+ _push(g_ascii_strtod((_number_part + buf).c_str(), NULL));
+ _number_part.clear();
+ }
+ }
+
+ action push_true {
+ _push(1.0);
+ }
+
+ action push_false {
+ _push(0.0);
+ }
+
+ action mode_abs {
+ _absolute = true;
+ }
+
+ action mode_rel {
+ _absolute = false;
+ }
+
+ action moveto {
+ _moveto_was_absolute = _absolute;
+ _moveTo(_pop_point());
+ }
+
+ action lineto {
+ _lineTo(_pop_point());
+ }
+
+ action horizontal_lineto {
+ _lineTo(Point(_pop_coord(X), _current[Y]));
+ }
+
+ action vertical_lineto {
+ _lineTo(Point(_current[X], _pop_coord(Y)));
+ }
+
+ action curveto {
+ Point p = _pop_point();
+ Point c1 = _pop_point();
+ Point c0 = _pop_point();
+ _curveTo(c0, c1, p);
+ }
+
+ action smooth_curveto {
+ Point p = _pop_point();
+ Point c1 = _pop_point();
+ _curveTo(_cubic_tangent, c1, p);
+ }
+
+ action quadratic_bezier_curveto {
+ Point p = _pop_point();
+ Point c = _pop_point();
+ _quadTo(c, p);
+ }
+
+ action smooth_quadratic_bezier_curveto {
+ Point p = _pop_point();
+ _quadTo(_quad_tangent, p);
+ }
+
+ action elliptical_arc {
+ Point point = _pop_point();
+ bool sweep = _pop_flag();
+ bool large_arc = _pop_flag();
+ double angle = rad_from_deg(_pop());
+ double ry = _pop();
+ double rx = _pop();
+
+ _arcTo(rx, ry, angle, large_arc, sweep, point);
+ }
+
+ action closepath {
+ _closePath();
+ }
+
+ wsp = (' ' | 9 | 10 | 13);
+ sign = ('+' | '-');
+ digit_sequence = digit+;
+ exponent = ('e' | 'E') sign? digit_sequence;
+ fractional_constant =
+ digit_sequence? '.' digit_sequence
+ | digit_sequence '.';
+ floating_point_constant =
+ fractional_constant exponent?
+ | digit_sequence exponent;
+ integer_constant = digit_sequence;
+ comma = ',';
+ comma_wsp = (wsp+ comma? wsp*) | (comma wsp*);
+
+ flag = ('0' %push_false | '1' %push_true);
+
+ number =
+ ( sign? integer_constant
+ | sign? floating_point_constant )
+ >start_number %push_number;
+
+ nonnegative_number =
+ ( integer_constant
+ | floating_point_constant)
+ >start_number %push_number;
+
+ coordinate = number $(number,1) %(number,0);
+ coordinate_pair = (coordinate $(coordinate_pair_a,1) %(coordinate_pair_a,0) comma_wsp? coordinate $(coordinate_pair_b,1) %(coordinate_pair_b,0)) $(coordinate_pair,1) %(coordinate_pair,0);
+ elliptical_arc_argument =
+ (number $(elliptical_arg_a,1) %(elliptical_arg_a,0) comma_wsp?
+ number $(elliptical_arg_b,1) %(elliptical_arg_b,0) comma_wsp?
+ number comma_wsp
+ flag comma_wsp? flag comma_wsp?
+ coordinate_pair)
+ %elliptical_arc;
+ elliptical_arc_argument_sequence =
+ elliptical_arc_argument $1 %0
+ (comma_wsp? elliptical_arc_argument $1 %0)*;
+ elliptical_arc =
+ ('A' %mode_abs| 'a' %mode_rel) wsp*
+ elliptical_arc_argument_sequence;
+
+ smooth_quadratic_bezier_curveto_argument =
+ coordinate_pair %smooth_quadratic_bezier_curveto;
+ smooth_quadratic_bezier_curveto_argument_sequence =
+ smooth_quadratic_bezier_curveto_argument $1 %0
+ (comma_wsp?
+ smooth_quadratic_bezier_curveto_argument $1 %0)*;
+ smooth_quadratic_bezier_curveto =
+ ('T' %mode_abs| 't' %mode_rel) wsp*
+ smooth_quadratic_bezier_curveto_argument_sequence;
+
+ quadratic_bezier_curveto_argument =
+ (coordinate_pair $1 %0 comma_wsp? coordinate_pair)
+ %quadratic_bezier_curveto;
+ quadratic_bezier_curveto_argument_sequence =
+ quadratic_bezier_curveto_argument $1 %0
+ (comma_wsp? quadratic_bezier_curveto_argument $1 %0)*;
+ quadratic_bezier_curveto =
+ ('Q' %mode_abs| 'q' %mode_rel) wsp*
+ quadratic_bezier_curveto_argument_sequence;
+
+ smooth_curveto_argument =
+ (coordinate_pair $1 %0 comma_wsp? coordinate_pair)
+ %smooth_curveto;
+ smooth_curveto_argument_sequence =
+ smooth_curveto_argument $1 %0
+ (comma_wsp? smooth_curveto_argument $1 %0)*;
+ smooth_curveto =
+ ('S' %mode_abs| 's' %mode_rel)
+ wsp* smooth_curveto_argument_sequence;
+
+ curveto_argument =
+ (coordinate_pair $1 %0 comma_wsp?
+ coordinate_pair $1 %0 comma_wsp?
+ coordinate_pair)
+ %curveto;
+ curveto_argument_sequence =
+ curveto_argument $1 %0
+ (comma_wsp? curveto_argument $1 %0)*;
+ curveto =
+ ('C' %mode_abs| 'c' %mode_rel)
+ wsp* curveto_argument_sequence;
+
+ vertical_lineto_argument = coordinate %vertical_lineto;
+ vertical_lineto_argument_sequence =
+ vertical_lineto_argument $(vertical_lineto_argument_a,1) %(vertical_lineto_argument_a,0)
+ (comma_wsp? vertical_lineto_argument $(vertical_lineto_argument_b,1) %(vertical_lineto_argument_b,0))*;
+ vertical_lineto =
+ ('V' %mode_abs| 'v' %mode_rel)
+ wsp* vertical_lineto_argument_sequence;
+
+ horizontal_lineto_argument = coordinate %horizontal_lineto;
+ horizontal_lineto_argument_sequence =
+ horizontal_lineto_argument $(horizontal_lineto_argument_a,1) %(horizontal_lineto_argument_a,0)
+ (comma_wsp? horizontal_lineto_argument $(horizontal_lineto_argument_b,1) %(horizontal_lineto_argument_b,0))*;
+ horizontal_lineto =
+ ('H' %mode_abs| 'h' %mode_rel)
+ wsp* horizontal_lineto_argument_sequence;
+
+ lineto_argument = coordinate_pair %lineto;
+ lineto_argument_sequence =
+ lineto_argument $1 %0
+ (comma_wsp? lineto_argument $1 %0)*;
+ lineto =
+ ('L' %mode_abs| 'l' %mode_rel) wsp*
+ lineto_argument_sequence;
+
+ closepath = ('Z' | 'z') %closepath;
+
+ moveto_argument = coordinate_pair %moveto;
+ moveto_argument_sequence =
+ moveto_argument $1 %0
+ (comma_wsp? lineto_argument $1 %0)*;
+ moveto =
+ ('M' %mode_abs | 'm' %mode_rel)
+ wsp* moveto_argument_sequence;
+
+ drawto_command =
+ closepath | lineto |
+ horizontal_lineto | vertical_lineto |
+ curveto | smooth_curveto |
+ quadratic_bezier_curveto |
+ smooth_quadratic_bezier_curveto |
+ elliptical_arc;
+
+ drawto_commands = drawto_command (wsp* drawto_command)*;
+ moveto_drawto_command_group = moveto wsp* drawto_commands?;
+ moveto_drawto_command_groups =
+ moveto_drawto_command_group
+ (wsp* moveto_drawto_command_group)*;
+
+ svg_path = wsp* moveto_drawto_command_groups? wsp*;
+
+
+ main := svg_path;
+
+ write exec;
+ }%%
+
+ if (finish) {
+ if (cs < svg_path_first_final) {
+ throw SVGPathParseError();
+ }
+ } else if (start != NULL) {
+ _number_part = std::string(start, pe);
+ }
+
+ if (finish) {
+ _pushCurve(NULL);
+ _sink.flush();
+ reset();
+ }
+}
+
+void parse_svg_path(char const *str, PathSink &sink)
+{
+ SVGPathParser parser(sink);
+ parser.parse(str);
+}
+
+void parse_svg_path_file(FILE *fi, PathSink &sink)
+{
+ static const size_t BUFFER_SIZE = 4096;
+ char buffer[BUFFER_SIZE];
+ size_t bytes_read;
+ SVGPathParser parser(sink);
+
+ while (true) {
+ bytes_read = fread(buffer, 1, BUFFER_SIZE, fi);
+ if (bytes_read < BUFFER_SIZE) {
+ parser.parse(buffer, bytes_read);
+ break;
+ } else {
+ parser.feed(buffer, bytes_read);
+ }
+ }
+}
+
+} // namespace Geom
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=ragel:cindent:expandtab:shiftwidth=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :