aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjaseg <git@jaseg.de>2023-03-22 12:12:40 +0100
committerjaseg <git@jaseg.de>2023-03-22 23:27:12 +0100
commita6adfe4d1d19096b45a5db144dd135b3fcf94371 (patch)
treedaca490fe3aae57291444eb463f770271b35c705
parent70a7a0aa95282370a2c1c1aad4db556319770a18 (diff)
downloadgerbolyze-a6adfe4d1d19096b45a5db144dd135b3fcf94371.tar.gz
gerbolyze-a6adfe4d1d19096b45a5db144dd135b3fcf94371.tar.bz2
gerbolyze-a6adfe4d1d19096b45a5db144dd135b3fcf94371.zip
svg-flatten: Add input scaling
-rw-r--r--svg-flatten/include/gerbolyze.hpp4
-rw-r--r--svg-flatten/src/main.cpp27
-rw-r--r--svg-flatten/src/out_gdsii.cpp121
-rw-r--r--svg-flatten/src/proto-gen.py239
-rw-r--r--svg-flatten/src/svg_doc.cpp19
-rw-r--r--svg-flatten/src/util.cpp8
-rw-r--r--svg-flatten/src/wasi_exception_workaround.cpp15
7 files changed, 419 insertions, 14 deletions
diff --git a/svg-flatten/include/gerbolyze.hpp b/svg-flatten/include/gerbolyze.hpp
index 06cbebc..8bd948a 100644
--- a/svg-flatten/include/gerbolyze.hpp
+++ b/svg-flatten/include/gerbolyze.hpp
@@ -264,8 +264,8 @@ namespace gerbolyze {
SVGDocument() : _valid(false) {}
/* true -> load successful */
- bool load(std::istream &in);
- bool load(std::string filename);
+ bool load(std::istream &in, double scale=1.0);
+ bool load(std::string filename, double scale=1.0);
/* true -> load successful */
bool valid() const { return _valid; }
operator bool() const { return valid(); }
diff --git a/svg-flatten/src/main.cpp b/svg-flatten/src/main.cpp
index 1d55437..1fe3454 100644
--- a/svg-flatten/src/main.cpp
+++ b/svg-flatten/src/main.cpp
@@ -137,7 +137,10 @@ int main(int argc, char **argv) {
"Do not preprocess input using usvg (do not use unless you know *exactly* what you're doing)",
0},
{"scale", {"--scale"},
- "Scale input svg lengths by this factor (-o gerber only).",
+ "Scale input SVG by the given factor.",
+ 1},
+ {"gerber_scale", {"--gerber-scale"},
+ "Scale Gerber output coordinates by the given factor.",
1},
{"exclude_groups", {"-e", "--exclude-groups"},
"Comma-separated list of group IDs to exclude from export. Takes precedence over --only-groups.",
@@ -233,20 +236,23 @@ int main(int argc, char **argv) {
PolygonSink *sink = nullptr;
PolygonSink *flattener = nullptr;
PolygonSink *dilater = nullptr;
+ cerr << "Render sink stack:" << endl;
if (fmt == "svg") {
string dark_color = args["svg_dark_color"] ? args["svg_dark_color"].as<string>() : "#000000";
string clear_color = args["svg_clear_color"] ? args["svg_clear_color"].as<string>() : "#ffffff";
sink = new SimpleSVGOutput(*out_f, only_polys, precision, dark_color, clear_color);
+ cerr << " * SVG sink " << endl;
} else if (fmt == "gbr" || fmt == "grb" || fmt == "gerber" || fmt == "gerber-outline") {
outline_mode = fmt == "gerber-outline";
- double scale = args["scale"].as<double>(1.0);
- if (scale != 1.0) {
- cerr << "Info: Loading scaled input @scale=" << scale << endl;
+ double gerber_scale = args["scale"].as<double>(1.0);
+ if (gerber_scale != 1.0) {
+ cerr << "Info: Scaling gerber output @gerber_scale=" << gerber_scale << endl;
}
- sink = new SimpleGerberOutput(*out_f, only_polys, 4, precision, scale, {0,0}, args["flip_gerber_polarity"]);
+ sink = new SimpleGerberOutput(*out_f, only_polys, 4, precision, gerber_scale, {0,0}, args["flip_gerber_polarity"]);
+ cerr << " * Gerber sink " << endl;
} else if (fmt == "s-exp" || fmt == "sexp" || fmt == "kicad") {
if (!args["sexp_mod_name"]) {
@@ -257,6 +263,7 @@ int main(int argc, char **argv) {
sink = new KicadSexpOutput(*out_f, args["sexp_mod_name"], sexp_layer, only_polys);
force_flatten = true;
is_sexp = true;
+ cerr << " * KiCAD SExp sink " << endl;
} else {
cerr << "Error: Unknown output format \"" << fmt << "\"" << endl;
@@ -268,11 +275,13 @@ int main(int argc, char **argv) {
if (args["dilate"]) {
dilater = new Dilater(*top_sink, args["dilate"].as<double>());
top_sink = dilater;
+ cerr << " * Dilater " << endl;
}
if (args["flatten"] || (force_flatten && !args["no_flatten"])) {
flattener = new Flattener(*top_sink);
top_sink = flattener;
+ cerr << " * Flattener " << endl;
}
/* Because the C++ stdlib is bullshit */
@@ -454,8 +463,14 @@ int main(int argc, char **argv) {
SVGDocument doc;
//cerr << "Loading temporary file " << frob << endl;
+
+ double scale = args["scale"].as<double>(1.0);
+ if (scale != 1.0) {
+ cerr << "Info: Loading scaled input @scale=" << scale << endl;
+ }
+
ifstream load_f(frob);
- if (!doc.load(load_f)) {
+ if (!doc.load(load_f, scale)) {
cerr << "Error loading input file \"" << in_f_name << "\", exiting." << endl;
return EXIT_FAILURE;
}
diff --git a/svg-flatten/src/out_gdsii.cpp b/svg-flatten/src/out_gdsii.cpp
new file mode 100644
index 0000000..fc7760b
--- /dev/null
+++ b/svg-flatten/src/out_gdsii.cpp
@@ -0,0 +1,121 @@
+/*
+ * This file is part of gerbolyze, a vector image preprocessing toolchain
+ * Copyright (C) 2021 Jan Sebastian Götte <gerbolyze@jaseg.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <cmath>
+#include <algorithm>
+#include <string>
+#include <iostream>
+#include <iomanip>
+#include <time.h>
+#include <gerbolyze.hpp>
+#include <svg_import_defs.h>
+
+using namespace gerbolyze;
+using namespace std;
+
+SimpleGDSIIOutput::SimpleGDSIIOutput(ostream &out, bool only_polys=false, double scale, d2p offset, bool flip_polarity, std::string libname)
+ : StreamPolygonSink(out, only_polys),
+ m_offset(offset),
+ m_scale(scale),
+ m_flip_pol(flip_polarity),
+ m_libname(libname)
+{
+}
+
+void SimpleGDSIIOutput::header_impl(d2p origin, d2p size) {
+ m_offset[0] += origin[0] * m_scale;
+ m_offset[1] += origin[1] * m_scale;
+ m_width = (size[0] - origin[0]) * m_scale;
+ m_height = (size[1] - origin[1]) * m_scale;
+
+ gds_wr16(GDS_HEADER, {600});
+
+ time_t t = time(NULL);
+ struct tm;
+ gmtime_r(&t, &tm);
+ gds_wr16(GDS_BGNLIB, {tm.tm_year, tm.tm_month, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
+ tm.tm_year, tm.tm_month, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec});
+
+ gds_wr_str(GDS_LIBNAME, m_libname);
+}
+
+void gds_wr_d(uint16_t tag, double value) {
+ uint64_t d_ul = reinterpret_cast<uint64_t>(value);
+ uint64_t sign = !!(casted & (1ULL<<63));
+ int exp = (casted >> 52) & 0x7ffULL;
+ uint64_t mant = (casted & ((1ULL<<52)-1)) | (1ULL<<52);
+
+ int new_exp = (exp - 1023) / 4 + 64;
+ int exp_mod = (exp + 1) % 4;
+ uint64_t new_mant = mant * (1<<exp_mod);
+
+ gds_wr16
+}
+
+SimpleGDSIIOutput& SimpleGDSIIOutput::operator<<(GerberPolarityToken pol) {
+ assert(pol == GRB_POL_DARK || pol == GRB_POL_CLEAR);
+
+ if (m_outline_mode) {
+ assert(pol == GRB_POL_DARK);
+ }
+
+ if ((pol == GRB_POL_DARK) != m_flip_pol) {
+ m_out << "%LPD*%" << endl;
+ } else {
+ m_out << "%LPC*%" << endl;
+ }
+
+ return *this;
+}
+SimpleGDSIIOutput& SimpleGDSIIOutput::operator<<(const Polygon &poly) {
+ if (poly.size() < 3 && !m_outline_mode) {
+ cerr << "Warning: " << poly.size() << "-element polygon passed to SimpleGerberOutput" << endl;
+ return *this;
+ }
+
+ /* NOTE: Clipper and gerber both have different fixed-point scales. We get points in double mm. */
+ double x = round((poly[0][0] * m_scale + m_offset[0]) * m_gerber_scale);
+ double y = round((m_height - poly[0][1] * m_scale + m_offset[1]) * m_gerber_scale);
+ if (!m_outline_mode) {
+ m_out << "G36*" << endl;
+ }
+
+ m_out << "X" << setw(m_digits_int + m_digits_frac) << setfill('0') << std::internal /* isn't C++ a marvel of engineering? */ << (long long int)x
+ << "Y" << setw(m_digits_int + m_digits_frac) << setfill('0') << std::internal << (long long int)y
+ << "D02*" << endl;
+ m_out << "G01*" << endl;
+
+ for (size_t i=1; i<poly.size(); i++) {
+ double x = round((poly[i][0] * m_scale + m_offset[0]) * m_gerber_scale);
+ double y = round((m_height - poly[i][1] * m_scale + m_offset[1]) * m_gerber_scale);
+ m_out << "X" << setw(m_digits_int + m_digits_frac) << setfill('0') << std::internal << (long long int)x
+ << "Y" << setw(m_digits_int + m_digits_frac) << setfill('0') << std::internal << (long long int)y
+ << "D01*" << endl;
+ }
+
+ if (!m_outline_mode) {
+ m_out << "G37*" << endl;
+ }
+
+ return *this;
+}
+
+void SimpleGDSIIOutput::footer_impl() {
+
+}
+
diff --git a/svg-flatten/src/proto-gen.py b/svg-flatten/src/proto-gen.py
new file mode 100644
index 0000000..2f45980
--- /dev/null
+++ b/svg-flatten/src/proto-gen.py
@@ -0,0 +1,239 @@
+#!/usr/bin/env python3
+
+import re
+import textwrap
+import ast
+
+svg_str = lambda content: content if isinstance(content, str) else '\n'.join(str(c) for c in content)
+
+class Pattern:
+ def __init__(self, w, h, content):
+ self.w = w
+ self.h = h
+ self.content = content
+
+ @property
+ def svg_id(self):
+ return f'pat-{id(self):16x}'
+
+ def __str__(self):
+ return textwrap.dedent(f'''
+ <pattern id="{self.svg_id}" viewBox="0,0,{self.w},{self.h}" width="{self.w}" height="{self.h}" patternUnits="userSpaceOnUse">
+ {svg_str(self.content)}
+ </pattern>''')
+
+ def make_rect(x, y, w, h):
+ return f'<rect x="{x}" y="{y}" w="{w}" h="{h}" fill="url(#{self.svg_id})"/>'
+
+class CirclePattern(Pattern):
+ def __init__(self, d, w, h=None):
+ self.d = d
+ self.w = w
+ self.h = h or w
+
+ @property
+ def content(self):
+ return f'<circle cx={self.w/2} cy={self.h/2} r={self.d/2}/>'
+
+make_layer = lambda layer_name, content: \
+ f'<g id="g-{layer_name.replace(" ", "-")}" inkscape:label="{layer_name}" inkscape:groupmode="layer">{svg_str(content)}</g>'
+
+svg_template = textwrap.dedent('''
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
+ <svg version="1.1" width="{w}mm" height="{h}mm" viewBox="0 0 {w} {h}" id="svg18" sodipodi:docname="proto.svg"
+ inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs id="defs2">
+ {defs}
+ </defs>
+ <sodipodi:namedview inkscape:current-layer="g-top-copper" id="namedview4" pagecolor="#ffffff" bordercolor="#666666"
+ borderopacity="1.0" inkscape:showpageshadow="2" inkscape:pageopacity="0.0" inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1" inkscape:document-units="mm" showgrid="false" inkscape:zoom="2.8291492"
+ inkscape:cx="157.29111" inkscape:cy="80.943063" inkscape:window-width="1920" inkscape:window-height="1011"
+ inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" />
+ {layers}
+ </svg>
+''')
+
+class PatternProtoArea:
+ def __init__(self, pitch_x, pitch_y=None):
+ self.pitch_x = pitch_x
+ self.pitch_y = pitch_y or pitch_x
+
+ @property
+ def pitch(self):
+ if self.pitch_x != self.pitch_y:
+ raise ValueError('Pattern has different X and Y pitches')
+ return self.pitch_x
+
+ def fit_rect(self, x, y, w, h, center=True):
+ w_fit, h_fit = round(w - (w % self.pitch_x), 6), round(h - (h % self.pitch_y), 6)
+
+ if center:
+ x = x + (w-w_fit)/2
+ y = y + (h-h_fit)/2
+ return x, y, w_fit, h_fit
+
+ else:
+ return x, y, w_fit, h_fit
+
+class THTProtoAreaCircles:
+ def __init__(self, pad_dia=2.0, drill=1.0, pitch=2.54, sides='both', plated=True):
+ super(pitch)
+ self.pad_dia = pad_dia
+ self.drill = drill
+ self.drill_pattern = CirclePattern(self.drill, self.pitch)
+ self.pad_pattern = CirclePattern(self.pad_dia, self.pitch)
+ self.patterns = [self.drill_pattern, self.pad_pattern]
+ self.plated = plated
+ self.sides = sides
+
+ def generate(self, x, y, w, h, center=True):
+ x, y, w, h = self.fit_rect(x, y, w, h, center)
+ drill = 'plated drill' if self.plated else 'nonplated drill'
+ d = { drill: self.drill_pattern.make_rect(x, y, w, h) }
+
+ if self.sides in ('top', 'both'):
+ d['top copper'] = self.pad_pattern.make_rect(x, y, w, h)
+ if self.sides in ('bottom', 'both'):
+ d['bottom copper'] = self.pad_pattern.make_rect(x, y, w, h)
+
+ return d
+
+class ProtoBoard:
+ def __init__(self, desc_str):
+ pass
+
+def convert_to_mm(value, unit):
+ match unit.lower():
+ case 'mm': return value
+ case 'cm': return value*10
+ case 'in': return value*25.4
+ case 'mil': return value/1000*25.4
+ raise ValueError(f'Invalid unit {unit}, allowed units are mm, cm, in, and mil.')
+
+value_re = re.compile('([0-9]*\.?[0-9]+)(cm|mm|in|mil|%)')
+def eval_value(value, total_length=None):
+ if not isinstance(value, str):
+ return None
+
+ m = value_re.match(value)
+ number, unit = m.groups()
+ if unit == '%':
+ if total_length is None:
+ raise ValueError('Percentages are not allowed for this value')
+ return total_length * float(number) / 100
+ return convert_to_mm(float(number), unit)
+
+class PropLayout:
+ def __init__(self, content, direction, proportions):
+ self.content = content
+ self.direction = direction
+ self.proportions = proportions
+ if len(content) != len(proportions):
+ raise ValueError('proportions and content must have same length')
+
+ def layout(self, length):
+ out = [ eval_value(value, length) for value in self.proportions ]
+ total_length = sum(value for value in out if value is not None)
+ if length - total_length < -1e-6:
+ raise ValueError(f'Proportions sum to {total_length} mm, which is greater than the available space of {length} mm.')
+
+ leftover = length - total_length
+ sum_props = sum( (value or 1.0) for value in self.proportions if not isinstance(value, str) )
+ return [ (leftover * (value or 1.0) / sum_props if not isinstance(value, str) else calculated)
+ for value, calculated in zip(self.proportions, out) ]
+
+ def __str__(self):
+ children = ', '.join( f'{elem}:{width}' for elem, width in zip(self.content, self.proportions))
+ return f'PropLayout[{self.direction.upper()}]({children})'
+
+def _map_expression(node):
+ match node:
+ case ast.Name():
+ return node.id
+
+ case ast.Constant():
+ return node.value
+
+ case ast.BinOp(op=ast.BitOr()) | ast.BinOp(op=ast.BitAnd()):
+ left_prop = right_prop = None
+
+ left, right = node.left, node.right
+
+ if isinstance(left, ast.BinOp) and isinstance(left.op, ast.MatMult):
+ left_prop = _map_expression(left.right)
+ left = left.left
+
+ if isinstance(right, ast.BinOp) and isinstance(right.op, ast.MatMult):
+ right_prop = _map_expression(right.right)
+ right = right.left
+
+ direction = 'h' if isinstance(node.op, ast.BitOr) else 'v'
+ left, right = _map_expression(left), _map_expression(right)
+
+ if isinstance(left, PropLayout) and left.direction == direction and left_prop is None:
+ left.content.append(right)
+ left.proportions.append(right_prop)
+ return left
+
+ elif isinstance(right, PropLayout) and right.direction == direction and right_prop is None:
+ right.content.insert(0, left)
+ right.proportions.insert(0, left_prop)
+ return right
+
+ else:
+ return PropLayout([left, right], direction, [left_prop, right_prop])
+
+ case ast.BinOp(op=ast.MatMult()):
+ raise SyntaxError(f'Unexpected width specification "{ast.unparse(node.right)}"')
+
+ case _:
+ raise SyntaxError(f'Invalid layout expression "{ast.unparse(node)}"')
+
+def parse_layout(expr):
+ ''' Example layout:
+
+ ( tht @ 2in | smd ) @ 50% / tht
+ '''
+
+ expr = re.sub(r'\s', '', expr).lower()
+ expr = re.sub(r'([0-9]*\.?[0-9]+)(mm|cm|in|mil|%)', r'"\1\2"', expr)
+ expr = expr.replace('/', '&')
+ try:
+ expr = ast.parse(expr, mode='eval').body
+ match expr:
+ case ast.Name():
+ return PropLayout([expr.id], 'h', [None])
+
+ case ast.BinOp(op=ast.MatMult()):
+ assert isinstance(expr.right, ast.Constant)
+ return PropLayout([_map_expression(expr.left)], 'h', [expr.right.value])
+
+ case _:
+ return _map_expression(expr)
+ except SyntaxError as e:
+ raise SyntaxError('Invalid layout expression') from e
+
+if __name__ == '__main__':
+ import sys
+ for line in [
+ 'tht',
+ 'tht@1mm',
+ 'tht|tht',
+ 'tht@1mm|tht',
+ 'tht|tht|tht',
+ 'tht@1mm|tht@2mm|tht@3mm',
+ '(tht@1mm|tht@2mm)|tht@3mm',
+ 'tht@1mm|(tht@2mm|tht@3mm)',
+ 'tht@2|tht|tht',
+ '(tht@1mm|tht|tht@3mm) / tht',
+ ]:
+ layout = parse_layout(line)
+ print(line, '->', layout)
+ print(' ', layout.layout(100))
+ print()
+
diff --git a/svg-flatten/src/svg_doc.cpp b/svg-flatten/src/svg_doc.cpp
index de56fa1..323a12b 100644
--- a/svg-flatten/src/svg_doc.cpp
+++ b/svg-flatten/src/svg_doc.cpp
@@ -32,14 +32,14 @@ using namespace gerbolyze;
using namespace std;
using namespace ClipperLib;
-bool gerbolyze::SVGDocument::load(string filename) {
+bool gerbolyze::SVGDocument::load(string filename, double scale) {
ifstream in_f;
in_f.open(filename);
- return in_f && load(in_f);
+ return in_f && load(in_f, scale);
}
-bool gerbolyze::SVGDocument::load(istream &in) {
+bool gerbolyze::SVGDocument::load(istream &in, double scale) {
/* Load XML document */
auto res = svg_doc.load(in);
if (!res) {
@@ -62,8 +62,8 @@ bool gerbolyze::SVGDocument::load(istream &in) {
/* usvg resolves all units, but instead of outputting some reasonable absolute length like mm, it converts
* everything to px, which depends on usvg's DPI setting (--dpi).
*/
- page_w_mm = page_w / assumed_usvg_dpi * 25.4;
- page_h_mm = page_h / assumed_usvg_dpi * 25.4;
+ page_w_mm = page_w / assumed_usvg_dpi * 25.4 * scale;
+ page_h_mm = page_h / assumed_usvg_dpi * 25.4 * scale;
if (!(page_w_mm > 0.0 && page_h_mm > 0.0 && page_w_mm < 10e3 && page_h_mm < 10e3)) {
cerr << "Warning: Page has zero or negative size, or is larger than 10 x 10 meters! Parsed size: " << page_w << " x " << page_h << " millimeter" << endl;
}
@@ -256,6 +256,11 @@ void gerbolyze::SVGDocument::export_svg_path(RenderContext &ctx, const pugi::xml
bool has_fill = fill_color;
bool has_stroke = stroke_color && stroke_width > 0.0;
+ cerr << "processing svg path" << endl;
+ cerr << " * " << fill_paths.size() << " fill paths" << endl;
+ cerr << " * " << stroke_closed.size() << " closed strokes" << endl;
+ cerr << " * " << stroke_open.size() << " open strokes" << endl;
+
/* In outline mode, identify drills before applying clip */
if (ctx.settings().outline_mode && has_fill && fill_color != GRB_PATTERN_FILL) {
/* Polsby-Popper test */
@@ -301,8 +306,10 @@ void gerbolyze::SVGDocument::export_svg_path(RenderContext &ctx, const pugi::xml
c.AddPaths(ctx.clip(), ptClip, /* closed */ true);
c.StrictlySimple(true);
+ cerr << "clipping " << fill_paths.size() << " paths, got polytree with " << ptree_fill.ChildCount() << " top-level children" << endl;
/* fill rules are nonzero since both subject and clip have already been normalized by clipper. */
c.Execute(ctIntersection, ptree_fill, pftNonZero, pftNonZero);
+ cerr << " > " << ptree_fill.ChildCount() << " clipped fill ptree top-level children" << endl;
}
/* Call out to pattern tiler for pattern fills. The path becomes the clip here. */
@@ -402,6 +409,7 @@ void gerbolyze::SVGDocument::export_svg_path(RenderContext &ctx, const pugi::xml
stroke_clip.AddPaths(stroke_closed, ptSubject, /* closed */ true);
stroke_clip.AddPaths(stroke_open, ptSubject, /* closed */ false);
stroke_clip.Execute(ctDifference, ptree, pftNonZero, pftNonZero);
+ cerr << " > " << ptree.ChildCount() << " clipped stroke ptree top-level children" << endl;
/* Did any part of the path clip the clip path (which defaults to the document border)? */
bool nothing_clipped = ptree.Total() == 0;
@@ -520,6 +528,7 @@ void gerbolyze::SVGDocument::render_to_list(const RenderSettings &rset, vector<p
void gerbolyze::SVGDocument::setup_viewport_clip() {
/* Set up view port clip path */
Path vb_path;
+ cerr << "setting up viewport clip at " << vb_x << ", " << vb_y << " with size " << vb_w << ", " << vb_h << endl;
for (d2p &p : vector<d2p> {
{vb_x, vb_y},
{vb_x+vb_w, vb_y},
diff --git a/svg-flatten/src/util.cpp b/svg-flatten/src/util.cpp
index 0597360..1af5fb6 100644
--- a/svg-flatten/src/util.cpp
+++ b/svg-flatten/src/util.cpp
@@ -34,13 +34,19 @@ int gerbolyze::run_cargo_command(const char *cmd_name, std::vector<std::string>
bool found = false;
int proc_rc = -1;
for (int i=0; i<3; i++) {
+ std::string envvar_cx;
const char *envvar_val;
switch (i) {
case 0:
if ((envvar_val = getenv(envvar)) == NULL) {
continue;
} else {
- cmdline_c[0] = envvar_val;
+ if (envvar_val[0] == '~') {
+ envvar_cx = homedir_s + std::string(envvar_val+1);
+ cmdline_c[0] = envvar_cx.c_str();
+ } else {
+ cmdline_c[0] = envvar_val;
+ }
}
break;
diff --git a/svg-flatten/src/wasi_exception_workaround.cpp b/svg-flatten/src/wasi_exception_workaround.cpp
new file mode 100644
index 0000000..9d20744
--- /dev/null
+++ b/svg-flatten/src/wasi_exception_workaround.cpp
@@ -0,0 +1,15 @@
+
+#include <typeinfo>
+#include <cstdlib>
+using namespace std;
+
+void __cxa_allocate_exception(size_t size) {
+ (void) size;
+ abort();
+}
+
+void __cxa_throw(void* thrown_exception, struct std::type_info * tinfo, void (*dest)(void*)) {
+ (void) thrown_exception, (void) tinfo, (void) dest;
+ abort();
+}
+