]> granicus.if.org Git - graphviz/commitdiff
add a very basic SVG analyzer based on SVG++
authorMagnus Jacobsson <Magnus.Jacobsson@berotec.se>
Thu, 8 Apr 2021 20:51:26 +0000 (22:51 +0200)
committerMagnus Jacobsson <Magnus.Jacobsson@berotec.se>
Thu, 12 Aug 2021 05:52:26 +0000 (07:52 +0200)
tests/CMakeLists.txt
tests/svg_analyzer.cpp [new file with mode: 0644]
tests/svg_analyzer.h [new file with mode: 0644]
tests/svg_analyzer_interface.h [new file with mode: 0644]
tests/svgpp_context.cpp [new file with mode: 0644]
tests/svgpp_context.h [new file with mode: 0644]
tests/svgpp_document_traverser.cpp [new file with mode: 0644]
tests/svgpp_document_traverser.h [new file with mode: 0644]

index 5976bec034790666222770d5ec96d41f1cff91c0..9ac50e16b45049bf7932caae64353d342869e542 100644 (file)
@@ -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 (file)
index 0000000..37f0d74
--- /dev/null
@@ -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 (file)
index 0000000..e01502c
--- /dev/null
@@ -0,0 +1,50 @@
+#pragma once
+
+#include <cstddef>
+
+#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 (file)
index 0000000..dc63cd7
--- /dev/null
@@ -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 (file)
index 0000000..307c0ad
--- /dev/null
@@ -0,0 +1,195 @@
+#include <any>
+
+#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 (file)
index 0000000..a4825c1
--- /dev/null
@@ -0,0 +1,75 @@
+#pragma once
+
+#include <any>
+
+#include <svgpp/svgpp.hpp>
+
+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 <typename Range>
+  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 <class Range> 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 (file)
index 0000000..47fd50f
--- /dev/null
@@ -0,0 +1,33 @@
+#include <rapidxml_ns/rapidxml_ns.hpp>
+#include <svgpp/policy/xml/rapidxml_ns.hpp>
+#include <svgpp/svgpp.hpp>
+
+#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<processed_elements_t>,
+        svgpp::processed_attributes<
+            svgpp::traits::shapes_attributes_by_element>,
+        svgpp::basic_shapes_policy<svgpp::policy::basic_shapes::raw>>::
+        load_document(xml_root_element, context);
+  }
+}
diff --git a/tests/svgpp_document_traverser.h b/tests/svgpp_document_traverser.h
new file mode 100644 (file)
index 0000000..e338ca4
--- /dev/null
@@ -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);