From: Magnus Jacobsson Date: Thu, 8 Apr 2021 20:51:26 +0000 (+0200) Subject: add a very basic SVG analyzer based on SVG++ X-Git-Tag: 2.49.0~24^2~1 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=e27608d1118f78a0a87defb203a490a12959ed80;p=graphviz add a very basic SVG analyzer based on SVG++ --- diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5976bec03..9ac50e16b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -11,6 +11,13 @@ enable_testing() # separate test can be as small as possible add_library(test_common STATIC catch2_main.cpp + svgpp_context.cpp + svgpp_context.h + svgpp_document_traverser.cpp + svgpp_document_traverser.h + svg_analyzer.cpp + svg_analyzer.h + svg_analyzer_interface.h ../cmd/dot/dot_builtins.c ) set_target_properties(test_common PROPERTIES CXX_STANDARD 20) diff --git a/tests/svg_analyzer.cpp b/tests/svg_analyzer.cpp new file mode 100644 index 000000000..37f0d74c7 --- /dev/null +++ b/tests/svg_analyzer.cpp @@ -0,0 +1,30 @@ +#include "svg_analyzer.h" +#include "svgpp_context.h" +#include "svgpp_document_traverser.h" + +SVGAnalyzer::SVGAnalyzer(char *text) { + SvgppContext context{this}; + traverseDocumentWithSvgpp(context, text); +} + +void SVGAnalyzer::on_enter_element_svg() { m_num_svgs++; } + +void SVGAnalyzer::on_enter_element_g() { m_num_groups++; } + +void SVGAnalyzer::on_enter_element_circle() { m_num_circles++; } + +void SVGAnalyzer::on_enter_element_ellipse() { m_num_ellipses++; } + +void SVGAnalyzer::on_enter_element_line() { m_num_lines++; } + +void SVGAnalyzer::on_enter_element_path() { m_num_paths++; } + +void SVGAnalyzer::on_enter_element_polygon() { m_num_polygons++; } + +void SVGAnalyzer::on_enter_element_polyline() { m_num_polylines++; } + +void SVGAnalyzer::on_enter_element_rect() { m_num_rects++; } + +void SVGAnalyzer::on_enter_element_title() { m_num_titles++; } + +void SVGAnalyzer::on_enter_element_unknown() { m_num_unknowns++; } diff --git a/tests/svg_analyzer.h b/tests/svg_analyzer.h new file mode 100644 index 000000000..e01502c9f --- /dev/null +++ b/tests/svg_analyzer.h @@ -0,0 +1,50 @@ +#pragma once + +#include + +#include "svg_analyzer_interface.h" + +/** + * @brief The SVGAnalyzer class analyzes the contents of an SVG document. + */ + +class SVGAnalyzer : public ISVGAnalyzer { +public: + SVGAnalyzer(char *text); + void on_enter_element_svg() override; + void on_enter_element_g() override; + void on_enter_element_circle() override; + void on_enter_element_ellipse() override; + void on_enter_element_line() override; + void on_enter_element_path() override; + void on_enter_element_polygon() override; + void on_enter_element_polyline() override; + void on_enter_element_rect() override; + void on_enter_element_title() override; + void on_enter_element_unknown() override; + std::size_t num_svgs() const { return m_num_svgs; }; + std::size_t num_groups() const { return m_num_groups; }; + std::size_t num_circles() const { return m_num_circles; }; + std::size_t num_ellipses() const { return m_num_ellipses; }; + std::size_t num_lines() const { return m_num_lines; }; + std::size_t num_paths() const { return m_num_paths; }; + std::size_t num_polygons() const { return m_num_polygons; }; + std::size_t num_polylines() const { return m_num_polylines; }; + std::size_t num_rects() const { return m_num_rects; }; + std::size_t num_titles() const { return m_num_titles; }; + std::size_t num_unknowns() const { return m_num_unknowns; }; + +private: + std::size_t m_num_svgs = 1; // the top level svg is implicit. See + // https://github.com/svgpp/svgpp/issues/98 + std::size_t m_num_groups = 0; + std::size_t m_num_circles = 0; + std::size_t m_num_ellipses = 0; + std::size_t m_num_lines = 0; + std::size_t m_num_paths = 0; + std::size_t m_num_polygons = 0; + std::size_t m_num_polylines = 0; + std::size_t m_num_rects = 0; + std::size_t m_num_titles = 0; + std::size_t m_num_unknowns = 0; +}; diff --git a/tests/svg_analyzer_interface.h b/tests/svg_analyzer_interface.h new file mode 100644 index 000000000..dc63cd7f7 --- /dev/null +++ b/tests/svg_analyzer_interface.h @@ -0,0 +1,26 @@ +#pragma once + +/** + * @brief The ISVGAnalyzer class is an interface class declaring + * callbacks that can be implemented by an SVGAnalyzer class. Its + * purpose is to isolate the SVGAnalyzer class from the SvgppContext + * class to avoid recompiling the SvgppContext class when the + * SVGAnalyzer class is changed, which is expected to happen + * frequently. + */ + +class ISVGAnalyzer { +public: + virtual ~ISVGAnalyzer() = default; + virtual void on_enter_element_svg() = 0; + virtual void on_enter_element_g() = 0; + virtual void on_enter_element_circle() = 0; + virtual void on_enter_element_ellipse() = 0; + virtual void on_enter_element_line() = 0; + virtual void on_enter_element_path() = 0; + virtual void on_enter_element_polygon() = 0; + virtual void on_enter_element_polyline() = 0; + virtual void on_enter_element_rect() = 0; + virtual void on_enter_element_title() = 0; + virtual void on_enter_element_unknown() = 0; +}; diff --git a/tests/svgpp_context.cpp b/tests/svgpp_context.cpp new file mode 100644 index 000000000..307c0ad06 --- /dev/null +++ b/tests/svgpp_context.cpp @@ -0,0 +1,195 @@ +#include + +#include "svg_analyzer_interface.h" +#include "svgpp_context.h" + +SvgppContext::SvgppContext(ISVGAnalyzer *svgAnalyzer) + : m_svgAnalyzer(svgAnalyzer){}; + +void SvgppContext::on_enter_element(svgpp::tag::element::svg e) { + (void)e; + m_svgAnalyzer->on_enter_element_svg(); +} + +void SvgppContext::on_enter_element(svgpp::tag::element::g e) { + (void)e; + m_svgAnalyzer->on_enter_element_g(); +} + +void SvgppContext::on_enter_element(svgpp::tag::element::circle e) { + (void)e; + m_svgAnalyzer->on_enter_element_circle(); +} + +void SvgppContext::on_enter_element(svgpp::tag::element::ellipse e) { + (void)e; + m_svgAnalyzer->on_enter_element_ellipse(); +} + +void SvgppContext::on_enter_element(svgpp::tag::element::line e) { + (void)e; + m_svgAnalyzer->on_enter_element_line(); +} + +void SvgppContext::on_enter_element(svgpp::tag::element::path e) { + (void)e; + m_svgAnalyzer->on_enter_element_path(); +} + +void SvgppContext::on_enter_element(svgpp::tag::element::polygon e) { + (void)e; + m_svgAnalyzer->on_enter_element_polygon(); +} + +void SvgppContext::on_enter_element(svgpp::tag::element::polyline e) { + (void)e; + m_svgAnalyzer->on_enter_element_polyline(); +} + +void SvgppContext::on_enter_element(svgpp::tag::element::rect e) { + (void)e; + m_svgAnalyzer->on_enter_element_rect(); +} + +void SvgppContext::on_enter_element(svgpp::tag::element::title e) { + (void)e; + m_svgAnalyzer->on_enter_element_title(); +} + +void SvgppContext::on_enter_element(svgpp::tag::element::any) { + m_svgAnalyzer->on_enter_element_unknown(); +} + +void SvgppContext::on_exit_element() {} + +void SvgppContext::path_move_to(double x, double y, + svgpp::tag::coordinate::absolute c) { + (void)x; + (void)y; + (void)c; +} + +void SvgppContext::path_line_to(double x, double y, + svgpp::tag::coordinate::absolute c) { + (void)x; + (void)y; + (void)c; +} + +void SvgppContext::path_cubic_bezier_to(double x1, double y1, double x2, + double y2, double x, double y, + svgpp::tag::coordinate::absolute c) { + (void)x1; + (void)y1; + (void)x2; + (void)y2; + (void)x; + (void)y; + (void)c; +} + +void SvgppContext::path_quadratic_bezier_to( + double x1, double y1, double x, double y, + svgpp::tag::coordinate::absolute c) { + (void)x1; + (void)y1; + (void)x; + (void)y; + (void)c; +} + +void SvgppContext::path_elliptical_arc_to(double rx, double ry, + double x_axis_rotation, + bool large_arc_flag, bool sweep_flag, + double x, double y, + svgpp::tag::coordinate::absolute c) { + (void)rx; + (void)ry; + (void)x_axis_rotation; + (void)large_arc_flag; + (void)sweep_flag; + (void)x; + (void)y; + (void)c; +} + +void SvgppContext::path_close_subpath() {} + +void SvgppContext::path_exit() {} + +void SvgppContext::set(svgpp::tag::attribute::cy a, const double v) { + (void)a; + (void)v; +} + +void SvgppContext::set(svgpp::tag::attribute::cx a, const double v) { + (void)a; + (void)v; +} + +void SvgppContext::set(svgpp::tag::attribute::r a, const double v) { + (void)a; + (void)v; +} + +void SvgppContext::set(svgpp::tag::attribute::rx a, const double v) { + (void)a; + (void)v; +} + +void SvgppContext::set(svgpp::tag::attribute::ry a, const double v) { + (void)a; + (void)v; +} + +void SvgppContext::set(svgpp::tag::attribute::x1 a, const double v) { + (void)a; + (void)v; +} + +void SvgppContext::set(svgpp::tag::attribute::y1 a, const double v) { + (void)a; + (void)v; +} + +void SvgppContext::set(svgpp::tag::attribute::x2 a, const double v) { + (void)a; + (void)v; +} + +void SvgppContext::set(svgpp::tag::attribute::y2 a, const double v) { + (void)a; + (void)v; +} + +void SvgppContext::set(svgpp::tag::attribute::x a, const double v) { + (void)a; + (void)v; +} + +void SvgppContext::set(svgpp::tag::attribute::y a, const double v) { + (void)a; + (void)v; +} + +void SvgppContext::set(svgpp::tag::attribute::width a, const double v) { + (void)a; + (void)v; +} + +void SvgppContext::set(svgpp::tag::attribute::height a, const double v) { + (void)a; + (void)v; +} + +void SvgppContext::set_impl(svgpp::tag::attribute::points &points, + const std::any &range) { + (void)points; + (void)range; + // ignore for now +} + +void SvgppContext::set_text_impl(const std::any &range) { + (void)range; + // ignore for now +} diff --git a/tests/svgpp_context.h b/tests/svgpp_context.h new file mode 100644 index 000000000..a4825c13c --- /dev/null +++ b/tests/svgpp_context.h @@ -0,0 +1,75 @@ +#pragma once + +#include + +#include + +class ISVGAnalyzer; + +/** + * @brief The SvgppContext class provides a context containing SVG element + * callbacks for the SVG++ parser which is called from the SVG document + * traverser and forwards these callbacks to the SVG analyzer. It's separated + * from the SVG analyzer to avoid the very time consuming recompilation of the + * SVG document traverser when then SVG analyzer header is changed, which is + * expected to happen often as new functionality is added. + * + * Most of this is taken from + * http://svgpp.org/lesson01.html#handling-shapes-geometry. + */ + +class SvgppContext { +public: + SvgppContext(ISVGAnalyzer *svgAnalyzer); + void on_enter_element(svgpp::tag::element::svg e); + void on_enter_element(svgpp::tag::element::g e); + void on_enter_element(svgpp::tag::element::circle e); + void on_enter_element(svgpp::tag::element::ellipse e); + void on_enter_element(svgpp::tag::element::line e); + void on_enter_element(svgpp::tag::element::path e); + void on_enter_element(svgpp::tag::element::polygon e); + void on_enter_element(svgpp::tag::element::polyline e); + void on_enter_element(svgpp::tag::element::rect e); + void on_enter_element(svgpp::tag::element::title e); + void on_enter_element(svgpp::tag::element::any e); + void on_exit_element(); + void path_move_to(double x, double y, svgpp::tag::coordinate::absolute); + void path_line_to(double x, double y, svgpp::tag::coordinate::absolute); + void path_cubic_bezier_to(double x1, double y1, double x2, double y2, + double x, double y, + svgpp::tag::coordinate::absolute); + void path_quadratic_bezier_to(double x1, double y1, double x, double y, + svgpp::tag::coordinate::absolute); + void path_elliptical_arc_to(double rx, double ry, double x_axis_rotation, + bool large_arc_flag, bool sweep_flag, double x, + double y, svgpp::tag::coordinate::absolute); + void path_close_subpath(); + void path_exit(); + void set(svgpp::tag::attribute::cy cy, const double v); + void set(svgpp::tag::attribute::cx cx, const double v); + void set(svgpp::tag::attribute::r r, const double v); + void set(svgpp::tag::attribute::rx rx, const double v); + void set(svgpp::tag::attribute::ry ry, const double v); + void set(svgpp::tag::attribute::x1 x1, const double v); + void set(svgpp::tag::attribute::y1 y1, const double v); + void set(svgpp::tag::attribute::x2 x2, const double v); + void set(svgpp::tag::attribute::y2 y2, const double v); + template + void set(svgpp::tag::attribute::points points, const Range &range) { + set_impl(points, range); + } + void set(svgpp::tag::attribute::x a, const double v); + void set(svgpp::tag::attribute::y y, const double v); + void set(svgpp::tag::attribute::width width, const double v); + void set(svgpp::tag::attribute::height height, const double v); + template void set_text(const Range &range) { + set_text_impl(range); + } + +private: + void set_impl(svgpp::tag::attribute::points &points, const std::any &range); + void set_text_impl(const std::any &range); + +private: + ISVGAnalyzer *m_svgAnalyzer = nullptr; +}; diff --git a/tests/svgpp_document_traverser.cpp b/tests/svgpp_document_traverser.cpp new file mode 100644 index 000000000..47fd50fe2 --- /dev/null +++ b/tests/svgpp_document_traverser.cpp @@ -0,0 +1,33 @@ +#include +#include +#include + +#include "svgpp_context.h" +#include "svgpp_document_traverser.h" + +// most of this is taken from +// http://svgpp.org/lesson01.html#handling-shapes-geometry + +void traverseDocumentWithSvgpp(SvgppContext &context, char *text) { + rapidxml_ns::xml_document<> doc; + doc.parse<0>(text); + if (rapidxml_ns::xml_node<> *svg_element = doc.first_node("svg")) { + const rapidxml_ns::xml_node<> *xml_root_element = svg_element; + + using processed_elements_t = boost::mpl::set< + // SVG Structural Elements + svgpp::tag::element::svg, svgpp::tag::element::g, + // SVG Shape Elements + svgpp::tag::element::circle, svgpp::tag::element::ellipse, + svgpp::tag::element::line, svgpp::tag::element::path, + svgpp::tag::element::polygon, svgpp::tag::element::polyline, + svgpp::tag::element::rect, svgpp::tag::element::title>::type; + + svgpp::document_traversal< + svgpp::processed_elements, + svgpp::processed_attributes< + svgpp::traits::shapes_attributes_by_element>, + svgpp::basic_shapes_policy>:: + load_document(xml_root_element, context); + } +} diff --git a/tests/svgpp_document_traverser.h b/tests/svgpp_document_traverser.h new file mode 100644 index 000000000..e338ca437 --- /dev/null +++ b/tests/svgpp_document_traverser.h @@ -0,0 +1,13 @@ +#pragma once + +/** + * @brief The traverseDocumentWithSvgpp function traverses the SVG + * document through the SVG++ document loader and provides it with a + * context containing callbacks for handling SVG elements. It is + * separated from the SvgppContext class to reduce the compile time of + * the SvgppContext class. + */ + +class SvgppContext; + +void traverseDocumentWithSvgpp(SvgppContext &context, char *text);