aboutsummaryrefslogtreecommitdiff
path: root/svg-flatten/src
diff options
context:
space:
mode:
authorjaseg <git@jaseg.de>2021-08-18 21:28:58 +0200
committerjaseg <git@jaseg.de>2021-08-18 21:28:58 +0200
commitfae8532b05b8c3cd79cd09a6b1986bc8ff9ad306 (patch)
tree8bb42fdb3da0ba13c74031b7ad6c277611bf34eb /svg-flatten/src
parentaaade1b168b3780a9c2ed1387165086ede33bc54 (diff)
downloadgerbolyze-fae8532b05b8c3cd79cd09a6b1986bc8ff9ad306.tar.gz
gerbolyze-fae8532b05b8c3cd79cd09a6b1986bc8ff9ad306.tar.bz2
gerbolyze-fae8532b05b8c3cd79cd09a6b1986bc8ff9ad306.zip
svg-flatten: Fix include/exclude logic
Diffstat (limited to 'svg-flatten/src')
-rw-r--r--svg-flatten/src/main.cpp9
-rw-r--r--svg-flatten/src/svg_doc.cpp44
-rw-r--r--svg-flatten/src/test/svg_tests.py71
3 files changed, 100 insertions, 24 deletions
diff --git a/svg-flatten/src/main.cpp b/svg-flatten/src/main.cpp
index 54ce896..8512547 100644
--- a/svg-flatten/src/main.cpp
+++ b/svg-flatten/src/main.cpp
@@ -377,6 +377,15 @@ int main(int argc, char **argv) {
return EXIT_FAILURE;
}
+ /*
+ cerr << "Selectors:" << endl;
+ for (auto &elem : sel.include) {
+ cerr << " + " << elem << endl;
+ }
+ for (auto &elem : sel.exclude) {
+ cerr << " - " << elem << endl;
+ }
+ */
doc.render(rset, *top_sink, sel);
remove(frob.c_str());
diff --git a/svg-flatten/src/svg_doc.cpp b/svg-flatten/src/svg_doc.cpp
index d90e00d..5a27163 100644
--- a/svg-flatten/src/svg_doc.cpp
+++ b/svg-flatten/src/svg_doc.cpp
@@ -108,9 +108,10 @@ double gerbolyze::SVGDocument::doc_units_to_mm(double px) const {
return px / (vb_w / page_w_mm);
}
-bool IDElementSelector::match(const pugi::xml_node &node, bool included, bool is_root) const {
+bool IDElementSelector::match(const pugi::xml_node &node, bool is_toplevel, bool parent_include) const {
string id = node.attribute("id").value();
- if (is_root && layers) {
+ cerr << "match id=" << id << " toplevel=" << is_toplevel << " parent=" << parent_include << endl;
+ if (is_toplevel && layers) {
bool layer_match = std::find(layers->begin(), layers->end(), id) != layers->end();
if (!layer_match) {
cerr << "Rejecting layer \"" << id << "\"" << endl;
@@ -123,12 +124,24 @@ bool IDElementSelector::match(const pugi::xml_node &node, bool included, bool is
bool include_match = std::find(include.begin(), include.end(), id) != include.end();
bool exclude_match = std::find(exclude.begin(), exclude.end(), id) != exclude.end();
+ cerr << " excl=" << exclude_match << " incl=" << include_match << endl;
- if (exclude_match || (!included && !include_match)) {
+ if (is_toplevel) {
+ if (!include.empty())
+ parent_include = false;
+ else
+ parent_include = true;
+ }
+
+ if (exclude_match) {
return false;
}
- return true;
+ if (include_match) {
+ return true;
+ }
+
+ return parent_include;
}
/* Recursively export all SVG elements in the given group. */
@@ -164,11 +177,10 @@ void gerbolyze::SVGDocument::export_svg_group(RenderContext &ctx, const pugi::xm
/* Iterate over the group's children, exporting them one by one. */
for (const auto &node : group.children()) {
- if (!ctx.match(node))
- continue;
-
string name(node.name());
- RenderContext elem_ctx(ctx, xform2d(node.attribute("transform").value()), clip_path);
+ bool match = ctx.match(node);
+ RenderContext elem_ctx(ctx, xform2d(node.attribute("transform").value()), clip_path, match);
+
if (name == "g") {
if (ctx.root()) { /* Treat top-level groups as "layers" like inkscape does. */
cerr << "Forwarding layer name to sink: \"" << node.attribute("id").value() << "\"" << endl;
@@ -184,9 +196,15 @@ void gerbolyze::SVGDocument::export_svg_group(RenderContext &ctx, const pugi::xm
}
} else if (name == "path") {
+ if (!match)
+ continue;
+
export_svg_path(elem_ctx, node);
} else if (name == "image") {
+ if (!match)
+ continue;
+
ImageVectorizer *vec = ctx.settings().m_vec_sel.select(node);
if (!vec) {
cerr << "Cannot resolve vectorizer for node \"" << node.attribute("id").value() << "\"" << endl;
@@ -261,7 +279,7 @@ void gerbolyze::SVGDocument::export_svg_path(RenderContext &ctx, const pugi::xml
} else {
PolyTreeToPaths(ptree_fill, fill_paths);
- RenderContext local_ctx(ctx, xform2d(), fill_paths);
+ RenderContext local_ctx(ctx, xform2d(), fill_paths, true);
pattern->tile(local_ctx);
}
@@ -366,7 +384,7 @@ void gerbolyze::SVGDocument::export_svg_path(RenderContext &ctx, const pugi::xml
} else {
Paths clip;
PolyTreeToPaths(ptree, clip);
- RenderContext local_ctx(ctx, xform2d(), clip);
+ RenderContext local_ctx(ctx, xform2d(), clip, true);
pattern->tile(local_ctx);
}
@@ -490,16 +508,16 @@ gerbolyze::RenderContext::RenderContext(const RenderSettings &settings,
}
gerbolyze::RenderContext::RenderContext(RenderContext &parent, xform2d transform) :
- RenderContext(parent, transform, parent.clip())
+ RenderContext(parent, transform, parent.clip(), parent.included())
{
}
-gerbolyze::RenderContext::RenderContext(RenderContext &parent, xform2d transform, ClipperLib::Paths &clip) :
+gerbolyze::RenderContext::RenderContext(RenderContext &parent, xform2d transform, ClipperLib::Paths &clip, bool included) :
m_sink(parent.sink()),
m_settings(parent.settings()),
m_mat(parent.mat()),
m_root(false),
- m_included(parent.included()),
+ m_included(included),
m_sel(parent.sel()),
m_clip(clip)
{
diff --git a/svg-flatten/src/test/svg_tests.py b/svg-flatten/src/test/svg_tests.py
index c2e5d50..4db827e 100644
--- a/svg-flatten/src/test/svg_tests.py
+++ b/svg-flatten/src/test/svg_tests.py
@@ -82,7 +82,7 @@ class SVGRoundTripTests(unittest.TestCase):
'pattern_stroke_dashed'
}
- def compare_images(self, reference, output, test_name, mean, vectorizer_test=False, rsvg_workaround=False):
+ def compare_images(self, reference, output, test_name, mean=test_mean_default, vectorizer_test=False, rsvg_workaround=False):
ref, out = Image.open(reference), Image.open(output)
if vectorizer_test:
@@ -116,6 +116,49 @@ class SVGRoundTripTests(unittest.TestCase):
self.assertTrue(delta.mean() < mean,
f'Expected mean pixel difference between images to be <{mean}, was {delta.mean():.5g}')
+
+ def run_svg_group_selector_test(self, mode, groups):
+ test_in_svg = 'testdata/group_test_input.svg'
+
+ with tempfile.NamedTemporaryFile(suffix='.svg') as tmp_out_svg,\
+ tempfile.NamedTemporaryFile(suffix='.svg') as tmp_ref_svg,\
+ tempfile.NamedTemporaryFile(suffix='.png') as tmp_out_png,\
+ tempfile.NamedTemporaryFile(suffix='.png') as tmp_in_png:
+
+ if mode == 'inc':
+ group_arg = { 'only_groups': ','.join(groups) }
+ elif mode == 'exc':
+ group_arg = { 'exclude_groups': ','.join(groups) }
+ run_svg_flatten(test_in_svg, tmp_out_svg.name, format='svg', **group_arg)
+
+ with open(test_in_svg, 'r') as in_f:
+ with open(tmp_ref_svg.name, 'w') as out_f:
+ if mode == 'inc':
+ css = '#layer1 { fill: none; }\n'
+ css += '\n'.join(f'#{group} {{ fill: black; }}' for group in groups)
+ elif mode == 'exc':
+ css = '\n'.join(f'#{group} {{ fill: none; }}' for group in groups)
+ else:
+ raise ValueError(f'invalid mode "{mode}"')
+ out_f.write(in_f.read().replace('/* {CSS GOES HERE} */', css))
+
+ run_cargo_cmd('resvg', [tmp_out_svg.name, tmp_out_png.name], check=True, stdout=subprocess.DEVNULL)
+ run_cargo_cmd('resvg', [tmp_ref_svg.name, tmp_in_png.name], check=True, stdout=subprocess.DEVNULL)
+
+ tc_id = f'group_sel_test_{mode}_{"_".join(groups)}'
+ try:
+ self.compare_images(tmp_in_png, tmp_out_png, tc_id, mean=0.001)
+
+ except AssertionError as e:
+ shutil.copyfile(tmp_in_png.name, f'/tmp/gerbolyze-fail-{tc_id}-in.png')
+ shutil.copyfile(tmp_out_png.name, f'/tmp/gerbolyze-fail-{tc_id}-out.png')
+ msg, *rest = e.args
+ msg += '\nFailing test renderings copied to:\n'
+ msg += f' /tmp/gerbolyze-fail-{tc_id}-{{in|out}}.png\n'
+ e.args = (msg, *rest)
+ raise e
+
+
def run_svg_round_trip_test(self, test_in_svg):
with tempfile.NamedTemporaryFile(suffix='.svg') as tmp_out_svg,\
tempfile.NamedTemporaryFile(suffix='.png') as tmp_out_png,\
@@ -128,18 +171,18 @@ class SVGRoundTripTests(unittest.TestCase):
if not vectorizer_test:
run_svg_flatten(test_in_svg, tmp_out_svg.name, format='svg')
- else:
- run_svg_flatten(test_in_svg, tmp_out_svg.name, format='svg',
- svg_white_is_gerber_dark=True,
- clear_color='black', dark_color='white')
-
- if contours_test:
+ elif contours_test:
run_svg_flatten(test_in_svg, tmp_out_svg.name,
clear_color='black', dark_color='white',
svg_white_is_gerber_dark=True,
format='svg',
vectorizer='binary-contours')
+ else:
+ run_svg_flatten(test_in_svg, tmp_out_svg.name, format='svg',
+ svg_white_is_gerber_dark=True,
+ clear_color='black', dark_color='white')
+
if not use_rsvg: # default!
run_cargo_cmd('resvg', [tmp_out_svg.name, tmp_out_png.name], check=True, stdout=subprocess.DEVNULL)
run_cargo_cmd('resvg', [test_in_svg, tmp_in_png.name], check=True, stdout=subprocess.DEVNULL)
@@ -156,10 +199,10 @@ class SVGRoundTripTests(unittest.TestCase):
except AssertionError as e:
shutil.copyfile(tmp_in_png.name, f'/tmp/gerbolyze-fail-{test_in_svg.stem}-in.png')
shutil.copyfile(tmp_out_png.name, f'/tmp/gerbolyze-fail-{test_in_svg.stem}-out.png')
- foo = list(e.args)
- foo[0] += '\nFailing test renderings copied to:\n'
- foo[0] += f' /tmp/gerbolyze-fail-{test_in_svg.stem}-{{in|out}}.png\n'
- e.args = tuple(foo)
+ msg, *rest = e.args
+ msg += '\nFailing test renderings copied to:\n'
+ msg += f' /tmp/gerbolyze-fail-{test_in_svg.stem}-{{in|out}}.png\n'
+ e.args = (msg, *rest)
raise e
for test_in_svg in Path('testdata/svg').glob('*.svg'):
@@ -167,5 +210,11 @@ for test_in_svg in Path('testdata/svg').glob('*.svg'):
gen = lambda testcase: lambda self: self.run_svg_round_trip_test(testcase)
setattr(SVGRoundTripTests, f'test_{test_in_svg.stem}', gen(test_in_svg))
+for group in ["g0", "g00", "g000", "g0000", "g00000", "g0001", "g001", "g0010", "g002", "g01", "g010", "g0100", "g011",
+ "g02", "g020", "g03", "path846-59", "path846-3-2", "path846-5-2", "path846-3-3-8"]:
+ gen = lambda mode, group: lambda self: self.run_svg_group_selector_test(mode, group)
+ setattr(SVGRoundTripTests, f'test_group_sel_inc_{group}', gen('inc', [group]))
+ setattr(SVGRoundTripTests, f'test_group_sel_exc_{group}', gen('exc', [group]))
+
if __name__ == '__main__':
unittest.main()