From d09cf6ef3b6ea82f7ff67719527cd563569e0893 Mon Sep 17 00:00:00 2001
From: jaseg <git@jaseg.de>
Date: Mon, 20 Jun 2022 10:24:26 +0200
Subject: svg-flatten: Add 'complete pattern tiles only' switch

This is not part of the SVG spec, but it is useful for generating proto
boards using SVG patterns.
---
 svg-flatten/include/geom2d.hpp    | 27 +++++++++++++++++++--------
 svg-flatten/include/gerbolyze.hpp |  1 +
 svg-flatten/src/main.cpp          |  5 +++++
 svg-flatten/src/svg_pattern.cpp   | 23 +++++++++++++++++++++++
 4 files changed, 48 insertions(+), 8 deletions(-)

diff --git a/svg-flatten/include/geom2d.hpp b/svg-flatten/include/geom2d.hpp
index 6a52d0b..4fafd80 100644
--- a/svg-flatten/include/geom2d.hpp
+++ b/svg-flatten/include/geom2d.hpp
@@ -143,17 +143,28 @@ namespace gerbolyze {
             /* Transform given clipper paths */
             void transform_paths(ClipperLib::Paths &paths) {
                 for (auto &p : paths) {
-                    std::transform(p.begin(), p.end(), p.begin(),
-                            [this](ClipperLib::IntPoint p) -> ClipperLib::IntPoint {
-                                d2p out(this->doc2phys(d2p{p.X / clipper_scale, p.Y / clipper_scale}));
-                                return {
-                                    (ClipperLib::cInt)round(out[0] * clipper_scale),
-                                    (ClipperLib::cInt)round(out[1] * clipper_scale)
-                                };
-                            });
+                    transform_clipper_path(p);
                 }
             }
 
+            void transform_clipper_path(ClipperLib::Path &path) {
+                std::transform(path.begin(), path.end(), path.begin(),
+                        [this](ClipperLib::IntPoint p) -> ClipperLib::IntPoint {
+                            d2p out(this->doc2phys(d2p{p.X / clipper_scale, p.Y / clipper_scale}));
+                            return {
+                                (ClipperLib::cInt)round(out[0] * clipper_scale),
+                                (ClipperLib::cInt)round(out[1] * clipper_scale)
+                            };
+                        });
+            }
+
+            void transform_polygon(Polygon &poly) {
+                std::transform(poly.begin(), poly.end(), poly.begin(),
+                        [this](d2p p) -> d2p {
+                            return this->doc2phys(d2p{p[0], p[1]});
+                        });
+            }
+
             string dbg_str() {
                 ostringstream os;
                 os << "xform2d< " << setw(5);
diff --git a/svg-flatten/include/gerbolyze.hpp b/svg-flatten/include/gerbolyze.hpp
index cd837ba..9de5572 100644
--- a/svg-flatten/include/gerbolyze.hpp
+++ b/svg-flatten/include/gerbolyze.hpp
@@ -197,6 +197,7 @@ namespace gerbolyze {
         VectorizerSelectorizer &m_vec_sel;
         bool outline_mode = false;
         bool flip_color_interpretation = false;
+        bool pattern_complete_tiles_only = false;
     };
 
     class RenderContext {
diff --git a/svg-flatten/src/main.cpp b/svg-flatten/src/main.cpp
index 188aa57..35fcbfb 100644
--- a/svg-flatten/src/main.cpp
+++ b/svg-flatten/src/main.cpp
@@ -73,6 +73,9 @@ int main(int argc, char **argv) {
             {"flip_svg_color_interpretation", {"-i", "--svg-white-is-gerber-dark"},
                 "Flip polarity of SVG color interpretation. This affects only SVG primitives like paths and NOT embedded bitmaps. With -i: white -> silk there/\"dark\" gerber primitive.",
                 0},
+            {"pattern_complete_tiles_only", {"--pattern-complete-tiles-only"},
+                "Break SVG spec by only rendering complete pattern tiles, i.e. pattern tiles that entirely fit the target area, instead of performing clipping.",
+                0},
             {"min_feature_size", {"-d", "--trace-space"},
                 "Minimum feature size of elements in vectorized graphics (trace/space) in mm. Default: 0.1mm.",
                 1},
@@ -423,6 +426,7 @@ int main(int argc, char **argv) {
 
     VectorizerSelectorizer vec_sel(vectorizer, args["vectorizer_map"] ? args["vectorizer_map"].as<string>() : "");
     bool flip_svg_colors = args["flip_svg_color_interpretation"];
+    bool pattern_complete_tiles_only = args["pattern_complete_tiles_only"];
 
     RenderSettings rset {
         min_feature_size,
@@ -431,6 +435,7 @@ int main(int argc, char **argv) {
         vec_sel,
         outline_mode,
         flip_svg_colors,
+        pattern_complete_tiles_only,
     };
 
     SVGDocument doc;
diff --git a/svg-flatten/src/svg_pattern.cpp b/svg-flatten/src/svg_pattern.cpp
index cdff7a7..fc74d73 100644
--- a/svg-flatten/src/svg_pattern.cpp
+++ b/svg-flatten/src/svg_pattern.cpp
@@ -108,6 +108,29 @@ void gerbolyze::Pattern::tile (gerbolyze::RenderContext &ctx) {
 
             /* Export the pattern tile's content like a group */
             RenderContext elem_ctx(pat_ctx, elem_xf);
+
+            if (ctx.settings().pattern_complete_tiles_only) {
+                ClipperLib::Clipper c;
+
+                double eps = 1e-6;
+                Polygon poly = {{eps, eps}, {inst_w-eps, eps}, {inst_w-eps, inst_h-eps}, {eps, inst_h-eps}};
+                elem_ctx.mat().transform_polygon(poly);
+                ClipperLib::Path path(poly.size());
+                for (size_t i=0; i<poly.size(); i++) {
+                    long long int x = poly[i][0] * clipper_scale, y = poly[i][1] * clipper_scale;
+                    path[i] = {x, y};
+                }
+
+                ClipperLib::Paths out;
+                c.StrictlySimple(true);
+                c.AddPath(path, ClipperLib::ptSubject, /* closed */ true);
+                c.AddPaths(elem_ctx.clip(), ClipperLib::ptClip, /* closed */ true);
+                c.Execute(ClipperLib::ctDifference, out, ClipperLib::pftNonZero);
+                if (out.size() > 0) {
+                    continue;
+                }
+            }
+
             doc->export_svg_group(elem_ctx, _node);
         }
     }
-- 
cgit