Merge pull request #4123 from mapnik/svg-css

SVG: basic CSS support
This commit is contained in:
Artem Pavlenko 2020-02-07 14:26:10 +00:00 committed by GitHub
commit 1784c4f03e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 690 additions and 48 deletions

View file

@ -28,7 +28,8 @@ src/json/libmapnik-json.a:
src/renderer_common/render_thunk_extractor.os \
src/json/libmapnik-json.a \
src/wkt/libmapnik-wkt.a \
src/css_color_grammar_x3.os \
src/css/css_grammar_x3.os \
src/css/css_color_grammar_x3.os \
src/expression_grammar_x3.os \
src/transform_expression_grammar_x3.os \
src/image_filter_grammar_x3.os \

View file

@ -28,6 +28,7 @@ subdirglobs = [
('.', 'mapnik/*.hpp'),
('agg', '../deps/agg/include/agg*'),
('cairo', 'mapnik/cairo/*.hpp'),
('css', 'mapnik/css/*.hpp'),
('csv', 'mapnik/csv/*.hpp'),
('deps/mapbox', '../deps/mapbox/variant/include/mapbox/*.hpp'),
('deps/mapbox', '../deps/mapbox/geometry/include/mapbox/*.hpp'),

View file

@ -25,7 +25,7 @@
#ifndef MAPNIK_CSS_COLOR_GRAMMAR_X3_DEF_HPP
#define MAPNIK_CSS_COLOR_GRAMMAR_X3_DEF_HPP
#include <mapnik/css_color_grammar_x3.hpp>
#include <mapnik/css/css_color_grammar_x3.hpp>
#include <mapnik/util/hsl.hpp>
#include <mapnik/safe_cast.hpp>

View file

@ -0,0 +1,73 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2020 Artem Pavlenko
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*****************************************************************************/
#ifndef MAPNIK_CSS_GRAMMAR_X3_HPP
#define MAPNIK_CSS_GRAMMAR_X3_HPP
#include <vector>
#include <unordered_map>
#pragma GCC diagnostic push
#include <mapnik/warning_ignore.hpp>
#include <boost/spirit/home/x3.hpp>
#pragma GCC diagnostic pop
namespace mapnik
{
using property_value_type = boost::iterator_range<char const*>;
using css_key_value = std::pair<std::string, property_value_type>;
using definition_type = std::vector<css_key_value>;
using css_data = std::unordered_multimap<std::string, definition_type>;
namespace x3 = boost::spirit::x3;
namespace css_grammar
{
class css_tag;
class ident_tag;
class skipper_tag;
class classes_tag;
//
using ident_grammar_type = x3::rule<ident_tag, std::string>;
using css_grammar_type = x3::rule<css_tag, css_data>;
using css_skipper_type = x3::rule<skipper_tag, x3::unused_type const>;
using css_classes_type = x3::rule<classes_tag, std::vector<std::string>>;
ident_grammar_type const ident{"IDENT"};
css_grammar_type const css_grammar{"css"};
css_skipper_type const css_skipper{"css skipper"};
css_classes_type const css_classes{"css classes"};
BOOST_SPIRIT_DECLARE(ident_grammar_type,
css_classes_type,
css_grammar_type,
css_skipper_type);
}
css_grammar::ident_grammar_type const ident_grammar();
css_grammar::css_classes_type const classes();
css_grammar::css_grammar_type const grammar();
css_grammar::css_skipper_type const skipper();
}
#endif // MAPNIK_CSS_GRAMMAR_X3_HPP

View file

@ -0,0 +1,220 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2020 Artem Pavlenko
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*****************************************************************************/
#ifndef MAPNIK_CSS_GRAMMAR_X3_DEF_HPP
#define MAPNIK_CSS_GRAMMAR_X3_DEF_HPP
#include <mapnik/css/css_grammar_x3.hpp>
#include <mapnik/json/unicode_string_grammar_x3.hpp>
#pragma GCC diagnostic push
#include <mapnik/warning_ignore.hpp>
#include <boost/fusion/adapted/std_pair.hpp>
#pragma GCC diagnostic pop
/*
The grammar below is LL(1) (but note that most UA's should not use it directly, since it doesn't express the parsing conventions, only the CSS1 syntax). The format of the productions is optimized for human consumption and some shorthand notation beyond yacc [15] is used:
* : 0 or more
+ : 1 or more
? : 0 or 1
| : separates alternatives
[] : grouping
The productions are:
stylesheet
: [CDO|CDC]* [ import [CDO|CDC]* ]* [ ruleset [CDO|CDC]* ]*
;
import
: IMPORT_SYM [STRING|URL] ';' // E.g., @import url(fun.css);
;
unary_operator
: '-' | '+'
;
operator
: '/' | ',' | // empty
;
property
: IDENT
;
ruleset
: selector [ ',' selector ]*
'{' declaration [ ';' declaration ]* '}'
;
selector
: simple_selector+ [ pseudo_element | solitary_pseudo_element ]?
| solitary_pseudo_element
;
// An "id" is an ID that is attached to an element type
// on its left, as in: P#p007
// A "solitary_id" is an ID that is not so attached,
/ as in: #p007
// Analogously for classes and pseudo-classes.
simple_selector
: element_name id? class? pseudo_class? // eg: H1.subject
| solitary_id class? pseudo_class? // eg: #xyz33
| solitary_class pseudo_class? // eg: .author
| solitary_pseudo_class // eg: :link
;
element_name
: IDENT
;
pseudo_class // as in: A:link
: LINK_PSCLASS_AFTER_IDENT
| VISITED_PSCLASS_AFTER_IDENT
| ACTIVE_PSCLASS_AFTER_IDENT
;
solitary_pseudo_class // as in: :link
: LINK_PSCLASS
| VISITED_PSCLASS
| ACTIVE_PSCLASS
;
class // as in: P.note
: CLASS_AFTER_IDENT
;
solitary_class // as in: .note
: CLASS
;
pseudo_element // as in: P:first-line
: FIRST_LETTER_AFTER_IDENT
| FIRST_LINE_AFTER_IDENT
;
solitary_pseudo_element // as in: :first-line
: FIRST_LETTER
| FIRST_LINE
;
// There is a constraint on the id and solitary_id that the
// part after the "#" must be a valid HTML ID value;
// e.g., "#x77" is OK, but "#77" is not.
id
: HASH_AFTER_IDENT
;
solitary_id
: HASH
;
declaration
: property ':' expr prio?
| // empty // Prevents syntax errors...
;
prio
: IMPORTANT_SYM // !important
;
expr
: term [ operator term ]*
;
term
: unary_operator?
[ NUMBER | STRING | PERCENTAGE | LENGTH | EMS | EXS
| IDENT | hexcolor | URL | RGB ]
;
// There is a constraint on the color that it must
// have either 3 or 6 hex-digits (i.e., [0-9a-fA-F])
// after the "#"; e.g., "#000" is OK, but "#abcd" is not.
hexcolor
: HASH | HASH_AFTER_IDENT
;
*/
namespace mapnik {
namespace x3 = boost::spirit::x3;
namespace css_grammar {
using x3::lit;
using x3::attr;
using x3::no_case;
using x3::no_skip;
using x3::lexeme;
using x3::alpha;
using x3::alnum;
using x3::char_;
using x3::raw;
using x3::standard::space;
// import unicode string rule
namespace { auto const& css_string = mapnik::json::grammar::unicode_string; }
auto assign_def = [] (auto const& ctx)
{
for (auto const& k : std::get<0>(_attr(ctx)))
{
_val(ctx).emplace(k, std::get<1>(_attr(ctx)));
}
};
auto assign_key = [] (auto const& ctx)
{
_val(ctx).first = std::move(_attr(ctx));
};
auto assign_value = [] (auto const& ctx) {_val(ctx).second = std::move(_attr(ctx));};
// rules
x3::rule<class simple_selector_tag, std::string> const simple_selector {"Simple selector"};
x3::rule<class selector_tag, std::vector<std::string>> const selector {"Selector"};
x3::rule<class value_tag, property_value_type> const value {"Value"};
x3::rule<class key_value_tag, css_key_value> const key_value {"CSS Key/Value"};
x3::rule<class definition_tag, std::pair<std::vector<std::string>, definition_type>> const definition {"CSS Definition"};
auto const ident_def = alpha >> *(alnum | char_('-'));
auto const simple_selector_def = lexeme[ident >> -((char_('.') | char_('#') | char_(':')) >> ident)]
| lexeme[char_('#') >> ident >> -((char_('.') | char_(':')) >> ident)]
| lexeme[char_('.') >> ident >> -(char_(':') >> ident)];
auto const selector_def = simple_selector % lit(',');
auto const value_def = raw[lexeme[+~char_(";}")]];
auto const key_value_def = lexeme[ident][assign_key] >> lit(':') >> value[assign_value] >> -lit(';');
auto const definition_def = selector >> lit('{') >> *key_value >> lit('}');
auto const css_grammar_def = *definition[assign_def];
auto const css_skipper_def = space | "/*" >> *(char_ - "*/") >> "*/";
auto const css_classes_def = +lexeme[ident];
#pragma GCC diagnostic push
#include <mapnik/warning_ignore.hpp>
BOOST_SPIRIT_DEFINE(
ident,
css_classes,
simple_selector,
selector,
value,
key_value,
definition,
css_grammar,
css_skipper
);
#pragma GCC diagnostic pop
} //css_grammar
} //mapnik
#endif //MAPNIK_CSS_GRAMMAR_X3_DEF_HPP

View file

@ -0,0 +1,54 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2020 Artem Pavlenko
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*****************************************************************************/
#ifndef MAPNIK_CSS_UNIT_VALUE_HPP
#define MAPNIK_CSS_UNIT_VALUE_HPP
#include <boost/spirit/home/x3.hpp>
namespace mapnik {
namespace x3 = boost::spirit::x3;
// units
struct css_unit_value : x3::symbols<double>
{
const double DPI = 90;
css_unit_value()
{
add
("px", 1.0)
("pt", DPI/72.0)
("pc", DPI/6.0)
("mm", DPI/25.4)
("cm", DPI/2.54)
("in", static_cast<double>(DPI))
//("em", 1.0/16.0) // default pixel size for body (usually 16px)
// ^^ this doesn't work currently as 'e' in 'em' interpreted as part of scientific notation.
;
}
};
} //mapnik
#endif //MAPNIK_CSS_GRAMMAR_X3_DEF_HPP

View file

@ -26,7 +26,7 @@
#include <mapnik/image_filter_grammar_x3.hpp>
#include <mapnik/image_filter_types.hpp>
#include <mapnik/css_color_grammar_x3.hpp>
#include <mapnik/css/css_color_grammar_x3.hpp>
#pragma GCC diagnostic push
#include <mapnik/warning_ignore.hpp>

View file

@ -25,6 +25,7 @@
// mapnik
#include <mapnik/config.hpp>
#include <mapnik/css/css_grammar_x3.hpp>
#include <mapnik/svg/svg_path_attributes.hpp>
#include <mapnik/svg/svg_converter.hpp>
#include <mapnik/svg/svg_path_adapter.hpp>
@ -83,8 +84,10 @@ public:
bool is_defs_;
bool strict_;
bool ignore_;
bool css_style_;
std::map<std::string, gradient> gradient_map_;
std::map<std::string, boost::property_tree::detail::rapidxml::xml_node<char> const*> node_cache_;
mapnik::css_data css_data_;
agg::trans_affine viewbox_tr_{};
error_handler err_handler_;
};

View file

@ -152,7 +152,6 @@ else: # unix, non-macos
source = Split(
"""
expression_grammar_x3.cpp
css_color_grammar_x3.cpp
fs.cpp
request.cpp
well_known_srs.cpp
@ -225,6 +224,8 @@ source = Split(
raster_colorizer.cpp
mapped_memory_cache.cpp
marker_cache.cpp
css/css_color_grammar_x3.cpp
css/css_grammar_x3.cpp
svg/svg_parser.cpp
svg/svg_path_parser.cpp
svg/svg_points_parser.cpp

View file

@ -24,7 +24,7 @@
#include <mapnik/color.hpp>
#include <mapnik/color_factory.hpp>
#include <mapnik/config_error.hpp>
#include <mapnik/css_color_grammar_x3.hpp>
#include <mapnik/css/css_color_grammar_x3.hpp>
namespace mapnik {

View file

@ -20,7 +20,7 @@
*
*****************************************************************************/
#include <mapnik/css_color_grammar_x3_def.hpp>
#include <mapnik/css/css_color_grammar_x3_def.hpp>
#if BOOST_VERSION < 107000
#include <mapnik/image_filter_types.hpp>
#endif

View file

@ -0,0 +1,56 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2020 Artem Pavlenko
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*****************************************************************************/
#include <mapnik/css/css_grammar_x3_def.hpp>
namespace mapnik { namespace css_grammar {
namespace x3 = boost::spirit::x3;
using iterator_type = char const*;
using context_type = x3::phrase_parse_context<css_skipper_type>::type;
BOOST_SPIRIT_INSTANTIATE(ident_grammar_type, iterator_type, context_type);
BOOST_SPIRIT_INSTANTIATE(css_classes_type, iterator_type, context_type);
BOOST_SPIRIT_INSTANTIATE(css_grammar_type, iterator_type, context_type);
BOOST_SPIRIT_INSTANTIATE(css_skipper_type, iterator_type, x3::unused_type);
}
css_grammar::ident_grammar_type const ident_grammar()
{
return css_grammar::ident;
}
css_grammar::css_classes_type const classes()
{
return css_grammar::css_classes;
}
css_grammar::css_grammar_type const grammar()
{
return css_grammar::css_grammar;
}
css_grammar::css_skipper_type const skipper()
{
return css_grammar::css_skipper;
}
}

View file

@ -21,6 +21,7 @@
*****************************************************************************/
#include <mapnik/debug.hpp>
#include <mapnik/css/css_unit_value.hpp>
#include <mapnik/color_factory.hpp>
#include <mapnik/svg/svg_parser.hpp>
#include <mapnik/svg/svg_path_parser.hpp>
@ -57,6 +58,24 @@
namespace mapnik { namespace svg {
namespace {
void print_css(mapnik::css_data & data)
{
for (auto const& kv : data)
{
std::cerr << std::get<0>(kv) << " {" << std::endl;
for (auto const& def : std::get<1>(kv))
{
auto const& r = std::get<1>(def);
std::cerr << " " << std::get<0>(def) << ":"
<< std::string(r.begin(), r.end());
}
std::cerr << "}" << std::endl;
}
}
}
using util::name_to_int;
using util::operator"" _case;
@ -259,17 +278,7 @@ double parse_svg_value(T & err_handler, const char* str, bool & is_percent)
{
namespace x3 = boost::spirit::x3;
double val = 0.0;
x3::symbols<double> units;
units.add
("px", 1.0)
("pt", DPI/72.0)
("pc", DPI/6.0)
("mm", DPI/25.4)
("cm", DPI/2.54)
("in", static_cast<double>(DPI))
//("em", 1.0/16.0) // default pixel size for body (usually 16px)
// ^^ this doesn't work currently as 'e' in 'em' interpreted as part of scientific notation.
;
css_unit_value units;
const char* cur = str; // phrase_parse mutates the first iterator
const char* end = str + std::strlen(str);
@ -370,15 +379,107 @@ bool parse_id_from_url (char const* str, std::string & id)
x3::space);
}
void process_css(svg_parser & parser, rapidxml::xml_node<char> const* node)
{
auto const& css = parser.css_data_;
std::unordered_map<std::string, mapnik::property_value_type> style;
std::string element_name = node->name();
auto itr = css.find(element_name);
if (itr != css.end())
{
//std::cerr << "-> element key:" << element_name << std::endl;
for (auto const& def : std::get<1>(*itr))
{
style[std::get<0>(def)] = std::get<1>(def);
}
}
auto const* class_attr = node->first_attribute("class");
if (class_attr != nullptr)
{
auto const* value = class_attr->value();
if (std::strlen(value) > 0)
{
char const* first = value;
char const* last = first + std::strlen(value);
std::vector<std::string> classes;
bool result = boost::spirit::x3::phrase_parse(first, last, mapnik::classes(), mapnik::skipper(), classes);
if (result)
{
for (auto const& class_name : classes)
{
std::string solitary_class_key = std::string(".") + class_name;
auto range = css.equal_range(solitary_class_key);
for (auto itr = range.first; itr != range.second; ++itr)
{
//std::cerr << "<" << element_name << ">";
//std::cerr << "--> solitary class key:" << solitary_class_key << std::endl;
for (auto const& def : std::get<1>(*itr))
{
style[std::get<0>(def)] = std::get<1>(def);
}
}
std::string class_key = element_name + solitary_class_key;
range = css.equal_range(class_key);
for (auto itr = range.first; itr != range.second; ++itr)
{
//std::cerr << "<" << element_name << ">";
//std::cerr << "---> class key:" << class_key << std::endl;
for (auto const& def : std::get<1>(*itr))
{
style[std::get<0>(def)] = std::get<1>(def);
}
}
}
}
//else
//{
// std::cerr << "Failed to parse styles..." << std::endl;
//}
}
}
auto const* id_attr = node->first_attribute("id");
if (id_attr != nullptr)
{
auto const* value = id_attr->value();
if (std::strlen(value) > 0)
{
std::string id_key = std::string("#") + value;
itr = css.find(id_key);
if (itr != css.end())
{
//std::cerr << "<" << element_name << ">";
//std::cerr << "----> ID key:" << id_key << std::endl;
for (auto const& def : std::get<1>(*itr))
{
style[std::get<0>(def)] = std::get<1>(def);
}
}
}
}
// If style has properties, create or update style attribute
if (style.size() > 0)
{
std::ostringstream ss;
for (auto const& def : style)
{
auto const& r = std::get<1>(def);
std::string val{r.begin(), r.end()};
//std::cerr << "PARSE ATTR:" << std::get<0>(def) << ":" << val << std::endl;
parse_attr(parser, std::get<0>(def).c_str(), val.c_str());
}
}
}
void traverse_tree(svg_parser & parser, rapidxml::xml_node<char> const* node)
{
if (parser.ignore_) return;
auto const* name = node->name();
auto name = name_to_int(node->name());
switch (node->type())
{
case rapidxml::node_element:
{
switch(name_to_int(name))
switch(name)
{
case "defs"_case:
{
@ -407,19 +508,23 @@ void traverse_tree(svg_parser & parser, rapidxml::xml_node<char> const* node)
if (!parser.is_defs_) // FIXME
{
switch (name_to_int(name))
switch (name)
{
case "g"_case:
if (node->first_node() != nullptr)
{
parser.path_.push_attr();
parse_id(parser, node);
if (parser.css_style_)
process_css(parser, node);
parse_attr(parser, node);
}
break;
case "use"_case:
parser.path_.push_attr();
parse_id(parser, node);
if (parser.css_style_)
process_css(parser, node);
parse_attr(parser, node);
parse_use(parser, node);
parser.path_.pop_attr();
@ -427,10 +532,12 @@ void traverse_tree(svg_parser & parser, rapidxml::xml_node<char> const* node)
default:
parser.path_.push_attr();
parse_id(parser, node);
if (parser.css_style_)
process_css(parser, node);
parse_attr(parser, node);
if (parser.path_.display())
{
parse_element(parser, name, node);
parse_element(parser, node->name(), node);
}
parser.path_.pop_attr();
}
@ -441,32 +548,41 @@ void traverse_tree(svg_parser & parser, rapidxml::xml_node<char> const* node)
parse_id(parser, node);
}
for (auto const* child = node->first_node();
child; child = child->next_sibling())
if ("style"_case == name)
{
traverse_tree(parser, child);
// <style> element is not expected to have nested elements
// we're only interested in DATA or CDATA
for (auto const* child = node->first_node();
child; child = child->next_sibling())
{
if (child->type() == rapidxml::node_data ||
child->type() == rapidxml::node_cdata)
{
auto const grammar = mapnik::grammar();
auto const skipper = mapnik::skipper();
char const* first = child->value();
char const* last = first + child->value_size();
bool result = boost::spirit::x3::phrase_parse(first, last, grammar, skipper, parser.css_data_);
if (result && first == last && !parser.css_data_.empty())
{
parser.css_style_ = true;
//print_css(parser.css_data_);
}
}
}
}
else
{
for (auto const* child = node->first_node();
child; child = child->next_sibling())
{
traverse_tree(parser, child);
}
}
end_element(parser, node);
}
break;
#if 0 //
// Data nodes
case rapidxml::node_data:
case rapidxml::node_cdata:
{
if (node->value_size() > 0) // Don't add empty text nodes
{
// parsed text values should have leading and trailing
// whitespace trimmed.
//std::string trimmed = node->value();
//mapnik::util::trim(trimmed);
//std::cerr << "CDATA:" << node->value() << std::endl;
}
}
break;
#endif
default:
break;
}
@ -1426,6 +1542,7 @@ svg_parser::svg_parser(svg_converter_type & path, bool strict)
: path_(path),
is_defs_(false),
ignore_(false),
css_style_(false),
err_handler_(strict) {}
svg_parser::~svg_parser() {}

@ -1 +1 @@
Subproject commit c3982b76602bcbfea6403793ac2f3fab44552270
Subproject commit e83b7f7c6d2d646a638bba404a0d637609722a09

View file

@ -2,8 +2,8 @@
#include <mapnik/safe_cast.hpp>
#include <mapnik/color.hpp>
#include <mapnik/css_color_grammar_x3.hpp>
#include <mapnik/css_color_grammar_x3_def.hpp>
#include <mapnik/css/css_color_grammar_x3.hpp>
#include <mapnik/css/css_color_grammar_x3_def.hpp>
#include <sstream>

View file

@ -0,0 +1,109 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2020 Artem Pavlenko
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*****************************************************************************/
#include "catch.hpp"
#include <mapnik/debug.hpp>
#include <mapnik/marker.hpp>
#include <mapnik/marker_cache.hpp>
#include <mapnik/image_util.hpp>
#include <mapnik/svg/svg_parser.hpp>
#include <mapnik/svg/svg_converter.hpp>
#include <mapnik/svg/svg_path_adapter.hpp>
#include <mapnik/svg/svg_renderer_agg.hpp>
#include <mapnik/svg/svg_path_attributes.hpp>
#include <boost/range/combine.hpp>
#pragma GCC diagnostic push
#include <mapnik/warning_ignore_agg.hpp>
#include "agg_rasterizer_scanline_aa.h"
#include "agg_basics.h"
#include "agg_rendering_buffer.h"
#include "agg_renderer_base.h"
#include "agg_pixfmt_rgba.h"
#include "agg_scanline_u.h"
#pragma GCC diagnostic pop
namespace
{
mapnik::image_rgba8 render_svg(std::string const& filename, double scale_factor)
{
using pixfmt = agg::pixfmt_rgba32_pre;
using renderer_base = agg::renderer_base<pixfmt>;
using renderer_solid = agg::renderer_scanline_aa_solid<renderer_base>;
agg::rasterizer_scanline_aa<> ras_ptr;
agg::scanline_u8 sl;
std::shared_ptr<mapnik::marker const> marker = mapnik::marker_cache::instance().find(filename, false);
mapnik::marker_svg const& svg = mapnik::util::get<mapnik::marker_svg>(*marker);
double svg_width, svg_height ;
std::tie(svg_width, svg_height) = svg.dimensions();
int image_width = static_cast<int>(std::round(svg_width));
int image_height = static_cast<int>(std::round(svg_height));
mapnik::image_rgba8 im(image_width, image_height, true, true);
agg::rendering_buffer buf(im.bytes(), im.width(), im.height(), im.row_size());
pixfmt pixf(buf);
renderer_base renb(pixf);
agg::trans_affine mtx = agg::trans_affine_translation(-0.5 * svg_width, -0.5 * svg_height);
mtx.scale(scale_factor);
mtx.translate(0.5 * svg_width, 0.5 * svg_height);
mapnik::svg::vertex_stl_adapter<mapnik::svg::svg_path_storage> stl_storage(svg.get_data()->source());
mapnik::svg::svg_path_adapter svg_path(stl_storage);
mapnik::svg::renderer_agg<mapnik::svg_path_adapter,
mapnik::svg_attribute_type,
renderer_solid,
agg::pixfmt_rgba32_pre > renderer(svg_path,
svg.get_data()->attributes());
double opacity = 1.0;
renderer.render(ras_ptr, sl, renb, mtx, opacity, {0, 0, svg_width, svg_height});
return im;
}
bool equal(mapnik::image_rgba8 const& im1, mapnik::image_rgba8 const& im2)
{
if (im1.width() != im2.width() || im1.height() != im2.height())
return false;
for(auto tup : boost::combine(im1, im2))
{
if (boost::get<0>(tup) != boost::get<1>(tup)) return false;
}
return true;
}
}
TEST_CASE("SVG renderer") {
SECTION("SVG octocat inline/css")
{
double scale_factor = 1.0;
std::string octocat_inline("./test/data/svg/octocat.svg");
std::string octocat_css("./test/data/svg/octocat-css.svg");
auto image1 = render_svg(octocat_inline, 1.0);
auto image2 = render_svg(octocat_css, 1.0);
REQUIRE(equal(image1, image2));
}
}

View file

@ -74,7 +74,16 @@ struct main_marker_visitor
double opacity = 1;
double w, h;
std::tie(w, h) = marker.dimensions();
if (w == 0 || h == 0)
{
if (verbose_)
{
std::clog << "Invalid SVG dimensions: " << w << "," << h << " using svgBBOX()" << std::endl;
}
auto b = marker.bounding_box();
w = b.width();
h = b.height();
}
double svg_width = w * scale_factor_;
double svg_height = h * scale_factor_;
@ -83,9 +92,7 @@ struct main_marker_visitor
int output_height = static_cast<int>(std::round(svg_height));
if (verbose_)
{
std::clog << "Found width of '" << w << "' and height of '" << h << "'\n";
auto b = marker.bounding_box();
std::clog << "SVG BBOX:" << b << std::endl;
std::clog << "SVG width of '" << w << "' and height of '" << h << "'\n";
std::clog << "Output image dimensions:[" << output_width << "," << output_height << "]" << std::endl;
}
mapnik::image_rgba8 im(output_width, output_height, true, true);