diff --git a/.gitignore b/.gitignore
index 567609b..252026b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
build/
+*.pem
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000..85f3da2
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,3 @@
+{
+ "recommendations": ["biomejs.biome", "ziglang.vscode-zig"]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..7b6a3fe
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,13 @@
+{
+ "editor.defaultFormatter": "biomejs.biome",
+ "editor.formatOnSave": true,
+ "[javascript]": {
+ "editor.defaultFormatter": "biomejs.biome"
+ },
+ "[typescript]": {
+ "editor.defaultFormatter": "biomejs.biome"
+ },
+ "[react]": {
+ "editor.defaultFormatter": "biomejs.biome"
+ }
+}
diff --git a/biome.json b/biome.json
new file mode 100644
index 0000000..1e62364
--- /dev/null
+++ b/biome.json
@@ -0,0 +1,23 @@
+{
+ "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
+ "vcs": {
+ "enabled": false,
+ "clientKind": "git",
+ "useIgnoreFile": false
+ },
+ "files": {
+ "ignoreUnknown": false,
+ "ignore": ["**/alpine-3.10.4.js", "**/path-data-polyfill.js"]
+ },
+ "formatter": {
+ "enabled": true,
+ "indentStyle": "space",
+ "lineWidth": 120,
+ "indentWidth": 4
+ },
+ "linter": {
+ "enabled": true,
+ "rules": { "recommended": true, "complexity": { "useLiteralKeys": "info" } }
+ },
+ "javascript": { "formatter": { "quoteStyle": "double" } }
+}
diff --git a/native/.gitignore b/native/.gitignore
index dc6b79a..b588143 100644
--- a/native/.gitignore
+++ b/native/.gitignore
@@ -1,5 +1,5 @@
-zig-cache/
-zig-out/
+*zig-cache/
+*zig-out/
*.o
*.a
*.dylib
diff --git a/native/src/gingerbread.zig b/native/src/gingerbread.zig
index e4e341a..e09bb66 100644
--- a/native/src/gingerbread.zig
+++ b/native/src/gingerbread.zig
@@ -11,7 +11,7 @@ const wasm = @import("wasm.zig");
const a = wasm.allocator;
-fn trace(allocator: std.mem.Allocator, layer_name: []const u8, scale_factor: f64, image_pixels: [*]u8, image_width: usize, image_height: usize, writer: anytype) !void {
+fn trace(allocator: std.mem.Allocator, layer_name: []const u8, scale_factor: f64, image_pixels: [*]u8, image_width: usize, image_height: usize, writer: anytype, width_mm: f64) !void {
var bitmap = try potrace.Bitmap.from_image(allocator, .{
.pixels = image_pixels,
.w = image_width,
@@ -39,7 +39,7 @@ fn trace(allocator: std.mem.Allocator, layer_name: []const u8, scale_factor: f64
print("Polylist fractured\n", .{});
- try pcb.polylist_to_footprint(polylist, layer_name, scale_factor, writer);
+ try pcb.polylist_to_footprint(polylist, layer_name, scale_factor, writer, width_mm);
}
test "trace" {
@@ -70,7 +70,7 @@ export fn conversion_start() void {
pcb.start_pcb(conversion_buffer.?.writer()) catch @panic("memory");
}
-export fn conversion_add_raster_layer(layer: u32, scale_factor: f64, image_pixels: [*]u8, image_width: u32, image_height: u32) void {
+export fn conversion_add_raster_layer(layer: u32, scale_factor: f64, image_pixels: [*]u8, image_width: u32, image_height: u32, width_mm: f64) void {
const layer_name = switch (layer) {
1 => "F.Cu",
2 => "B.Cu",
@@ -81,7 +81,7 @@ export fn conversion_add_raster_layer(layer: u32, scale_factor: f64, image_pixel
else => "Unknown",
};
- trace(a, layer_name, scale_factor, image_pixels, image_width, image_height, conversion_buffer.?.writer()) catch @panic("memory");
+ trace(a, layer_name, scale_factor, image_pixels, image_width, image_height, conversion_buffer.?.writer(), width_mm) catch @panic("memory");
}
export fn conversion_start_poly() void {
@@ -91,9 +91,21 @@ export fn conversion_start_poly() void {
export fn conversion_add_poly_point(
x: f64,
y: f64,
+ layer_number: u32,
scale_factor: f64,
+ width_mm: f64,
) void {
- pcb.add_xx_poly_point(.{ .x = x, .y = y }, scale_factor, conversion_buffer.?.writer()) catch @panic("memory");
+ const layer_name = switch (layer_number) {
+ 1 => "F.Cu",
+ 2 => "B.Cu",
+ 3 => "F.SilkS",
+ 4 => "B.SilkS",
+ 5 => "F.Mask",
+ 6 => "B.Mask",
+ else => "Unknown",
+ };
+
+ pcb.add_xx_poly_point(.{ .x = x, .y = y }, layer_name, scale_factor, conversion_buffer.?.writer(), width_mm) catch @panic("memory");
}
export fn conversion_end_poly(layer: u32, width: f32, fill: bool) void {
@@ -115,3 +127,7 @@ export fn conversion_finish() wasm.StringResult {
pcb.end_pcb(&conversion_buffer.?.writer()) catch @panic("memory");
return wasm.return_string(conversion_buffer.?.toOwnedSlice() catch @panic("memory"));
}
+
+export fn set_mirror_back_layers(val: bool) void {
+ pcb.mirror_back_layers = val;
+}
diff --git a/native/src/pcb.zig b/native/src/pcb.zig
index 15cefb0..52308b5 100644
--- a/native/src/pcb.zig
+++ b/native/src/pcb.zig
@@ -6,6 +6,10 @@ const Poly = geometry.Poly;
const PolyList = geometry.PolyList;
const FauxUUID = @import("fauxuuid.zig").FauxUUID;
+fn is_back_layer(layer: []const u8) bool {
+ return std.ascii.startsWithIgnoreCase(layer, "B.");
+}
+
pub fn start_pcb(writer: anytype) !void {
try writer.writeAll("(kicad_pcb (version 20211014) (generator pcbnew)\n");
try writer.writeAll("(layers\n");
@@ -29,31 +33,41 @@ pub fn start_xx_poly(kind: []const u8, writer: anytype) !void {
try writer.print(" ({s}_poly\n", .{kind});
try writer.writeAll(" (pts\n");
}
+pub var mirror_back_layers: bool = true;
+
+pub fn add_xx_poly_point(pt: geometry.Point, layer_name: []const u8, scale_factor: f64, writer: anytype, width_mm: f64) !void {
+ const scaled_x = pt.x * scale_factor;
+ const scaled_y = pt.y * scale_factor;
+
+ // For back layers, mirror around y=0 then translate back
+ const final_x = if (is_back_layer(layer_name) and mirror_back_layers)
+ -scaled_x + width_mm
+ else
+ scaled_x;
-pub fn add_xx_poly_point(pt: geometry.Point, scale_factor: f64, writer: anytype) !void {
- try writer.print(" (xy {d:.3} {d:.3})\n", .{ pt.x * scale_factor, pt.y * scale_factor });
+ try writer.print(" (xy {d:.3} {d:.3})\n", .{ final_x, scaled_y });
}
-pub fn end_xx_poly(layer: []const u8, width: f64, fill: bool, writer: anytype) !void {
+pub fn end_xx_poly(layer_name: []const u8, line_width: f64, fill: bool, writer: anytype) !void {
try writer.writeAll(" )\n");
- try writer.print(" (layer \"{s}\")\n", .{layer});
- try writer.print(" (width {d:.3})\n", .{width});
+ try writer.print(" (layer \"{s}\")\n", .{layer_name});
+ try writer.print(" (width {d:.3})\n", .{line_width});
try writer.print(" (fill {s})\n", .{if (fill) "solid" else "none"});
try writer.print(" (tstamp \"{s}\")\n", .{FauxUUID.init()});
try writer.writeAll(" )\n");
}
-pub fn points_to_xx_poly(kind: []const u8, pts: []geometry.Point, scale_factor: f64, layer: []const u8, width: f64, fill: bool, writer: anytype) !void {
+pub fn points_to_xx_poly(kind: []const u8, pts: []geometry.Point, scale_factor: f64, layer_name: []const u8, line_width: f64, fill: bool, writer: anytype, width_mm: f64) !void {
try start_xx_poly(kind, writer);
for (pts) |pt| {
- try add_xx_poly_point(pt, scale_factor, writer);
+ try add_xx_poly_point(pt, layer_name, scale_factor, writer, width_mm);
}
- try end_xx_poly(layer, width, fill, writer);
+ try end_xx_poly(layer_name, line_width, fill, writer);
}
-pub fn polylist_to_footprint(polylist: PolyList, layer: []const u8, scale_factor: f64, writer: anytype) !void {
+pub fn polylist_to_footprint(polylist: PolyList, layer: []const u8, scale_factor: f64, writer: anytype, width_mm: f64) !void {
try writer.writeAll("(footprint \"Graphics\"\n");
try writer.print(" (layer \"{s}\")\n", .{layer});
try writer.writeAll(" (at 0 0)\n");
@@ -62,7 +76,7 @@ pub fn polylist_to_footprint(polylist: PolyList, layer: []const u8, scale_factor
try writer.print(" (tedit \"{s}\")\n", .{FauxUUID.init()});
for (polylist.items) |poly| {
- try points_to_xx_poly("fp", poly.outline, scale_factor, layer, 0, true, writer);
+ try points_to_xx_poly("fp", poly.outline, scale_factor, layer, 0, true, writer, width_mm);
}
try writer.writeAll(")\n");
diff --git a/web/help.html b/web/help.html
index 4974ef9..b26f0c0 100644
--- a/web/help.html
+++ b/web/help.html
@@ -49,6 +49,11 @@
Drills
Affinity and KiCAD.
+ Mirror back layers
+ Gingerbread allows you to mirror the B.SilkS, B.Mask, and B.Cu layers so that the it is correctly mirrored in the output. This can be switched
+ off if you don't want this behavior.
+
+
Exporting your design
When exporting you design to an SVG for Gingerbread, click the More button and setup the export parameters as shown below so that "Rasterize" is set to
"Nothing", "Export text as curves" is checked, and "Flatten transforms" is checked.
diff --git a/web/index.html b/web/index.html
index 29948a3..2fe7e05 100644
--- a/web/index.html
+++ b/web/index.html
@@ -126,6 +126,13 @@
Layers
+
+
+
+ Mirror back layers
+
+
+
diff --git a/web/scripts/libgingerbread.js b/web/scripts/libgingerbread.js
index d7e72b0..41a7b5b 100644
--- a/web/scripts/libgingerbread.js
+++ b/web/scripts/libgingerbread.js
@@ -1,33 +1,49 @@
import { ZigWASM } from "./zigwasm.js";
export class LibGingerbread {
- static wasm_src = "./native/gingerbread.wasm";
- static wasm_module;
+ static #wasm_src = "./native/gingerbread.wasm";
+ static #wasm_module = null;
constructor(zig) {
this.zig = zig;
}
static async new() {
- if (this.wasm_module == null) {
- this.wasm_module = await ZigWASM.compile(this.wasm_src);
+ if (!LibGingerbread.#wasm_module) {
+ LibGingerbread.#wasm_module = await ZigWASM.compile(LibGingerbread.#wasm_src);
}
-
- return new this(await ZigWASM.new(this.wasm_module));
+ return new LibGingerbread(await ZigWASM.new(LibGingerbread.#wasm_module));
}
conversion_start() {
this.zig.exports.conversion_start();
}
- conversion_add_raster_layer(layer, scale, image) {
+ conversion_add_raster_layer(layer, scale, imageData, width_mm) {
if (!this.image_array_ptr) {
- this.image_array_ptr = this.zig.allocate(image.data.byteLength);
+ this.image_array_ptr = this.zig.allocate(imageData.data.byteLength);
}
- this.image_array_ptr.u8().set(image.data);
+ this.image_array_ptr.u8().set(imageData.data);
- this.zig.exports.conversion_add_raster_layer(layer, scale, this.image_array_ptr.address, image.width, image.height);
+ try {
+ this.zig.exports.conversion_add_raster_layer(
+ layer,
+ scale,
+ this.image_array_ptr.address,
+ imageData.width,
+ imageData.height,
+ width_mm,
+ );
+ } catch (error) {
+ console.log("===================conversion_add_raster_layer error============================");
+ console.log("layer:", layer, "scale:", scale, "width:", imageData.width, "height:", imageData.height);
+ console.log("imageData:", imageData);
+ console.log("image_array_ptr:", this.image_array_ptr);
+ console.error("WASM error in conversion_add_raster_layer:", error);
+ console.log("================================================");
+ throw error;
+ }
}
conversion_finish() {
@@ -39,16 +55,19 @@ export class LibGingerbread {
this.zig.exports.conversion_start_poly();
}
- conversion_add_poly_point(x, y, scale_factor) {
- this.zig.exports.conversion_add_poly_point(x, y, scale_factor);
+ conversion_add_poly_point(x, y, layer_number, scale_factor, width_mm) {
+ this.zig.exports.conversion_add_poly_point(x, y, layer_number, scale_factor, width_mm);
}
- conversion_end_poly(layer, width, fill) {
- this.zig.exports.conversion_end_poly(layer, width, fill);
+ conversion_end_poly(layer, line_width, fill) {
+ this.zig.exports.conversion_end_poly(layer, line_width, fill);
}
conversion_add_drill(x, y, d, scale_factor) {
this.zig.exports.conversion_add_drill(x, y, d, scale_factor);
}
+ set_mirror_back_layers(val) {
+ this.zig.exports.set_mirror_back_layers(val);
+ }
}
diff --git a/web/scripts/main.js b/web/scripts/main.js
index 28932dc..5aff97b 100644
--- a/web/scripts/main.js
+++ b/web/scripts/main.js
@@ -23,20 +23,20 @@ class Design {
{
name: "Drill",
type: "drill",
- selector: "#Drill, #Drills, [*|label=\"Drills\"]",
+ selector: '#Drill, #Drills, [*|label="Drills"]',
color: "Fuchsia",
},
{
name: "FSilkS",
type: "raster",
- selector: "#FSilkS, #F\\.SilkS, [*|label=\"F\\.SilkS\"]",
+ selector: '#FSilkS, #F\\.SilkS, [*|label="F\\.SilkS"]',
color: "white",
number: 3,
},
{
name: "FMask",
type: "raster",
- selector: "#FMask, #F\\.Mask, [*|label=\"F\\.Mask\"]",
+ selector: '#FMask, #F\\.Mask, [*|label="F\\.Mask"]',
color: "black",
is_mask: true,
number: 5,
@@ -44,21 +44,21 @@ class Design {
{
name: "FCu",
type: "raster",
- selector: "#FCu, #F\\.Cu, [*|label=\"F\\.Cu\"]",
+ selector: '#FCu, #F\\.Cu, [*|label="F\\.Cu"]',
color: "gold",
number: 1,
},
{
name: "BCu",
type: "raster",
- selector: "#BCu, #B\\.Cu, [*|label=\"B\\.Cu\"]",
+ selector: '#BCu, #B\\.Cu, [*|label="B\\.Cu"]',
color: "gold",
number: 2,
},
{
name: "BMask",
type: "raster",
- selector: "#BMask, #B\\.Mask, [*|label=\"B\\.Mask\"]",
+ selector: '#BMask, #B\\.Mask, [*|label="B\\.Mask"]',
color: "black",
is_mask: true,
number: 6,
@@ -66,14 +66,14 @@ class Design {
{
name: "BSilkS",
type: "raster",
- selector: "#BSilkS, #B\\.SilkS, [*|label=\"B\\.SilkS\"]",
+ selector: '#BSilkS, #B\\.SilkS, [*|label="B\\.SilkS"]',
color: "white",
number: 4,
},
{
name: "EdgeCuts",
type: "vector",
- selector: "#EdgeCuts, #Edge\\.Cuts, [*|label=\"Edge\\.Cuts\"]",
+ selector: '#EdgeCuts, #Edge\\.Cuts, [*|label="Edge\\.Cuts"]',
color: "PeachPuff",
force_color: true,
number: 7,
@@ -88,6 +88,7 @@ class Design {
this._mask_opacity = 0.9;
this.determine_size();
this.make_layers();
+ this._mirror_back_layers = true;
const resize_observer = new ResizeObserver(() => {
this.cvs.resize_to_container();
@@ -194,6 +195,15 @@ class Design {
this.draw();
}
+ get mirror_back_layers() {
+ return this._mirror_back_layers;
+ }
+
+ set mirror_back_layers(val) {
+ this._mirror_back_layers = val;
+ this.draw();
+ }
+
async draw_layers(layers, side) {
const cvs = this.cvs;
@@ -212,11 +222,7 @@ class Design {
if (this.preview_layout === "both") {
cvs.draw_image_two_up(await layer.get_preview_bitmap(), side);
} else if (this.preview_layout.endsWith("-spread")) {
- cvs.draw_image_n_up(
- await layer.get_preview_bitmap(),
- i,
- layers.length
- );
+ cvs.draw_image_n_up(await layer.get_preview_bitmap(), i, layers.length);
} else {
cvs.draw_image(await layer.get_preview_bitmap());
}
@@ -236,21 +242,11 @@ class Design {
this.preview_layout === "front-spread" ||
this.preview_layout === "both"
) {
- await this.draw_layers(
- ["EdgeCuts", "FCu", "FMask", "FSilkS", "Drill"],
- "left"
- );
+ await this.draw_layers(["EdgeCuts", "FCu", "FMask", "FSilkS", "Drill"], "left");
}
- if (
- this.preview_layout === "back" ||
- this.preview_layout === "back-spread" ||
- this.preview_layout === "both"
- ) {
- await this.draw_layers(
- ["EdgeCuts", "BCu", "BMask", "BSilkS", "Drill"],
- "right"
- );
+ if (this.preview_layout === "back" || this.preview_layout === "back-spread" || this.preview_layout === "both") {
+ await this.draw_layers(["EdgeCuts", "BCu", "BMask", "BSilkS", "Drill"], "right");
}
}
@@ -262,55 +258,88 @@ class Design {
async export(method) {
const gingerbread = await LibGingerbread.new();
+ gingerbread.onRuntimeError = (error) => {
+ console.error("WASM Runtime Error:", error);
+ console.error("Stack trace:", error.stack);
+ };
console.log(gingerbread);
gingerbread.conversion_start();
+ gingerbread.set_mirror_back_layers(this._mirror_back_layers);
for (const layer of this.layers) {
switch (layer.type) {
- case "raster":
+ case "raster": {
const bm = await layer.get_raster_bitmap();
const imgdata = await yak.ImageData_from_ImageBitmap(bm);
- gingerbread.conversion_add_raster_layer(
- layer.number,
- this.trace_scale_factor,
- imgdata
- );
+
+ // Check that the ImageData is valid
+ const imgdata_sum = imgdata.data.reduce((a, b) => a + b, 0);
+
+ if (imgdata_sum === 0) {
+ console.log("Skipping layer:", layer.name, "because it has no data");
+ continue;
+ }
+
+ try {
+ gingerbread.conversion_add_raster_layer(
+ layer.number,
+ this.trace_scale_factor,
+ imgdata,
+ Number.parseFloat(this.width_mm),
+ );
+ } catch (error) {
+ console.log("imgdata:", imgdata);
+ console.error("WASM error in conversion_add_raster_layer:", error, {
+ layer: layer.name,
+ number: layer.number,
+ scale_factor: this.trace_scale_factor,
+ });
+ // throw error;
+ }
break;
- case "vector":
+ }
+ case "vector": {
for (const path of layer.get_paths()) {
gingerbread.conversion_start_poly();
for (const pt of path) {
gingerbread.conversion_add_poly_point(
pt[0],
pt[1],
- this.dpmm
+ layer.number,
+ this.dpmm,
+ Number.parseFloat(this.width_mm),
);
}
gingerbread.conversion_end_poly(layer.number, 1, false);
}
break;
- case "drill":
+ }
+ case "drill": {
for (const circle of layer.get_circles()) {
gingerbread.conversion_add_drill(
circle.cx.baseVal.value,
circle.cy.baseVal.value,
circle.r.baseVal.value * 2,
- this.dpmm
+ this.dpmm,
);
}
break;
- default:
+ }
+ default: {
throw `Unexpected layer type ${layer.type}`;
+ }
}
}
+ console.log("Conversion finished");
const footprint = gingerbread.conversion_finish();
- if (method == "clipboard") {
+ if (method === "clipboard") {
+ console.log("Copying to clipboard");
navigator.clipboard.writeText(footprint);
} else {
- let file = new File([footprint], "design.kicad_pcb");
+ const file = new File([footprint], "design.kicad_pcb");
yak.initiateDownload(file);
}
}
@@ -352,15 +381,12 @@ class Layer {
async get_preview_bitmap() {
if (!this.bitmap) {
- this.bitmap = await yak.createImageBitmap(
- this.svg,
- this.design.constructor.preview_width
- );
+ this.bitmap = await yak.createImageBitmap(this.svg, this.design.constructor.preview_width);
if (this.is_mask) {
this.bitmap = await yak.ImageBitmap_inverse_mask(
this.bitmap,
await this.design.edge_cuts.get_preview_bitmap(),
- this.color
+ this.color,
);
}
}
@@ -368,10 +394,7 @@ class Layer {
}
async get_raster_bitmap() {
- return await yak.createImageBitmap(
- this.svg,
- this.design.raster_width
- );
+ return await yak.createImageBitmap(this.svg, this.design.raster_width);
}
*get_paths() {
@@ -391,10 +414,7 @@ async function load_design_file(file) {
cvs = new PreviewCanvas(document.getElementById("preview-canvas"));
}
- const svg_doc = new DOMParser().parseFromString(
- await file.text(),
- "image/svg+xml"
- );
+ const svg_doc = new DOMParser().parseFromString(await file.text(), "image/svg+xml");
design = new Design(cvs, svg_doc);
@@ -433,7 +453,7 @@ document.addEventListener("alpine:init", () => {
async export_design(method) {
this.exporting = true;
await this.design.export(method);
- this.exporting = 'done';
+ this.exporting = "done";
window.setTimeout(() => {
this.exporting = false;
}, 3000);
@@ -443,3 +463,17 @@ document.addEventListener("alpine:init", () => {
},
}));
});
+
+LibGingerbread.onRuntimeError = (error) => {
+ console.error("WASM Runtime Error:", error);
+ console.error("Stack trace:", error.stack);
+
+ if (error?.message?.includes("unreachable")) {
+ console.error("WASM hit unreachable code - this likely means a panic occurred");
+ console.error("Last known operation:", LibGingerbread.lastOperation);
+ } else if (error.message) {
+ console.error("WASM error- this is probably a bug in the Gingerbread code");
+ } else {
+ throw new Error(`WASM execution failed: ${error.message}`);
+ }
+};
diff --git a/web/scripts/preview-canvas.js b/web/scripts/preview-canvas.js
index 35cdc12..cba5bf3 100644
--- a/web/scripts/preview-canvas.js
+++ b/web/scripts/preview-canvas.js
@@ -7,9 +7,7 @@ export class PreviewCanvas {
}
get one_rem() {
- return parseFloat(
- getComputedStyle(document.documentElement).fontSize
- );
+ return Number.parseFloat(getComputedStyle(document.documentElement).fontSize);
}
resize_to_container() {
@@ -47,10 +45,10 @@ export class PreviewCanvas {
}
draw_image(img, padding = [2, 2]) {
- padding = padding.map((x) => x * this.one_rem);
+ const scaled_padding = padding.map((x) => x * this.one_rem);
- const dst_w = this.w - padding[0] * 2;
- const dst_h = this.h - padding[1] * 2;
+ const dst_w = this.w - scaled_padding[0] * 2;
+ const dst_h = this.h - scaled_padding[1] * 2;
const { x, y, w, h } = this.calc_image_xywh(img, dst_w, dst_h);
@@ -59,29 +57,29 @@ export class PreviewCanvas {
draw_image_two_up(img, side = "left", padding = [2, 2]) {
let sign = +1;
- if (side == "left") {
+ if (side === "left") {
sign = -1;
}
- padding = padding.map((x) => x * this.one_rem);
+ const scaled_padding = padding.map((x) => x * this.one_rem);
- const dst_w = this.w / 2 - padding[0] * 2;
- const dst_h = this.h - padding[1] * 2;
+ const dst_w = this.w / 2 - scaled_padding[0] * 2;
+ const dst_h = this.h - scaled_padding[1] * 2;
const { x, y, w, h } = this.calc_image_xywh(img, dst_w, dst_h);
- this.ctx.drawImage(img, x + sign * (w / 2 + padding[0]), y, w, h);
+ this.ctx.drawImage(img, x + sign * (w / 2 + scaled_padding[0]), y, w, h);
}
draw_image_n_up(img, i, n, padding = [2, 2]) {
- padding = padding.map((x) => x * this.one_rem);
+ const scaled_padding = padding.map((x) => x * this.one_rem);
- const dst_w = this.w / n - padding[0] * 2;
- const dst_h = this.h - padding[1] * 2;
+ const dst_w = this.w / n - scaled_padding[0] * 2;
+ const dst_h = this.h - scaled_padding[1] * 2;
const { _, y, w, h } = this.calc_image_xywh(img, dst_w, dst_h);
- const n_w = (w + padding[0] / 2);
- const x_min = (this.w / 2) - (n * (n_w / 2)) + padding[0] / 4;
+ const n_w = w + scaled_padding[0] / 2;
+ const x_min = this.w / 2 - n * (n_w / 2) + scaled_padding[0] / 4;
this.ctx.drawImage(img, x_min + i * n_w, y, w, h);
}
diff --git a/web/scripts/wasi.js b/web/scripts/wasi.js
index 73994e2..0e24a62 100644
--- a/web/scripts/wasi.js
+++ b/web/scripts/wasi.js
@@ -28,8 +28,8 @@ export default class WASI {
environ_get: (environ, buf) => {
return WASI_ESUCCESS;
},
- environ_sizes_get: (count, buf_size) => {
- var view = getDataView();
+ environ_sizes_get: (count, buf_size) => {
+ const view = this.getDataView();
view.setUint32(count, 0, !0);
view.setUint32(buf_size, 0, !0);
@@ -53,15 +53,15 @@ export default class WASI {
});
// XXX: verify that this handles multiple lines correctly
- for (let iov of buffers) {
+ for (const iov of buffers) {
const newline = 10;
let i = 0;
- let newlineIndex = iov.lastIndexOf(newline, i);
+ const newlineIndex = iov.lastIndexOf(newline, i);
if (newlineIndex > -1) {
- let line = "",
- decoder = new TextDecoder();
+ let line = "";
+ const decoder = new TextDecoder();
- for (let buffer of this.buffers[fd]) {
+ for (const buffer of this.buffers[fd]) {
line += decoder.decode(buffer, { stream: true });
}
diff --git a/web/scripts/yak.js b/web/scripts/yak.js
index b6b5cf8..65f6d14 100644
--- a/web/scripts/yak.js
+++ b/web/scripts/yak.js
@@ -63,15 +63,17 @@ async function ImageBitmap_from_Blob(blob, width = 1000, context = null) {
nonsense. */
export async function createImageBitmap(image, width = 1000) {
const context = image;
+ let processedImage = image;
+
if (image instanceof XMLDocument) {
- image = Blob_from_SVGDocument(image);
+ processedImage = Blob_from_SVGDocument(image);
}
- if (image instanceof Blob) {
- return await ImageBitmap_from_Blob(image, width, context);
- } else {
- return await window.createImageBitmap(image);
+ if (processedImage instanceof Blob) {
+ return await ImageBitmap_from_Blob(processedImage, width, context);
}
+
+ return await window.createImageBitmap(processedImage);
}
export async function ImageData_from_ImageBitmap(bitmap) {
@@ -89,10 +91,8 @@ export async function ImageData_from_ImageBitmap(bitmap) {
/* Creates a copy of a Document, but with just the documentElement. */
export function cloneDocumentRoot(doc, type) {
return new DOMParser().parseFromString(
- new XMLSerializer().serializeToString(
- doc.documentElement.cloneNode(false)
- ),
- type
+ new XMLSerializer().serializeToString(doc.documentElement.cloneNode(false)),
+ type,
);
}
@@ -114,21 +114,21 @@ export function SVGElement_color(elm, stroke, fill) {
Recolor specifically means that it doesn't *add* color, it only modifies existing
colors. */
export function SVGElement_recolor(elm, stroke = undefined, fill = undefined) {
- stroke = stroke ?? fill;
- fill = fill ?? stroke;
+ const finalStroke = stroke ?? fill;
+ const finalFill = fill ?? stroke;
const invisible_values = ["none", "transparent"];
for (const el of elm.querySelectorAll("*")) {
- const {fill: current_fill, stroke: current_stroke} = SVGElement_get_effective_fill_and_stroke(el);
+ const { fill: current_fill, stroke: current_stroke } = SVGElement_get_effective_fill_and_stroke(el);
if (!invisible_values.includes(current_fill)) {
- el.style.fill = fill;
+ el.style.fill = finalFill;
el.style.fillOpacity = "1";
}
if (!invisible_values.includes(current_stroke)) {
- el.style.stroke = stroke;
+ el.style.stroke = finalStroke;
}
}
}
@@ -138,34 +138,30 @@ export function SVGElement_get_effective_fill_and_stroke(elm) {
let stroke = "";
let e = elm;
- while(e) {
- if(fill == "") {
+ while (e) {
+ if (fill === "" || fill === undefined || fill === null) {
fill = e.style.fill;
}
- if(stroke == "") {
+ if (stroke === "" || stroke === undefined || stroke === null) {
stroke = e.style.stroke;
}
e = e.parentElement;
}
- if(fill == "") {
+ if (fill === "" || fill === undefined || fill === null) {
fill = "black";
}
- if(stroke == "") {
+ if (stroke === "" || stroke === undefined || stroke === null) {
stroke = "none";
}
- return {fill: fill, stroke: stroke};
+ return { fill: fill, stroke: stroke };
}
/* Inverts the given ImageBitmap in a way that matches how KiCAD handles soldermask
layers */
-export async function ImageBitmap_inverse_mask(
- bitmap,
- background,
- color = "rgba(0, 0, 0, 1)"
-) {
+export async function ImageBitmap_inverse_mask(bitmap, background, color = "rgba(0, 0, 0, 1)") {
const canvas = document.createElement("canvas");
canvas.width = bitmap.width;
canvas.height = bitmap.height;
@@ -200,31 +196,32 @@ export async function ImageBitmap_inverse_mask(
Ported from https://gitlab.com/kicad/code/kicad/-/blob/2ee65b2d83923acb71aa77ce0efab09a3f2a8f44/bitmap2component/bitmap2component.cpp#L544
*/
export function* bezier_to_points(p1, p2, p3, p4, delta = 0.25) {
- // dd = maximal value of 2nd derivative over curve - this must occur at an endpoint.
- const dd0 =
- Math.pow(p1[0] - 2 * p2[0] + p3[0], 2) +
- Math.pow(p1[1] - 2 * p2[1] + p3[1], 2);
- const dd1 =
- Math.pow(p2[0] - 2 * p3[0] + p4[0], 2) +
- Math.pow(p2[1] - 2 * p3[1] + p4[1], 2);
+ // Calculate the maximum value of the second derivative over the curve
+ const dd0 = Math.pow(p1[0] - 2 * p2[0] + p3[0], 2) + Math.pow(p1[1] - 2 * p2[1] + p3[1], 2);
+ const dd1 = Math.pow(p2[0] - 2 * p3[0] + p4[0], 2) + Math.pow(p2[1] - 2 * p3[1] + p4[1], 2);
const dd = 6 * Math.sqrt(Math.max(dd0, dd1));
+
+ // Determine the interval size for approximation
const e2 = 8 * delta < dd ? (8 * delta) / dd : 1;
const interval = Math.sqrt(e2);
+ // Generate points along the bezier curve
for (let t = 0; t < 1; t += interval) {
+ const oneMinusT = 1 - t;
const x =
- p1[0] * Math.pow(1 - t, 3) +
- 3 * p2[0] * Math.pow(1 - t, 2) * t +
- 3 * p3[0] * (1 - t) * Math.pow(t, 2) +
+ p1[0] * Math.pow(oneMinusT, 3) +
+ 3 * p2[0] * Math.pow(oneMinusT, 2) * t +
+ 3 * p3[0] * oneMinusT * Math.pow(t, 2) +
p4[0] * Math.pow(t, 3);
const y =
- p1[1] * Math.pow(1 - t, 3) +
- 3 * p2[1] * Math.pow(1 - t, 2) * t +
- 3 * p3[1] * (1 - t) * Math.pow(t, 2) +
+ p1[1] * Math.pow(oneMinusT, 3) +
+ 3 * p2[1] * Math.pow(oneMinusT, 2) * t +
+ 3 * p3[1] * oneMinusT * Math.pow(t, 2) +
p4[1] * Math.pow(t, 3);
yield [x, y];
}
+ // Ensure the last point is included
yield p4;
}
@@ -260,12 +257,7 @@ export function* SVGPathData_to_points(pathdata) {
last = seg.values;
break;
case "C":
- yield* bezier_to_points(
- last,
- seg.values.slice(0, 2),
- seg.values.slice(2, 4),
- seg.values.slice(4, 6)
- );
+ yield* bezier_to_points(last, seg.values.slice(0, 2), seg.values.slice(2, 4), seg.values.slice(4, 6));
last = seg.values.slice(4, 6);
break;
case "Z":
@@ -274,7 +266,6 @@ export function* SVGPathData_to_points(pathdata) {
break;
default:
throw `Invalid path segment type ${seg.type}`;
- break;
}
}
}
diff --git a/web/scripts/zigwasm.js b/web/scripts/zigwasm.js
index 47d3fce..23ec9bf 100644
--- a/web/scripts/zigwasm.js
+++ b/web/scripts/zigwasm.js
@@ -1,7 +1,7 @@
import WASI from "./wasi.js";
class Ptr {
- constructor (zigwasm, address, length) {
+ constructor(zigwasm, address, length) {
this.zigwasm = zigwasm;
this.address = address;
this.byteLength = length;
@@ -14,19 +14,11 @@ class Ptr {
}
u8() {
- return new Uint8Array(
- this.zigwasm.memory.buffer,
- this.address,
- this.byteLength,
- );
+ return new Uint8Array(this.zigwasm.memory.buffer, this.address, this.byteLength);
}
u32() {
- return new Uint32Array(
- this.zigwasm.memory.buffer,
- this.address,
- this.byteLength / Uint32Array.BYTES_PER_ELEMENT,
- );
+ return new Uint32Array(this.zigwasm.memory.buffer, this.address, this.byteLength / Uint32Array.BYTES_PER_ELEMENT);
}
str() {
@@ -45,13 +37,26 @@ export class ZigWASM {
}
static async new(wasm_module) {
- const wasi = new WASI();
- const wasm_inst = await WebAssembly.instantiate(wasm_module, {
- wasi_snapshot_preview1: wasi.exports(),
- env: {},
- });
- wasi.setMemory(wasm_inst.exports.memory);
- return new this(wasm_inst, wasi);
+ if (!wasm_module) {
+ throw new Error("WebAssembly module is required");
+ }
+
+ try {
+ const wasi = new WASI();
+ const wasm_inst = await WebAssembly.instantiate(wasm_module, {
+ wasi_snapshot_preview1: wasi.exports(),
+ env: {},
+ });
+
+ if (!wasm_inst.exports.memory) {
+ throw new Error("WebAssembly instance is missing memory export");
+ }
+
+ wasi.setMemory(wasm_inst.exports.memory);
+ return new ZigWASM(wasm_inst, wasi);
+ } catch (error) {
+ throw new Error(`Failed to instantiate WebAssembly module: ${error.message}`);
+ }
}
get exports() {