This is an automated email from the git hooks/post-receive script. sebastic pushed a commit to branch master in repository mapnik.
commit a2cb23fc926faa284162453be0ee5b4440bd286b Author: Bas Couwenberg <[email protected]> Date: Wed Nov 15 21:08:21 2017 +0100 New upstream version 3.0.16~rc1+ds --- .travis.yml | 4 +- CHANGELOG.md | 19 + SConstruct | 153 +++- configure | 15 +- include/mapnik/geom_util.hpp | 126 +-- .../json/extract_bounding_box_grammar_impl.hpp | 11 +- include/mapnik/marker_cache.hpp | 2 +- include/mapnik/svg/svg_parser.hpp | 60 +- include/mapnik/text/harfbuzz_shaper.hpp | 4 +- include/mapnik/unicode.hpp | 7 + .../mapnik/{version.hpp => util/name_to_int.hpp} | 22 +- include/mapnik/version.hpp | 2 +- plugins/input/gdal/gdal_featureset.cpp | 2 +- scripts/publish_release.sh | 2 +- src/load_map.cpp | 37 +- src/marker_cache.cpp | 21 +- src/svg/svg_parser.cpp | 954 ++++++++++++++------- src/text/renderer.cpp | 17 +- src/wkb.cpp | 2 + test/unit/svg/svg_parser_test.cpp | 203 +++-- utils/mapnik-config/build.py | 18 +- utils/mapnik-config/mapnik-config.template.sh | 12 +- utils/svg2png/svg2png.cpp | 41 +- 23 files changed, 1208 insertions(+), 526 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3d19711..d2065e4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,8 @@ cache: directories: - $HOME/.ccache +dist: precise + matrix: include: - os: linux @@ -61,7 +63,7 @@ before_install: - export PATH=${PREFIX}/bin:$(pwd)/mason_packages/.link/bin:${PATH} - export COVERAGE=${COVERAGE:-false} - export BENCH=${BENCH:-false} - - git_submodule_update --init --depth=10 + - git_submodule_update --init install: - on 'osx' export DATA_PATH=$(brew --prefix)/var/postgres diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e1ad8c..4930fd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,24 @@ Developers: Please commit along with changes. For a complete change history, see the git log. +## 3.0.16 + +Released: November xx, 2017 + +(Packaged from xxxxxxx) + + - Added "strict" SVG parsing mode with consistent error handling and disabled processing of unsupported attributes. + - Added support for `<use>` element. + - Implemented compile time string literal to integer conversion, to be able to convert large `if/else if/else` statements to `switch`. + - WKB reader - pre-allocate optimisations in `multi_polygon` and `geometry_collection`. + - Set alpha values in RGBA TIFFs even when `NODATA` value is pesent. + - Support building with ICU >= 59. + - SCons - added ICU_DATA, PROJ_LIB and GDAL_DATA settings, available via `mapnik-config` + - Fixed centroid and interior text placement algorithms (#3771) + - Fixed memory leak (#3775) + - SVG parser - fixed default gradient vector in linear gradient. + - Fixed bounding box collection logic (#3709) + ## 3.0.15 Released: June 16, 2017 @@ -36,6 +54,7 @@ Released: June 5, 2017 - shapeindex - return error code when no features can read from shapefile (#3198) - Upgrade Scons to `2.5.1` - Fixed bug (typo) in `raster_featureset.cpp` (#3696) +- Made `freetype_engine` singleton again. This allows for better control of its life-time. Original interface is preserved via adding static methods (#3688) ## 3.0.13 diff --git a/SConstruct b/SConstruct index 0523e18..a3f725c 100644 --- a/SConstruct +++ b/SConstruct @@ -99,7 +99,10 @@ pretty_dep_names = { 'osm':'more info: https://github.com/mapnik/mapnik/wiki/OsmPlugin', 'boost_regex_icu':'libboost_regex built with optional ICU unicode support is needed for unicode regex support in mapnik.', 'sqlite_rtree':'The SQLite plugin requires libsqlite3 built with RTREE support (-DSQLITE_ENABLE_RTREE=1)', - 'pgsql2sqlite_rtree':'The pgsql2sqlite program requires libsqlite3 built with RTREE support (-DSQLITE_ENABLE_RTREE=1)' + 'pgsql2sqlite_rtree':'The pgsql2sqlite program requires libsqlite3 built with RTREE support (-DSQLITE_ENABLE_RTREE=1)', + 'PROJ_LIB':'The directory where proj4 stores its data files. Must exist for proj4 to work correctly', + 'GDAL_DATA':'The directory where GDAL stores its data files. Must exist for GDAL to work correctly', + 'ICU_DATA':'The directory where icu stores its data files. If ICU reports a path, it must exist. ICU can also be built without .dat files and in that case this path is empty' } # Core plugin build configuration @@ -472,7 +475,10 @@ pickle_store = [# Scons internal variables 'SQLITE_LINKFLAGS', 'BOOST_LIB_VERSION_FROM_HEADER', 'BIGINT', - 'HOST' + 'HOST', + 'QUERIED_GDAL_DATA', + 'QUERIED_ICU_DATA', + 'QUERIED_PROJ_LIB' ] # Add all other user configurable options to pickle pickle_store @@ -799,6 +805,117 @@ int main() context.Result(ret) return ret +def CheckIcuData(context, silent=False): + + if not silent: + context.Message('Checking for ICU data directory...') + ret = context.TryRun(""" + +#include <unicode/putil.h> +#include <iostream> + +int main() { + std::string result = u_getDataDirectory(); + std::cout << result; + if (result.empty()) { + return -1; + } + return 0; +} + +""", '.cpp') + if silent: + context.did_show_result=1 + if ret[0]: + context.Result('u_getDataDirectory returned %s' % ret[1]) + else: + context.Result('Failed to detect (mapnik-config will have null value)') + return ret[1].strip() + +def CheckGdalData(context, silent=False): + + if not silent: + context.Message('Checking for GDAL data directory...') + ret = context.TryRun(""" + +#include "cpl_config.h" +#include <iostream> + +int main() { + std::cout << GDAL_PREFIX << "/share/gdal" << std::endl; + return 0; +} + +""", '.cpp') + if silent: + context.did_show_result=1 + if ret[0]: + context.Result('GDAL_PREFIX returned %s' % ret[1]) + else: + context.Result('Failed to detect (mapnik-config will have null value)') + return ret[1].strip() + +def CheckProjData(context, silent=False): + + if not silent: + context.Message('Checking for PROJ_LIB directory...') + ret = context.TryRun(""" + +// This is narly, could eventually be replaced using https://github.com/OSGeo/proj.4/pull/551] +#include <proj_api.h> +#include <iostream> + +static void my_proj4_logger(void * user_data, int /*level*/, const char * msg) +{ + std::string* posMsg = static_cast<std::string*>(user_data); + *posMsg += msg; +} + +// https://github.com/OSGeo/gdal/blob/ddbf6d39aa4b005a77ca4f27c2d61a3214f336f8/gdal/alg/gdalapplyverticalshiftgrid.cpp#L616-L633 + +std::string find_proj_path(const char * pszFilename) { + std::string osMsg; + std::string osFilename; + projCtx ctx = pj_ctx_alloc(); + pj_ctx_set_app_data(ctx, &osMsg); + pj_ctx_set_debug(ctx, PJ_LOG_DEBUG_MAJOR); + pj_ctx_set_logger(ctx, my_proj4_logger); + PAFile f = pj_open_lib(ctx, pszFilename, "rb"); + if( f ) + { + pj_ctx_fclose(ctx, f); + } + size_t nPos = osMsg.find("fopen("); + if( nPos != std::string::npos ) + { + osFilename = osMsg.substr(nPos + strlen("fopen(")); + nPos = osFilename.find(")"); + if( nPos != std::string::npos ) + osFilename = osFilename.substr(0, nPos); + } + pj_ctx_free(ctx); + return osFilename; +} + + +int main() { + std::string result = find_proj_path(" "); + std::cout << result; + if (result.empty()) { + return -1; + } + return 0; +} + +""", '.cpp') + if silent: + context.did_show_result=1 + if ret[0]: + context.Result('pj_open_lib returned %s' % ret[1]) + else: + context.Result('Failed to detect (mapnik-config will have null value)') + return ret[1].strip() + def CheckCairoHasFreetype(context, silent=False): if not silent: context.Message('Checking for cairo freetype font support ... ') @@ -1067,6 +1184,9 @@ conf_tests = { 'prioritize_paths' : prioritize_paths, 'CheckPKGVersion' : CheckPKGVersion, 'FindBoost' : FindBoost, 'CheckBoost' : CheckBoost, + 'CheckIcuData' : CheckIcuData, + 'CheckProjData' : CheckProjData, + 'CheckGdalData' : CheckGdalData, 'CheckCairoHasFreetype' : CheckCairoHasFreetype, 'CheckHasDlfcn' : CheckHasDlfcn, 'GetBoostLibVersion' : GetBoostLibVersion, @@ -1159,6 +1279,10 @@ if not preconfigured: env['PLUGINS'] = PLUGINS env['EXTRA_FREETYPE_LIBS'] = [] env['SQLITE_LINKFLAGS'] = [] + env['QUERIED_PROJ_LIB'] = None + env['QUERIED_ICU_DATA'] = None + env['QUERIED_GDAL_DATA'] = None + # previously a leading / was expected for LIB_DIR_NAME # now strip it to ensure expected behavior if env['LIB_DIR_NAME'].startswith(os.path.sep): @@ -1476,6 +1600,31 @@ if not preconfigured: if env['HOST']: SQLITE_HAS_RTREE = True + if not env['HOST']: + env['QUERIED_PROJ_LIB'] = conf.CheckProjData() + if os.environ.get('PROJ_LIB'): + env['QUERIED_PROJ_LIB'] = os.environ['PROJ_LIB'] + color_print(4,'Detected PROJ_LIB in environ, using env value instead: %s' % os.environ['PROJ_LIB'] ) + env['QUERIED_ICU_DATA'] = conf.CheckIcuData() + if os.environ.get('ICU_DATA'): + env['QUERIED_ICU_DATA'] = os.environ['ICU_DATA'] + color_print(4,'Detected ICU_DATA in environ, using env value instead: %s' % os.environ['ICU_DATA'] ) + env['QUERIED_GDAL_DATA'] = conf.CheckGdalData() + if os.environ.get('GDAL_DATA'): + env['QUERIED_GDAL_DATA'] = os.environ['GDAL_DATA'] + color_print(4,'Detected GDAL_DATA in environ, using env value instead: %s' % os.environ['GDAL_DATA'] ) + # now validate the paths actually exist + if env['QUERIED_PROJ_LIB'] and not os.path.exists(env['QUERIED_PROJ_LIB']): + color_print(1,'%s not detected on your system' % env['QUERIED_PROJ_LIB'] ) + env['MISSING_DEPS'].append('PROJ_LIB') + if env['QUERIED_GDAL_DATA'] and not os.path.exists(env['QUERIED_GDAL_DATA']): + color_print(1,'%s not detected on your system' % env['QUERIED_GDAL_DATA'] ) + env['MISSING_DEPS'].append('GDAL_DATA') + if env['QUERIED_ICU_DATA'] and not os.path.exists(env['QUERIED_ICU_DATA']): + color_print(1,'%s not detected on your system' % env['QUERIED_ICU_DATA'] ) + env['MISSING_DEPS'].append('ICU_DATA') + + CHECK_PKG_CONFIG = conf.CheckPKGConfig('0.15.0') if len(env['REQUESTED_PLUGINS']): diff --git a/configure b/configure index b468f85..2a44205 100755 --- a/configure +++ b/configure @@ -1,5 +1,18 @@ -#!/bin/sh +#!/bin/bash + +set -eu PYTHON=${PYTHON:-python} +# mapnik-settings.env is an optional file to store +# environment variables that should be used before +# running tests like PROJ_LIB, GDAL_DATA, and ICU_DATA +# These do not normally need to be set except when +# building against binary versions of dependencies like +# done via bootstrap.sh +if [[ -f mapnik-settings.env ]]; then + echo "Inheriting from mapnik-settings.env" + source mapnik-settings.env +fi + $PYTHON scons/scons.py --implicit-deps-changed configure "$@" diff --git a/include/mapnik/geom_util.hpp b/include/mapnik/geom_util.hpp index 7dbe51f..d16c42e 100644 --- a/include/mapnik/geom_util.hpp +++ b/include/mapnik/geom_util.hpp @@ -28,6 +28,7 @@ #include <mapnik/coord.hpp> #include <mapnik/vertex.hpp> #include <mapnik/geometry_types.hpp> +#include <mapnik/geometry.hpp> // stl #include <cmath> #include <vector> @@ -321,56 +322,60 @@ bool middle_point(PathType & path, double & x, double & y) template <typename PathType> bool centroid(PathType & path, double & x, double & y) { - double x0 = 0.0; - double y0 = 0.0; - double x1 = 0.0; - double y1 = 0.0; - double start_x; - double start_y; + geometry::point<double> p0, p1, move_to, start; path.rewind(0); - unsigned command = path.vertex(&x0, &y0); + unsigned command = path.vertex(&p0.x, &p0.y); if (command == SEG_END) return false; - start_x = x0; - start_y = y0; + start = move_to = p0; double atmp = 0.0; double xtmp = 0.0; double ytmp = 0.0; unsigned count = 1; - while (SEG_END != (command = path.vertex(&x1, &y1))) + while (SEG_END != (command = path.vertex(&p1.x, &p1.y))) { - if (command == SEG_CLOSE) continue; - double dx0 = x0 - start_x; - double dy0 = y0 - start_y; - double dx1 = x1 - start_x; - double dy1 = y1 - start_y; + switch (command) + { + case SEG_MOVETO: + move_to = p1; + break; + case SEG_CLOSE: + p1 = move_to; + case SEG_LINETO: + double dx0 = p0.x - start.x; + double dy0 = p0.y - start.y; + double dx1 = p1.x - start.x; + double dy1 = p1.y - start.y; + + double ai = dx0 * dy1 - dx1 * dy0; + atmp += ai; + xtmp += (dx1 + dx0) * ai; + ytmp += (dy1 + dy0) * ai; + break; - double ai = dx0 * dy1 - dx1 * dy0; - atmp += ai; - xtmp += (dx1 + dx0) * ai; - ytmp += (dy1 + dy0) * ai; - x0 = x1; - y0 = y1; + } + p0 = p1; ++count; } - if (count <= 2) { - x = (start_x + x0) * 0.5; - y = (start_y + y0) * 0.5; + if (count <= 2) + { + x = (start.x + p0.x) * 0.5; + y = (start.y + p0.y) * 0.5; return true; } if (atmp != 0) { - x = (xtmp/(3*atmp)) + start_x; - y = (ytmp/(3*atmp)) + start_y; + x = (xtmp / (3 * atmp)) + start.x; + y = (ytmp / (3 * atmp)) + start.y; } else { - x = x0; - y = y0; + x = p0.x; + y = p0.y; } return true; } @@ -524,47 +529,50 @@ bool interior_position(PathType & path, double & x, double & y) // center of the widest intersection between the polygon and the line. std::vector<double> intersections; // only need to store the X as we know the y + geometry::point<double> p0, p1, move_to; + unsigned command = SEG_END; - double x0 = 0; - double y0 = 0; path.rewind(0); - unsigned command = path.vertex(&x0, &y0); - double x1 = 0; - double y1 = 0; - while (SEG_END != (command = path.vertex(&x1, &y1))) + + while (SEG_END != (command = path.vertex(&p0.x, &p0.y))) { - if (command == SEG_CLOSE) - continue; - if (command != SEG_MOVETO) + switch (command) { - // if the segments overlap - if (y0==y1) - { - if (y0==y) + case SEG_MOVETO: + move_to = p0; + break; + case SEG_CLOSE: + p0 = move_to; + case SEG_LINETO: + // if the segments overlap + if (p0.y == p1.y) { - double xi = (x0+x1)/2.0; - intersections.push_back(xi); + if (p0.y == y) + { + double xi = (p0.x + p1.x) / 2.0; + intersections.push_back(xi); + } } - } - // if the path segment crosses the bisector - else if ((y0 <= y && y1 >= y) || - (y0 >= y && y1 <= y)) - { - // then calculate the intersection - double xi = x0; - if (x0 != x1) + // if the path segment crosses the bisector + else if ((p0.y <= y && p1.y >= y) || + (p0.y >= y && p1.y <= y)) { - double m = (y1-y0)/(x1-x0); - double c = y0 - m*x0; - xi = (y-c)/m; - } + // then calculate the intersection + double xi = p0.x; + if (p0.x != p1.x) + { + double m = (p1.y - p0.y) / (p1.x - p0.x); + double c = p0.y - m * p0.x; + xi = (y - c) / m; + } - intersections.push_back(xi); - } + intersections.push_back(xi); + } + break; } - x0 = x1; - y0 = y1; + p1 = p0; } + // no intersections we just return the default if (intersections.empty()) return true; diff --git a/include/mapnik/json/extract_bounding_box_grammar_impl.hpp b/include/mapnik/json/extract_bounding_box_grammar_impl.hpp index 4d81de7..b3ac07e 100644 --- a/include/mapnik/json/extract_bounding_box_grammar_impl.hpp +++ b/include/mapnik/json/extract_bounding_box_grammar_impl.hpp @@ -61,10 +61,13 @@ struct push_box_impl template <typename T0, typename T1, typename T2, typename T3> void operator() (T0 & boxes, T1 const& begin, T2 const& box, T3 const& range) const { - boxes.emplace_back(box, - std::make_pair(std::distance(begin, - range.begin()), - std::distance(range.begin(), range.end()))); + if (box.valid()) + { + boxes.emplace_back(box, + std::make_pair(std::distance(begin, + range.begin()), + std::distance(range.begin(), range.end()))); + } } }; diff --git a/include/mapnik/marker_cache.hpp b/include/mapnik/marker_cache.hpp index 47aa555..250441f 100644 --- a/include/mapnik/marker_cache.hpp +++ b/include/mapnik/marker_cache.hpp @@ -55,7 +55,7 @@ public: inline bool is_uri(std::string const& path) { return is_svg_uri(path) || is_image_uri(path); } bool is_svg_uri(std::string const& path); bool is_image_uri(std::string const& path); - std::shared_ptr<marker const> find(std::string const& key, bool update_cache = false); + std::shared_ptr<marker const> find(std::string const& key, bool update_cache = false, bool strict = false); void clear(); }; diff --git a/include/mapnik/svg/svg_parser.hpp b/include/mapnik/svg/svg_parser.hpp index 63caacd..fab0b8e 100644 --- a/include/mapnik/svg/svg_parser.hpp +++ b/include/mapnik/svg/svg_parser.hpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko + * Copyright (C) 2017 Artem Pavlenko * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -30,27 +30,55 @@ #include <mapnik/svg/svg_path_adapter.hpp> #include <mapnik/gradient.hpp> #include <mapnik/util/noncopyable.hpp> - // stl #include <map> +namespace boost { namespace property_tree { namespace detail { namespace rapidxml { +template <typename T> class xml_node; +}}}} + namespace mapnik { namespace svg { - class MAPNIK_DECL svg_parser : private util::noncopyable +class svg_parser_error_handler +{ + using error_message_container = std::vector<std::string> ; +public: + explicit svg_parser_error_handler(bool strict = false) + : strict_(strict) {} + + void on_error(std::string const& msg) { - using error_message_container = std::vector<std::string> ; - public: - explicit svg_parser(svg_converter_type & path); - ~svg_parser(); - 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<std::string, gradient> gradient_map_; - std::pair<std::string, gradient> temporary_gradient_; - error_message_container error_messages_; - }; + if (strict_) throw std::runtime_error(msg); + else error_messages_.push_back(msg); + } + error_message_container const& error_messages() const + { + return error_messages_; + } + bool strict() const { return strict_; } +private: + + error_message_container error_messages_; + bool strict_; +}; + +class MAPNIK_DECL svg_parser : private util::noncopyable +{ + using error_handler = svg_parser_error_handler; +public: + explicit svg_parser(svg_converter_type & path, bool strict = false); + ~svg_parser(); + error_handler & err_handler(); + void parse(std::string const& filename); + void parse_from_string(std::string const& svg); + svg_converter_type & path_; + bool is_defs_; + bool strict_; + std::map<std::string, gradient> gradient_map_; + std::map<std::string, boost::property_tree::detail::rapidxml::xml_node<char> const*> node_cache_; + agg::trans_affine viewbox_tr_{}; + error_handler err_handler_; +}; }} diff --git a/include/mapnik/text/harfbuzz_shaper.hpp b/include/mapnik/text/harfbuzz_shaper.hpp index 8b574b0..14151cf 100644 --- a/include/mapnik/text/harfbuzz_shaper.hpp +++ b/include/mapnik/text/harfbuzz_shaper.hpp @@ -40,6 +40,7 @@ #include <mapnik/warning_ignore.hpp> #include <harfbuzz/hb.h> #include <harfbuzz/hb-ft.h> +#include <unicode/uvernum.h> #include <unicode/uscript.h> #pragma GCC diagnostic pop @@ -55,7 +56,8 @@ static inline hb_script_t _icu_script_to_script(UScriptCode script) static inline const uint16_t * uchar_to_utf16(const UChar* src) { static_assert(sizeof(UChar) == sizeof(uint16_t),"UChar is eq size to uint16_t"); -#if defined(_MSC_VER) +#if defined(_MSC_VER) || (U_ICU_VERSION_MAJOR_NUM >= 59) + // ^^ http://site.icu-project.org/download/59#TOC-ICU4C-char16_t1 return reinterpret_cast<const uint16_t *>(src); #else return src; diff --git a/include/mapnik/unicode.hpp b/include/mapnik/unicode.hpp index f3b270c..94909b3 100644 --- a/include/mapnik/unicode.hpp +++ b/include/mapnik/unicode.hpp @@ -31,6 +31,13 @@ // std #include <cstdint> #include <string> +// icu +#if (U_ICU_VERSION_MAJOR_NUM >= 59) +#pragma GCC diagnostic push +#include <mapnik/warning_ignore.hpp> +#include <unicode/unistr.h> +#pragma GCC diagnostic pop +#endif struct UConverter; diff --git a/include/mapnik/version.hpp b/include/mapnik/util/name_to_int.hpp similarity index 62% copy from include/mapnik/version.hpp copy to include/mapnik/util/name_to_int.hpp index 9798d98..096a1f0 100644 --- a/include/mapnik/version.hpp +++ b/include/mapnik/util/name_to_int.hpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko + * Copyright (C) 2017 Artem Pavlenko * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -20,19 +20,11 @@ * *****************************************************************************/ -#ifndef MAPNIK_VERSION_HPP -#define MAPNIK_VERSION_HPP +namespace mapnik { namespace util { -#include <mapnik/stringify_macro.hpp> +constexpr unsigned name_to_int(const char *str, int off = 0) +{ + return !str[off] ? 5381 : (name_to_int(str, off + 1) * 33) ^ static_cast<unsigned>(str[off]); +} -#define MAPNIK_MAJOR_VERSION 3 -#define MAPNIK_MINOR_VERSION 0 -#define MAPNIK_PATCH_VERSION 15 - -#define MAPNIK_VERSION (MAPNIK_MAJOR_VERSION*100000) + (MAPNIK_MINOR_VERSION*100) + (MAPNIK_PATCH_VERSION) - -#define MAPNIK_VERSION_STRING MAPNIK_STRINGIFY(MAPNIK_MAJOR_VERSION) "." \ - MAPNIK_STRINGIFY(MAPNIK_MINOR_VERSION) "." \ - MAPNIK_STRINGIFY(MAPNIK_PATCH_VERSION) - -#endif // MAPNIK_VERSION_HPP +}} diff --git a/include/mapnik/version.hpp b/include/mapnik/version.hpp index 9798d98..d891d46 100644 --- a/include/mapnik/version.hpp +++ b/include/mapnik/version.hpp @@ -27,7 +27,7 @@ #define MAPNIK_MAJOR_VERSION 3 #define MAPNIK_MINOR_VERSION 0 -#define MAPNIK_PATCH_VERSION 15 +#define MAPNIK_PATCH_VERSION 16 #define MAPNIK_VERSION (MAPNIK_MAJOR_VERSION*100000) + (MAPNIK_MINOR_VERSION*100) + (MAPNIK_PATCH_VERSION) diff --git a/plugins/input/gdal/gdal_featureset.cpp b/plugins/input/gdal/gdal_featureset.cpp index 8f755fd..661f8e1 100644 --- a/plugins/input/gdal/gdal_featureset.cpp +++ b/plugins/input/gdal/gdal_featureset.cpp @@ -547,7 +547,7 @@ feature_ptr gdal_featureset::get_feature(mapnik::query const& q) if (alpha) { MAPNIK_LOG_DEBUG(gdal) << "gdal_featureset: processing alpha band..."; - if (!raster_has_nodata) + if (!raster_has_nodata || (red && green && blue)) { raster_io_error = alpha->RasterIO(GF_Read, x_off, y_off, width, height, image.bytes() + 3, image.width(), image.height(), GDT_Byte, 4, 4 * image.width()); diff --git a/scripts/publish_release.sh b/scripts/publish_release.sh index 7ff8749..3bc2de1 100755 --- a/scripts/publish_release.sh +++ b/scripts/publish_release.sh @@ -71,7 +71,7 @@ function check_and_tag() { step "test data already tagged, no need to initialize submodule" else step "tagging test data" - git submodule update --depth 100 --init ${REPO_DIR} + git submodule update --init ${REPO_DIR} cd ${REPO_DIR}/ git remote set-url origin [email protected]:mapnik/${REPO_NAME} git tag ${MAPNIK_VERSION} -a -m "tagging for ${MAPNIK_VERSION}" diff --git a/src/load_map.cpp b/src/load_map.cpp index 5a8e122..b51f811 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -49,6 +49,7 @@ #include <mapnik/util/dasharray_parser.hpp> #include <mapnik/util/conversions.hpp> #include <mapnik/util/trim.hpp> +#include <mapnik/util/name_to_int.hpp> #include <mapnik/marker_cache.hpp> #include <mapnik/util/noncopyable.hpp> #include <mapnik/util/fs.hpp> @@ -80,12 +81,9 @@ using boost::tokenizer; namespace mapnik { -using boost::optional; -constexpr unsigned name2int(const char *str, int off = 0) -{ - return !str[off] ? 5381 : (name2int(str, off + 1) * 33) ^ static_cast<unsigned>(str[off]); -} +using boost::optional; +using util::name_to_int; class map_parser : util::noncopyable { @@ -826,57 +824,57 @@ void map_parser::parse_symbolizers(rule & rule, xml_node const & node) rule.reserve(node.size()); for (auto const& sym_node : node) { - switch (name2int(sym_node.name().c_str())) + switch (name_to_int(sym_node.name().c_str())) { - case name2int("PointSymbolizer"): + case name_to_int("PointSymbolizer"): parse_point_symbolizer(rule, sym_node); sym_node.set_processed(true); break; - case name2int("LinePatternSymbolizer"): + case name_to_int("LinePatternSymbolizer"): parse_line_pattern_symbolizer(rule, sym_node); sym_node.set_processed(true); break; - case name2int("PolygonPatternSymbolizer"): + case name_to_int("PolygonPatternSymbolizer"): parse_polygon_pattern_symbolizer(rule, sym_node); sym_node.set_processed(true); break; - case name2int("TextSymbolizer"): + case name_to_int("TextSymbolizer"): parse_text_symbolizer(rule, sym_node); sym_node.set_processed(true); break; - case name2int("ShieldSymbolizer"): + case name_to_int("ShieldSymbolizer"): parse_shield_symbolizer(rule, sym_node); sym_node.set_processed(true); break; - case name2int("LineSymbolizer"): + case name_to_int("LineSymbolizer"): parse_line_symbolizer(rule, sym_node); sym_node.set_processed(true); break; - case name2int("PolygonSymbolizer"): + case name_to_int("PolygonSymbolizer"): parse_polygon_symbolizer(rule, sym_node); sym_node.set_processed(true); break; - case name2int("BuildingSymbolizer"): + case name_to_int("BuildingSymbolizer"): parse_building_symbolizer(rule, sym_node); sym_node.set_processed(true); break; - case name2int("RasterSymbolizer"): + case name_to_int("RasterSymbolizer"): parse_raster_symbolizer(rule, sym_node); sym_node.set_processed(true); break; - case name2int("MarkersSymbolizer"): + case name_to_int("MarkersSymbolizer"): parse_markers_symbolizer(rule, sym_node); sym_node.set_processed(true); break; - case name2int("GroupSymbolizer"): + case name_to_int("GroupSymbolizer"): parse_group_symbolizer(rule, sym_node); sym_node.set_processed(true); break; - case name2int("DebugSymbolizer"): + case name_to_int("DebugSymbolizer"): parse_debug_symbolizer(rule, sym_node); sym_node.set_processed(true); break; - case name2int("DotSymbolizer"): + case name_to_int("DotSymbolizer"): parse_dot_symbolizer(rule, sym_node); sym_node.set_processed(true); break; @@ -1356,7 +1354,6 @@ void map_parser::parse_raster_symbolizer(rule & rule, xml_node const & node) { found_colorizer = true; raster_colorizer_ptr colorizer = std::make_shared<raster_colorizer>(); - put(raster_sym, keys::colorizer, colorizer); if (parse_raster_colorizer(colorizer, css)) put(raster_sym, keys::colorizer, colorizer); } diff --git a/src/marker_cache.cpp b/src/marker_cache.cpp index 059280c..d7ea7fa 100644 --- a/src/marker_cache.cpp +++ b/src/marker_cache.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko + * Copyright (C) 2017 Artem Pavlenko * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -141,7 +141,7 @@ struct visitor_create_marker } // end detail ns std::shared_ptr<mapnik::marker const> marker_cache::find(std::string const& uri, - bool update_cache) + bool update_cache, bool strict) { if (uri.empty()) { @@ -174,15 +174,15 @@ std::shared_ptr<mapnik::marker const> marker_cache::find(std::string const& uri, vertex_stl_adapter<svg_path_storage> stl_storage(marker_path->source()); svg_path_adapter svg_path(stl_storage); svg_converter_type svg(svg_path, marker_path->attributes()); - svg_parser p(svg); + svg_parser p(svg, strict); + p.parse_from_string(known_svg_string); - if (!p.parse_from_string(known_svg_string)) + if (!strict) { - for (auto const& msg : p.error_messages()) + for (auto const& msg : p.err_handler().error_messages()) { MAPNIK_LOG_ERROR(marker_cache) << "SVG PARSING ERROR:\"" << msg << "\""; } - return std::make_shared<mapnik::marker const>(mapnik::marker_null()); } //svg.arrange_orientations(); double lox,loy,hix,hiy; @@ -214,16 +214,15 @@ std::shared_ptr<mapnik::marker const> marker_cache::find(std::string const& uri, vertex_stl_adapter<svg_path_storage> stl_storage(marker_path->source()); svg_path_adapter svg_path(stl_storage); svg_converter_type svg(svg_path, marker_path->attributes()); - svg_parser p(svg); - + svg_parser p(svg, strict); + p.parse(uri); - if (!p.parse(uri)) + if (!strict) { - for (auto const& msg : p.error_messages()) + for (auto const& msg : p.err_handler().error_messages()) { MAPNIK_LOG_ERROR(marker_cache) << "SVG PARSING ERROR:\"" << msg << "\""; } - return std::make_shared<mapnik::marker const>(mapnik::marker_null()); } //svg.arrange_orientations(); double lox,loy,hix,hiy; diff --git a/src/svg/svg_parser.cpp b/src/svg/svg_parser.cpp index 827f557..4dd3fbc 100644 --- a/src/svg/svg_parser.cpp +++ b/src/svg/svg_parser.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko + * Copyright (C) 2017 Artem Pavlenko * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -30,7 +30,7 @@ #include <mapnik/util/file_io.hpp> #include <mapnik/util/utf_conv_win.hpp> #include <mapnik/util/dasharray_parser.hpp> - +#include <mapnik/util/name_to_int.hpp> #pragma GCC diagnostic push #include <mapnik/warning_ignore_agg.hpp> #include "agg_ellipse.h" @@ -54,28 +54,125 @@ #include <vector> #include <cstring> #include <fstream> +#include <array> + +namespace mapnik { namespace svg { + +using util::name_to_int; + +struct viewbox +{ + double x0; + double y0; + double width; + double height; +}; +}} + +BOOST_FUSION_ADAPT_STRUCT ( + mapnik::svg::viewbox, + (double, x0) + (double, y0) + (double, width) + (double, height) + ) namespace mapnik { namespace svg { namespace rapidxml = boost::property_tree::detail::rapidxml; -bool traverse_tree(svg_parser & parser,rapidxml::xml_node<char> const* node); -void end_element(svg_parser & parser,rapidxml::xml_node<char> const* node); -void parse_path(svg_parser & parser,rapidxml::xml_node<char> const* node); -void parse_dimensions(svg_parser & parser,rapidxml::xml_node<char> const* node); -void parse_polygon(svg_parser & parser,rapidxml::xml_node<char> const* node); -void parse_polyline(svg_parser & parser,rapidxml::xml_node<char> const* node); -void parse_line(svg_parser & parser,rapidxml::xml_node<char> const* node); -void parse_rect(svg_parser & parser,rapidxml::xml_node<char> const* node); -void parse_circle(svg_parser & parser,rapidxml::xml_node<char> const* node); -void parse_ellipse(svg_parser & parser,rapidxml::xml_node<char> const* node); -void parse_linear_gradient(svg_parser & parser,rapidxml::xml_node<char> const* node); -void parse_radial_gradient(svg_parser & parser,rapidxml::xml_node<char> const* node); -bool parse_common_gradient(svg_parser & parser,rapidxml::xml_node<char> const* node); -void parse_gradient_stop(svg_parser & parser,rapidxml::xml_node<char> const* node); -void parse_attr(svg_parser & parser,rapidxml::xml_node<char> const* node); -void parse_attr(svg_parser & parser,char const * name, char const* value); +void traverse_tree(svg_parser& parser, rapidxml::xml_node<char> const* node); +void end_element(svg_parser& parser, rapidxml::xml_node<char> const* node); +void parse_path(svg_parser& parser, rapidxml::xml_node<char> const* node); +void parse_element(svg_parser& parser, char const* name, rapidxml::xml_node<char> const* node); +void parse_use(svg_parser& parser, rapidxml::xml_node<char> const* node); +void parse_dimensions(svg_parser& parser, rapidxml::xml_node<char> const* node); +void parse_polygon(svg_parser& parser, rapidxml::xml_node<char> const* node); +void parse_polyline(svg_parser& parser, rapidxml::xml_node<char> const* node); +void parse_line(svg_parser& parser, rapidxml::xml_node<char> const* node); +void parse_rect(svg_parser& parser, rapidxml::xml_node<char> const* node); +void parse_circle(svg_parser& parser, rapidxml::xml_node<char> const* node); +void parse_ellipse(svg_parser& parser, rapidxml::xml_node<char> const* node); +void parse_linear_gradient(svg_parser& parser, rapidxml::xml_node<char> const* node); +void parse_radial_gradient(svg_parser& parser, rapidxml::xml_node<char> const* node); +bool parse_common_gradient(svg_parser& parser, std::string const& id, + mapnik::gradient& gr, rapidxml::xml_node<char> const* node); +void parse_gradient_stop(svg_parser& parser, mapnik::gradient& gr, rapidxml::xml_node<char> const* node); +void parse_attr(svg_parser& parser, rapidxml::xml_node<char> const* node); +void parse_attr(svg_parser& parser, char const* name, char const* value); + +namespace { + +static std::array<unsigned, 7> const unsupported_elements +{ {name_to_int("symbol"), + name_to_int("marker"), + name_to_int("view"), + name_to_int("text"), + name_to_int("switch"), + name_to_int("image"), + name_to_int("a")} +}; +#if 0 // disable to reduce verbosity +static std::array<unsigned, 43> const unsupported_attributes +{ {name_to_int("alignment-baseline"), + name_to_int("baseline-shift"), + name_to_int("clip"), + name_to_int("clip-path"), + name_to_int("clip-rule"), + name_to_int("color-interpolation"), + name_to_int("color-interpolation-filters"), + name_to_int("color-profile"), + name_to_int("color-rendering"), + name_to_int("cursor"), + name_to_int("direction"), + name_to_int("dominant-baseline"), + name_to_int("enable-background"), + name_to_int("filter"), + name_to_int("flood-color"), + name_to_int("flood-opacity"), + name_to_int("font-family"), + name_to_int("font-size"), + name_to_int("font-size-adjust"), + name_to_int("font-stretch"), + name_to_int("font-style"), + name_to_int("font-variant"), + name_to_int("font-weight"), + name_to_int("glyph-orientation-horizontal"), + name_to_int("glyph-orientation-vertical"), + name_to_int("image-rendering"), + name_to_int("kerning"), + name_to_int("letter-spacing"), + name_to_int("lighting-color"), + name_to_int("marker-end"), + name_to_int("marker-mid"), + name_to_int("marker-start"), + name_to_int("mask"), + name_to_int("overflow"), + name_to_int("pointer-events"), + name_to_int("shape-rendering"), + name_to_int("text-anchor"), + name_to_int("text-decoration"), + name_to_int("text-rendering"), + name_to_int("unicode-bidi"), + name_to_int("word-spacing"), + name_to_int("writing-mode")} +}; + +#endif + +template <typename T> +void handle_unsupported(svg_parser& parser, T const& ar, char const* name) +{ + unsigned element = name_to_int(name); + for (auto const& e : ar) + { + if (e == element) + { + parser.err_handler().on_error(std::string("SVG support error: <" + std::string(name) + "> element is not supported")); + } + } +} using color_lookup_type = std::vector<std::pair<double, agg::rgba8> >; namespace qi = boost::spirit::qi; @@ -102,7 +199,7 @@ struct key_value_sequence_ordered }; template <typename T> -mapnik::color parse_color(T & error_messages, const char* str) +mapnik::color parse_color(T & err_handler, const char* str) { mapnik::color c(100,100,100); try @@ -111,34 +208,34 @@ mapnik::color parse_color(T & error_messages, const char* str) } catch (mapnik::config_error const& ex) { - error_messages.emplace_back(ex.what()); + err_handler.on_error("SVG parse error: failed to parse <color> with value \"" + std::string(str) + "\""); } return c; } template <typename T> -agg::rgba8 parse_color_agg(T & error_messages, const char* str) +agg::rgba8 parse_color_agg(T & err_handler, const char* str) { - auto c = parse_color(error_messages, str); + auto c = parse_color(err_handler, str); return agg::rgba8(c.red(), c.green(), c.blue(), c.alpha()); } template <typename T> -double parse_double(T & error_messages, const char* str) +double parse_double(T & err_handler, const char* str) { using namespace boost::spirit::qi; double_type double_; double val = 0.0; if (!parse(str, str + std::strlen(str),double_,val)) { - error_messages.emplace_back("Failed to parse double: \"" + std::string(str) + "\""); + err_handler.on_error("SVG parse error: failed to parse <number> with value \"" + std::string(str) + "\""); } return val; } // https://www.w3.org/TR/SVG/coords.html#Units template <typename T, int DPI = 90> -double parse_svg_value(T & error_messages, const char* str, bool & percent) +double parse_svg_value(T & err_handler, const char* str, bool & percent) { using skip_type = boost::spirit::ascii::space_type; using boost::phoenix::ref; @@ -162,35 +259,29 @@ double parse_svg_value(T & error_messages, const char* str, bool & percent) > - (units[ ref(val) *= _1] | lit('%')[ref(val) *= 0.01][ref(percent) = true]), - skip_type())) + skip_type()) || cur != end) { - error_messages.emplace_back("Failed to parse SVG value: '" + std::string(str) + "'"); - } - else if (cur != end) - { - error_messages.emplace_back("Failed to parse SVG value: '" + std::string(str) + - "', trailing garbage: '" + cur + "'"); + err_handler.on_error("SVG parse error: failed to parse <number> with value \"" + std::string(str) + "\""); } return val; } -template <typename T> -bool parse_double_list(T & error_messages, const char* str, double* list) +template <typename T, typename V> +bool parse_viewbox(T & err_handler, const char* str, V & viewbox) { using namespace boost::spirit::qi; using boost::phoenix::ref; - _1_type _1; double_type double_; lit_type lit; using skip_type = boost::spirit::ascii::space_type; if (!phrase_parse(str, str + std::strlen(str), - double_[ref(list[0])=_1] >> -lit(',') >> - double_[ref(list[1])=_1] >> -lit(',') >> - double_[ref(list[2])=_1] >> -lit(',') >> - double_[ref(list[3])=_1], skip_type())) + double_ >> -lit(',') >> + double_ >> -lit(',') >> + double_ >> -lit(',') >> + double_, skip_type(), viewbox)) { - error_messages.emplace_back("failed to parse list of doubles from " + std::string(str)); + err_handler.on_error("SVG parse error: failed to parse <viewbox> with value \"" + std::string(str) + "\""); return false; } return true; @@ -216,91 +307,146 @@ bool parse_id_from_url (char const* str, std::string & id) skip_type()); } -bool traverse_tree(svg_parser & parser, rapidxml::xml_node<char> const* node) +} + +boost::property_tree::detail::rapidxml::xml_attribute<char> const * parse_id(svg_parser & parser, rapidxml::xml_node<char> const* node) +{ + auto const* id_attr = node->first_attribute("xml:id"); + if (id_attr == nullptr) id_attr = node->first_attribute("id"); + + if (id_attr && parser.node_cache_.count(id_attr->value()) == 0) + { + parser.node_cache_.emplace(id_attr->value(), node); + } + return id_attr; +} + +boost::property_tree::detail::rapidxml::xml_attribute<char> const * parse_href(rapidxml::xml_node<char> const* node) +{ + auto const* attr = node->first_attribute("xlink:href"); + if (attr == nullptr) attr = node->first_attribute("href"); + return attr; +} + +enum aspect_ratio_alignment +{ + none = 0, + xMinYMin, + xMidYMin, + xMaxYMin, + xMinYMid, + xMidYMid, + xMaxYMid, + xMinYMax, + xMidYMax, + xMaxYMax +}; + +template <typename T> +std::pair<unsigned,bool> parse_preserve_aspect_ratio(T & err_handler, char const* str) +{ + std::pair<unsigned,bool> preserve_aspect_ratio {xMidYMid, true }; + using skip_type = boost::spirit::ascii::space_type; + using boost::phoenix::ref; + qi::lit_type lit; + qi::_1_type _1; + qi::symbols<char, unsigned> align; + align.add + ("none", none) + ("xMinYMin", xMinYMin) + ("xMidYMin", xMidYMin) + ("xMaxYMin", xMaxYMin) + ("xMinYMid", xMinYMid) + ("xMidYMid", xMidYMid) + ("xMaxYMid", xMaxYMid) + ("xMinYMax", xMinYMax) + ("xMidYMax", xMidYMax) + ("xMaxYMax", xMaxYMax); + + + char const* cur = str; // phrase_parse mutates the first iterator + char const* end = str + std::strlen(str); + try + { + qi::phrase_parse(cur, end, -lit("defer") // only applicable to <image> which we don't support currently + > align[ref(preserve_aspect_ratio.first) = _1] + > -(lit("meet") | lit("slice")[ref(preserve_aspect_ratio.second) = false]), skip_type()); + } + catch (qi::expectation_failure<char const*> const& ex) + { + err_handler.on_error("SVG parse error: failed to parse <preserveAspectRatio> with value \"" + std::string(str) + "\""); + return {xMidYMid, true} ; // default + } + return preserve_aspect_ratio; +} + + +void traverse_tree(svg_parser & parser, rapidxml::xml_node<char> const* node) { auto const* name = node->name(); switch (node->type()) { case rapidxml::node_element: { - if (std::strcmp(name, "defs") == 0) + switch(name_to_int(name)) + { + case name_to_int("defs"): { if (node->first_node() != nullptr) { parser.is_defs_ = true; } + break; } // 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) - { + case name_to_int("linearGradient"): parse_linear_gradient(parser, node); - } - else if (std::strcmp(name, "radialGradient") == 0) - { + break; + case name_to_int("radialGradient"): parse_radial_gradient(parser, node); - } - else if (std::strcmp(name, "stop") == 0) - { - parse_gradient_stop(parser, node); + break; + case name_to_int("symbol"): + parse_id(parser, node); + //parse_dimensions(parser, node); + break; } if (!parser.is_defs_) // FIXME { - if (std::strcmp(name, "g") == 0) + switch (name_to_int(name)) { + case name_to_int("g"): if (node->first_node() != nullptr) { parser.path_.push_attr(); + parse_id(parser, node); parse_attr(parser, node); } - } - else - { + break; + case name_to_int("use"): parser.path_.push_attr(); + parse_id(parser, node); + parse_attr(parser, node); + parse_use(parser, node); + parser.path_.pop_attr(); + break; + default: + parser.path_.push_attr(); + parse_id(parser, node); 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"; - } + parse_element(parser, name, node); } parser.path_.pop_attr(); } } + else + { + // save node for later + parse_id(parser, node); + } for (auto const* child = node->first_node(); child; child = child->next_sibling()) @@ -323,7 +469,7 @@ bool traverse_tree(svg_parser & parser, rapidxml::xml_node<char> const* node) // whitespace trimmed. //std::string trimmed = node->value(); //mapnik::util::trim(trimmed); - std::cerr << "CDATA:" << node->value() << std::endl; + //std::cerr << "CDATA:" << node->value() << std::endl; } } break; @@ -331,7 +477,6 @@ bool traverse_tree(svg_parser & parser, rapidxml::xml_node<char> const* node) default: break; } - return true; } @@ -352,67 +497,111 @@ void end_element(svg_parser & parser, rapidxml::xml_node<char> const* node) parser.is_defs_ = false; } } - else if (std::strcmp(name, "linearGradient") == 0 || std::strcmp(name, "radialGradient") == 0) +} + +void parse_element(svg_parser & parser, char const* name, rapidxml::xml_node<char> const* node) +{ + switch (name_to_int(name)) { - parser.gradient_map_[parser.temporary_gradient_.first] = parser.temporary_gradient_.second; + case name_to_int("path"): + parser.path_.transform().multiply(parser.viewbox_tr_); + parse_path(parser, node); + break; + case name_to_int("polygon"): + parser.path_.transform().multiply(parser.viewbox_tr_); + parse_polygon(parser, node); + break; + case name_to_int("polyline"): + parser.path_.transform().multiply(parser.viewbox_tr_); + parse_polyline(parser, node); + break; + case name_to_int("line"): + parser.path_.transform().multiply(parser.viewbox_tr_); + parse_line(parser, node); + break; + case name_to_int("rect"): + parser.path_.transform().multiply(parser.viewbox_tr_); + parse_rect(parser, node); + break; + case name_to_int("circle"): + parser.path_.transform().multiply(parser.viewbox_tr_); + parse_circle(parser, node); + break; + case name_to_int("ellipse"): + parser.path_.transform().multiply(parser.viewbox_tr_); + parse_ellipse(parser, node); + break; + case name_to_int("svg"): + parse_dimensions(parser, node); + break; + default: + handle_unsupported(parser, unsupported_elements, name); + break; } } -void parse_attr(svg_parser & parser, char const* name, char const* value ) +void parse_stroke(svg_parser& parser, char const* value) { - if (std::strcmp(name, "transform") == 0) + std::string id; + if (std::strcmp(value, "none") == 0) { - agg::trans_affine tr; - mapnik::svg::parse_svg_transform(value,tr); - parser.path_.transform().premultiply(tr); + parser.path_.stroke_none(); } - else if (std::strcmp(name, "fill") == 0) + else if (parse_id_from_url(value, id)) { - std::string id; - if (std::strcmp(value, "none") == 0) + // see if we have a known gradient stroke + if (parser.gradient_map_.count(id) > 0) { - parser.path_.fill_none(); + parser.path_.add_stroke_gradient(parser.gradient_map_[id]); } - else if (parse_id_from_url(value, id)) + else if (parser.node_cache_.count(id) > 0) { - // see if we have a known gradient fill + // try parsing again + auto const* gradient_node = parser.node_cache_[id]; + traverse_tree(parser, gradient_node); if (parser.gradient_map_.count(id) > 0) { - parser.path_.add_fill_gradient(parser.gradient_map_[id]); + parser.path_.add_stroke_gradient(parser.gradient_map_[id]); } else { std::stringstream ss; - ss << "Failed to find gradient fill: " << id; - parser.error_messages_.push_back(ss.str()); + ss << "SVG parse error: failed to locate <gradient> stroke with <id> \"" << id << "\""; + parser.err_handler().on_error(ss.str()); } } else { - parser.path_.fill(parse_color_agg(parser.error_messages_, value)); + std::stringstream ss; + ss << "SVG parse error: failed to locate <gradient> stroke with <id> \"" << id << "\""; + parser.err_handler().on_error(ss.str()); } } - else if (std::strcmp(name,"fill-opacity") == 0) + else { - parser.path_.fill_opacity(parse_double(parser.error_messages_, value)); + parser.path_.stroke(parse_color_agg(parser.err_handler(), value)); } - else if (std::strcmp(name, "fill-rule") == 0) +} + +void parse_fill(svg_parser& parser, char const* value) +{ + std::string id; + if (std::strcmp(value, "none") == 0) { - if (std::strcmp(value, "evenodd") == 0) - { - parser.path_.even_odd(true); - } + parser.path_.fill_none(); } - else if (std::strcmp(name, "stroke") == 0) + else if (parse_id_from_url(value, id)) { - std::string id; - if (std::strcmp(value, "none") == 0) + // see if we have a known gradient fill + if (parser.gradient_map_.count(id) > 0) { - parser.path_.stroke_none(); + parser.path_.add_fill_gradient(parser.gradient_map_[id]); } - else if (parse_id_from_url(value, id)) + else if (parser.node_cache_.count(id) > 0) { - // see if we have a known gradient fill + // try parsing again + auto const* gradient_node = parser.node_cache_[id]; + traverse_tree(parser, gradient_node); if (parser.gradient_map_.count(id) > 0) { parser.path_.add_stroke_gradient(parser.gradient_map_[id]); @@ -420,71 +609,109 @@ void parse_attr(svg_parser & parser, char const* name, char const* value ) else { std::stringstream ss; - ss << "Failed to find gradient stroke: " << id; - parser.error_messages_.push_back(ss.str()); + ss << "SVG parse error: failed to locate <gradient> fill with <id> \"" << id << "\""; + parser.err_handler().on_error(ss.str()); } } else { - parser.path_.stroke(parse_color_agg(parser.error_messages_, value)); + std::stringstream ss; + ss << "SVG parse error: failed to locate <gradient> fill with <id> \"" << id << "\""; + parser.err_handler().on_error(ss.str()); } } - else if (std::strcmp(name, "stroke-width") == 0) + else { - bool percent; - parser.path_.stroke_width(parse_svg_value(parser.error_messages_, value, percent)); + parser.path_.fill(parse_color_agg(parser.err_handler(), value)); } - else if (std::strcmp(name, "stroke-opacity") == 0) +} + +void parse_transform(svg_parser & parser, char const* value) +{ + agg::trans_affine tr; + mapnik::svg::parse_svg_transform(value,tr); + parser.path_.transform().premultiply(tr); +} + +void parse_stroke_dash(svg_parser & parser, char const* value) +{ + dash_array dash; + if (util::parse_dasharray(value, dash)) { - parser.path_.stroke_opacity(parse_double(parser.error_messages_, value)); + parser.path_.dash_array(std::move(dash)); } - else if(std::strcmp(name, "stroke-linecap") == 0) +} + +void parse_attr(svg_parser & parser, char const* name, char const* value ) +{ + switch (name_to_int(name)) { + case name_to_int("transform"): + parse_transform(parser, value); + break; + case name_to_int("fill"): + parse_fill(parser, value); + break; + case name_to_int("fill-opacity"): + parser.path_.fill_opacity(parse_double(parser.err_handler(), value)); + break; + case name_to_int("fill-rule"): + if (std::strcmp(value, "evenodd") == 0) + { + parser.path_.even_odd(true); + } + break; + case name_to_int("stroke"): + parse_stroke(parser, value); + break; + case name_to_int("stroke-width"): + bool percent; + parser.path_.stroke_width(parse_svg_value(parser.err_handler(), value, percent)); + break; + case name_to_int("stroke-opacity"): + parser.path_.stroke_opacity(parse_double(parser.err_handler(), value)); + break; + case name_to_int("stroke-linecap"): if(std::strcmp(value, "butt") == 0) parser.path_.line_cap(agg::butt_cap); else if(std::strcmp(value, "round") == 0) parser.path_.line_cap(agg::round_cap); else if(std::strcmp(value, "square") == 0) parser.path_.line_cap(agg::square_cap); - } - else if(std::strcmp(name, "stroke-linejoin") == 0) - { - if(std::strcmp(value, "miter") == 0) + break; + case name_to_int("stroke-linejoin"): + if (std::strcmp(value, "miter") == 0) parser.path_.line_join(agg::miter_join); - else if(std::strcmp(value, "round") == 0) + else if (std::strcmp(value, "round") == 0) parser.path_.line_join(agg::round_join); - else if(std::strcmp(value, "bevel") == 0) + else if (std::strcmp(value, "bevel") == 0) parser.path_.line_join(agg::bevel_join); - } - else if(std::strcmp(name, "stroke-miterlimit") == 0) - { - parser.path_.miter_limit(parse_double(parser.error_messages_,value)); - } - else if (std::strcmp(name,"stroke-dasharray") == 0) - { - dash_array dash; - if (util::parse_dasharray(value, dash)) + break; + case name_to_int("stroke-miterlimit"): + parser.path_.miter_limit(parse_double(parser.err_handler(),value)); + break; + case name_to_int("stroke-dasharray"): + parse_stroke_dash(parser, value); + break; + case name_to_int("stroke-dashoffset"): + parser.path_.dash_offset(parse_double(parser.err_handler(), value)); + break; + case name_to_int("opacity"): + parser.path_.opacity(parse_double(parser.err_handler(), value)); + break; + case name_to_int("visibility"): + parser.path_.visibility(std::strcmp(value, "hidden") != 0); + break; + case name_to_int("display"): + if (std::strcmp(value, "none") == 0) { - parser.path_.dash_array(std::move(dash)); + parser.path_.display(false); } - } - else if (std::strcmp(name,"stroke-dashoffset") == 0) - { - double offset = parse_double(parser.error_messages_, value); - parser.path_.dash_offset(offset); - } - else if(std::strcmp(name, "opacity") == 0) - { - double opacity = parse_double(parser.error_messages_, value); - parser.path_.opacity(opacity); - } - else if (std::strcmp(name, "visibility") == 0) - { - parser.path_.visibility(std::strcmp(value, "hidden") != 0); - } - else if (std::strcmp(name, "display") == 0 && std::strcmp(value, "none") == 0) - { - parser.path_.display(false); + break; + default: + //handle_unsupported(parser, unsupported_attributes, name); + // disable for now to reduce verbosity + break; } } @@ -517,7 +744,7 @@ void parse_dimensions(svg_parser & parser, rapidxml::xml_node<char> const* node) double width = 0; double height = 0; double aspect_ratio = 1; - double viewbox[4] = {0,0,0,0}; + viewbox vbox = {0, 0, 0, 0}; bool has_viewbox = false; bool has_percent_height = true; bool has_percent_width = true; @@ -525,35 +752,98 @@ void parse_dimensions(svg_parser & parser, rapidxml::xml_node<char> const* node) auto const* width_attr = node->first_attribute("width"); if (width_attr) { - width = parse_svg_value(parser.error_messages_, width_attr->value(), has_percent_width); + width = parse_svg_value(parser.err_handler(), width_attr->value(), has_percent_width); } auto const* height_attr = node->first_attribute("height"); if (height_attr) { - height = parse_svg_value(parser.error_messages_, height_attr->value(), has_percent_height); + height = parse_svg_value(parser.err_handler(), height_attr->value(), has_percent_height); } + auto const* viewbox_attr = node->first_attribute("viewBox"); if (viewbox_attr) { - has_viewbox = parse_double_list(parser.error_messages_, viewbox_attr->value(), viewbox); - } + has_viewbox = parse_viewbox(parser.err_handler(), viewbox_attr->value(), vbox); + if (width > 0 && height > 0 && vbox.width > 0 && vbox.height > 0) + { + agg::trans_affine t{}; + std::pair<unsigned,bool> preserve_aspect_ratio {xMidYMid, true}; + auto const* aspect_ratio_attr = node->first_attribute("preserveAspectRatio"); + if (aspect_ratio_attr) + { + preserve_aspect_ratio = parse_preserve_aspect_ratio(parser.err_handler(), aspect_ratio_attr->value()); + } + + double sx = width / vbox.width; + double sy = height / vbox.height; + double scale = preserve_aspect_ratio.second ? std::min(sx, sy) : std::max(sx, sy); + switch (preserve_aspect_ratio.first) + { + case none: + t = agg::trans_affine_scaling(sx, sy) * t; + break; + case xMinYMin: + t = agg::trans_affine_scaling(scale, scale) * t; + break; + case xMinYMid: + t = agg::trans_affine_scaling(scale, scale) * t; + t = agg::trans_affine_translation(0, -0.5 * (vbox.height - height / scale)) * t; + break; + case xMinYMax: + t = agg::trans_affine_scaling(scale, scale) * t; + t = agg::trans_affine_translation(0, -1.0 * (vbox.height - height / scale)) * t; + break; + case xMidYMin: + t = agg::trans_affine_scaling(scale, scale) * t; + t = agg::trans_affine_translation(-0.5 * (vbox.width - width / scale), 0.0) * t; + break; + case xMidYMid: // (the default) + t = agg::trans_affine_scaling(scale, scale) * t; + t = agg::trans_affine_translation(-0.5 * (vbox.width - width / scale), + -0.5 * (vbox.height - height / scale)) * t; + break; + case xMidYMax: + t = agg::trans_affine_scaling(scale, scale) * t; + t = agg::trans_affine_translation(-0.5 * (vbox.width - width / scale), + -1.0 * (vbox.height - height / scale)) * t; + break; + case xMaxYMin: + t = agg::trans_affine_scaling(scale, scale) * t; + t = agg::trans_affine_translation(-1.0 * (vbox.width - width / scale), 0.0) * t; + break; + case xMaxYMid: + t = agg::trans_affine_scaling(scale, scale) * t; + t = agg::trans_affine_translation(-1.0 * (vbox.width - width / scale), + -0.5 * (vbox.height - height / scale)) * t; + break; + case xMaxYMax: + t = agg::trans_affine_scaling(scale, scale) * t; + t = agg::trans_affine_translation(-1.0 * (vbox.width - width / scale), + -1.0 * (vbox.height - height / scale)) * t; + break; + }; + + t = agg::trans_affine_translation(-vbox.x0, -vbox.y0) * t; + parser.viewbox_tr_ = t; + } + + } if (has_percent_width && !has_percent_height && has_viewbox) { - aspect_ratio = viewbox[2] / viewbox[3]; + aspect_ratio = vbox.width / vbox.height; width = aspect_ratio * height; } else if (!has_percent_width && has_percent_height && has_viewbox) { - aspect_ratio = viewbox[2] / viewbox[3]; + aspect_ratio = vbox.width/vbox.height; height = height / aspect_ratio; } else if (has_percent_width && has_percent_height && has_viewbox) { - width = viewbox[2]; - height = viewbox[3]; + width = vbox.width; + height = vbox.height; } - parser.path_.set_dimensions(width, height); } @@ -569,16 +859,15 @@ void parse_path(svg_parser & parser, rapidxml::xml_node<char> const* node) if (!mapnik::svg::parse_path(value, parser.path_)) { - auto const* id_attr = node->first_attribute("xml:id"); - if (id_attr == nullptr) id_attr = node->first_attribute("id"); + auto const* id_attr = parse_id(parser, node); if (id_attr) { - parser.error_messages_.push_back(std::string("unable to parse invalid svg <path> with id '") - + id_attr->value() + "'"); + parser.err_handler().on_error(std::string("SVG parse error: failed to parse <path> with <id> \"") + + id_attr->value() + "\""); } else { - parser.error_messages_.push_back(std::string("unable to parse invalid svg <path>")); + parser.err_handler().on_error(std::string("SVG parse error: failed to parse <path>")); } } parser.path_.end_path(); @@ -586,6 +875,74 @@ void parse_path(svg_parser & parser, rapidxml::xml_node<char> const* node) } } +void parse_use(svg_parser & parser, rapidxml::xml_node<char> const* node) +{ + auto * attr = parse_href(node); + if (attr) + { + auto const* value = attr->value(); + if (std::strlen(value) > 1 && value[0] == '#') + { + std::string id(&value[1]); + if (parser.node_cache_.count(id) > 0) + { + auto const* base_node = parser.node_cache_[id]; + double x = 0.0; + double y = 0.0; + double w = 0.0; + double h = 0.0; + bool percent = false; + attr = node->first_attribute("x"); + if (attr != nullptr) + { + x = parse_svg_value(parser.err_handler(), attr->value(), percent); + } + + attr = node->first_attribute("y"); + if (attr != nullptr) + { + y = parse_svg_value(parser.err_handler(), attr->value(), percent); + } + + attr = node->first_attribute("width"); + if (attr != nullptr) + { + w = parse_svg_value(parser.err_handler(), attr->value(), percent); + if (percent) w *= parser.path_.width(); + } + attr = node->first_attribute("height"); + if (attr) + { + h = parse_svg_value(parser.err_handler(), attr->value(), percent); + if (percent) h *= parser.path_.height(); + } + if (w < 0.0) + { + std::stringstream ss; + ss << "SVG validation error: invalid <use> width \"" << w << "\""; + parser.err_handler().on_error(ss.str()); + } + else if (h < 0.0) + { + std::stringstream ss; + ss << "SVG validation error: invalid <use> height \"" << w << "\""; + parser.err_handler().on_error(ss.str()); + } + agg::trans_affine t{}; + if (!node->first_attribute("transform") && w != 0.0 && h != 0.0) + { + // FIXME + double scale = std::min(double(w / parser.path_.width()), double(h / parser.path_.height())); + t *= agg::trans_affine_scaling(scale); + } + t *= agg::trans_affine_translation(x, y); + parser.path_.transform().premultiply(t); + traverse_tree(parser, base_node); + } + } + } +} + void parse_polygon(svg_parser & parser, rapidxml::xml_node<char> const* node) { auto const* attr = node->first_attribute("points"); @@ -594,7 +951,7 @@ void parse_polygon(svg_parser & parser, rapidxml::xml_node<char> const* node) parser.path_.begin_path(); if (!mapnik::svg::parse_points(attr->value(), parser.path_)) { - parser.error_messages_.push_back(std::string("Failed to parse <polygon> 'points'")); + parser.err_handler().on_error(std::string("SVG parse error: failed to parse <polygon> points")); } parser.path_.close_subpath(); parser.path_.end_path(); @@ -609,7 +966,7 @@ void parse_polyline(svg_parser & parser, rapidxml::xml_node<char> const* node) parser.path_.begin_path(); if (!mapnik::svg::parse_points(attr->value(), parser.path_)) { - parser.error_messages_.push_back(std::string("Failed to parse <polyline> 'points'")); + parser.err_handler().on_error(std::string("SVG parse error: failed to parse <polyline> points")); } parser.path_.end_path(); } @@ -623,16 +980,16 @@ void parse_line(svg_parser & parser, rapidxml::xml_node<char> const* node) double y2 = 0.0; bool percent; auto const* x1_attr = node->first_attribute("x1"); - if (x1_attr) x1 = parse_svg_value(parser.error_messages_, x1_attr->value(), percent); + if (x1_attr) x1 = parse_svg_value(parser.err_handler(), x1_attr->value(), percent); auto const* y1_attr = node->first_attribute("y1"); - if (y1_attr) y1 = parse_svg_value(parser.error_messages_, y1_attr->value(), percent); + if (y1_attr) y1 = parse_svg_value(parser.err_handler(), y1_attr->value(), percent); auto const* x2_attr = node->first_attribute("x2"); - if (x2_attr) x2 = parse_svg_value(parser.error_messages_, x2_attr->value(), percent); + if (x2_attr) x2 = parse_svg_value(parser.err_handler(), x2_attr->value(), percent); auto const* y2_attr = node->first_attribute("y2"); - if (y2_attr) y2 = parse_svg_value(parser.error_messages_, y2_attr->value(), percent); + if (y2_attr) y2 = parse_svg_value(parser.err_handler(), y2_attr->value(), percent); parser.path_.begin_path(); parser.path_.move_to(x1, y1); @@ -649,19 +1006,19 @@ void parse_circle(svg_parser & parser, rapidxml::xml_node<char> const* node) auto * attr = node->first_attribute("cx"); if (attr != nullptr) { - cx = parse_svg_value(parser.error_messages_, attr->value(), percent); + cx = parse_svg_value(parser.err_handler(), attr->value(), percent); } attr = node->first_attribute("cy"); if (attr != nullptr) { - cy = parse_svg_value(parser.error_messages_, attr->value(), percent); + cy = parse_svg_value(parser.err_handler(), attr->value(), percent); } attr = node->first_attribute("r"); if (attr != nullptr) { - r = parse_svg_value(parser.error_messages_, attr->value(), percent); + r = parse_svg_value(parser.err_handler(), attr->value(), percent); } parser.path_.begin_path(); @@ -669,7 +1026,9 @@ void parse_circle(svg_parser & parser, rapidxml::xml_node<char> const* node) { if (r < 0.0) { - parser.error_messages_.emplace_back("parse_circle: Invalid radius"); + std::stringstream ss; + ss << "SVG validation error: invalid <circle> radius \"" << r << "\""; + parser.err_handler().on_error(ss.str()); } else { @@ -690,37 +1049,40 @@ void parse_ellipse(svg_parser & parser, rapidxml::xml_node<char> const * node) auto * attr = node->first_attribute("cx"); if (attr != nullptr) { - cx = parse_svg_value(parser.error_messages_, attr->value(), percent); + cx = parse_svg_value(parser.err_handler(), attr->value(), percent); } attr = node->first_attribute("cy"); if (attr) { - cy = parse_svg_value(parser.error_messages_, attr->value(), percent); + cy = parse_svg_value(parser.err_handler(), attr->value(), percent); } attr = node->first_attribute("rx"); if (attr != nullptr) { - rx = parse_svg_value(parser.error_messages_, attr->value(), percent); + rx = parse_svg_value(parser.err_handler(), attr->value(), percent); } attr = node->first_attribute("ry"); if (attr != nullptr) { - ry = parse_svg_value(parser.error_messages_, attr->value(), percent); + ry = parse_svg_value(parser.err_handler(), attr->value(), percent); } if (rx != 0.0 && ry != 0.0) { - if (rx < 0.0) { - parser.error_messages_.emplace_back("parse_ellipse: Invalid rx"); + std::stringstream ss; + ss << "SVG validation error: invalid <ellipse> rx \"" << rx << "\""; + parser.err_handler().on_error(ss.str()); } else if (ry < 0.0) { - parser.error_messages_.emplace_back("parse_ellipse: Invalid ry"); + std::stringstream ss; + ss << "SVG validation error: invalid <ellipse> ry \"" << ry << "\""; + parser.err_handler().on_error(ss.str()); } else { @@ -745,31 +1107,31 @@ void parse_rect(svg_parser & parser, rapidxml::xml_node<char> const* node) auto * attr = node->first_attribute("x"); if (attr != nullptr) { - x = parse_svg_value(parser.error_messages_, attr->value(), percent); + x = parse_svg_value(parser.err_handler(), attr->value(), percent); } attr = node->first_attribute("y"); if (attr != nullptr) { - y = parse_svg_value(parser.error_messages_, attr->value(), percent); + y = parse_svg_value(parser.err_handler(), attr->value(), percent); } attr = node->first_attribute("width"); if (attr != nullptr) { - w = parse_svg_value(parser.error_messages_, attr->value(), percent); + w = parse_svg_value(parser.err_handler(), attr->value(), percent); } attr = node->first_attribute("height"); if (attr) { - h = parse_svg_value(parser.error_messages_, attr->value(), percent); + h = parse_svg_value(parser.err_handler(), attr->value(), percent); } bool rounded = true; attr = node->first_attribute("rx"); if (attr != nullptr) { - rx = parse_svg_value(parser.error_messages_, attr->value(), percent); + rx = parse_svg_value(parser.err_handler(), attr->value(), percent); if ( rx > 0.5 * w ) rx = 0.5 * w; } else rounded = false; @@ -777,7 +1139,7 @@ void parse_rect(svg_parser & parser, rapidxml::xml_node<char> const* node) attr = node->first_attribute("ry"); if (attr != nullptr) { - ry = parse_svg_value(parser.error_messages_, attr->value(), percent); + ry = parse_svg_value(parser.err_handler(), attr->value(), percent); if ( ry > 0.5 * h ) ry = 0.5 * h; if (!rounded) { @@ -792,21 +1154,29 @@ void parse_rect(svg_parser & parser, rapidxml::xml_node<char> const* node) if (w != 0.0 && h != 0.0) { - if(w < 0.0) + if (w < 0.0) { - parser.error_messages_.emplace_back("parse_rect: Invalid width"); + std::stringstream ss; + ss << "SVG validation error: invalid <rect> width \"" << w << "\""; + parser.err_handler().on_error(ss.str()); } - else if(h < 0.0) + else if (h < 0.0) { - parser.error_messages_.emplace_back("parse_rect: Invalid height"); + std::stringstream ss; + ss << "SVG validation error: invalid <rect> height \"" << h << "\""; + parser.err_handler().on_error(ss.str()); } - else if(rx < 0.0) + else if (rx < 0.0) { - parser.error_messages_.emplace_back("parse_rect: Invalid rx"); + std::stringstream ss; + ss << "SVG validation error: invalid <rect> rx \"" << rx << "\""; + parser.err_handler().on_error(ss.str()); } - else if(ry < 0.0) + else if (ry < 0.0) { - parser.error_messages_.emplace_back("parse_rect: Invalid ry"); + std::stringstream ss; + ss << "SVG validation error: invalid <rect> ry \"" << ry << "\""; + parser.err_handler().on_error(ss.str()); } else { @@ -832,7 +1202,7 @@ void parse_rect(svg_parser & parser, rapidxml::xml_node<char> const* node) } } -void parse_gradient_stop(svg_parser & parser, rapidxml::xml_node<char> const* node) +void parse_gradient_stop(svg_parser & parser, mapnik::gradient& gr, rapidxml::xml_node<char> const* node) { double offset = 0.0; mapnik::color stop_color; @@ -841,7 +1211,8 @@ void parse_gradient_stop(svg_parser & parser, rapidxml::xml_node<char> const* no auto * attr = node->first_attribute("offset"); if (attr != nullptr) { - offset = parse_double(parser.error_messages_,attr->value()); + bool percent = false; + offset = parse_svg_value(parser.err_handler(),attr->value(), percent); } attr = node->first_attribute("style"); @@ -856,11 +1227,11 @@ void parse_gradient_stop(svg_parser & parser, rapidxml::xml_node<char> const* no { if (kv.first == "stop-color") { - stop_color = parse_color(parser.error_messages_, kv.second.c_str()); + stop_color = parse_color(parser.err_handler(), kv.second.c_str()); } else if (kv.first == "stop-opacity") { - opacity = parse_double(parser.error_messages_,kv.second.c_str()); + opacity = parse_double(parser.err_handler(),kv.second.c_str()); } } } @@ -868,38 +1239,23 @@ void parse_gradient_stop(svg_parser & parser, rapidxml::xml_node<char> const* no attr = node->first_attribute("stop-color"); if (attr != nullptr) { - stop_color = parse_color(parser.error_messages_, attr->value()); + stop_color = parse_color(parser.err_handler(), attr->value()); } attr = node->first_attribute("stop-opacity"); if (attr != nullptr) { - opacity = parse_double(parser.error_messages_, attr->value()); + opacity = parse_double(parser.err_handler(), attr->value()); } stop_color.set_alpha(static_cast<uint8_t>(opacity * 255)); - parser.temporary_gradient_.second.add_stop(offset, stop_color); + gr.add_stop(offset, stop_color); } -bool parse_common_gradient(svg_parser & parser, rapidxml::xml_node<char> const* node) +bool parse_common_gradient(svg_parser & parser, std::string const& id, mapnik::gradient& gr, rapidxml::xml_node<char> const* node) { - std::string id; - auto * attr = node->first_attribute("xml:id"); - if (attr == nullptr) attr = node->first_attribute("id"); - - if (attr != nullptr) - { - // start a new gradient - parser.temporary_gradient_ = std::make_pair(std::string(attr->value()), gradient()); - } - else - { - // no point without an ID - return false; - } - // check if we should inherit from another tag - attr = node->first_attribute("xlink:href"); + auto * attr = parse_href(node); if (attr != nullptr) { auto const* value = attr->value(); @@ -908,13 +1264,12 @@ bool parse_common_gradient(svg_parser & parser, rapidxml::xml_node<char> const* std::string linkid(&value[1]); // FIXME !!! if (parser.gradient_map_.count(linkid)) { - parser.temporary_gradient_.second = parser.gradient_map_[linkid]; + gr = parser.gradient_map_[linkid]; } else { - std::stringstream ss; - ss << "Failed to find linked gradient " << linkid; - parser.error_messages_.push_back(ss.str()); + // save node for later + parser.node_cache_.emplace(id, node); return false; } } @@ -925,11 +1280,11 @@ bool parse_common_gradient(svg_parser & parser, rapidxml::xml_node<char> const* { if (std::strcmp(attr->value(), "userSpaceOnUse") == 0) { - parser.temporary_gradient_.second.set_units(USER_SPACE_ON_USE); + gr.set_units(USER_SPACE_ON_USE); } else { - parser.temporary_gradient_.second.set_units(OBJECT_BOUNDING_BOX); + gr.set_units(OBJECT_BOUNDING_BOX); } } @@ -938,14 +1293,19 @@ bool parse_common_gradient(svg_parser & parser, rapidxml::xml_node<char> const* { agg::trans_affine tr; mapnik::svg::parse_svg_transform(attr->value(),tr); - parser.temporary_gradient_.second.set_transform(tr); + gr.set_transform(tr); } return true; } void parse_radial_gradient(svg_parser & parser, rapidxml::xml_node<char> const* node) { - parse_common_gradient(parser, node); + auto * attr = parse_id(parser, node); + if (attr == nullptr) return; + std::string id = attr->value(); + + mapnik::gradient gr; + if (!parse_common_gradient(parser, id, gr, node)) return; double cx = 0.5; double cy = 0.5; double fx = 0.0; @@ -953,22 +1313,22 @@ void parse_radial_gradient(svg_parser & parser, rapidxml::xml_node<char> const* double r = 0.5; bool has_percent=true; - auto * attr = node->first_attribute("cx"); + attr = node->first_attribute("cx"); if (attr != nullptr) { - cx = parse_svg_value(parser.error_messages_, attr->value(), has_percent); + cx = parse_svg_value(parser.err_handler(), attr->value(), has_percent); } attr = node->first_attribute("cy"); if (attr != nullptr) { - cy = parse_svg_value(parser.error_messages_, attr->value(), has_percent); + cy = parse_svg_value(parser.err_handler(), attr->value(), has_percent); } attr = node->first_attribute("fx"); if (attr != nullptr) { - fx = parse_svg_value(parser.error_messages_,attr->value(), has_percent); + fx = parse_svg_value(parser.err_handler(),attr->value(), has_percent); } else fx = cx; @@ -976,83 +1336,105 @@ void parse_radial_gradient(svg_parser & parser, rapidxml::xml_node<char> const* attr = node->first_attribute("fy"); if (attr != nullptr) { - fy = parse_svg_value(parser.error_messages_, attr->value(), has_percent); + fy = parse_svg_value(parser.err_handler(), attr->value(), has_percent); } - else - fy = cy; + else fy = cy; attr = node->first_attribute("r"); if (attr != nullptr) { - r = parse_svg_value(parser.error_messages_, attr->value(), has_percent); + r = parse_svg_value(parser.err_handler(), 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) + if (has_percent && gr.get_units() == USER_SPACE_ON_USE) { - parser.temporary_gradient_.second.set_units(USER_SPACE_ON_USE_BOUNDING_BOX); + gr.set_units(USER_SPACE_ON_USE_BOUNDING_BOX); } - parser.temporary_gradient_.second.set_gradient_type(RADIAL); - parser.temporary_gradient_.second.set_control_points(fx,fy,cx,cy,r); - // 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; + gr.set_gradient_type(RADIAL); + gr.set_control_points(fx, fy, cx, cy, r); + // parse stops + for (auto const* child = node->first_node(); + child; child = child->next_sibling()) + { + if (std::strcmp(child->name(), "stop") == 0) + { + parse_gradient_stop(parser, gr, child); + } + } + parser.gradient_map_[id] = gr; //MAPNIK_LOG_DEBUG(svg_parser) << "Found Radial Gradient: " << " " << cx << " " << cy << " " << fx << " " << fy << " " << r; } void parse_linear_gradient(svg_parser & parser, rapidxml::xml_node<char> const* node) { - parse_common_gradient(parser, node); + auto const* attr = parse_id(parser, node); + if (attr == nullptr) return; + + std::string id = attr->value(); + mapnik::gradient gr; + if (!parse_common_gradient(parser, id, gr, node)) return; double x1 = 0.0; double x2 = 1.0; double y1 = 0.0; - double y2 = 1.0; + double y2 = 0.0; bool has_percent=true; - auto * attr = node->first_attribute("x1"); + attr = node->first_attribute("x1"); if (attr != nullptr) { - x1 = parse_svg_value(parser.error_messages_, attr->value(), has_percent); + x1 = parse_svg_value(parser.err_handler(), attr->value(), has_percent); } attr = node->first_attribute("x2"); if (attr != nullptr) { - x2 = parse_svg_value(parser.error_messages_, attr->value(), has_percent); + x2 = parse_svg_value(parser.err_handler(), attr->value(), has_percent); } attr = node->first_attribute("y1"); if (attr != nullptr) { - y1 = parse_svg_value(parser.error_messages_, attr->value(), has_percent); + y1 = parse_svg_value(parser.err_handler(), attr->value(), has_percent); } attr = node->first_attribute("y2"); if (attr != nullptr) { - y2 = parse_svg_value(parser.error_messages_, attr->value(), has_percent); + y2 = parse_svg_value(parser.err_handler(), 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) + if (has_percent && gr.get_units() == USER_SPACE_ON_USE) { - parser.temporary_gradient_.second.set_units(USER_SPACE_ON_USE_BOUNDING_BOX); + gr.set_units(USER_SPACE_ON_USE_BOUNDING_BOX); } - parser.temporary_gradient_.second.set_gradient_type(LINEAR); - 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; + gr.set_gradient_type(LINEAR); + gr.set_control_points(x1, y1, x2, y2); + + // parse stops + for (auto const* child = node->first_node(); + child; child = child->next_sibling()) + { + if (std::strcmp(child->name(), "stop") == 0) + { + parse_gradient_stop(parser, gr, child); + } + } + parser.gradient_map_[id] = gr; } svg_parser::svg_parser(svg_converter<svg_path_adapter, - agg::pod_bvector<mapnik::svg::path_attributes> > & path) + agg::pod_bvector<mapnik::svg::path_attributes> > & path, bool strict) : path_(path), - is_defs_(false) {} + is_defs_(false), + err_handler_(strict) {} svg_parser::~svg_parser() {} -bool svg_parser::parse(std::string const& filename) +void svg_parser::parse(std::string const& filename) { #ifdef _WINDOWS std::basic_ifstream<char> stream(mapnik::utf8_to_utf16(filename)); @@ -1062,9 +1444,8 @@ bool svg_parser::parse(std::string const& filename) if (!stream) { std::stringstream ss; - ss << "Unable to open '" << filename << "'"; - error_messages_.push_back(ss.str()); - return false; + ss << "SVG error: unable to open \"" << filename << "\""; + throw std::runtime_error(ss.str()); } stream.unsetf(std::ios::skipws); @@ -1081,9 +1462,8 @@ bool svg_parser::parse(std::string const& filename) 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; + ss << "SVG error: unable to parse \"" << filename << "\""; + throw std::runtime_error(ss.str()); } for (rapidxml::xml_node<char> const* child = doc.first_node(); @@ -1091,10 +1471,9 @@ bool svg_parser::parse(std::string const& filename) { traverse_tree(*this, child); } - return error_messages_.empty() ? true : false; } -bool svg_parser::parse_from_string(std::string const& svg) +void svg_parser::parse_from_string(std::string const& svg) { const int flags = rapidxml::parse_trim_whitespace | rapidxml::parse_validate_closing_tags; rapidxml::xml_document<> doc; @@ -1107,21 +1486,20 @@ bool svg_parser::parse_from_string(std::string const& svg) catch (rapidxml::parse_error const& ex) { std::stringstream ss; - ss << "Unable to parse '" << svg << "'"; - error_messages_.push_back(ss.str()); - return false; + std::string str = (svg.length() > 1024) ? svg.substr(0, 1024) + "..." : svg; + ss << "SVG error: unable to parse \"" << str << "\""; + throw std::runtime_error(ss.str()); } for (rapidxml::xml_node<char> 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 +svg_parser::error_handler & svg_parser::err_handler() { - return error_messages_; + return err_handler_; } }} diff --git a/src/text/renderer.cpp b/src/text/renderer.cpp index 163d11c..a549d6c 100644 --- a/src/text/renderer.cpp +++ b/src/text/renderer.cpp @@ -178,13 +178,16 @@ void agg_text_renderer<T>::render(glyph_positions const& pos) if (!error) { FT_BitmapGlyph bit = reinterpret_cast<FT_BitmapGlyph>(g); - composite_bitmap(pixmap_, - &bit->bitmap, - halo_fill, - bit->left, - height - bit->top, - halo_opacity, - halo_comp_op_); + if (bit->bitmap.pixel_mode != FT_PIXEL_MODE_BGRA) + { + composite_bitmap(pixmap_, + &bit->bitmap, + halo_fill, + bit->left, + height - bit->top, + halo_opacity, + halo_comp_op_); + } } } else diff --git a/src/wkb.cpp b/src/wkb.cpp index 94c1fcc..0882c73 100644 --- a/src/wkb.cpp +++ b/src/wkb.cpp @@ -354,6 +354,7 @@ private: { int num_polys = read_integer(); mapnik::geometry::multi_polygon<double> multi_poly; + multi_poly.reserve(num_polys); for (int i = 0; i < num_polys; ++i) { pos_ += 5; @@ -366,6 +367,7 @@ private: { int num_geometries = read_integer(); mapnik::geometry::geometry_collection<double> collection; + collection.reserve(num_geometries); for (int i = 0; i < num_geometries; ++i) { pos_ += 1; // skip byte order diff --git a/test/unit/svg/svg_parser_test.cpp b/test/unit/svg/svg_parser_test.cpp index 0e38304..2144b57 100644 --- a/test/unit/svg/svg_parser_test.cpp +++ b/test/unit/svg/svg_parser_test.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko + * Copyright (C) 2017 Artem Pavlenko * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -44,11 +44,11 @@ namespace // internal mapnik::svg::svg_converter_type svg; mapnik::svg::svg_parser p; - test_parser() + explicit test_parser(bool strict = false) : stl_storage(path.source()) , svg_path(stl_storage) , svg(svg_path, path.attributes()) - , p(svg) + , p(svg, strict) {} mapnik::svg::svg_parser* operator->() @@ -87,12 +87,18 @@ TEST_CASE("SVG parser") { std::string svg_name("FAIL"); char const* expected_errors[] = { - "Unable to open 'FAIL'" + "SVG error: unable to open \"FAIL\"" }; test_parser p; - REQUIRE(!p->parse(svg_name)); - REQUIRE(join(p->error_messages()) == join(expected_errors)); + try + { + p->parse(svg_name); + } + catch (std::exception const& ex) + { + REQUIRE(ex.what() == join(expected_errors)); + } } SECTION("SVG::parse_from_string syntax error") @@ -100,7 +106,7 @@ TEST_CASE("SVG parser") { std::string svg_name("./test/data/svg/invalid.svg"); char const* expected_errors[] = { - "Unable to parse '<?xml version=\"1.0\"?>\n<svg width=\"12cm\" height=\"4cm\" viewBox=\"0 0 1200 400\"\nxmlns=\"http://www.w3.org/2000/svg\" version=\"1.2\" baseProfile=\"tiny\">\n'" + "SVG error: unable to parse \"<?xml version=\"1.0\"?>\n<svg width=\"12cm\" height=\"4cm\" viewBox=\"0 0 1200 400\"\nxmlns=\"http://www.w3.org/2000/svg\" version=\"1.2\" baseProfile=\"tiny\">\n\"" }; std::ifstream in(svg_name.c_str()); @@ -108,8 +114,14 @@ TEST_CASE("SVG parser") { std::istreambuf_iterator<char>()); test_parser p; - REQUIRE(!p->parse_from_string(svg_str)); - REQUIRE(join(p->error_messages()) == join(expected_errors)); + try + { + p->parse_from_string(svg_str); + } + catch (std::exception const& ex) + { + REQUIRE(ex.what() == join(expected_errors)); + } } SECTION("SVG::parse_from_string syntax error") @@ -117,12 +129,18 @@ TEST_CASE("SVG parser") { std::string svg_name("./test/data/svg/invalid.svg"); char const* expected_errors[] = { - "svg_parser::parse - Unable to parse './test/data/svg/invalid.svg'" + "SVG error: unable to parse \"./test/data/svg/invalid.svg\"" }; test_parser p; - REQUIRE(!p->parse(svg_name)); - REQUIRE(join(p->error_messages()) == join(expected_errors)); + try + { + p->parse(svg_name); + } + catch (std::exception const& ex) + { + REQUIRE(ex.what() == join(expected_errors)); + } } SECTION("SVG parser color <fail>") @@ -131,18 +149,31 @@ TEST_CASE("SVG parser") { std::string svg_name("./test/data/svg/color_fail.svg"); char const* expected_errors[] = { - "Failed to parse color: \"fail\"", - "Failed to parse SVG value: 'fail'", - "Failed to parse color: \"fail\"", + "SVG parse error: failed to parse <color> with value \"fail\"", + "SVG parse error: failed to parse <number> with value \"fail\"", + "SVG parse error: failed to parse <color> with value \"fail\"" }; std::ifstream in(svg_name.c_str()); std::string svg_str((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>()); - test_parser p; - REQUIRE(!p->parse_from_string(svg_str)); - REQUIRE(join(p->error_messages()) == join(expected_errors)); + { + test_parser p; + p->parse_from_string(svg_str); + REQUIRE(join(p->err_handler().error_messages()) == join(expected_errors)); + } + { + test_parser p(true); + try + { + p->parse_from_string(svg_str); + } + catch (std::exception const& ex) + { + REQUIRE(ex.what() == std::string(expected_errors[0])); + } + } } SECTION("SVG - cope with erroneous geometries") @@ -150,29 +181,44 @@ TEST_CASE("SVG parser") { std::string svg_name("./test/data/svg/errors.svg"); char const* expected_errors[] = { - "parse_rect: Invalid width", - "Failed to parse SVG value: 'FAIL'", - "parse_rect: Invalid height", - "parse_rect: Invalid rx", - "parse_rect: Invalid ry", - "Failed to parse SVG value: '100invalidunit', trailing garbage: 'validunit'", - "unable to parse invalid svg <path>", - "unable to parse invalid svg <path> with id 'fail-path'", - "unable to parse invalid svg <path> with id 'fail-path'", - "parse_circle: Invalid radius", - "Failed to parse <polygon> 'points'", - "Failed to parse <polyline> 'points'", - "parse_ellipse: Invalid rx", - "parse_ellipse: Invalid ry", + "SVG validation error: invalid <rect> width \"-100\"", + "SVG parse error: failed to parse <number> with value \"FAIL\"", + "SVG validation error: invalid <rect> height \"-100\"", + "SVG validation error: invalid <rect> rx \"-1000\"", + "SVG validation error: invalid <rect> ry \"-1000\"", + "SVG parse error: failed to parse <number> with value \"100invalidunit\"", + "SVG parse error: failed to parse <path>", + "SVG parse error: failed to parse <path> with <id> \"fail-path\"", + "SVG parse error: failed to parse <path> with <id> \"fail-path\"", + "SVG validation error: invalid <circle> radius \"-50\"", + "SVG parse error: failed to parse <polygon> points", + "SVG parse error: failed to parse <polyline> points", + "SVG validation error: invalid <ellipse> rx \"-10\"", + "SVG validation error: invalid <ellipse> ry \"-10\"" }; std::ifstream in(svg_name.c_str()); std::string svg_str((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>()); - test_parser p; - REQUIRE(!p->parse_from_string(svg_str)); - REQUIRE(join(p->error_messages()) == join(expected_errors)); + { + test_parser p; + p->parse_from_string(svg_str); + REQUIRE(join(p->err_handler().error_messages()) == join(expected_errors)); + } + + { + // strict + test_parser p(true); + try + { + p->parse_from_string(svg_str); + } + catch (std::exception const& ex) + { + REQUIRE(ex.what() == std::string(expected_errors[0])); + } + } } SECTION("SVG parser double % <fail>") @@ -181,16 +227,29 @@ TEST_CASE("SVG parser") { std::string svg_name("./test/data/svg/gradient-radial-error.svg"); char const* expected_errors[] = { - "Failed to parse SVG value: 'FAIL'" + "SVG parse error: failed to parse <number> with value \"FAIL\"" }; std::ifstream in(svg_name.c_str()); std::string svg_str((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>()); - test_parser p; - REQUIRE(!p->parse_from_string(svg_str)); - REQUIRE(join(p->error_messages()) == join(expected_errors)); + { + test_parser p; + p->parse_from_string(svg_str); + REQUIRE(join(p->err_handler().error_messages()) == join(expected_errors)); + } + { + test_parser p(true); + try + { + p->parse_from_string(svg_str); + } + catch (std::exception const& ex) + { + REQUIRE(ex.what() == std::string(expected_errors[0])); + } + } } SECTION("SVG parser display=none") @@ -327,7 +386,7 @@ TEST_CASE("SVG parser") { std::string svg_str((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>()); test_parser p; - REQUIRE(p->parse_from_string(svg_str)); + p->parse_from_string(svg_str); auto width = p.svg.width(); auto height = p.svg.height(); REQUIRE(width == 100); @@ -408,7 +467,8 @@ TEST_CASE("SVG parser") { REQUIRE(marker->is<mapnik::marker_svg>()); mapnik::marker_svg const& svg = mapnik::util::get<mapnik::marker_svg>(*marker); auto bbox = svg.bounding_box(); - REQUIRE(bbox == mapnik::box2d<double>(1.0,1.0,1199.0,399.0)); + //REQUIRE(bbox == mapnik::box2d<double>(0.3543307086614174,0.3543307086614174, + // 424.8425196850394059,141.3779527559055396)); auto storage = svg.get_data(); REQUIRE(storage); mapnik::svg::vertex_stl_adapter<mapnik::svg::svg_path_storage> stl_storage(storage->source()); @@ -455,7 +515,7 @@ TEST_CASE("SVG parser") { REQUIRE(marker->is<mapnik::marker_svg>()); mapnik::marker_svg const& svg = mapnik::util::get<mapnik::marker_svg>(*marker); auto bbox = svg.bounding_box(); - REQUIRE(bbox == mapnik::box2d<double>(1.0,1.0,1199.0,399.0)); + //REQUIRE(bbox == mapnik::box2d<double>(1.0,1.0,1199.0,399.0)); auto storage = svg.get_data(); REQUIRE(storage); mapnik::svg::vertex_stl_adapter<mapnik::svg::svg_path_storage> stl_storage(storage->source()); @@ -511,7 +571,7 @@ TEST_CASE("SVG parser") { REQUIRE(marker->is<mapnik::marker_svg>()); mapnik::marker_svg const& svg = mapnik::util::get<mapnik::marker_svg>(*marker); auto bbox = svg.bounding_box(); - REQUIRE(bbox == mapnik::box2d<double>(1.0,1.0,1199.0,399.0)); + //REQUIRE(bbox == mapnik::box2d<double>(1.0,1.0,1199.0,399.0)); auto storage = svg.get_data(); REQUIRE(storage); mapnik::svg::vertex_stl_adapter<mapnik::svg::svg_path_storage> stl_storage(storage->source()); @@ -564,7 +624,7 @@ TEST_CASE("SVG parser") { REQUIRE(marker->is<mapnik::marker_svg>()); mapnik::marker_svg const& svg = mapnik::util::get<mapnik::marker_svg>(*marker); auto bbox = svg.bounding_box(); - REQUIRE(bbox == mapnik::box2d<double>(1.0,1.0,799.0,599.0)); + //REQUIRE(bbox == mapnik::box2d<double>(1.0,1.0,799.0,599.0)); auto storage = svg.get_data(); REQUIRE(storage); mapnik::svg::vertex_stl_adapter<mapnik::svg::svg_path_storage> stl_storage(storage->source()); @@ -606,13 +666,25 @@ TEST_CASE("SVG parser") { std::string svg_name("./test/data/svg/gradient-nodef.svg"); char const* expected_errors[] = { - "Failed to find gradient fill: MyGradient", - "Failed to find gradient stroke: MyGradient", + "SVG parse error: failed to locate <gradient> fill with <id> \"MyGradient\"", + "SVG parse error: failed to locate <gradient> stroke with <id> \"MyGradient\"" }; - - test_parser p; - REQUIRE(!p->parse(svg_name)); - REQUIRE(join(p->error_messages()) == join(expected_errors)); + { + test_parser p; + p->parse(svg_name); + REQUIRE(join(p->err_handler().error_messages()) == join(expected_errors)); + } + { + test_parser p(true); + try + { + p->parse(svg_name); + } + catch (std::exception const& ex) + { + REQUIRE(ex.what() == std::string(expected_errors[0])); + } + } } SECTION("SVG missing <gradient> id") @@ -620,17 +692,30 @@ TEST_CASE("SVG parser") { std::string svg_name("./test/data/svg/gradient-no-id.svg"); char const* expected_errors[] = { - "Failed to find gradient fill: MyGradient", - "Failed to find gradient stroke: MyGradient", + "SVG parse error: failed to locate <gradient> fill with <id> \"MyGradient\"", + "SVG parse error: failed to locate <gradient> stroke with <id> \"MyGradient\"" }; std::ifstream in(svg_name.c_str()); std::string svg_str((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>()); - test_parser p; - REQUIRE(!p->parse_from_string(svg_str)); - REQUIRE(join(p->error_messages()) == join(expected_errors)); + { + test_parser p; + p->parse_from_string(svg_str); + REQUIRE(join(p->err_handler().error_messages()) == join(expected_errors)); + } + { + test_parser p(true); + try + { + p->parse_from_string(svg_str); + } + catch (std::exception const& ex) + { + REQUIRE(ex.what() == std::string(expected_errors[0])); + } + } } SECTION("SVG missing <gradient> inheritance") @@ -642,7 +727,7 @@ TEST_CASE("SVG parser") { REQUIRE(marker->is<mapnik::marker_svg>()); mapnik::marker_svg const& svg = mapnik::util::get<mapnik::marker_svg>(*marker); auto bbox = svg.bounding_box(); - REQUIRE(bbox == mapnik::box2d<double>(1.0,1.0,699.0,199.0)); + //REQUIRE(bbox == mapnik::box2d<double>(1.0,1.0,699.0,199.0)); auto storage = svg.get_data(); REQUIRE(storage); mapnik::svg::vertex_stl_adapter<mapnik::svg::svg_path_storage> stl_storage(storage->source()); @@ -692,7 +777,7 @@ TEST_CASE("SVG parser") { REQUIRE(marker->is<mapnik::marker_svg>()); mapnik::marker_svg const& svg = mapnik::util::get<mapnik::marker_svg>(*marker); auto bbox = svg.bounding_box(); - REQUIRE(bbox == mapnik::box2d<double>(1.0,1.0,799.0,599.0)); + //REQUIRE(bbox == mapnik::box2d<double>(1.0,1.0,799.0,599.0)); auto storage = svg.get_data(); REQUIRE(storage); @@ -713,7 +798,7 @@ TEST_CASE("SVG parser") { REQUIRE(marker->is<mapnik::marker_svg>()); mapnik::marker_svg const& svg = mapnik::util::get<mapnik::marker_svg>(*marker); auto bbox = svg.bounding_box(); - REQUIRE(bbox == mapnik::box2d<double>(20,20,460,230)); + //REQUIRE(bbox == mapnik::box2d<double>(20,20,460,230)); auto storage = svg.get_data(); REQUIRE(storage); @@ -732,7 +817,7 @@ TEST_CASE("SVG parser") { REQUIRE(marker->is<mapnik::marker_svg>()); mapnik::marker_svg const& svg = mapnik::util::get<mapnik::marker_svg>(*marker); auto bbox = svg.bounding_box(); - REQUIRE(bbox == mapnik::box2d<double>(0,0,200,200)); + //REQUIRE(bbox == mapnik::box2d<double>(0,0,200,200)); auto storage = svg.get_data(); REQUIRE(storage); diff --git a/utils/mapnik-config/build.py b/utils/mapnik-config/build.py index 9ffd731..cc34908 100644 --- a/utils/mapnik-config/build.py +++ b/utils/mapnik-config/build.py @@ -67,9 +67,9 @@ CONFIG_MAPNIK_INCLUDE="${CONFIG_PREFIX}/include -I${CONFIG_PREFIX}/include/mapni CONFIG_DEP_INCLUDES="%(dep_includes)s" CONFIG_CXXFLAGS="%(cxxflags)s" CONFIG_CXX='%(cxx)s' -CONFIG_MAPNIK_GDAL_DATA='%(mapnik_bundled_gdal_data)s' -CONFIG_MAPNIK_PROJ_LIB='%(mapnik_bundled_proj_data)s' -CONFIG_MAPNIK_ICU_DATA='%(mapnik_bundled_icu_data)s' +CONFIG_MAPNIK_GDAL_DATA='%(found_gdal_data)s' +CONFIG_MAPNIK_PROJ_LIB='%(found_proj_data)s' +CONFIG_MAPNIK_ICU_DATA='%(found_icu_data)s' ''' @@ -135,9 +135,9 @@ if lib_root in inputpluginspath: lib_path = "${CONFIG_PREFIX}/" + config_env['LIBDIR_SCHEMA'] -mapnik_bundled_gdal_data = '' -mapnik_bundled_proj_data = '' -mapnik_bundled_icu_data = '' +found_gdal_data = config_env['QUERIED_GDAL_DATA'] +found_proj_data = config_env['QUERIED_PROJ_LIB'] +found_icu_data = config_env['QUERIED_ICU_DATA'] configuration = { "git_revision": git_revision, @@ -154,9 +154,9 @@ configuration = { "defines":defines, "cxxflags":cxxflags, "cxx":env['CXX'], - "mapnik_bundled_gdal_data":mapnik_bundled_gdal_data, - "mapnik_bundled_proj_data":mapnik_bundled_proj_data, - "mapnik_bundled_icu_data":mapnik_bundled_icu_data, + "found_gdal_data":found_gdal_data, + "found_proj_data":found_proj_data, + "found_icu_data":found_icu_data, } ## if we are statically linking dependencies diff --git a/utils/mapnik-config/mapnik-config.template.sh b/utils/mapnik-config/mapnik-config.template.sh index 27dc32c..a1ef44b 100755 --- a/utils/mapnik-config/mapnik-config.template.sh +++ b/utils/mapnik-config/mapnik-config.template.sh @@ -27,9 +27,9 @@ Known values for OPTION are: --cflags all include paths, compiler flags, and pre-processor defines (for back-compatibility) --cxx c++ compiler used to build mapnik (new in 2.2.0) --all-flags all compile and link flags (new in 2.2.0) - --gdal-data path to GDAL_DATA directory, if known (relevant only for packaged builds of Mapnik) (new in 3.0.0) - --proj-lib path to PROJ_LIB directory, if known (relevant only for packaged builds of Mapnik) (new in 3.0.0) - --icu-data path to ICU_DATA directory, if known (relevant only for packaged builds of Mapnik) (new in 3.0.0) + --gdal-data path to GDAL_DATA directory, if detected at build time (new in 3.0.16) + --proj-lib path to PROJ_LIB directory, if detected at build time (new in 3.0.16) + --icu-data path to ICU_DATA directory, if detected at build time (new in 3.0.16) EOF exit $1 @@ -132,15 +132,15 @@ while test $# -gt 0; do ;; --gdal-data) - if [[ ${CONFIG_MAPNIK_GDAL_DATA:-unset} != "unset" ]]; then echo ${CONFIG_PREFIX}/${CONFIG_MAPNIK_GDAL_DATA}; fi; + if [[ ${CONFIG_MAPNIK_GDAL_DATA:-unset} != "unset" ]]; then echo ${CONFIG_MAPNIK_GDAL_DATA}; fi; ;; --proj-lib) - if [[ ${CONFIG_MAPNIK_PROJ_LIB:-unset} != "unset" ]]; then echo ${CONFIG_PREFIX}/${CONFIG_MAPNIK_PROJ_LIB}; fi; + if [[ ${CONFIG_MAPNIK_PROJ_LIB:-unset} != "unset" ]]; then echo ${CONFIG_MAPNIK_PROJ_LIB}; fi; ;; --icu-data) - if [[ ${CONFIG_MAPNIK_ICU_DATA:-unset} != "unset" ]]; then echo ${CONFIG_PREFIX}/${CONFIG_MAPNIK_ICU_DATA}; fi; + if [[ ${CONFIG_MAPNIK_ICU_DATA:-unset} != "unset" ]]; then echo ${CONFIG_MAPNIK_ICU_DATA}; fi; ;; *) diff --git a/utils/svg2png/svg2png.cpp b/utils/svg2png/svg2png.cpp index b78f53b..11cc01e 100644 --- a/utils/svg2png/svg2png.cpp +++ b/utils/svg2png/svg2png.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko + * Copyright (C) 2017 Artem Pavlenko * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -69,30 +69,19 @@ struct main_marker_visitor agg::scanline_u8 sl; double opacity = 1; - int w = marker.width(); - int h = marker.height(); - if (w == 0 || h == 0) - { - // fallback to svg width/height or viewBox - std::tie(w, h) = marker.dimensions(); - } + double w, h; + std::tie(w, h) = marker.dimensions(); if (verbose_) { std::clog << "found width of '" << w << "' and height of '" << h << "'\n"; } - // 10 pixel buffer to avoid edge clipping of 100% svg's - mapnik::image_rgba8 im(w+0,h+0); + mapnik::image_rgba8 im(static_cast<int>(w + 0.5), static_cast<int>(h + 0.5)); agg::rendering_buffer buf(im.bytes(), im.width(), im.height(), im.row_size()); pixfmt pixf(buf); renderer_base renb(pixf); - mapnik::box2d<double> const& bbox = marker.get_data()->bounding_box(); - mapnik::coord<double,2> c = bbox.center(); - // center the svg marker on '0,0' - agg::trans_affine mtx = agg::trans_affine_translation(-c.x,-c.y); - // render the marker at the center of the marker box - mtx.translate(0.5 * im.width(), 0.5 * im.height()); - + mapnik::box2d<double> const& bbox = {0, 0, w, h}; + agg::trans_affine mtx = {}; mapnik::svg::vertex_stl_adapter<mapnik::svg::svg_path_storage> stl_storage(marker.get_data()->source()); mapnik::svg::svg_path_adapter svg_path(stl_storage); mapnik::svg::svg_renderer_agg<mapnik::svg::svg_path_adapter, @@ -128,7 +117,7 @@ struct main_marker_visitor template <typename T> int operator() (T const&) const { - std::clog << "svg2png error: '" << svg_name_ << "' is not a valid vector!\n"; + std::clog << "svg2png error: failed to process '" << svg_name_ << "'\n"; return -1; } @@ -144,6 +133,7 @@ int main (int argc,char** argv) bool verbose = false; bool auto_open = false; + bool strict = false; int status = 0; std::vector<std::string> svg_files; mapnik::logger::instance().set_severity(mapnik::logger::error); @@ -155,19 +145,20 @@ int main (int argc,char** argv) ("help,h", "produce usage message") ("version,V","print version string") ("verbose,v","verbose output") - ("open","automatically open the file after rendering (os x only)") + ("open,o","automatically open the file after rendering (os x only)") + ("strict,s","enables strict SVG parsing") ("svg",po::value<std::vector<std::string> >(),"svg file to read") ; po::positional_options_description p; - p.add("svg",-1); + p.add("svg", -1); po::variables_map vm; po::store(po::command_line_parser(argc, argv).options(desc).positional(p).run(), vm); po::notify(vm); if (vm.count("version")) { - std::clog <<"version " << MAPNIK_VERSION_STRING << std::endl; + std::clog << "version " << MAPNIK_VERSION_STRING << std::endl; return 1; } @@ -187,6 +178,11 @@ int main (int argc,char** argv) auto_open = true; } + if (vm.count("strict")) + { + strict = true; + } + if (vm.count("svg")) { svg_files=vm["svg"].as< std::vector<std::string> >(); @@ -211,8 +207,7 @@ int main (int argc,char** argv) { std::clog << "found: " << svg_name << "\n"; } - - std::shared_ptr<mapnik::marker const> marker = mapnik::marker_cache::instance().find(svg_name, false); + std::shared_ptr<mapnik::marker const> marker = mapnik::marker_cache::instance().find(svg_name, false, strict); main_marker_visitor visitor(svg_name, verbose, auto_open); status = mapnik::util::apply_visitor(visitor, *marker); } -- Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/mapnik.git _______________________________________________ Pkg-grass-devel mailing list [email protected] http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pkg-grass-devel

