diff --git a/.gitmodules b/.gitmodules index d2185cdfa..4cca9a4a8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,4 +5,4 @@ [submodule "test/data-visual"] path = test/data-visual url = https://github.com/mapnik/test-data-visual.git - branch = master + branch = master \ No newline at end of file diff --git a/SConstruct b/SConstruct index 9f3c0f4a9..359801da3 100644 --- a/SConstruct +++ b/SConstruct @@ -395,7 +395,7 @@ opts.AddVariables( BoolVariable('FULL_LIB_PATH', 'Embed the full and absolute path to libmapnik when linking ("install_name" on OS X/rpath on Linux)', 'True'), BoolVariable('ENABLE_SONAME', 'Embed a soname in libmapnik on Linux', 'True'), EnumVariable('THREADING','Set threading support','multi', ['multi','single']), - EnumVariable('XMLPARSER','Set xml parser','libxml2', ['libxml2','ptree']), + EnumVariable('XMLPARSER','Set xml parser','ptree', ['libxml2','ptree']), BoolVariable('DEMO', 'Compile demo c++ application', 'True'), BoolVariable('PGSQL2SQLITE', 'Compile and install a utility to convert postgres tables to sqlite', 'False'), BoolVariable('SHAPEINDEX', 'Compile and install a utility to generate shapefile indexes in the custom format (.index) Mapnik supports', 'True'), @@ -1266,18 +1266,19 @@ if not preconfigured: # libxml2 should be optional but is currently not # https://github.com/mapnik/mapnik/issues/913 - if env.get('XML2_LIBS') or env.get('XML2_INCLUDES'): - REQUIRED_LIBSHEADERS.insert(0,['libxml2','libxml/parser.h',True,'C']) - if env.get('XML2_INCLUDES'): - inc_path = env['XML2_INCLUDES'] - env.AppendUnique(CPPPATH = fix_path(inc_path)) - if env.get('XML2_LIBS'): - lib_path = env['XML2_LIBS'] - env.AppendUnique(LIBPATH = fix_path(lib_path)) - elif conf.parse_config('XML2_CONFIG',checks='--cflags'): - env['HAS_LIBXML2'] = True - else: - env['MISSING_DEPS'].append('libxml2') + if env.get('XMLPARSER') and env['XMLPARSER'] == 'libxml2': + if env.get('XML2_LIBS') or env.get('XML2_INCLUDES'): + OPTIONAL_LIBSHEADERS.insert(0,['libxml2','libxml/parser.h',True,'C']) + if env.get('XML2_INCLUDES'): + inc_path = env['XML2_INCLUDES'] + env.AppendUnique(CPPPATH = fix_path(inc_path)) + if env.get('XML2_LIBS'): + lib_path = env['XML2_LIBS'] + env.AppendUnique(LIBPATH = fix_path(lib_path)) + elif conf.parse_config('XML2_CONFIG',checks='--cflags'): + env['HAS_LIBXML2'] = True + else: + env['MISSING_DEPS'].append('libxml2') if not env['HOST']: if conf.CheckHasDlfcn(): diff --git a/benchmark/run b/benchmark/run index b17f82ad3..f91853995 100755 --- a/benchmark/run +++ b/benchmark/run @@ -55,5 +55,5 @@ run test_offset_converter 10 1000 --threads 1 ./benchmark/out/test_quad_tree \ - --iterations 10000 \ + --iterations 1000 \ --threads 10 diff --git a/bootstrap.sh b/bootstrap.sh index d68daa802..5d38b16eb 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -60,7 +60,6 @@ function install_mason_deps() { install freetype 2.5.5 libfreetype & install harfbuzz 0.9.40 libharfbuzz & install jpeg_turbo 1.4.0 libjpeg & - install libxml2 2.9.2 libxml2 & install libpng 1.6.17 libpng & install webp 0.4.2 libwebp & install icu 54.1 & @@ -117,8 +116,6 @@ PG_INCLUDES = '${MASON_LINKED_REL}/include' PG_LIBS = '${MASON_LINKED_REL}/lib' FREETYPE_INCLUDES = '${MASON_LINKED_REL}/include/freetype2' FREETYPE_LIBS = '${MASON_LINKED_REL}/lib' -XML2_INCLUDES = '${MASON_LINKED_REL}/include/libxml2' -XML2_LIBS = '${MASON_LINKED_REL}/lib' SVG_RENDERER = True CAIRO_INCLUDES = '${MASON_LINKED_REL}/include' CAIRO_LIBS = '${MASON_LINKED_REL}/lib' diff --git a/include/mapnik/gradient.hpp b/include/mapnik/gradient.hpp index eb70be4c2..f8eff6e7d 100644 --- a/include/mapnik/gradient.hpp +++ b/include/mapnik/gradient.hpp @@ -79,8 +79,9 @@ class MAPNIK_DECL gradient public: gradient(); gradient(gradient const& other); - gradient& operator=(const gradient& rhs); - + gradient(gradient && other); + gradient& operator=(gradient rhs); + bool operator==(gradient const& other) const; void set_gradient_type(gradient_e grad); gradient_e get_gradient_type() const; @@ -100,7 +101,7 @@ public: void get_control_points(double &x1, double &y1, double &x2, double &y2) const; private: - void swap(const gradient& other) throw(); + void swap(gradient& other) throw(); }; } diff --git a/include/mapnik/image_filter.hpp b/include/mapnik/image_filter.hpp index 37c332786..8301adefd 100644 --- a/include/mapnik/image_filter.hpp +++ b/include/mapnik/image_filter.hpp @@ -254,10 +254,10 @@ void apply_convolution_3x3(Src const& src_view, Dst & dst_view, Filter const& fi typename Src::x_iterator dst_it = dst_view.row_begin(0); // top row - for (std::size_t x = 0 ; x < static_cast(src_view.width()); ++x) + for (std::ptrdiff_t x = 0 ; x < src_view.width(); ++x) { (*dst_it)[3] = src_loc[loc11][3]; // Dst.a = Src.a - for (std::size_t i = 0; i < 3; ++i) + for (std::ptrdiff_t i = 0; i < 3; ++i) { bits32f p[9]; @@ -275,7 +275,7 @@ void apply_convolution_3x3(Src const& src_view, Dst & dst_view, Filter const& fi p[6] = src_loc[loc02][i]; } - if ( x == static_cast(src_view.width())-1) + if ( x == (src_view.width())-1) { p[5] = p[4]; p[8] = p[7]; @@ -296,15 +296,15 @@ void apply_convolution_3x3(Src const& src_view, Dst & dst_view, Filter const& fi ++dst_it; } // carrige-return - src_loc += point2(-static_cast(src_view.width()),1); + src_loc += point2(-src_view.width(),1); // 1... height-1 rows - for (std::size_t y = 1; y(src_view.height())-1; ++y) + for (std::ptrdiff_t y = 1; y < src_view.height()-1; ++y) { - for (std::size_t x = 0; x < static_cast(src_view.width()); ++x) + for (std::ptrdiff_t x = 0; x < src_view.width(); ++x) { (*dst_it)[3] = src_loc[loc11][3]; // Dst.a = Src.a - for (std::size_t i = 0; i < 3; ++i) + for (std::ptrdiff_t i = 0; i < 3; ++i) { bits32f p[9]; @@ -325,7 +325,7 @@ void apply_convolution_3x3(Src const& src_view, Dst & dst_view, Filter const& fi p[6] = src_loc[loc02][i]; } - if ( x == static_cast(src_view.width()) - 1) + if ( x == (src_view.width()) - 1) { p[2] = p[1]; p[5] = p[4]; @@ -343,15 +343,15 @@ void apply_convolution_3x3(Src const& src_view, Dst & dst_view, Filter const& fi ++src_loc.x(); } // carrige-return - src_loc += point2(-static_cast(src_view.width()),1); + src_loc += point2(-src_view.width(),1); } // bottom row - //src_loc = src_view.xy_at(0,static_cast(src_view.height())-1); - for (std::size_t x = 0 ; x < static_cast(src_view.width()); ++x) + //src_loc = src_view.xy_at(0,src_view.height()-1); + for (std::ptrdiff_t x = 0 ; x < src_view.width(); ++x) { (*dst_it)[3] = src_loc[loc11][3]; // Dst.a = Src.a - for (std::size_t i = 0; i < 3; ++i) + for (std::ptrdiff_t i = 0; i < 3; ++i) { bits32f p[9]; @@ -369,7 +369,7 @@ void apply_convolution_3x3(Src const& src_view, Dst & dst_view, Filter const& fi p[3] = src_loc[loc01][i]; } - if ( x == static_cast(src_view.width())-1) + if ( x == (src_view.width())-1) { p[2] = p[1]; p[5] = p[4]; @@ -431,10 +431,10 @@ void apply_filter(Src & src, color_to_alpha const& op) double cr = static_cast(op.color.red())/255.0; double cg = static_cast(op.color.green())/255.0; double cb = static_cast(op.color.blue())/255.0; - for (std::size_t y=0; y(src_view.height()); ++y) + for (std::ptrdiff_t y = 0; y < src_view.height(); ++y) { rgba8_view_t::x_iterator src_it = src_view.row_begin(static_cast(y)); - for (std::size_t x=0; x(src_view.width()); ++x) + for (std::ptrdiff_t x = 0; x < src_view.width(); ++x) { uint8_t & r = get_color(src_it[x], red_t()); uint8_t & g = get_color(src_it[x], green_t()); @@ -485,17 +485,17 @@ template void apply_filter(Src & src, colorize_alpha const& op) { using namespace boost::gil; - std::size_t size = op.size(); + std::ptrdiff_t size = op.size(); if (op.size() == 1) { // no interpolation if only one stop mapnik::filter::color_stop const& stop = op[0]; mapnik::color const& c = stop.color; rgba8_view_t src_view = rgba8_view(src); - for (std::size_t y=0; y(src_view.height()); ++y) + for (std::ptrdiff_t y = 0; y < src_view.height(); ++y) { rgba8_view_t::x_iterator src_it = src_view.row_begin(static_cast(y)); - for (std::size_t x=0; x(src_view.width()); ++x) + for (std::ptrdiff_t x = 0; x < src_view.width(); ++x) { uint8_t & r = get_color(src_it[x], red_t()); uint8_t & g = get_color(src_it[x], green_t()); @@ -533,10 +533,10 @@ void apply_filter(Src & src, colorize_alpha const& op) if (grad_lut.build_lut()) { rgba8_view_t src_view = rgba8_view(src); - for (std::size_t y=0; y(src_view.height()); ++y) + for (std::ptrdiff_t y = 0; y < src_view.height(); ++y) { rgba8_view_t::x_iterator src_it = src_view.row_begin(static_cast(y)); - for (std::size_t x=0; x(src_view.width()); ++x) + for (std::ptrdiff_t x = 0; x < src_view.width(); ++x) { uint8_t & r = get_color(src_it[x], red_t()); uint8_t & g = get_color(src_it[x], green_t()); @@ -598,10 +598,10 @@ void apply_filter(Src & src, scale_hsla const& transform) if (tinting || set_alpha) { rgba8_view_t src_view = rgba8_view(src); - for (std::size_t y=0; y(src_view.height()); ++y) + for (std::ptrdiff_t y = 0; y < src_view.height(); ++y) { rgba8_view_t::x_iterator src_it = src_view.row_begin(static_cast(y)); - for (std::size_t x=0; x(src_view.width()); ++x) + for (std::ptrdiff_t x = 0; x < src_view.width(); ++x) { uint8_t & r = get_color(src_it[x], red_t()); uint8_t & g = get_color(src_it[x], green_t()); @@ -681,10 +681,10 @@ void apply_filter(Src & src, gray const& /*op*/) rgba8_view_t src_view = rgba8_view(src); - for (std::size_t y=0; y(src_view.height()); ++y) + for (std::ptrdiff_t y = 0; y < src_view.height(); ++y) { rgba8_view_t::x_iterator src_it = src_view.row_begin(static_cast(y)); - for (std::size_t x=0; x(src_view.width()); ++x) + for (std::ptrdiff_t x = 0; x < src_view.width(); ++x) { // formula taken from boost/gil/color_convert.hpp:rgb_to_luminance uint8_t & r = get_color(src_it[x], red_t()); @@ -699,7 +699,7 @@ void apply_filter(Src & src, gray const& /*op*/) template void x_gradient_impl(Src const& src_view, Dst const& dst_view) { - for (std::size_t y=0; y(src_view.height()); ++y) + for (std::ptrdiff_t y = 0; y < src_view.height(); ++y) { typename Src::x_iterator src_it = src_view.row_begin(static_cast(y)); typename Dst::x_iterator dst_it = dst_view.row_begin(static_cast(y)); @@ -708,13 +708,13 @@ void x_gradient_impl(Src const& src_view, Dst const& dst_view) dst_it[0][1] = 128 + (src_it[0][1] - src_it[1][1]) / 2; dst_it[0][2] = 128 + (src_it[0][2] - src_it[1][2]) / 2; - dst_it[dst_view.width()-1][0] = 128 + (src_it[static_cast(src_view.width())-2][0] - src_it[static_cast(src_view.width())-1][0]) / 2; - dst_it[dst_view.width()-1][1] = 128 + (src_it[static_cast(src_view.width())-2][1] - src_it[static_cast(src_view.width())-1][1]) / 2; - dst_it[dst_view.width()-1][2] = 128 + (src_it[static_cast(src_view.width())-2][2] - src_it[static_cast(src_view.width())-1][2]) / 2; + dst_it[dst_view.width()-1][0] = 128 + (src_it[(src_view.width())-2][0] - src_it[(src_view.width())-1][0]) / 2; + dst_it[dst_view.width()-1][1] = 128 + (src_it[(src_view.width())-2][1] - src_it[(src_view.width())-1][1]) / 2; + dst_it[dst_view.width()-1][2] = 128 + (src_it[(src_view.width())-2][2] - src_it[(src_view.width())-1][2]) / 2; - dst_it[0][3] = dst_it[static_cast(src_view.width())-1][3] = 255; + dst_it[0][3] = dst_it[(src_view.width())-1][3] = 255; - for (std::size_t x=1; x(src_view.width())-1; ++x) + for (std::ptrdiff_t x = 1; x < src_view.width()-1; ++x) { dst_it[x][0] = 128 + (src_it[x-1][0] - src_it[x+1][0]) / 2; dst_it[x][1] = 128 + (src_it[x-1][1] - src_it[x+1][1]) / 2; @@ -746,10 +746,10 @@ void apply_filter(Src & src, invert const& /*op*/) rgba8_view_t src_view = rgba8_view(src); - for (std::size_t y=0; y(src_view.height()); ++y) + for (std::ptrdiff_t y = 0; y < src_view.height(); ++y) { rgba8_view_t::x_iterator src_it = src_view.row_begin(static_cast(y)); - for (std::size_t x=0; x(src_view.width()); ++x) + for (std::ptrdiff_t x = 0; x < src_view.width(); ++x) { // we only work with premultiplied source, // thus all color values must be <= alpha diff --git a/include/mapnik/svg/svg_parser.hpp b/include/mapnik/svg/svg_parser.hpp index ef9eafc06..63caacd8b 100644 --- a/include/mapnik/svg/svg_parser.hpp +++ b/include/mapnik/svg/svg_parser.hpp @@ -38,15 +38,18 @@ namespace mapnik { namespace svg { class MAPNIK_DECL svg_parser : private util::noncopyable { + using error_message_container = std::vector ; public: explicit svg_parser(svg_converter_type & path); ~svg_parser(); - void parse(std::string const& filename); - void parse_from_string(std::string const& svg); + error_message_container const& error_messages() const; + bool parse(std::string const& filename); + bool parse_from_string(std::string const& svg); svg_converter_type & path_; bool is_defs_; std::map gradient_map_; std::pair temporary_gradient_; + error_message_container error_messages_; }; }} diff --git a/include/mapnik/svg/svg_parser_exception.hpp b/include/mapnik/svg/svg_parser_exception.hpp new file mode 100644 index 000000000..89139da88 --- /dev/null +++ b/include/mapnik/svg/svg_parser_exception.hpp @@ -0,0 +1,54 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2015 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_PARSER_EXCEPTION_HPP +#define MAPNIK_SVG_PARSER_EXCEPTION_HPP + +// mapnik +#include +#include + +// stl +#include + +namespace mapnik { namespace svg { + +class MAPNIK_DECL svg_parser_exception : public std::exception +{ +public: + svg_parser_exception(std::string const& message) + : message_(message) {} + + ~svg_parser_exception() throw() {} + + virtual const char* what() const throw() + { + return message_.c_str(); + } +private: + std::string message_; +}; + +}} + + +#endif // MAPNIK_SVG_PARSER_EXCEPTION_HPP diff --git a/src/build.py b/src/build.py index 520a34710..bdc343803 100644 --- a/src/build.py +++ b/src/build.py @@ -84,7 +84,8 @@ if '-DHAVE_WEBP' in env['CPPDEFINES']: lib_env['LIBS'].append('webp') enabled_imaging_libraries.append('webp_reader.cpp') -lib_env['LIBS'].append('xml2') +if env['XMLPARSER'] == 'libxml2' and env['HAS_LIBXML2']: + lib_env['LIBS'].append('xml2') if '-DBOOST_REGEX_HAS_ICU' in env['CPPDEFINES']: lib_env['LIBS'].append('icui18n') diff --git a/src/gradient.cpp b/src/gradient.cpp index ddf665870..037cf8fc7 100644 --- a/src/gradient.cpp +++ b/src/gradient.cpp @@ -66,13 +66,36 @@ gradient::gradient(gradient const& other) units_(other.units_), gradient_type_(other.gradient_type_) {} -gradient & gradient::operator=(const gradient& rhs) +gradient::gradient(gradient && other) + : transform_(std::move(other.transform_)), + x1_(std::move(other.x1_)), + y1_(std::move(other.y1_)), + x2_(std::move(other.x2_)), + y2_(std::move(other.y2_)), + r_(std::move(other.r_)), + stops_(std::move(other.stops_)), + units_(std::move(other.units_)), + gradient_type_(std::move(other.gradient_type_)) {} + +gradient & gradient::operator=(gradient rhs) { - gradient tmp(rhs); - swap(tmp); + swap(rhs); return *this; } +bool gradient::operator==(gradient const& other) const +{ + return transform_ == other.transform_ && + x1_ == other.x1_ && + y1_ == other.y1_ && + x2_ == other.x2_ && + y2_ == other.y2_ && + r_ == other.r_ && + std::equal(stops_.begin(),stops_.end(), other.stops_.begin()), + units_ == other.units_ && + gradient_type_ == other.gradient_type_; +} + void gradient::set_gradient_type(gradient_e grad) { gradient_type_=grad; @@ -108,7 +131,7 @@ void gradient::add_stop(double offset,mapnik::color const& c) bool gradient::has_stop() const { - return ! stops_.empty(); + return !stops_.empty(); } stop_array const& gradient::get_stop_array() const @@ -116,13 +139,17 @@ stop_array const& gradient::get_stop_array() const return stops_; } -void gradient::swap(const gradient& other) throw() +void gradient::swap(gradient& other) throw() { - gradient_type_=other.gradient_type_; - stops_=other.stops_; - units_=other.units_; - transform_=other.transform_; - other.get_control_points(x1_,y1_,x2_,y2_,r_); + std::swap(gradient_type_, other.gradient_type_); + std::swap(stops_, other.stops_); + std::swap(units_, other.units_); + std::swap(transform_, other.transform_); + std::swap(x1_, other.x1_); + std::swap(y1_, other.y1_); + std::swap(x2_, other.x2_); + std::swap(y2_, other.y2_); + std::swap(r_, other.r_); } void gradient::set_control_points(double x1, double y1, double x2, double y2, double r) diff --git a/src/marker_cache.cpp b/src/marker_cache.cpp index 3f8f9396b..330f4d094 100644 --- a/src/marker_cache.cpp +++ b/src/marker_cache.cpp @@ -175,7 +175,15 @@ std::shared_ptr marker_cache::find(std::string const& uri, svg_path_adapter svg_path(stl_storage); svg_converter_type svg(svg_path, marker_path->attributes()); svg_parser p(svg); - p.parse_from_string(known_svg_string); + + if (!p.parse_from_string(known_svg_string)) + { + for (auto const& msg : p.error_messages()) + { + MAPNIK_LOG_ERROR(marker_cache) << "SVG PARSING ERROR:\"" << msg << "\""; + } + return std::make_shared(mapnik::marker_null()); + } //svg.arrange_orientations(); double lox,loy,hix,hiy; svg.bounding_rect(&lox, &loy, &hix, &hiy); @@ -207,7 +215,16 @@ std::shared_ptr marker_cache::find(std::string const& uri, svg_path_adapter svg_path(stl_storage); svg_converter_type svg(svg_path, marker_path->attributes()); svg_parser p(svg); - p.parse(uri); + + + if (!p.parse(uri)) + { + for (auto const& msg : p.error_messages()) + { + MAPNIK_LOG_ERROR(marker_cache) << "SVG PARSING ERROR:\"" << msg << "\""; + } + return std::make_shared(mapnik::marker_null()); + } //svg.arrange_orientations(); double lox,loy,hix,hiy; svg.bounding_rect(&lox, &loy, &hix, &hiy); diff --git a/src/svg/svg_parser.cpp b/src/svg/svg_parser.cpp index 548ccd294..06c720298 100644 --- a/src/svg/svg_parser.cpp +++ b/src/svg/svg_parser.cpp @@ -26,7 +26,8 @@ #include #include #include - +#include +#include #include "agg_ellipse.h" #include "agg_rounded_rect.h" #include "agg_span_gradient.h" @@ -41,42 +42,40 @@ #include #include #include +// rapidxml +#include #pragma GCC diagnostic pop #include #include #include #include - -// xml2 -#include - +#include namespace mapnik { namespace svg { -bool parse_reader(svg_parser & parser,xmlTextReaderPtr reader); -void process_node(svg_parser & parser,xmlTextReaderPtr reader); -void start_element(svg_parser & parser,xmlTextReaderPtr reader); -void end_element(svg_parser & parser,xmlTextReaderPtr reader); -void parse_path(svg_parser & parser,xmlTextReaderPtr reader); -void parse_dimensions(svg_parser & parser,xmlTextReaderPtr reader); -void parse_polygon(svg_parser & parser,xmlTextReaderPtr reader); -void parse_polyline(svg_parser & parser,xmlTextReaderPtr reader); -void parse_line(svg_parser & parser,xmlTextReaderPtr reader); -void parse_rect(svg_parser & parser,xmlTextReaderPtr reader); -void parse_circle(svg_parser & parser,xmlTextReaderPtr reader); -void parse_ellipse(svg_parser & parser,xmlTextReaderPtr reader); -void parse_linear_gradient(svg_parser & parser,xmlTextReaderPtr reader); -void parse_radial_gradient(svg_parser & parser,xmlTextReaderPtr reader); -bool parse_common_gradient(svg_parser & parser,xmlTextReaderPtr reader); -void parse_gradient_stop(svg_parser & parser,xmlTextReaderPtr reader); -void parse_attr(svg_parser & parser,xmlTextReaderPtr reader); -void parse_attr(svg_parser & parser,const xmlChar * name, const xmlChar * value ); +namespace rapidxml = boost::property_tree::detail::rapidxml; + +bool traverse_tree(svg_parser & parser,rapidxml::xml_node const* node); +void end_element(svg_parser & parser,rapidxml::xml_node const* node); +void parse_path(svg_parser & parser,rapidxml::xml_node const* node); +void parse_dimensions(svg_parser & parser,rapidxml::xml_node const* node); +void parse_polygon(svg_parser & parser,rapidxml::xml_node const* node); +void parse_polyline(svg_parser & parser,rapidxml::xml_node const* node); +void parse_line(svg_parser & parser,rapidxml::xml_node const* node); +void parse_rect(svg_parser & parser,rapidxml::xml_node const* node); +void parse_circle(svg_parser & parser,rapidxml::xml_node const* node); +void parse_ellipse(svg_parser & parser,rapidxml::xml_node const* node); +void parse_linear_gradient(svg_parser & parser,rapidxml::xml_node const* node); +void parse_radial_gradient(svg_parser & parser,rapidxml::xml_node const* node); +bool parse_common_gradient(svg_parser & parser,rapidxml::xml_node const* node); +void parse_gradient_stop(svg_parser & parser,rapidxml::xml_node const* node); +void parse_attr(svg_parser & parser,rapidxml::xml_node const* node); +void parse_attr(svg_parser & parser,char const * name, char const* value); + using color_lookup_type = std::vector >; - namespace qi = boost::spirit::qi; - using pairs_type = std::vector >; template @@ -99,7 +98,8 @@ struct key_value_sequence_ordered qi::rule key, value; }; -agg::rgba8 parse_color(const char* str) +template +mapnik::color parse_color(T & error_messages, const char* str) { mapnik::color c(100,100,100); try @@ -108,25 +108,38 @@ agg::rgba8 parse_color(const char* str) } catch (mapnik::config_error const& ex) { - MAPNIK_LOG_ERROR(svg_parser) << ex.what(); + + error_messages.emplace_back(ex.what()); } + return c; +} + +template +agg::rgba8 parse_color_agg(T & error_messages, const char* str) +{ + auto c = parse_color(error_messages, str); return agg::rgba8(c.red(), c.green(), c.blue(), c.alpha()); } -double parse_double(const char* str) +template +double parse_double(T & error_messages, const char* str) { using namespace boost::spirit::qi; qi::double_type double_; double val = 0.0; - parse(str, str + std::strlen(str),double_,val); + if (!parse(str, str + std::strlen(str),double_,val)) + { + error_messages.emplace_back("Failed to parse double: \"" + std::string(str) + "\""); + } return val; } -/* - * parse a double that might end with a % - * if it does then set the ref bool true and divide the result by 100 - */ -double parse_double_optional_percent(const char* str, bool &percent) + +// parse a double that might end with a % +// if it does then set the ref bool true and divide the result by 100 + +template +double parse_double_optional_percent(T & error_messages, const char* str, bool &percent) { using namespace boost::spirit::qi; using boost::phoenix::ref; @@ -135,12 +148,15 @@ double parse_double_optional_percent(const char* str, bool &percent) qi::char_type char_; double val = 0.0; - parse(str, str + std::strlen(str),double_[ref(val)=_1, ref(percent) = false] - >> -char_('%')[ref(val)/100.0, ref(percent) = true]); + if (!parse(str, str + std::strlen(str),double_[ref(val)=_1, ref(percent) = false] + >> -char_('%')[ref(val) /= 100.0, ref(percent) = true])) + { + error_messages.emplace_back("Failed to parse double (optional %) from " + std::string(str)); + } return val; } -bool parse_style (const char* str, pairs_type & v) +bool parse_style (char const* str, pairs_type & v) { using namespace boost::spirit::qi; using skip_type = boost::spirit::ascii::space_type; @@ -148,540 +164,497 @@ bool parse_style (const char* str, pairs_type & v) return phrase_parse(str, str + std::strlen(str), kv_parser, skip_type(), v); } -bool parse_reader(svg_parser & parser, xmlTextReaderPtr reader) +bool parse_id_from_url (char const* str, std::string & id) { - int ret = xmlTextReaderRead(reader); - try { - while (ret == 1) + using namespace boost::spirit::qi; + using skip_type = boost::spirit::ascii::space_type; + qi::_1_type _1; + qi::char_type char_; + qi::lit_type lit; + return phrase_parse(str, str + std::strlen(str), + lit("url") > "(" > "#" > *(char_ - lit(')'))[boost::phoenix::ref(id) += _1] > ")", + skip_type()); +} + +bool traverse_tree(svg_parser & parser, rapidxml::xml_node const* node) +{ + auto const* name = node->name(); + switch (node->type()) + { + case rapidxml::node_element: + { + if (std::strcmp(name, "defs") == 0) { - process_node(parser,reader); - ret = xmlTextReaderRead(reader); + if (node->first_node() != nullptr) + { + parser.is_defs_ = true; + } + } + // the gradient tags *should* be in defs, but illustrator seems not to put them in there so + // accept them anywhere + else if (std::strcmp(name, "linearGradient") == 0) + { + parse_linear_gradient(parser, node); + } + else if (std::strcmp(name, "radialGradient") == 0) + { + parse_radial_gradient(parser, node); + } + else if (std::strcmp(name, "stop") == 0) + { + parse_gradient_stop(parser, node); + } + + if (!parser.is_defs_) // FIXME + { + if (std::strcmp(name, "g") == 0) + { + if (node->first_node() != nullptr) + { + parser.path_.push_attr(); + parse_attr(parser, node); + } + } + else + { + parser.path_.push_attr(); + parse_attr(parser, node); + if (parser.path_.display()) + { + if (std::strcmp(name, "path") == 0) + { + parse_path(parser, node); + } + else if (std::strcmp("polygon", name) == 0) + { + parse_polygon(parser, node); + } + else if (std::strcmp("polyline", name) == 0) + { + parse_polyline(parser, node); + } + else if (std::strcmp(name, "line") == 0) + { + parse_line(parser, node); + } + else if (std::strcmp(name, "rect") == 0) + { + parse_rect(parser, node); + } + else if (std::strcmp(name, "circle") == 0) + { + parse_circle(parser, node); + } + else if (std::strcmp(name, "ellipse") == 0) + { + parse_ellipse(parser, node); + } + else if (std::strcmp(name, "svg") == 0) + { + parse_dimensions(parser, node); + } + else + { + //std::cerr << "unprocessed node <--[" << node->name() << "]\n"; + } + } + parser.path_.pop_attr(); + } + } + + for (auto const* child = node->first_node(); + child; child = child->next_sibling()) + { + traverse_tree(parser, child); + } + + end_element(parser, node); + } + break; +#if 0 // + // Data nodes + case rapidxml::node_data: + case rapidxml::node_cdata: + { + + if (node->value_size() > 0) // Don't add empty text nodes + { + // parsed text values should have leading and trailing + // whitespace trimmed. + //std::string trimmed = node->value(); + //mapnik::util::trim(trimmed); + std::cerr << "CDATA:" << node->value() << std::endl; } } - catch (std::exception const& ex) - { - xmlFreeTextReader(reader); - throw ex; - } - xmlFreeTextReader(reader); - if (ret != 0) - { - // parsing failure - return false; + break; +#endif + default: + break; } return true; } -void start_element(svg_parser & parser, xmlTextReaderPtr reader) +void end_element(svg_parser & parser, rapidxml::xml_node const* node) { - const xmlChar *name; - name = xmlTextReaderConstName(reader); - - if (xmlStrEqual(name, BAD_CAST "defs")) + auto const* name = node->name(); + if (!parser.is_defs_ && std::strcmp(name, "g") == 0) { - if (xmlTextReaderIsEmptyElement(reader) == 0) - parser.is_defs_ = true; - } - // the gradient tags *should* be in defs, but illustrator seems not to put them in there so - // accept them anywhere - else if (xmlStrEqual(name, BAD_CAST "linearGradient")) - { - parse_linear_gradient(parser,reader); - } - else if (xmlStrEqual(name, BAD_CAST "radialGradient")) - { - parse_radial_gradient(parser,reader); - } - else if (xmlStrEqual(name, BAD_CAST "stop")) - { - parse_gradient_stop(parser,reader); - } - if ( !parser.is_defs_ ) - { - - if (xmlStrEqual(name, BAD_CAST "g")) + if (node->first_node() != nullptr) { - if (xmlTextReaderIsEmptyElement(reader) == 0) - { - parser.path_.push_attr(); - parse_attr(parser,reader); - } - } - else - { - parser.path_.push_attr(); - parse_attr(parser,reader); - if (parser.path_.display()) - { - if (xmlStrEqual(name, BAD_CAST "path")) - { - parse_path(parser,reader); - } - else if (xmlStrEqual(name, BAD_CAST "polygon") ) - { - parse_polygon(parser,reader); - } - else if (xmlStrEqual(name, BAD_CAST "polyline")) - { - parse_polyline(parser,reader); - } - else if (xmlStrEqual(name, BAD_CAST "line")) - { - parse_line(parser,reader); - } - else if (xmlStrEqual(name, BAD_CAST "rect")) - { - parse_rect(parser,reader); - } - else if (xmlStrEqual(name, BAD_CAST "circle")) - { - parse_circle(parser,reader); - } - else if (xmlStrEqual(name, BAD_CAST "ellipse")) - { - parse_ellipse(parser,reader); - } - else if (xmlStrEqual(name, BAD_CAST "svg")) - { - parse_dimensions(parser,reader); - } -#ifdef MAPNIK_LOG - else if (!xmlStrEqual(name, BAD_CAST "svg")) - { - MAPNIK_LOG_WARN(svg_parser) << "svg_parser: Unhandled svg element=" << name; - } -#endif - } parser.path_.pop_attr(); } } -} - -void end_element(svg_parser & parser, xmlTextReaderPtr reader) -{ - const xmlChar *name; - name = xmlTextReaderConstName(reader); - - - if (!parser.is_defs_ && xmlStrEqual(name, BAD_CAST "g")) + else if (std::strcmp(name, "defs") == 0) { - parser.path_.pop_attr(); + if (node->first_node() != nullptr) + { + parser.is_defs_ = false; + } } - else if (xmlStrEqual(name, BAD_CAST "defs")) - { - parser.is_defs_ = false; - } - else if ((xmlStrEqual(name, BAD_CAST "linearGradient")) || (xmlStrEqual(name, BAD_CAST "radialGradient"))) + else if (std::strcmp(name, "linearGradient") == 0 || std::strcmp(name, "radialGradient") == 0) { parser.gradient_map_[parser.temporary_gradient_.first] = parser.temporary_gradient_.second; } - } -void process_node(svg_parser & parser, xmlTextReaderPtr reader) +void parse_attr(svg_parser & parser, char const* name, char const* value ) { - int node_type = xmlTextReaderNodeType(reader); - switch (node_type) - { - case 1: //start element - start_element(parser,reader); - break; - case 15:// end element - end_element(parser,reader); - break; - default: - break; - } -} - -void parse_attr(svg_parser & parser, const xmlChar * name, const xmlChar * value ) -{ - if (xmlStrEqual(name, BAD_CAST "transform")) + if (std::strcmp(name, "transform") == 0) { agg::trans_affine tr; - mapnik::svg::parse_svg_transform((const char*) value,tr); + mapnik::svg::parse_svg_transform(value,tr); parser.path_.transform().premultiply(tr); } - else if (xmlStrEqual(name, BAD_CAST "fill")) + else if (std::strcmp(name, "fill") == 0) { - if (xmlStrEqual(value, BAD_CAST "none")) + std::string id; + if (std::strcmp(value, "none") == 0) { parser.path_.fill_none(); } - else if (boost::starts_with((const char*)value, "url(#")) + else if (parse_id_from_url(value, id)) { // see if we have a known gradient fill - std::string id = std::string((const char*)&value[5]); - // get rid of the trailing ) - id.erase(id.end()-1); if (parser.gradient_map_.count(id) > 0) { parser.path_.add_fill_gradient(parser.gradient_map_[id]); } else { - MAPNIK_LOG_ERROR(svg_parser) << "Failed to find gradient fill: " << id; + std::stringstream ss; + ss << "Failed to find gradient fill: " << id; + parser.error_messages_.push_back(ss.str()); } } else { - parser.path_.fill(parse_color((const char*) value)); + parser.path_.fill(parse_color_agg(parser.error_messages_, value)); } } - else if (xmlStrEqual(name, BAD_CAST "fill-opacity")) + else if (std::strcmp(name,"fill-opacity") == 0) { - parser.path_.fill_opacity(parse_double((const char*) value)); + parser.path_.fill_opacity(parse_double(parser.error_messages_, value)); } - else if (xmlStrEqual(name, BAD_CAST "fill-rule")) + else if (std::strcmp(name, "fill-rule") == 0) { - if (xmlStrEqual(value, BAD_CAST "evenodd")) + if (std::strcmp(value, "evenodd") == 0) { parser.path_.even_odd(true); } } - else if (xmlStrEqual(name, BAD_CAST "stroke")) + else if (std::strcmp(name, "stroke") == 0) { - if (xmlStrEqual(value, BAD_CAST "none")) + std::string id; + if (std::strcmp(value, "none") == 0) { parser.path_.stroke_none(); } - else if (boost::starts_with((const char*)value, "url(#")) + else if (parse_id_from_url(value, id)) { // see if we have a known gradient fill - std::string id = std::string((const char*)&value[5]); - // get rid of the trailing ) - id.erase(id.end()-1); if (parser.gradient_map_.count(id) > 0) { parser.path_.add_stroke_gradient(parser.gradient_map_[id]); } else { - MAPNIK_LOG_ERROR(svg_parser) << "Failed to find gradient fill: " << id; + std::stringstream ss; + ss << "Failed to find gradient stroke: " << id; + parser.error_messages_.push_back(ss.str()); } } else { - parser.path_.stroke(parse_color((const char*) value)); + parser.path_.stroke(parse_color_agg(parser.error_messages_, value)); } } - else if (xmlStrEqual(name, BAD_CAST "stroke-width")) + else if (std::strcmp(name, "stroke-width") == 0) { - parser.path_.stroke_width(parse_double((const char*)value)); + parser.path_.stroke_width(parse_double(parser.error_messages_, value)); } - else if (xmlStrEqual(name, BAD_CAST "stroke-opacity")) + else if (std::strcmp(name, "stroke-opacity") == 0) { - parser.path_.stroke_opacity(parse_double((const char*)value)); + parser.path_.stroke_opacity(parse_double(parser.error_messages_, value)); } - else if(xmlStrEqual(name,BAD_CAST "stroke-width")) + else if(std::strcmp(name, "stroke-linecap") == 0) { - parser.path_.stroke_width(parse_double((const char*) value)); - } - else if(xmlStrEqual(name,BAD_CAST "stroke-linecap")) - { - if(xmlStrEqual(value,BAD_CAST "butt")) + if(std::strcmp(value, "butt") == 0) parser.path_.line_cap(agg::butt_cap); - else if(xmlStrEqual(value,BAD_CAST "round")) + else if(std::strcmp(value, "round") == 0) parser.path_.line_cap(agg::round_cap); - else if(xmlStrEqual(value,BAD_CAST "square")) + else if(std::strcmp(value, "square") == 0) parser.path_.line_cap(agg::square_cap); } - else if(xmlStrEqual(name,BAD_CAST "stroke-linejoin")) + else if(std::strcmp(name, "stroke-linejoin") == 0) { - if(xmlStrEqual(value,BAD_CAST "miter")) + if(std::strcmp(value, "miter") == 0) parser.path_.line_join(agg::miter_join); - else if(xmlStrEqual(value,BAD_CAST "round")) + else if(std::strcmp(value, "round") == 0) parser.path_.line_join(agg::round_join); - else if(xmlStrEqual(value,BAD_CAST "bevel")) + else if(std::strcmp(value, "bevel") == 0) parser.path_.line_join(agg::bevel_join); } - else if(xmlStrEqual(name,BAD_CAST "stroke-miterlimit")) + else if(std::strcmp(name, "stroke-miterlimit") == 0) { - parser.path_.miter_limit(parse_double((const char*)value)); + parser.path_.miter_limit(parse_double(parser.error_messages_,value)); } - else if(xmlStrEqual(name, BAD_CAST "opacity")) + else if(std::strcmp(name, "opacity") == 0) { - double opacity = parse_double((const char*)value); + double opacity = parse_double(parser.error_messages_, value); parser.path_.opacity(opacity); } - else if (xmlStrEqual(name, BAD_CAST "visibility")) + else if (std::strcmp(name, "visibility") == 0) { - parser.path_.visibility(!xmlStrEqual(value, BAD_CAST "hidden")); + parser.path_.visibility(std::strcmp(value, "hidden") != 0); } - else if (xmlStrEqual(name, BAD_CAST "display") && xmlStrEqual(value, BAD_CAST "none")) + else if (std::strcmp(name, "display") == 0 && std::strcmp(value, "none") == 0) { parser.path_.display(false); } } -void parse_attr(svg_parser & parser, xmlTextReaderPtr reader) +void parse_attr(svg_parser & parser, rapidxml::xml_node const* node) { - const xmlChar *name, *value; - - if (xmlTextReaderMoveToFirstAttribute(reader) == 1) + for (rapidxml::xml_attribute const* attr = node->first_attribute(); + attr; attr = attr->next_attribute()) { - do + auto const* name = attr->name(); + if (std::strcmp(name, "style") == 0) { - name = xmlTextReaderConstName(reader); - value = xmlTextReaderConstValue(reader); - - if (xmlStrEqual(name, BAD_CAST "style")) + using cont_type = std::vector >; + using value_type = cont_type::value_type; + cont_type vec; + parse_style(attr->value(), vec); + for (value_type kv : vec ) { - using cont_type = std::vector >; - using value_type = cont_type::value_type; - cont_type vec; - parse_style((const char*)value, vec); - for (value_type kv : vec ) - { - parse_attr(parser,BAD_CAST kv.first.c_str(),BAD_CAST kv.second.c_str()); - } + parse_attr(parser, kv.first.c_str(), kv.second.c_str()); } - else - { - parse_attr(parser,name,value); - } - } while(xmlTextReaderMoveToNextAttribute(reader) == 1); - } - xmlTextReaderMoveToElement(reader); -} - -void parse_dimensions(svg_parser & parser, xmlTextReaderPtr reader) -{ - xmlChar *value; - double width = 0; - double height = 0; - value = xmlTextReaderGetAttribute(reader, BAD_CAST "width"); - if (value) - { - width = parse_double((const char*)value); - xmlFree(value); - } - xmlChar *value2; - value2 = xmlTextReaderGetAttribute(reader, BAD_CAST "width"); - if (value2) - { - height = parse_double((const char*)value2); - xmlFree(value2); - } - parser.path_.set_dimensions(width,height); - -} -void parse_path(svg_parser & parser, xmlTextReaderPtr reader) -{ - xmlChar *value; - - value = xmlTextReaderGetAttribute(reader, BAD_CAST "d"); - if (value) - { - // d="" (empty paths) are valid - if (std::strlen((const char*)value) < 1) - { - xmlFree(value); } else + { + parse_attr(parser,name, attr->value()); + } + } +} + +void parse_dimensions(svg_parser & parser, rapidxml::xml_node const* node) +{ + double width = 0; + double height = 0; + auto const* width_attr = node->first_attribute("width"); + if (width_attr) + { + width = parse_double(parser.error_messages_, width_attr->value()); + } + auto const* height_attr = node->first_attribute("height"); + if (height_attr) + { + height = parse_double(parser.error_messages_, height_attr->value()); + } + parser.path_.set_dimensions(width, height); +} + +void parse_path(svg_parser & parser, rapidxml::xml_node const* node) +{ + auto const* attr = node->first_attribute("d"); + if (attr != nullptr) + { + auto const* value = attr->value(); + if (std::strlen(value) > 0) { parser.path_.begin_path(); - if (!mapnik::svg::parse_path((const char*) value, parser.path_)) + if (!mapnik::svg::parse_path(value, parser.path_)) { - xmlFree(value); - xmlChar *id_value; - id_value = xmlTextReaderGetAttribute(reader, BAD_CAST "xml:id"); - if (!id_value) id_value = xmlTextReaderGetAttribute(reader, BAD_CAST "id"); - if (id_value) + auto const* id_attr = node->first_attribute("xml:id"); + if (id_attr == nullptr) id_attr = node->first_attribute("id"); + if (id_attr) { - std::string id_string((const char *) id_value); - xmlFree(id_value); - throw std::runtime_error(std::string("unable to parse invalid svg with id '") + id_string + "'"); + parser.error_messages_.push_back(std::string("unable to parse invalid svg with id '") + + id_attr->value() + "'"); } else { - throw std::runtime_error("unable to parse invalid svg "); + parser.error_messages_.push_back(std::string("unable to parse invalid svg ")); } } parser.path_.end_path(); - xmlFree(value); } } } -void parse_polygon(svg_parser & parser, xmlTextReaderPtr reader) +void parse_polygon(svg_parser & parser, rapidxml::xml_node const* node) { - xmlChar *value; - - value = xmlTextReaderGetAttribute(reader, BAD_CAST "points"); - if (value) + auto const* attr = node->first_attribute("points"); + if (attr != nullptr) { parser.path_.begin_path(); - if (!mapnik::svg::parse_points((const char*) value, parser.path_)) + if (!mapnik::svg::parse_points(attr->value(), parser.path_)) { - xmlFree(value); - throw std::runtime_error("Failed to parse "); + parser.error_messages_.push_back(std::string("Failed to parse 'points'")); } parser.path_.close_subpath(); parser.path_.end_path(); - xmlFree(value); } } -void parse_polyline(svg_parser & parser, xmlTextReaderPtr reader) +void parse_polyline(svg_parser & parser, rapidxml::xml_node const* node) { - xmlChar *value; - - value = xmlTextReaderGetAttribute(reader, BAD_CAST "points"); - if (value) + auto const* attr = node->first_attribute("points"); + if (attr != nullptr) { parser.path_.begin_path(); - if (!mapnik::svg::parse_points((const char*) value, parser.path_)) + if (!mapnik::svg::parse_points(attr->value(), parser.path_)) { - xmlFree(value); - throw std::runtime_error("Failed to parse "); + parser.error_messages_.push_back(std::string("Failed to parse 'points'")); } - parser.path_.end_path(); - xmlFree(value); } } -void parse_line(svg_parser & parser, xmlTextReaderPtr reader) +void parse_line(svg_parser & parser, rapidxml::xml_node const* node) { - xmlChar *value; double x1 = 0.0; double y1 = 0.0; double x2 = 0.0; double y2 = 0.0; - value = xmlTextReaderGetAttribute(reader, BAD_CAST "x1"); - if (value) - { - x1 = parse_double((const char*)value); - xmlFree(value); - } + auto const* x1_attr = node->first_attribute("x1"); + if (x1_attr) x1 = parse_double(parser.error_messages_, x1_attr->value()); - value = xmlTextReaderGetAttribute(reader, BAD_CAST "y1"); - if (value) - { - y1 = parse_double((const char*)value); - xmlFree(value); - } + auto const* y1_attr = node->first_attribute("y1"); + if (y1_attr) y1 = parse_double(parser.error_messages_, y1_attr->value()); - value = xmlTextReaderGetAttribute(reader, BAD_CAST "x2"); - if (value) - { - x2 = parse_double((const char*)value); - xmlFree(value); - } + auto const* x2_attr = node->first_attribute("x2"); + if (x2_attr) x2 = parse_double(parser.error_messages_, x2_attr->value()); - value = xmlTextReaderGetAttribute(reader, BAD_CAST "y2"); - if (value) - { - y2 = parse_double((const char*)value); - xmlFree(value); - } + auto const* y2_attr = node->first_attribute("y2"); + if (y2_attr) y2 = parse_double(parser.error_messages_, y2_attr->value()); parser.path_.begin_path(); parser.path_.move_to(x1, y1); parser.path_.line_to(x2, y2); parser.path_.end_path(); - } -void parse_circle(svg_parser & parser, xmlTextReaderPtr reader) +void parse_circle(svg_parser & parser, rapidxml::xml_node const* node) { - xmlChar *value; double cx = 0.0; double cy = 0.0; double r = 0.0; - value = xmlTextReaderGetAttribute(reader, BAD_CAST "cx"); - if (value) + + auto * attr = node->first_attribute("cx"); + if (attr != nullptr) { - cx = parse_double((const char*)value); - xmlFree(value); + cx = parse_double(parser.error_messages_, attr->value()); } - value = xmlTextReaderGetAttribute(reader, BAD_CAST "cy"); - if (value) + attr = node->first_attribute("cy"); + if (attr != nullptr) { - cy = parse_double((const char*)value); - xmlFree(value); + cy = parse_double(parser.error_messages_, attr->value()); } - value = xmlTextReaderGetAttribute(reader, BAD_CAST "r"); - if (value) + attr = node->first_attribute("r"); + if (attr != nullptr) { - r = parse_double((const char*)value); - xmlFree(value); + r = parse_double(parser.error_messages_, attr->value()); } parser.path_.begin_path(); - if(r != 0.0) { - if(r < 0.0) throw std::runtime_error("parse_circle: Invalid radius"); - agg::ellipse c(cx, cy, r, r); - parser.path_.storage().concat_path(c); + if (r < 0.0) + { + parser.error_messages_.emplace_back("parse_circle: Invalid radius"); + } + else + { + agg::ellipse c(cx, cy, r, r); + parser.path_.storage().concat_path(c); + } } - parser.path_.end_path(); } -void parse_ellipse(svg_parser & parser, xmlTextReaderPtr reader) +void parse_ellipse(svg_parser & parser, rapidxml::xml_node const * node) { - xmlChar *value; double cx = 0.0; double cy = 0.0; double rx = 0.0; double ry = 0.0; - value = xmlTextReaderGetAttribute(reader, BAD_CAST "cx"); - if (value) + auto * attr = node->first_attribute("cx"); + if (attr != nullptr) { - cx = parse_double((const char*)value); - xmlFree(value); + cx = parse_double(parser.error_messages_, attr->value()); } - value = xmlTextReaderGetAttribute(reader, BAD_CAST "cy"); - if (value) + attr = node->first_attribute("cy"); + if (attr) { - cy = parse_double((const char*)value); - xmlFree(value); + cy = parse_double(parser.error_messages_, attr->value()); } - value = xmlTextReaderGetAttribute(reader, BAD_CAST "rx"); - if (value) + attr = node->first_attribute("rx"); + if (attr != nullptr) { - rx = parse_double((const char*)value); - xmlFree(value); + rx = parse_double(parser.error_messages_, attr->value()); } - value = xmlTextReaderGetAttribute(reader, BAD_CAST "ry"); - if (value) + attr = node->first_attribute("ry"); + if (attr != nullptr) { - ry = parse_double((const char*)value); - xmlFree(value); + ry = parse_double(parser.error_messages_, attr->value()); } - parser.path_.begin_path(); - - if(rx != 0.0 && ry != 0.0) + if (rx != 0.0 && ry != 0.0) { - if(rx < 0.0) throw std::runtime_error("parse_ellipse: Invalid rx"); - if(ry < 0.0) throw std::runtime_error("parse_ellipse: Invalid ry"); - agg::ellipse c(cx, cy, rx, ry); - parser.path_.storage().concat_path(c); + + if (rx < 0.0) + { + parser.error_messages_.emplace_back("parse_ellipse: Invalid rx"); + } + else if (ry < 0.0) + { + parser.error_messages_.emplace_back("parse_ellipse: Invalid ry"); + } + else + { + parser.path_.begin_path(); + agg::ellipse c(cx, cy, rx, ry); + parser.path_.storage().concat_path(c); + parser.path_.end_path(); + } } - - parser.path_.end_path(); - } -void parse_rect(svg_parser & parser, xmlTextReaderPtr reader) +void parse_rect(svg_parser & parser, rapidxml::xml_node const* node) { // http://www.w3.org/TR/SVGTiny12/shapes.html#RectElement - xmlChar *value; double x = 0.0; double y = 0.0; double w = 0.0; @@ -689,187 +662,155 @@ void parse_rect(svg_parser & parser, xmlTextReaderPtr reader) double rx = 0.0; double ry = 0.0; - value = xmlTextReaderGetAttribute(reader, BAD_CAST "x"); - if (value) + auto * attr = node->first_attribute("x"); + if (attr != nullptr) { - x = parse_double((const char*)value); - xmlFree(value); + x = parse_double(parser.error_messages_, attr->value()); } - value = xmlTextReaderGetAttribute(reader, BAD_CAST "y"); - if (value) + attr = node->first_attribute("y"); + if (attr != nullptr) { - y = parse_double((const char*)value); - xmlFree(value); + y = parse_double(parser.error_messages_, attr->value()); } - value = xmlTextReaderGetAttribute(reader, BAD_CAST "width"); - if (value) + attr = node->first_attribute("width"); + if (attr != nullptr) { - w = parse_double((const char*)value); - xmlFree(value); + w = parse_double(parser.error_messages_, attr->value()); } - value = xmlTextReaderGetAttribute(reader, BAD_CAST "height"); - if (value) + attr = node->first_attribute("height"); + if (attr) { - h = parse_double((const char*)value); - xmlFree(value); + h = parse_double(parser.error_messages_, attr->value()); } bool rounded = true; - value = xmlTextReaderGetAttribute(reader, BAD_CAST "rx"); - if (value) + attr = node->first_attribute("rx"); + if (attr != nullptr) { - rx = parse_double((const char*)value); + rx = parse_double(parser.error_messages_, attr->value()); if ( rx > 0.5 * w ) rx = 0.5 * w; - xmlFree(value); } else rounded = false; - value = xmlTextReaderGetAttribute(reader, BAD_CAST "ry"); - if (value) + attr = node->first_attribute("ry"); + if (attr != nullptr) { - ry = parse_double((const char*)value); + ry = parse_double(parser.error_messages_, attr->value()); if ( ry > 0.5 * h ) ry = 0.5 * h; if (!rounded) { rx = ry; rounded = true; } - xmlFree(value); } else if (rounded) { ry = rx; } - if(w != 0.0 && h != 0.0) + if (w != 0.0 && h != 0.0) { - if(w < 0.0) throw std::runtime_error("parse_rect: Invalid width"); - if(h < 0.0) throw std::runtime_error("parse_rect: Invalid height"); - if(rx < 0.0) throw std::runtime_error("parse_rect: Invalid rx"); - if(ry < 0.0) throw std::runtime_error("parse_rect: Invalid ry"); - parser.path_.begin_path(); - - if(rounded) + if(w < 0.0) { - agg::rounded_rect r; - r.rect(x,y,x+w,y+h); - r.radius(rx,ry); - parser.path_.storage().concat_path(r); + parser.error_messages_.emplace_back("parse_rect: Invalid width"); + } + else if(h < 0.0) + { + parser.error_messages_.emplace_back("parse_rect: Invalid height"); + } + else if(rx < 0.0) + { + parser.error_messages_.emplace_back("parse_rect: Invalid rx"); + } + else if(ry < 0.0) + { + parser.error_messages_.emplace_back("parse_rect: Invalid ry"); } else { - parser.path_.move_to(x, y); - parser.path_.line_to(x + w, y); - parser.path_.line_to(x + w, y + h); - parser.path_.line_to(x, y + h); - parser.path_.close_subpath(); + parser.path_.begin_path(); + + if(rounded) + { + agg::rounded_rect r; + r.rect(x,y,x+w,y+h); + r.radius(rx,ry); + parser.path_.storage().concat_path(r); + } + else + { + parser.path_.move_to(x, y); + parser.path_.line_to(x + w, y); + parser.path_.line_to(x + w, y + h); + parser.path_.line_to(x, y + h); + parser.path_.close_subpath(); + } + parser.path_.end_path(); } - parser.path_.end_path(); } } - -/* - * -*/ -void parse_gradient_stop(svg_parser & parser, xmlTextReaderPtr reader) +void parse_gradient_stop(svg_parser & parser, rapidxml::xml_node const* node) { - xmlChar *value; - double offset = 0.0; mapnik::color stop_color; double opacity = 1.0; - value = xmlTextReaderGetAttribute(reader, BAD_CAST "offset"); - if (value) + auto * attr = node->first_attribute("offset"); + if (attr != nullptr) { - offset = parse_double((const char*)value); - xmlFree(value); + offset = parse_double(parser.error_messages_,attr->value()); } - value = xmlTextReaderGetAttribute(reader, BAD_CAST "style"); - if (value) + attr = node->first_attribute("style"); + if (attr != nullptr) { using cont_type = std::vector >; using value_type = cont_type::value_type; cont_type vec; - parse_style((const char*)value, vec); + parse_style(attr->value(), vec); for (value_type kv : vec ) { if (kv.first == "stop-color") { - try - { - stop_color = mapnik::parse_color(kv.second.c_str()); - } - catch (mapnik::config_error const& ex) - { - MAPNIK_LOG_ERROR(svg_parser) << ex.what(); - } + stop_color = parse_color(parser.error_messages_, kv.second.c_str()); } else if (kv.first == "stop-opacity") { - opacity = parse_double(kv.second.c_str()); + opacity = parse_double(parser.error_messages_,kv.second.c_str()); } } - xmlFree(value); } - value = xmlTextReaderGetAttribute(reader, BAD_CAST "stop-color"); - if (value) + attr = node->first_attribute("stop-color"); + if (attr != nullptr) { - try - { - stop_color = mapnik::parse_color((const char *) value); - } - catch (mapnik::config_error const& ex) - { - MAPNIK_LOG_ERROR(svg_parser) << ex.what(); - } - xmlFree(value); + stop_color = parse_color(parser.error_messages_, attr->value()); } - value = xmlTextReaderGetAttribute(reader, BAD_CAST "stop-opacity"); - if (value) + attr = node->first_attribute("stop-opacity"); + if (attr != nullptr) { - opacity = parse_double((const char *) value); - xmlFree(value); + opacity = parse_double(parser.error_messages_, attr->value()); } - stop_color.set_alpha(static_cast(opacity * 255)); parser.temporary_gradient_.second.add_stop(offset, stop_color); - - /* - MAPNIK_LOG_DEBUG(svg_parser) << "\tFound Stop: " << offset << " " - << (unsigned)stop_color.red() << " " - << (unsigned)stop_color.green() << " " - << (unsigned)stop_color.blue() << " " - << (unsigned)stop_color.alpha(); - */ } -bool parse_common_gradient(svg_parser & parser, xmlTextReaderPtr reader) +bool parse_common_gradient(svg_parser & parser, rapidxml::xml_node const* node) { - xmlChar *value; - std::string id; - value = xmlTextReaderGetAttribute(reader, BAD_CAST "xml:id"); - if (!value) value = xmlTextReaderGetAttribute(reader, BAD_CAST "id"); + auto * attr = node->first_attribute("xml:id"); + if (attr == nullptr) attr = node->first_attribute("id"); - if (value) + if (attr != nullptr) { // start a new gradient - gradient new_grad; - id = std::string((const char *) value); - parser.temporary_gradient_ = std::make_pair(id, new_grad); - xmlFree(value); + parser.temporary_gradient_ = std::make_pair(std::string(attr->value()), gradient()); } else { @@ -878,28 +819,31 @@ bool parse_common_gradient(svg_parser & parser, xmlTextReaderPtr reader) } // check if we should inherit from another tag - value = xmlTextReaderGetAttribute(reader, BAD_CAST "xlink:href"); - if (value) + attr = node->first_attribute("xlink:href"); + if (attr != nullptr) { - if (value[0] == '#') + auto const* value = attr->value(); + if (std::strlen(value) > 1 && value[0] == '#') { - std::string linkid = (const char *) &value[1]; + std::string linkid(&value[1]); // FIXME !!! if (parser.gradient_map_.count(linkid)) { parser.temporary_gradient_.second = parser.gradient_map_[linkid]; } else { - MAPNIK_LOG_ERROR(svg_parser) << "Failed to find linked gradient " << linkid; + std::stringstream ss; + ss << "Failed to find linked gradient " << linkid; + parser.error_messages_.push_back(ss.str()); + return false; } } - xmlFree(value); } - value = xmlTextReaderGetAttribute(reader, BAD_CAST "gradientUnits"); - if (value) + attr = node->first_attribute("gradientUnits"); + if (attr != nullptr) { - if (xmlStrEqual(value, BAD_CAST "userSpaceOnUse")) + if (std::strcmp(attr->value(), "userSpaceOnUse") == 0) { parser.temporary_gradient_.second.set_units(USER_SPACE_ON_USE); } @@ -907,39 +851,21 @@ bool parse_common_gradient(svg_parser & parser, xmlTextReaderPtr reader) { parser.temporary_gradient_.second.set_units(OBJECT_BOUNDING_BOX); } - xmlFree(value); } - value = xmlTextReaderGetAttribute(reader, BAD_CAST "gradientTransform"); - if (value) + attr = node->first_attribute("gradientTransform"); + if (attr != nullptr) { agg::trans_affine tr; - mapnik::svg::parse_svg_transform((const char*) value,tr); + mapnik::svg::parse_svg_transform(attr->value(),tr); parser.temporary_gradient_.second.set_transform(tr); - xmlFree(value); } - return true; } -/** - * -*/ -void parse_radial_gradient(svg_parser & parser, xmlTextReaderPtr reader) +void parse_radial_gradient(svg_parser & parser, rapidxml::xml_node const* node) { - if (!parse_common_gradient(parser,reader)) - return; - - xmlChar *value; + parse_common_gradient(parser, node); double cx = 0.5; double cy = 0.5; double fx = 0.0; @@ -947,43 +873,38 @@ void parse_radial_gradient(svg_parser & parser, xmlTextReaderPtr reader) double r = 0.5; bool has_percent=true; - value = xmlTextReaderGetAttribute(reader, BAD_CAST "cx"); - if (value) + auto * attr = node->first_attribute("cx"); + if (attr != nullptr) { - cx = parse_double_optional_percent((const char*)value, has_percent); - xmlFree(value); + cx = parse_double_optional_percent(parser.error_messages_, attr->value(), has_percent); } - value = xmlTextReaderGetAttribute(reader, BAD_CAST "cy"); - if (value) + attr = node->first_attribute("cy"); + if (attr != nullptr) { - cy = parse_double_optional_percent((const char*)value, has_percent); - xmlFree(value); + cy = parse_double_optional_percent(parser.error_messages_, attr->value(), has_percent); } - value = xmlTextReaderGetAttribute(reader, BAD_CAST "fx"); - if (value) + attr = node->first_attribute("fx"); + if (attr != nullptr) { - fx = parse_double_optional_percent((const char*)value, has_percent); - xmlFree(value); + fx = parse_double_optional_percent(parser.error_messages_,attr->value(), has_percent); } else fx = cx; - value = xmlTextReaderGetAttribute(reader, BAD_CAST "fy"); - if (value) + attr = node->first_attribute("fy"); + if (attr != nullptr) { - fy = parse_double_optional_percent((const char*)value, has_percent); - xmlFree(value); + fy = parse_double_optional_percent(parser.error_messages_, attr->value(), has_percent); } else fy = cy; - value = xmlTextReaderGetAttribute(reader, BAD_CAST "r"); - if (value) + attr = node->first_attribute("r"); + if (attr != nullptr) { - r = parse_double_optional_percent((const char*)value, has_percent); - xmlFree(value); + r = parse_double_optional_percent(parser.error_messages_, attr->value(), has_percent); } // this logic for detecting %'s will not support mixed coordinates. if (has_percent && parser.temporary_gradient_.second.get_units() == USER_SPACE_ON_USE) @@ -999,44 +920,38 @@ void parse_radial_gradient(svg_parser & parser, xmlTextReaderPtr reader) //MAPNIK_LOG_DEBUG(svg_parser) << "Found Radial Gradient: " << " " << cx << " " << cy << " " << fx << " " << fy << " " << r; } -void parse_linear_gradient(svg_parser & parser, xmlTextReaderPtr reader) +void parse_linear_gradient(svg_parser & parser, rapidxml::xml_node const* node) { - if (!parse_common_gradient(parser,reader)) - return; + parse_common_gradient(parser, node); - xmlChar *value; double x1 = 0.0; double x2 = 1.0; double y1 = 0.0; double y2 = 1.0; bool has_percent=true; - value = xmlTextReaderGetAttribute(reader, BAD_CAST "x1"); - if (value) + auto * attr = node->first_attribute("x1"); + if (attr != nullptr) { - x1 = parse_double_optional_percent((const char*)value, has_percent); - xmlFree(value); + x1 = parse_double_optional_percent(parser.error_messages_, attr->value(), has_percent); } - value = xmlTextReaderGetAttribute(reader, BAD_CAST "x2"); - if (value) + attr = node->first_attribute("x2"); + if (attr != nullptr) { - x2 = parse_double_optional_percent((const char*)value, has_percent); - xmlFree(value); + x2 = parse_double_optional_percent(parser.error_messages_, attr->value(), has_percent); } - value = xmlTextReaderGetAttribute(reader, BAD_CAST "y1"); - if (value) + attr = node->first_attribute("y1"); + if (attr != nullptr) { - y1 = parse_double_optional_percent((const char*)value, has_percent); - xmlFree(value); + y1 = parse_double_optional_percent(parser.error_messages_, attr->value(), has_percent); } - value = xmlTextReaderGetAttribute(reader, BAD_CAST "y2"); - if (value) + attr = node->first_attribute("y2"); + if (attr != nullptr) { - y2 = parse_double_optional_percent((const char*)value, has_percent); - xmlFree(value); + y2 = parse_double_optional_percent(parser.error_messages_, attr->value(), has_percent); } // this logic for detecting %'s will not support mixed coordinates. if (has_percent && parser.temporary_gradient_.second.get_units() == USER_SPACE_ON_USE) @@ -1048,8 +963,6 @@ void parse_linear_gradient(svg_parser & parser, xmlTextReaderPtr reader) parser.temporary_gradient_.second.set_control_points(x1,y1,x2,y2); // add this here in case we have no end tag, will be replaced if we do parser.gradient_map_[parser.temporary_gradient_.first] = parser.temporary_gradient_.second; - - //MAPNIK_LOG_DEBUG(svg_parser) << "Found Linear Gradient: " << "(" << x1 << " " << y1 << "),(" << x2 << " " << y2 << ")"; } svg_parser::svg_parser(svg_converter stream(filename.c_str()); + if (!stream) { - MAPNIK_LOG_ERROR(svg_parser) << "Unable to open '" << filename << "'"; + std::stringstream ss; + ss << "Unable to open '" << filename << "'"; + error_messages_.push_back(ss.str()); + return false; } - else if (!parse_reader(*this,reader)) + + stream.unsetf(std::ios::skipws); + std::vector buffer(std::istreambuf_iterator(stream.rdbuf()), + std::istreambuf_iterator()); + buffer.push_back(0); + + const int flags = rapidxml::parse_trim_whitespace | rapidxml::parse_validate_closing_tags; + rapidxml::xml_document<> doc; + try { - MAPNIK_LOG_ERROR(svg_parser) << "Unable to parse '" << filename << "'"; + doc.parse(buffer.data()); } + catch (rapidxml::parse_error const& ex) + { + std::stringstream ss; + ss << "svg_parser::parse - Unable to parse '" << filename << "'"; + error_messages_.push_back(ss.str()); + return false; + } + + for (rapidxml::xml_node const* child = doc.first_node(); + child; child = child->next_sibling()) + { + traverse_tree(*this, child); + } + return error_messages_.empty() ? true : false; } -void svg_parser::parse_from_string(std::string const& svg) +bool svg_parser::parse_from_string(std::string const& svg) { - xmlTextReaderPtr reader = xmlReaderForMemory(svg.c_str(),safe_cast(svg.size()),nullptr,nullptr, - (XML_PARSE_NOBLANKS | XML_PARSE_NOCDATA | XML_PARSE_NOERROR | XML_PARSE_NOWARNING)); - if (reader == nullptr) + const int flags = rapidxml::parse_trim_whitespace | rapidxml::parse_validate_closing_tags; + rapidxml::xml_document<> doc; + std::vector buffer(svg.begin(), svg.end()); + buffer.push_back(0); + try { - MAPNIK_LOG_ERROR(svg_parser) << "Unable to parse '" << svg << "'"; + doc.parse(buffer.data()); } - else if (!parse_reader(*this,reader)) + catch (rapidxml::parse_error const& ex) { - MAPNIK_LOG_ERROR(svg_parser) << "Unable to parse '" << svg << "'"; + std::stringstream ss; + ss << "Unable to parse '" << svg << "'"; + error_messages_.push_back(ss.str()); + return false; } + for (rapidxml::xml_node const* child = doc.first_node(); + child; child = child->next_sibling()) + { + traverse_tree(*this, child); + } + return error_messages_.empty() ? true : false; } +svg_parser::error_message_container const& svg_parser::error_messages() const +{ + return error_messages_; +} }} diff --git a/test/cleanup.hpp b/test/cleanup.hpp index 264a6b559..f175e72b4 100644 --- a/test/cleanup.hpp +++ b/test/cleanup.hpp @@ -1,9 +1,11 @@ #ifndef TEST_MEMORY_CLEANUP #define TEST_MEMORY_CLEANUP +#if defined(HAVE_LIBXML2) #include #include #include +#endif #if defined(HAVE_CAIRO) #include @@ -21,6 +23,7 @@ inline void run_cleanup() // only call this once, on exit // to make sure valgrind output is clean // http://xmlsoft.org/xmlmem.html +#if defined(HAVE_LIBXML2) xmlCleanupCharEncodingHandlers(); xmlCleanupEncodingAliases(); xmlCleanupGlobals(); @@ -29,6 +32,7 @@ inline void run_cleanup() xmlCleanupInputCallbacks(); xmlCleanupOutputCallbacks(); xmlCleanupMemory(); +#endif #if defined(HAVE_CAIRO) // http://cairographics.org/manual/cairo-Error-handling.html#cairo-debug-reset-static-data @@ -45,9 +49,9 @@ inline void run_cleanup() #endif // https://trac.osgeo.org/proj/wiki/ProjAPI#EnvironmentFunctions pj_deallocate_grids(); -#endif +#endif } } -#endif \ No newline at end of file +#endif diff --git a/test/data b/test/data index 503856319..d0a23b2a5 160000 --- a/test/data +++ b/test/data @@ -1 +1 @@ -Subproject commit 5038563194e75b95af155a337ce48f6478534022 +Subproject commit d0a23b2a512d2ea83f08a9c1dc50e9b9b4a08dd5 diff --git a/test/unit/svg/svg_parser_test.cpp b/test/unit/svg/svg_parser_test.cpp index 0c2752a29..e71a974ec 100644 --- a/test/unit/svg/svg_parser_test.cpp +++ b/test/unit/svg/svg_parser_test.cpp @@ -27,11 +27,15 @@ #include #include #include +#include +#include +#include #include -#include #include -#include // for xmlInitParser(), xmlCleanupParser() + #include +#include +#include namespace detail { @@ -51,7 +55,207 @@ struct vertex_equal TEST_CASE("SVG parser") { - xmlInitParser(); + SECTION("SVG i/o") + { + std::string svg_name("FAIL"); + std::shared_ptr marker = mapnik::marker_cache::instance().find(svg_name, false); + REQUIRE(marker); + REQUIRE(marker->is()); + } + + SECTION("SVG::parse i/o") + { + std::string svg_name("FAIL"); + + using namespace mapnik::svg; + mapnik::svg_storage_type path; + vertex_stl_adapter stl_storage(path.source()); + svg_path_adapter svg_path(stl_storage); + svg_converter_type svg(svg_path, path.attributes()); + svg_parser p(svg); + + if (!p.parse(svg_name)) + { + auto const& errors = p.error_messages(); + REQUIRE(errors.size() == 1); + REQUIRE(errors[0] == "Unable to open 'FAIL'"); + } + } + + SECTION("SVG::parse_from_string syntax error") + { + std::string svg_name("./test/data/svg/invalid.svg"); + std::ifstream in(svg_name.c_str()); + std::string svg_str((std::istreambuf_iterator(in)), + std::istreambuf_iterator()); + + using namespace mapnik::svg; + mapnik::svg_storage_type path; + vertex_stl_adapter stl_storage(path.source()); + svg_path_adapter svg_path(stl_storage); + svg_converter_type svg(svg_path, path.attributes()); + svg_parser p(svg); + + if (!p.parse_from_string(svg_str)) + { + auto const& errors = p.error_messages(); + REQUIRE(errors.size() == 1); + REQUIRE(errors[0] == "Unable to parse '\n\n'"); + } + } + + SECTION("SVG::parse_from_string syntax error") + { + std::string svg_name("./test/data/svg/invalid.svg"); + + using namespace mapnik::svg; + mapnik::svg_storage_type path; + vertex_stl_adapter stl_storage(path.source()); + svg_path_adapter svg_path(stl_storage); + svg_converter_type svg(svg_path, path.attributes()); + svg_parser p(svg); + + if (!p.parse(svg_name)) + { + auto const& errors = p.error_messages(); + REQUIRE(errors.size() == 1); + REQUIRE(errors[0] == "svg_parser::parse - Unable to parse './test/data/svg/invalid.svg'"); + } + } + + SECTION("SVG parser color ") + { + + std::string svg_name("./test/data/svg/color_fail.svg"); + std::ifstream in(svg_name.c_str()); + std::string svg_str((std::istreambuf_iterator(in)), + std::istreambuf_iterator()); + + using namespace mapnik::svg; + mapnik::svg_storage_type path; + vertex_stl_adapter stl_storage(path.source()); + svg_path_adapter svg_path(stl_storage); + svg_converter_type svg(svg_path, path.attributes()); + svg_parser p(svg); + + if (!p.parse_from_string(svg_str)) + { + auto const& errors = p.error_messages(); + REQUIRE(errors.size() == 3); + REQUIRE(errors[0] == "Failed to parse color: \"fail\""); + REQUIRE(errors[1] == "Failed to parse double: \"fail\""); + REQUIRE(errors[2] == "Failed to parse color: \"fail\""); + } + } + + SECTION("SVG - cope with erroneous geometries") + { + std::string svg_name("./test/data/svg/errors.svg"); + std::ifstream in(svg_name.c_str()); + std::string svg_str((std::istreambuf_iterator(in)), + std::istreambuf_iterator()); + + using namespace mapnik::svg; + mapnik::svg_storage_type path; + vertex_stl_adapter stl_storage(path.source()); + svg_path_adapter svg_path(stl_storage); + svg_converter_type svg(svg_path, path.attributes()); + svg_parser p(svg); + + if (!p.parse_from_string(svg_str)) + { + auto const& errors = p.error_messages(); + REQUIRE(errors.size() == 14); + REQUIRE(errors[0] == "parse_rect: Invalid width"); + REQUIRE(errors[1] == "Failed to parse double: \"FAIL\""); + REQUIRE(errors[2] == "parse_rect: Invalid height"); + REQUIRE(errors[3] == "parse_rect: Invalid rx"); + REQUIRE(errors[4] == "parse_rect: Invalid ry"); + REQUIRE(errors[5] == "unable to parse invalid svg "); + REQUIRE(errors[6] == "unable to parse invalid svg with id 'fail-path'"); + REQUIRE(errors[7] == "unable to parse invalid svg with id 'fail-path'"); + REQUIRE(errors[8] == "parse_circle: Invalid radius"); + REQUIRE(errors[9] == "Failed to parse 'points'"); + REQUIRE(errors[10] == "Failed to parse 'points'"); + REQUIRE(errors[11] == "parse_ellipse: Invalid rx"); + REQUIRE(errors[12] == "parse_ellipse: Invalid ry"); + REQUIRE(errors[13] == "parse_rect: Invalid height"); + } + } + + SECTION("SVG parser double % ") + { + + std::string svg_name("./test/data/svg/gradient-radial-error.svg"); + std::ifstream in(svg_name.c_str()); + std::string svg_str((std::istreambuf_iterator(in)), + std::istreambuf_iterator()); + + using namespace mapnik::svg; + mapnik::svg_storage_type path; + vertex_stl_adapter stl_storage(path.source()); + svg_path_adapter svg_path(stl_storage); + svg_converter_type svg(svg_path, path.attributes()); + svg_parser p(svg); + + if (!p.parse_from_string(svg_str)) + { + auto const& errors = p.error_messages(); + REQUIRE(errors.size() == 1); + REQUIRE(errors[0] == "Failed to parse double (optional %) from FAIL"); + } + } + + SECTION("SVG parser display=none") + { + + std::string svg_name("./test/data/svg/invisible.svg"); + std::shared_ptr marker = mapnik::marker_cache::instance().find(svg_name, false); + REQUIRE(marker); + REQUIRE(marker->is()); + mapnik::marker_svg const& svg = mapnik::util::get(*marker); + auto bbox = svg.bounding_box(); + REQUIRE(bbox == mapnik::box2d(0, 0, 1, 1)); + auto storage = svg.get_data(); + REQUIRE(storage); + mapnik::svg::vertex_stl_adapter stl_storage(storage->source()); + mapnik::svg::svg_path_adapter path(stl_storage); + double x,y; + REQUIRE(path.vertex(&x,&y) == mapnik::SEG_END); + } + + SECTION("SVG parser stroke-linecap=square") + { + + std::string svg_name("./test/data/svg/stroke-linecap-square.svg"); + std::shared_ptr marker = mapnik::marker_cache::instance().find(svg_name, false); + REQUIRE(marker); + REQUIRE(marker->is()); + mapnik::marker_svg const& svg = mapnik::util::get(*marker); + auto bbox = svg.bounding_box(); + REQUIRE(bbox == mapnik::box2d(5, 60, 220, 60)); + auto storage = svg.get_data(); + REQUIRE(storage); + mapnik::svg::vertex_stl_adapter stl_storage(storage->source()); + mapnik::svg::svg_path_adapter path(stl_storage); + + auto const& attrs = storage->attributes(); + agg::line_cap_e expected_cap(agg::square_cap); + REQUIRE(attrs.size() == 1 ); + REQUIRE(attrs[0].line_cap == expected_cap); + + double x,y; + unsigned cmd; + std::vector> vec; + while ((cmd = path.vertex(&x,&y)) != mapnik::SEG_END) + { + vec.emplace_back(x, y, cmd); + } + std::vector> expected = { std::make_tuple(5, 60, 1), + std::make_tuple(220, 60, 2) }; + REQUIRE(std::equal(expected.begin(),expected.end(), vec.begin())); + } + SECTION("SVG ") { // @@ -129,6 +333,71 @@ TEST_CASE("SVG parser") { REQUIRE(std::equal(expected.begin(),expected.end(), vec.begin(),detail::vertex_equal<3>())); } + SECTION("SVG rounded s missing rx or ry") + { + std::string svg_name("./test/data/svg/rounded_rect-missing-one-radius.svg"); + std::shared_ptr marker = mapnik::marker_cache::instance().find(svg_name, false); + REQUIRE(marker); + REQUIRE(marker->is()); + mapnik::marker_svg const& svg = mapnik::util::get(*marker); + auto bbox = svg.bounding_box(); + REQUIRE(bbox == mapnik::box2d(0, 0, 20, 15)); + auto storage = svg.get_data(); + REQUIRE(storage); + mapnik::svg::vertex_stl_adapter stl_storage(storage->source()); + mapnik::svg::svg_path_adapter path(stl_storage); + double x,y; + unsigned cmd; + std::vector> vec; + + while ((cmd = path.vertex(&x,&y)) != mapnik::SEG_END) + { + vec.emplace_back(x, y, cmd); + } + std::vector> expected = {std::make_tuple(0, 5,1), + std::make_tuple(0.481856, 2.85842,2), + std::make_tuple(1.83455, 1.12961,2), + std::make_tuple(3.79736, 0.146789,2), + std::make_tuple(5, 0,2), + std::make_tuple(15, 0,2), + std::make_tuple(17.1416, 0.481856,2), + std::make_tuple(18.8704, 1.83455,2), + std::make_tuple(19.8532, 3.79736,2), + std::make_tuple(20, 5,2), + std::make_tuple(20, 10,2), + std::make_tuple(19.5181, 12.1416,2), + std::make_tuple(18.1654, 13.8704,2), + std::make_tuple(16.2026, 14.8532,2), + std::make_tuple(15, 15,2), + std::make_tuple(5, 15,2), + std::make_tuple(2.85842, 14.5181,2), + std::make_tuple(1.12961, 13.1654,2), + std::make_tuple(0.146789, 11.2026,2), + std::make_tuple(0, 10,2), + std::make_tuple(0, 10,95)}; + + REQUIRE(std::equal(expected.begin(),expected.end(), vec.begin(),detail::vertex_equal<3>())); + } + + SECTION("SVG beveled ") + { + std::string svg_name("./test/data/svg/stroke-linejoin-bevel.svg"); + std::shared_ptr marker = mapnik::marker_cache::instance().find(svg_name, false); + REQUIRE(marker); + REQUIRE(marker->is()); + mapnik::marker_svg const& svg = mapnik::util::get(*marker); + auto bbox = svg.bounding_box(); + REQUIRE(bbox == mapnik::box2d(10, 10, 30, 25)); + auto storage = svg.get_data(); + REQUIRE(storage); + mapnik::svg::vertex_stl_adapter stl_storage(storage->source()); + + auto const& attrs = storage->attributes(); + agg::line_join_e expected_join(agg::bevel_join); + REQUIRE(attrs.size() == 1 ); + REQUIRE(attrs[0].line_join == expected_join); + } + SECTION("SVG ") { // @@ -147,17 +416,11 @@ TEST_CASE("SVG parser") { unsigned cmd; std::vector> vec; std::size_t num_vertices = path.total_vertices(); - //std::cerr << "Num vertices = " << num_vertices << std::endl; - //std::cerr << "{"; for (std::size_t i = 0; i < num_vertices; ++i) { cmd = path.vertex(&x,&y); vec.emplace_back(x, y, cmd); - //if (vec.size() > 1) std::cerr << ","; - //std::cerr << std::setprecision(6) << "std::make_tuple(" << x << ", " << y << ", " << cmd << ")"; } - //std::cerr << "}" << std::endl; - std::vector> expected = {std::make_tuple(1, 1, 1), std::make_tuple(1199, 1, 2), std::make_tuple(1199, 399, 2), @@ -291,5 +554,205 @@ TEST_CASE("SVG parser") { REQUIRE(std::equal(expected.begin(),expected.end(), vec.begin())); } - xmlCleanupParser(); + SECTION("SVG ") + { + // + std::string svg_name("./test/data/svg/gradient.svg"); + std::shared_ptr marker = mapnik::marker_cache::instance().find(svg_name, false); + REQUIRE(marker); + REQUIRE(marker->is()); + mapnik::marker_svg const& svg = mapnik::util::get(*marker); + auto bbox = svg.bounding_box(); + REQUIRE(bbox == mapnik::box2d(1.0,1.0,799.0,599.0)); + auto storage = svg.get_data(); + REQUIRE(storage); + mapnik::svg::vertex_stl_adapter stl_storage(storage->source()); + mapnik::svg::svg_path_adapter path(stl_storage); + double x,y; + unsigned cmd; + std::vector> vec; + std::size_t num_vertices = path.total_vertices(); + + for (std::size_t i = 0; i < num_vertices; ++i) + { + cmd = path.vertex(&x,&y); + vec.emplace_back(x, y, cmd); + } + + std::vector> expected = {std::make_tuple(1, 1, 1), + std::make_tuple(799, 1, 2), + std::make_tuple(799, 599, 2), + std::make_tuple(1, 599, 2), + std::make_tuple(1, 1, 79), + std::make_tuple(0, 0, 0), + std::make_tuple(100, 100, 1), + std::make_tuple(700, 100, 2), + std::make_tuple(700, 300, 2), + std::make_tuple(100, 300, 2), + std::make_tuple(100, 100, 79), + std::make_tuple(0, 0, 0), + std::make_tuple(100, 320, 1), + std::make_tuple(700, 320, 2), + std::make_tuple(700, 520, 2), + std::make_tuple(100, 520, 2), + std::make_tuple(100, 320, 79)}; + + REQUIRE(std::equal(expected.begin(),expected.end(), vec.begin())); + } + + SECTION("SVG missing def") + { + std::string svg_name("./test/data/svg/gradient-nodef.svg"); + using namespace mapnik::svg; + mapnik::svg_storage_type path; + vertex_stl_adapter stl_storage(path.source()); + svg_path_adapter svg_path(stl_storage); + svg_converter_type svg(svg_path, path.attributes()); + svg_parser p(svg); + REQUIRE(!p.parse(svg_name)); + auto const& errors = p.error_messages(); + REQUIRE(errors.size() == 2); + REQUIRE(errors[0] == "Failed to find gradient fill: MyGradient"); + REQUIRE(errors[1] == "Failed to find gradient stroke: MyGradient"); + } + + SECTION("SVG missing id") + { + + std::string svg_name("./test/data/svg/gradient-no-id.svg"); + std::ifstream in(svg_name.c_str()); + std::string svg_str((std::istreambuf_iterator(in)), + std::istreambuf_iterator()); + + using namespace mapnik::svg; + mapnik::svg_storage_type path; + vertex_stl_adapter stl_storage(path.source()); + svg_path_adapter svg_path(stl_storage); + svg_converter_type svg(svg_path, path.attributes()); + svg_parser p(svg); + + if (!p.parse_from_string(svg_str)) + { + auto const& errors = p.error_messages(); + REQUIRE(errors.size() == 2); + REQUIRE(errors[0] == "Failed to find gradient fill: MyGradient"); + REQUIRE(errors[1] == "Failed to find gradient stroke: MyGradient"); + } + } + + SECTION("SVG missing inheritance") + { + // + std::string svg_name("./test/data/svg/gradient-inherit.svg"); + std::shared_ptr marker = mapnik::marker_cache::instance().find(svg_name, false); + REQUIRE(marker); + REQUIRE(marker->is()); + mapnik::marker_svg const& svg = mapnik::util::get(*marker); + auto bbox = svg.bounding_box(); + REQUIRE(bbox == mapnik::box2d(1.0,1.0,699.0,199.0)); + auto storage = svg.get_data(); + REQUIRE(storage); + mapnik::svg::vertex_stl_adapter stl_storage(storage->source()); + + auto const& attrs = storage->attributes(); + REQUIRE(attrs.size() == 3 ); + REQUIRE(attrs[1].fill_gradient == attrs[2].fill_gradient); + + mapnik::svg::svg_path_adapter path(stl_storage); + double x,y; + unsigned cmd; + std::vector> vec; + std::size_t num_vertices = path.total_vertices(); + for (std::size_t i = 0; i < num_vertices; ++i) + { + cmd = path.vertex(&x,&y); + vec.emplace_back(x, y, cmd); + } + + std::vector> expected = {std::make_tuple(1, 1, 1), + std::make_tuple(699, 1, 2), + std::make_tuple(699, 199, 2), + std::make_tuple(1, 199, 2), + std::make_tuple(1, 1, 79), + std::make_tuple(0, 0, 0), + std::make_tuple(100, 50, 1), + std::make_tuple(300, 50, 2), + std::make_tuple(300, 150, 2), + std::make_tuple(100, 150, 2), + std::make_tuple(100, 50, 79), + std::make_tuple(0, 0, 0), + std::make_tuple(400, 50, 1), + std::make_tuple(600, 50, 2), + std::make_tuple(600, 150, 2), + std::make_tuple(400, 150, 2), + std::make_tuple(400, 50, 79)}; + + REQUIRE(std::equal(expected.begin(),expected.end(), vec.begin())); + } + + SECTION("SVG with transformations") + { + // + std::string svg_name("./test/data/svg/gradient-transform.svg"); + std::shared_ptr marker = mapnik::marker_cache::instance().find(svg_name, false); + REQUIRE(marker); + REQUIRE(marker->is()); + mapnik::marker_svg const& svg = mapnik::util::get(*marker); + auto bbox = svg.bounding_box(); + REQUIRE(bbox == mapnik::box2d(1.0,1.0,799.0,599.0)); + auto storage = svg.get_data(); + REQUIRE(storage); + + auto const& attrs = storage->attributes(); + REQUIRE(attrs.size() == 3 ); + REQUIRE(attrs[1].fill_gradient == attrs[2].fill_gradient); + REQUIRE(attrs[1].fill_gradient.get_gradient_type() == mapnik::RADIAL); + agg::trans_affine transform; + transform *= agg::trans_affine_translation(240,155); + REQUIRE(attrs[1].fill_gradient.get_transform() == transform); + } + + SECTION("SVG with xlink:href") + { + std::string svg_name("./test/data/svg/gradient-xhref.svg"); + std::shared_ptr marker = mapnik::marker_cache::instance().find(svg_name, false); + REQUIRE(marker); + REQUIRE(marker->is()); + mapnik::marker_svg const& svg = mapnik::util::get(*marker); + auto bbox = svg.bounding_box(); + REQUIRE(bbox == mapnik::box2d(20,20,460,230)); + auto storage = svg.get_data(); + REQUIRE(storage); + + auto const& attrs = storage->attributes(); + REQUIRE(attrs.size() == 2 ); + REQUIRE(attrs[0].fill_gradient.get_gradient_type() == mapnik::LINEAR); + REQUIRE(attrs[1].fill_gradient.get_gradient_type() == mapnik::LINEAR); + REQUIRE(attrs[1].fill_gradient.has_stop()); + } + + SECTION("SVG with radial percents") + { + std::string svg_name("./test/data/svg/gradient-radial-percents.svg"); + std::shared_ptr marker = mapnik::marker_cache::instance().find(svg_name, false); + REQUIRE(marker); + REQUIRE(marker->is()); + mapnik::marker_svg const& svg = mapnik::util::get(*marker); + auto bbox = svg.bounding_box(); + REQUIRE(bbox == mapnik::box2d(0,0,200,200)); + auto storage = svg.get_data(); + REQUIRE(storage); + + double x1, x2, y1, y2, r; + auto const& attrs = storage->attributes(); + REQUIRE(attrs.size() == 1 ); + REQUIRE(attrs[0].fill_gradient.get_gradient_type() == mapnik::RADIAL); + REQUIRE(attrs[0].fill_gradient.has_stop()); + attrs[0].fill_gradient.get_control_points(x1, y1, x2, y2, r); + REQUIRE(x1 == 0); + REQUIRE(y1 == 0.25); + REQUIRE(x2 == 0.10); + REQUIRE(y2 == 0.10); + REQUIRE(r == 0.75); + } } diff --git a/utils/svg2png/svg2png.cpp b/utils/svg2png/svg2png.cpp index 21041b280..3e00663bb 100644 --- a/utils/svg2png/svg2png.cpp +++ b/utils/svg2png/svg2png.cpp @@ -48,8 +48,6 @@ #include "agg_pixfmt_rgba.h" #include "agg_scanline_u.h" -#include // for xmlInitParser(), xmlCleanupParser() - struct main_marker_visitor { main_marker_visitor(std::string const& svg_name, @@ -59,18 +57,6 @@ struct main_marker_visitor verbose_(verbose), auto_open_(auto_open) {} - int operator() (mapnik::marker_null const&) - { - std::clog << "svg2png error: '" << svg_name_ << "' is not a valid vector!\n"; - return -1; - } - - int operator() (mapnik::marker_rgba8 const&) - { - std::clog << "svg2png error: '" << svg_name_ << "' is not a valid vector!\n"; - return -1; - } - int operator() (mapnik::marker_svg const& marker) { using pixfmt = agg::pixfmt_rgba32_pre; @@ -130,6 +116,14 @@ struct main_marker_visitor return status; } + // default + template + int operator() (T const&) + { + std::clog << "svg2png error: '" << svg_name_ << "' is not a valid vector!\n"; + return -1; + } + private: std::string const& svg_name_; bool verbose_; @@ -202,8 +196,6 @@ int main (int argc,char** argv) return 0; } - xmlInitParser(); - while (itr != svg_files.end()) { std::string svg_name (*itr++); @@ -217,16 +209,15 @@ int main (int argc,char** argv) status = mapnik::util::apply_visitor(visitor, *marker); } } + catch (std::exception const& ex) + { + std::clog << "Exception caught:" << ex.what() << std::endl; + return -1; + } catch (...) { std::clog << "Exception of unknown type!" << std::endl; - xmlCleanupParser(); return -1; } - - // only call this once, on exit - // to make sure valgrind output is clean - // http://xmlsoft.org/xmlmem.html - xmlCleanupParser(); return status; }