[mapnik] 01/06: New upstream version 3.0.16~rc1+ds

Bas Couwenberg sebastic at debian.org
Wed Nov 15 21:39:00 UTC 2017


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 <sebastic at xs4all.nl>
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 git at github.com: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



More information about the Pkg-grass-devel mailing list