SVG path parser - boost::spirit::x3 based implementation

This commit is contained in:
artemp 2016-12-22 14:57:26 +01:00
parent 9d0096eff6
commit 30749031cf
5 changed files with 266 additions and 178 deletions

View file

@ -1,61 +0,0 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2016 Artem Pavlenko
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*****************************************************************************/
#ifndef MAPNIK_SVG_PATH_GRAMMAR_HPP
#define MAPNIK_SVG_PATH_GRAMMAR_HPP
// spirit
#pragma GCC diagnostic push
#include <mapnik/warning_ignore.hpp>
#include <boost/spirit/include/qi.hpp>
#pragma GCC diagnostic pop
namespace mapnik { namespace svg {
using namespace boost::spirit;
template <typename Iterator, typename PathType, typename SkipType>
struct svg_path_grammar : qi::grammar<Iterator, void(PathType&), SkipType>
{
// ctor
svg_path_grammar();
// rules
qi::rule<Iterator, void(PathType&), SkipType> start;
qi::rule<Iterator, void(PathType&), SkipType> cmd;
qi::rule<Iterator, void(PathType&), SkipType> drawto_cmd;
qi::rule<Iterator, qi::locals<bool>, void(PathType&), SkipType> M; // M,m
qi::rule<Iterator, qi::locals<bool>, void(PathType&), SkipType> L; // L,l
qi::rule<Iterator, qi::locals<bool>, void(PathType&), SkipType> H; // H,h
qi::rule<Iterator, qi::locals<bool>, void(PathType&), SkipType> V; // V,v
qi::rule<Iterator, qi::locals<bool>, void(PathType&), SkipType> C; // C,c
qi::rule<Iterator, qi::locals<bool>, void(PathType&), SkipType> S; // S,s
qi::rule<Iterator, qi::locals<bool>, void(PathType&), SkipType> Q; // Q,q
qi::rule<Iterator, qi::locals<bool>, void(PathType&), SkipType> T; // T,t
qi::rule<Iterator, qi::locals<bool>, void(PathType&), SkipType> A; // A,a
qi::rule<Iterator, void(PathType&), SkipType> Z; // Z,z
qi::rule<Iterator, boost::fusion::vector2<double, double>(), SkipType> coord;
};
}}
#endif // MAPNIK_SVG_PATH_GRAMMAR_HPP

View file

@ -1,111 +0,0 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2016 Artem Pavlenko
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*****************************************************************************/
// NOTE: This is an implementation header file and is only meant to be included
// from implementation files. It therefore doesn't have an include guard.
// mapnik
#include <mapnik/svg/svg_path_grammar.hpp>
#include <mapnik/svg/svg_path_commands.hpp>
// spirit
#pragma GCC diagnostic push
#include <mapnik/warning_ignore.hpp>
#include <boost/spirit/include/phoenix_function.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#pragma GCC diagnostic pop
namespace mapnik { namespace svg {
using namespace boost::spirit;
using namespace boost::phoenix;
template <typename Iterator, typename PathType, typename SkipType>
svg_path_grammar<Iterator, PathType, SkipType>::svg_path_grammar()
: svg_path_grammar::base_type(start)
{
qi::_1_type _1;
qi::_2_type _2;
qi::_3_type _3;
qi::_4_type _4;
qi::_5_type _5;
qi::_a_type _a;
qi::lit_type lit;
qi::_r1_type _r1;
qi::double_type double_;
qi::int_type int_;
qi::no_case_type no_case;
// commands
function<move_to> move_to_;
function<hline_to> hline_to_;
function<vline_to> vline_to_;
function<line_to> line_to_;
function<curve4> curve4_;
function<curve4_smooth> curve4_smooth_;
function<curve3> curve3_;
function<curve3_smooth> curve3_smooth_;
function<arc_to> arc_to_;
function<close> close_;
//
start = +cmd(_r1);
cmd = M(_r1) >> *drawto_cmd(_r1);
drawto_cmd = L(_r1) | H(_r1) | V(_r1) | C(_r1) | S(_r1) | Q(_r1) | T(_r1) | A(_r1) | Z(_r1);
M = (lit('M')[_a = false] | lit('m')[_a = true]) >> coord[move_to_(_r1, _1, _a)] // move_to
>> *(-lit(',') >> coord[line_to_(_r1, _1, _a)]); // *line_to
H = (lit('H')[_a = false] | lit('h')[_a = true])
>> (double_[ hline_to_(_r1, _1,_a) ] % -lit(',')) ; // +hline_to
V = (lit('V')[_a = false] | lit('v')[_a = true])
>> (double_ [ vline_to_(_r1, _1,_a) ] % -lit(',')); // +vline_to
L = (lit('L')[_a = false] | lit('l')[_a = true])
>> (coord [ line_to_(_r1, _1, _a) ] % -lit(',')); // +line_to
C = (lit('C')[_a = false] | lit('c')[_a = true])
>> ((coord >> -lit(',') >> coord >> -lit(',') >> coord)[curve4_(_r1, _1, _2, _3, _a)] % -lit(',')); // +curve4
S = (lit('S')[_a = false] | lit('s')[_a = true])
>> ((coord >> -lit(',') >> coord) [ curve4_smooth_(_r1, _1,_2,_a) ] % -lit(',')); // +curve4_smooth (smooth curveto)
Q = (lit('Q')[_a = false] | lit('q')[_a = true])
>> ((coord >> -lit(',') >> coord) [ curve3_(_r1, _1,_2,_a) ] % -lit(',')); // +curve3 (quadratic-bezier-curveto)
T = (lit('T')[_a = false] | lit('t')[_a = true])
>> ((coord ) [ curve3_smooth_(_r1, _1,_a) ] % -lit(',')); // +curve3_smooth (smooth-quadratic-bezier-curveto)
A = (lit('A')[_a = false] | lit('a')[_a = true])
>> ((coord >> -lit(',') >> double_ >> -lit(',') >> int_ >> -lit(',') >> int_ >> -lit(',') >> coord)
[arc_to_(_r1, _1, _2, _3, _4, _5, _a)] % -lit(',')); // arc_to;
Z = no_case[lit('z')] [close_(_r1)]; // close path
coord = double_ >> -lit(',') >> double_;
}
} // namespace svg
} // namespace mapnik

View file

@ -0,0 +1,48 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2016 Artem Pavlenko
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*****************************************************************************/
#ifndef MAPNIK_SVG_PATH_GRAMMAR_X3_HPP
#define MAPNIK_SVG_PATH_GRAMMAR_X3_HPP
#pragma GCC diagnostic push
#include <mapnik/warning_ignore.hpp>
#include <boost/spirit/home/x3.hpp>
#pragma GCC diagnostic pop
namespace mapnik { namespace svg { namespace grammar {
namespace x3 = boost::spirit::x3;
class relative_tag;
class svg_path_tag;
using svg_path_grammar_type = x3::rule<class svg_rule_tag>;
BOOST_SPIRIT_DECLARE(svg_path_grammar_type);
}
grammar::svg_path_grammar_type svg_path_grammar();
}}
#endif // MAPNIK_SVG_PATH_GRAMMAR_X3_HPP

View file

@ -0,0 +1,195 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2016 Artem Pavlenko
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*****************************************************************************/
#ifndef MAPNIK_SVG_PATH_GRAMMAR_X3_DEF_HPP
#define MAPNIK_SVG_PATH_GRAMMAR_X3_DEF_HPP
#include <mapnik/global.hpp>
#include <mapnik/config.hpp>
#include <mapnik/svg/svg_path_grammar_x3.hpp>
#pragma GCC diagnostic push
#include <mapnik/warning_ignore.hpp>
#include <boost/fusion/adapted/std_tuple.hpp>
#pragma GCC diagnostic pop
namespace mapnik { namespace svg { namespace grammar {
namespace x3 = boost::spirit::x3;
using x3::lit;
using x3::double_;
using x3::int_;
using x3::no_case;
using coord_type = std::tuple<double,double>;
auto const move_to = [] (auto const& ctx)
{
x3::get<svg_path_tag>(ctx).get().move_to(std::get<0>(_attr(ctx)), std::get<1>(_attr(ctx)), x3::get<relative_tag>(ctx));
};
auto const line_to = [] (auto const & ctx)
{
x3::get<svg_path_tag>(ctx).get().line_to(std::get<0>(_attr(ctx)), std::get<1>(_attr(ctx)), x3::get<relative_tag>(ctx));
};
auto const hline_to = [] (auto const& ctx)
{
x3::get<svg_path_tag>(ctx).get().hline_to(_attr(ctx), x3::get<relative_tag>(ctx));
};
auto const vline_to = [] (auto const& ctx)
{
x3::get<svg_path_tag>(ctx).get().vline_to(_attr(ctx), x3::get<relative_tag>(ctx));
};
auto const curve4 = [] (auto const& ctx)
{
auto const& attr = _attr(ctx);
auto const& p0 = boost::fusion::at_c<0>(attr);
auto const& p1 = boost::fusion::at_c<1>(attr);
auto const& p2 = boost::fusion::at_c<2>(attr);
x3::get<svg_path_tag>(ctx).get().curve4(std::get<0>(p0),std::get<1>(p0),
std::get<0>(p1),std::get<1>(p1),
std::get<0>(p2),std::get<1>(p2),
x3::get<relative_tag>(ctx));
};
auto const curve4_smooth = [] (auto const& ctx)
{
auto const& attr = _attr(ctx);
auto const& p0 = boost::fusion::at_c<0>(attr);
auto const& p1 = boost::fusion::at_c<1>(attr);
x3::get<svg_path_tag>(ctx).get().curve4(std::get<0>(p0),std::get<1>(p0),
std::get<0>(p1),std::get<1>(p1),
x3::get<relative_tag>(ctx));
};
auto const curve3 = [] (auto const& ctx)
{
auto const& attr = _attr(ctx);
auto const& p0 = boost::fusion::at_c<0>(attr);
auto const& p1 = boost::fusion::at_c<1>(attr);
x3::get<svg_path_tag>(ctx).get().curve3(std::get<0>(p0),std::get<1>(p0),
std::get<0>(p1),std::get<1>(p1),
x3::get<relative_tag>(ctx));
};
auto const curve3_smooth = [] (auto const& ctx)
{
auto const& attr = _attr(ctx);
x3::get<svg_path_tag>(ctx).get().curve3(std::get<0>(attr),std::get<1>(attr),
x3::get<relative_tag>(ctx));
};
inline double deg2rad(double deg)
{
return (M_PI * deg) / 180.0;
}
auto const arc_to = [] (auto & ctx)
{
auto const& attr = _attr(ctx);
auto const& p = boost::fusion::at_c<0>(attr);
double angle = boost::fusion::at_c<1>(attr);
int large_arc_flag = boost::fusion::at_c<2>(attr);
int sweep_flag = boost::fusion::at_c<3>(attr);
auto const& v = boost::fusion::at_c<4>(attr);
x3::get<svg_path_tag>(ctx).get().arc_to(std::get<0>(p),std::get<1>(p),
deg2rad(angle), large_arc_flag, sweep_flag,
std::get<0>(v),std::get<1>(v),
x3::get<relative_tag>(ctx));
};
auto const close_path = [] (auto const& ctx)
{
x3::get<svg_path_tag>(ctx).get().close_subpath();
};
auto const relative = [] (auto const& ctx)
{
x3::get<relative_tag>(ctx).get() = true;
};
auto const absolute = [] (auto const& ctx)
{
x3::get<relative_tag>(ctx).get() = false;
};
svg_path_grammar_type const svg_path = "SVG Path";
auto const coord = x3::rule<class coord_tag, coord_type>{} = double_ > -lit(',') > double_;
auto const M = x3::rule<class M_tag> {} = (lit('M')[absolute] | lit('m')[relative])
> coord[move_to] // move_to
> *(-lit(',') >> coord[line_to]); // *line_to
auto const H = x3::rule<class H_tag> {} = (lit('H')[absolute] | lit('h')[relative])
> (double_[ hline_to] % -lit(',')) ; // +hline_to
auto const V = x3::rule<class V_tag> {} = (lit('V')[absolute] | lit('v')[relative])
> (double_[ vline_to] % -lit(',')) ; // +vline_to
auto const L = x3::rule<class L_tag> {} = (lit('L')[absolute] | lit('l')[relative])
> (coord [line_to] % -lit(',')); // +line_to
auto const C = x3::rule<class C_tag> {} = (lit('C')[absolute] | lit('c')[relative])
> ((coord > -lit(',') > coord > -lit(',') > coord)[curve4] % -lit(',')); // +curve4
auto const S = x3::rule<class S_tag> {} = (lit('S')[absolute] | lit('s')[relative])
> ((coord > -lit(',') > coord) [curve4_smooth] % -lit(',')); // +curve4_smooth (smooth curveto)
auto const Q = x3::rule<class Q_tag> {} = (lit('Q')[absolute] | lit('q')[relative])
> ((coord > -lit(',') > coord) [curve3] % -lit(',')); // +curve3 (quadratic-bezier-curveto)
auto const T = x3::rule<class T_tag> {} = (lit('T')[absolute] | lit('t')[relative])
> ((coord ) [curve3_smooth] % -lit(',')); // +curve3_smooth (smooth-quadratic-bezier-curveto)
auto const A = x3::rule<class A_tag> {} = (lit('A')[absolute] | lit('a')[relative])
> ((coord > -lit(',') > double_ > -lit(',') > int_ > -lit(',') > int_ > -lit(',') > coord)
[arc_to] % -lit(',')); // arc_to;
auto const Z = x3::rule<class Z_tag>{} = no_case[lit('z')] [close_path]; // close path
auto const drawto_cmd = x3::rule<class drawto_cmd_tag> {} = L | H | V | C | S | Q | T | A | Z;
auto const cmd = x3::rule<class cmd_tag> {} = M > *drawto_cmd ;
auto const svg_path_def = +cmd;
#pragma GCC diagnostic push
#include <mapnik/warning_ignore.hpp>
BOOST_SPIRIT_DEFINE(
svg_path
);
#pragma GCC diagnostic pop
}
grammar::svg_path_grammar_type svg_path_grammar()
{
return grammar::svg_path;
}
}}
#endif // MAPNIK_SVG_PATH_GRAMMAR_X3_HPP

View file

@ -23,7 +23,7 @@
// mapnik
#include <mapnik/svg/svg_path_parser.hpp>
#include <mapnik/svg/svg_path_grammar_impl.hpp>
#include <mapnik/svg/svg_path_grammar_x3_def.hpp>
// stl
#include <cstring>
#include <string>
@ -35,14 +35,31 @@ template <typename PathType>
bool parse_path(const char* wkt, PathType& p)
{
using namespace boost::spirit;
using iterator_type = const char*;
using skip_type = ascii::space_type;
static const svg_path_grammar<iterator_type, PathType, skip_type> g;
using iterator_type = char const*;
using skip_type = x3::ascii::space_type;
skip_type space;
iterator_type first = wkt;
iterator_type last = wkt + std::strlen(wkt);
bool status = qi::phrase_parse(first, last, (g)(boost::phoenix::ref(p)), skip_type());
return (status && (first == last));
bool relative = false;
auto const grammar = x3::with<mapnik::svg::grammar::svg_path_tag>(std::ref(p))
[ x3::with<mapnik::svg::grammar::relative_tag>(std::ref(relative))
[mapnik::svg::svg_path_grammar()]];
try
{
if (!x3::phrase_parse(first, last, grammar, space)
|| first != last)
{
throw std::runtime_error("Failed to parse svg-path");
}
}
catch (...)
{
return false;
}
return true;
}
template bool MAPNIK_DECL parse_path<svg_converter_type>(const char*, svg_converter_type&);
} // namespace svg