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..e75d898
--- /dev/null
+++ b/biome.json
@@ -0,0 +1,34 @@
+{
+ "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
+ "vcs": {
+ "enabled": false,
+ "clientKind": "git",
+ "useIgnoreFile": false
+ },
+ "files": {
+ "ignoreUnknown": false,
+ "ignore": [
+ "web/scripts/alpine-3.10.4.js"
+ ]
+ },
+ "formatter": {
+ "enabled": true,
+ "indentStyle": "space",
+ "lineWidth": 120,
+ "indentWidth": 4
+ },
+ "linter": {
+ "enabled": true,
+ "rules": {
+ "recommended": true,
+ "complexity": {
+ "useLiteralKeys": "info"
+ }
+ }
+ },
+ "javascript": {
+ "formatter": {
+ "quoteStyle": "double"
+ }
+ }
+}
\ No newline at end of file
diff --git a/native/.gitignore b/native/.gitignore
index dc6b79a..ac61e5b 100644
--- a/native/.gitignore
+++ b/native/.gitignore
@@ -1,4 +1,5 @@
zig-cache/
+.zig-cache/
zig-out/
*.o
*.a
diff --git a/native/build.zig b/native/build.zig
index ecdf431..93e9ee1 100644
--- a/native/build.zig
+++ b/native/build.zig
@@ -14,12 +14,12 @@ pub fn build(b: *std.Build) void {
});
const libpotrace_flags = .{ "-std=gnu17", "-DHAVE_CONFIG_H" };
libpotrace.linkLibC();
- libpotrace.addIncludePath(.{ .path = "lib/potrace-1.16/src" });
- libpotrace.addIncludePath(.{ .path = "lib/potrace-config" });
- libpotrace.addCSourceFile(.{ .file = .{ .path = "lib/potrace-1.16/src/curve.c" }, .flags = &libpotrace_flags });
- libpotrace.addCSourceFile(.{ .file = .{ .path = "lib/potrace-1.16/src/trace.c" }, .flags = &libpotrace_flags });
- libpotrace.addCSourceFile(.{ .file = .{ .path = "lib/potrace-1.16/src/decompose.c" }, .flags = &libpotrace_flags });
- libpotrace.addCSourceFile(.{ .file = .{ .path = "lib/potrace-1.16/src/potracelib.c" }, .flags = &libpotrace_flags });
+ libpotrace.addIncludePath(b.path("lib/potrace-1.16/src"));
+ libpotrace.addIncludePath(b.path("lib/potrace-config"));
+ libpotrace.addCSourceFile(.{ .file = b.path("lib/potrace-1.16/src/curve.c"), .flags = &libpotrace_flags });
+ libpotrace.addCSourceFile(.{ .file = b.path("lib/potrace-1.16/src/trace.c"), .flags = &libpotrace_flags });
+ libpotrace.addCSourceFile(.{ .file = b.path("lib/potrace-1.16/src/decompose.c"), .flags = &libpotrace_flags });
+ libpotrace.addCSourceFile(.{ .file = b.path("lib/potrace-1.16/src/potracelib.c"), .flags = &libpotrace_flags });
const libclipper2 = b.addStaticLibrary(.{
.name = "clipper2",
@@ -30,54 +30,47 @@ pub fn build(b: *std.Build) void {
const libclipper2_flags = .{ "-std=gnu++17", "-fno-exceptions", "-Dthrow=abort" };
libclipper2.linkLibC();
libclipper2.linkSystemLibrary("c++");
- libclipper2.addIncludePath(.{ .path = "lib/clipper2/CPP/Clipper2Lib" });
- libclipper2.addIncludePath(.{ .path = "src" });
+ libclipper2.addIncludePath(b.path("lib/clipper2/CPP/Clipper2Lib"));
+ libclipper2.addIncludePath(b.path("src"));
libclipper2.addCSourceFile(.{
- .file = .{ .path = "lib/clipper2/CPP/Clipper2Lib/clipper.engine.cpp" },
+ .file = b.path("lib/clipper2/CPP/Clipper2Lib/clipper.engine.cpp"),
.flags = &libclipper2_flags,
});
libclipper2.addCSourceFile(.{
- .file = .{ .path = "lib/clipper2/CPP/Clipper2Lib/clipper.offset.cpp" },
+ .file = b.path("lib/clipper2/CPP/Clipper2Lib/clipper.offset.cpp"),
.flags = &libclipper2_flags,
});
libclipper2.addCSourceFile(.{
- .file = .{ .path = "src/clipperwrapper.cpp" },
+ .file = b.path("src/clipperwrapper.cpp"),
.flags = &libclipper2_flags,
});
- const libgingerbread = b.addExecutable(.{
- .name = "gingerbread",
- .root_source_file = .{ .path = "src/gingerbread.zig" },
- .version = .{ .major = 1, .minor = 0, .patch = 0 },
- .target = target,
- .optimize = optimize,
- .strip = true
- });
+ const libgingerbread = b.addExecutable(.{ .name = "gingerbread", .root_source_file = b.path("src/gingerbread.zig"), .version = .{ .major = 1, .minor = 0, .patch = 0 }, .target = target, .optimize = optimize, .strip = true });
libgingerbread.entry = .disabled;
libgingerbread.rdynamic = true;
libgingerbread.wasi_exec_model = std.builtin.WasiExecModel.reactor;
libgingerbread.linkLibC();
libgingerbread.linkLibrary(libpotrace);
libgingerbread.linkLibrary(libclipper2);
- libgingerbread.addIncludePath(.{ .path = "src" });
- libgingerbread.addIncludePath(.{ .path = "lib/potrace-1.16/src" });
+ libgingerbread.addIncludePath(b.path("src"));
+ libgingerbread.addIncludePath(b.path("lib/potrace-1.16/src"));
b.installArtifact(libgingerbread);
// const main = b.addTest(.{
// .name = "main",
- // .root_source_file = .{ .path = "src/tests.zig" },
+ // .root_source_file = b.path("src/tests.zig"),
// .target = target,
// .optimize = optimize,
// .link_libc = true,
// });
// main.linkLibrary(libpotrace);
// main.linkLibrary(libclipper2);
- // main.addIncludePath(.{ .path = "src/" });
- // main.addIncludePath(.{ .path = "lib/potrace-1.16/src" });
- // main.addIncludePath(.{ .path = "lib/potrace-config" });
- // main.addIncludePath(.{ .path = "lib/stb" });
- // main.addCSourceFile(.{ .file = .{ .path = "src/load_image.c" }, .flags = &.{
+ // main.addIncludePath(b.path("src/"));
+ // main.addIncludePath(b.path("lib/potrace-1.16/src"));
+ // main.addIncludePath(b.path("lib/potrace-config"));
+ // main.addIncludePath(b.path("lib/stb"));
+ // main.addCSourceFile(.{ .file = b.path("src/load_image.c"), .flags = &.{
// "-std=gnu17",
// } });
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..7b8c52d 100644
--- a/web/help.html
+++ b/web/help.html
@@ -17,43 +17,61 @@
About Gingerbread
acknowledgements are at the bottom of this page .
Using Gingerbread
- At the moment, Gingerbread is intended to work with SVGs created in Affinity Designer. You'll need to make sure your design matches what Gingerbread expects.
+ At the moment, Gingerbread is intended to work with SVGs created in Affinity Designer or Inkscape. You'll need to make sure your design matches what Gingerbread expects.
+ If using Inkscape, saving your file as an Inkscape SVG is necessary to preserve the layer names.
Page settings
First, it's highly recommended to change your page settings to use millimeters and 2540 DPI, as shown here:
You might be wondering why that specific DPI? Well, 2540 DPI happens to be 1000 dots per mm, which helpfully avoids rounding issues when
- exporting the design from Affinity and when converting the outline and drills. You can use other DPIs by changing the DPI setting in Gingerbread once your design is loaded.
+ exporting the design from your vector editor and when converting the outline and drills. You can use other DPIs by changing the DPI setting in Gingerbread once your design is loaded.
On very slim designs, you might be experiencing issues where the drill holes on either the Drills or Edge.Cuts layers become off-centered from their
intended placement. In that case it'll help to extend the canvas size inside Affinity to be square. Make sure to select "Anchor on page" to preserve your designs aspect ratio.
Creating an outline
- The outline should be drawn on a layer named Edge.Cuts in Affinity. Gingerbread handles this layer in a specific way to make sure that there is a 1-to-1 match
- between the size and units in Affinity and KiCAD. This approach can't handle as many complex edge cases as the rasterization approach used by the graphic layers, but as
+
The outline should be drawn on a layer named Edge.Cuts in your vector editor. Gingerbread handles this layer in a specific way to make sure that there is a 1-to-1 match
+ between the size and units in the SVG and KiCAD. This approach can't handle as many complex edge cases as the rasterization approach used by the graphic layers, but as
long as your paths have been converted to curves it should handle them well. The outline layer can contain multiple curves, with inside curves getting converted to
"cut-outs".
Graphics layers
- Non-transparent areas on layers named F.SilkS, B.SilkS, F.Cu, and B.Cu in Affinity are converted to their respective
+
Non-transparent areas on layers named F.SilkS, B.SilkS, F.Cu, and B.Cu in the SVG are converted to their respective
layers in KiCAD. Note that F.Mask and B.Mask are "inverted" like they are in KiCAD, meaning that non-transparent areas indicate where to
- remove the soldermask- the preview in Gingerbread will shows the mask layers as they would appear on the printed board.
+ remove the soldermask- the preview in Gingerbread will shows the mask layers as they would appear on the printed board.
Gingerbread converts these layers by rasterizing all the items on each layer to black and white, re-tracing the raster image to polygons, and placing the resulting
polygons into KiCAD. While this might seem odd, it works extremely well for a variety of SVGs.
Drills
- Items on the layer named Drills in Affinity are also handled in a specific way. Gingerbread walks through all of the shapes in that layer and converts
+
Items on the layer named Drills in the SVG are also handled in a specific way. Gingerbread walks through all of the shapes in that layer and converts
only circles into corresponding non-plated through hole drills in KiCAD. Just as with the board outline, this is done to preserve position and size between
- Affinity and KiCAD.
+ the SVG 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
+ on if you want this behavior.
+
+
Exporting your design
+ Affinity Designer
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.
You can save this as a preset to avoid having to change these every time you export.
+ Inkscape
+ Note: Inkscape support is new and relatively untested. Please do try it out, and if you run into problems,
+ open an issue on Github with info on what went wrong!
+ Select File->Save As (or Save a Copy ) and select Inkscape SVG (*.svg)
+ from the filetype dropdown in the lower right. To ensure fonts and text are preserved as-is, we recommend converting text to paths before saving:
+
+ Select a single text item (doesn't matter which, as long as it's text)
+ Click Edit->Select Same->Object Type or press Shift+Alt+A to select all text objects
+ Convert them all to paths by clicking Path->Object to Path or pressing Shift+Ctrl+C .
+
+
Converting your design
Once the SVG is exported, drag and drop it onto the Gingerbread web page. Once loaded, you should see a preview of your design. Use the options in the right pane to
@@ -61,7 +79,7 @@
Converting your design
into KiCAD's PCBNew.
Copyright and acknowledgements
- Gingerbread is (c) 2022 by Winterbloom LLC & Alethea Katherine Flowers
+ Gingerbread is (c) 2022 by Winterbloom LLC & Alethea Katherine Flowers
Gingerbread is available under the MIT License:
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/bulma.js b/web/scripts/bulma.js
new file mode 100644
index 0000000..7493325
--- /dev/null
+++ b/web/scripts/bulma.js
@@ -0,0 +1,22 @@
+// From https://bulma.io/documentation/components/navbar/#navbarJsExample
+document.addEventListener('DOMContentLoaded', () => {
+
+ // Get all "navbar-burger" elements
+ const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
+
+ // Add a click event on each of them
+ $navbarBurgers.forEach( el => {
+ el.addEventListener('click', () => {
+
+ // Get the target from the "data-target" attribute
+ const target = el.dataset.target;
+ const $target = document.getElementById(target);
+
+ // Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
+ el.classList.toggle('is-active');
+ $target.classList.toggle('is-active');
+
+ });
+ });
+
+});
diff --git a/web/scripts/dragdrop.js b/web/scripts/dragdrop.js
index 5ba3568..54f1aed 100644
--- a/web/scripts/dragdrop.js
+++ b/web/scripts/dragdrop.js
@@ -1,6 +1,5 @@
-
export class DropTarget {
- constructor (elm, callback) {
+ constructor(elm, callback) {
this.elm = elm;
this.callback = callback;
@@ -9,7 +8,7 @@ export class DropTarget {
(e) => {
e.preventDefault();
},
- false
+ false,
);
elm.addEventListener(
@@ -18,7 +17,7 @@ export class DropTarget {
e.preventDefault();
e.dataTransfer.dropEffect = "move";
},
- false
+ false,
);
elm.addEventListener(
@@ -32,7 +31,7 @@ export class DropTarget {
callback(files);
}
},
- false
+ false,
);
}
}
diff --git a/web/scripts/libgingerbread.js b/web/scripts/libgingerbread.js
index d7e72b0..877c262 100644
--- a/web/scripts/libgingerbread.js
+++ b/web/scripts/libgingerbread.js
@@ -9,25 +9,42 @@ export class LibGingerbread {
}
static async new() {
- if (this.wasm_module == null) {
- this.wasm_module = await ZigWASM.compile(this.wasm_src);
+ if (LibGingerbread.wasm_module == null) {
+ 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 +56,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..a8963e1 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=\"Drill\"], [*|label=\"Drills\"]",
color: "Fuchsia",
},
{
name: "FSilkS",
type: "raster",
- selector: "#FSilkS, #F\\.SilkS, [*|label=\"F\\.SilkS\"]",
+ selector: "#FSilkS, #F\\.SilkS, [*|label=\"FSilkS\"], [*|label=\"F\\.SilkS\"]",
color: "white",
number: 3,
},
{
name: "FMask",
type: "raster",
- selector: "#FMask, #F\\.Mask, [*|label=\"F\\.Mask\"]",
+ selector: "#FMask, #F\\.Mask, [*|label=\"FMask\"], [*|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=\"FCu\"], [*|label=\"F\\.Cu\"]",
color: "gold",
number: 1,
},
{
name: "BCu",
type: "raster",
- selector: "#BCu, #B\\.Cu, [*|label=\"B\\.Cu\"]",
+ selector: "#BCu, #B\\.Cu, [*|label=\"BCu\"], [*|label=\"B\\.Cu\"]",
color: "gold",
number: 2,
},
{
name: "BMask",
type: "raster",
- selector: "#BMask, #B\\.Mask, [*|label=\"B\\.Mask\"]",
+ selector: "#BMask, #B\\.Mask, [*|label=\"BMask\"], [*|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=\"BSilkS\"], [*|label=\"B\\.SilkS\"]",
color: "white",
number: 4,
},
{
name: "EdgeCuts",
type: "vector",
- selector: "#EdgeCuts, #Edge\\.Cuts, [*|label=\"Edge\\.Cuts\"]",
+ selector: "#EdgeCuts, #Edge\\.Cuts, [*|label=\"EdgeCuts\"], [*|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 = false;
const resize_observer = new ResizeObserver(() => {
this.cvs.resize_to_container();
@@ -153,16 +154,16 @@ class Design {
}
get edge_cuts() {
- return this.layers_by_name["EdgeCuts"];
+ return this.layers_by_name.EdgeCuts;
}
get mask_color() {
- return this.layers_by_name["FMask"].color;
+ return this.layers_by_name.FMask.color;
}
set mask_color(val) {
- this.layers_by_name["FMask"].color = val;
- this.layers_by_name["BMask"].color = val;
+ this.layers_by_name.FMask.color = val;
+ this.layers_by_name.BMask.color = val;
this.draw();
}
@@ -176,12 +177,12 @@ class Design {
}
get silk_color() {
- return this.layers_by_name["FSilkS"].color;
+ return this.layers_by_name.FSilkS.color;
}
set silk_color(val) {
- this.layers_by_name["FSilkS"].color = val;
- this.layers_by_name["BSilkS"].color = val;
+ this.layers_by_name.FSilkS.color = val;
+ this.layers_by_name.BSilkS.color = val;
this.draw();
}
@@ -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,87 @@ 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") {
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 +380,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 +393,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 +413,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 +452,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 +462,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/path-data-polyfill.js b/web/scripts/path-data-polyfill.js
index 2bb47c1..077e307 100644
--- a/web/scripts/path-data-polyfill.js
+++ b/web/scripts/path-data-polyfill.js
@@ -1,4 +1,3 @@
-
// @info
// Polyfill for SVG getPathData() and setPathData() methods. Based on:
// - SVGPathSeg polyfill by Philip Rogers (MIT License)
@@ -12,1152 +11,1122 @@
// @license
// MIT License
if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathData) {
- (function() {
- var commandsMap = {
- "Z":"Z", "M":"M", "L":"L", "C":"C", "Q":"Q", "A":"A", "H":"H", "V":"V", "S":"S", "T":"T",
- "z":"Z", "m":"m", "l":"l", "c":"c", "q":"q", "a":"a", "h":"h", "v":"v", "s":"s", "t":"t"
- };
-
- var Source = function(string) {
- this._string = string;
- this._currentIndex = 0;
- this._endIndex = this._string.length;
- this._prevCommand = null;
- this._skipOptionalSpaces();
- };
-
- var isIE = window.navigator.userAgent.indexOf("MSIE ") !== -1;
-
- Source.prototype = {
- parseSegment: function() {
- var char = this._string[this._currentIndex];
- var command = commandsMap[char] ? commandsMap[char] : null;
-
- if (command === null) {
- // Possibly an implicit command. Not allowed if this is the first command.
- if (this._prevCommand === null) {
- return null;
- }
-
- // Check for remaining coordinates in the current command.
- if (
- (char === "+" || char === "-" || char === "." || (char >= "0" && char <= "9")) && this._prevCommand !== "Z"
- ) {
- if (this._prevCommand === "M") {
- command = "L";
- }
- else if (this._prevCommand === "m") {
- command = "l";
- }
- else {
- command = this._prevCommand;
- }
- }
- else {
- command = null;
- }
-
- if (command === null) {
- return null;
- }
- }
- else {
- this._currentIndex += 1;
- }
-
- this._prevCommand = command;
-
- var values = null;
- var cmd = command.toUpperCase();
-
- if (cmd === "H" || cmd === "V") {
- values = [this._parseNumber()];
- }
- else if (cmd === "M" || cmd === "L" || cmd === "T") {
- values = [this._parseNumber(), this._parseNumber()];
- }
- else if (cmd === "S" || cmd === "Q") {
- values = [this._parseNumber(), this._parseNumber(), this._parseNumber(), this._parseNumber()];
- }
- else if (cmd === "C") {
- values = [
- this._parseNumber(),
- this._parseNumber(),
- this._parseNumber(),
- this._parseNumber(),
- this._parseNumber(),
- this._parseNumber()
- ];
- }
- else if (cmd === "A") {
- values = [
- this._parseNumber(),
- this._parseNumber(),
- this._parseNumber(),
- this._parseArcFlag(),
- this._parseArcFlag(),
- this._parseNumber(),
- this._parseNumber()
- ];
- }
- else if (cmd === "Z") {
+ (() => {
+ const commandsMap = {
+ Z: "Z",
+ M: "M",
+ L: "L",
+ C: "C",
+ Q: "Q",
+ A: "A",
+ H: "H",
+ V: "V",
+ S: "S",
+ T: "T",
+ z: "Z",
+ m: "m",
+ l: "l",
+ c: "c",
+ q: "q",
+ a: "a",
+ h: "h",
+ v: "v",
+ s: "s",
+ t: "t",
+ };
+
+ const Source = function (string) {
+ this._string = string;
+ this._currentIndex = 0;
+ this._endIndex = this._string.length;
+ this._prevCommand = null;
this._skipOptionalSpaces();
- values = [];
- }
-
- if (values === null || values.indexOf(null) >= 0) {
- // Unknown command or known command with invalid values
- return null;
- }
- else {
- return {type: command, values: values};
- }
- },
-
- hasMoreData: function() {
- return this._currentIndex < this._endIndex;
- },
-
- peekSegmentType: function() {
- var char = this._string[this._currentIndex];
- return commandsMap[char] ? commandsMap[char] : null;
- },
-
- initialCommandIsMoveTo: function() {
- // If the path is empty it is still valid, so return true.
- if (!this.hasMoreData()) {
- return true;
- }
-
- var command = this.peekSegmentType();
- // Path must start with moveTo.
- return command === "M" || command === "m";
- },
-
- _isCurrentSpace: function() {
- var char = this._string[this._currentIndex];
- return char <= " " && (char === " " || char === "\n" || char === "\t" || char === "\r" || char === "\f");
- },
-
- _skipOptionalSpaces: function() {
- while (this._currentIndex < this._endIndex && this._isCurrentSpace()) {
- this._currentIndex += 1;
- }
-
- return this._currentIndex < this._endIndex;
- },
-
- _skipOptionalSpacesOrDelimiter: function() {
- if (
- this._currentIndex < this._endIndex &&
- !this._isCurrentSpace() &&
- this._string[this._currentIndex] !== ","
- ) {
- return false;
- }
-
- if (this._skipOptionalSpaces()) {
- if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === ",") {
- this._currentIndex += 1;
- this._skipOptionalSpaces();
- }
- }
- return this._currentIndex < this._endIndex;
- },
-
- // Parse a number from an SVG path. This very closely follows genericParseNumber(...) from
- // Source/core/svg/SVGParserUtilities.cpp.
- // Spec: http://www.w3.org/TR/SVG11/single-page.html#paths-PathDataBNF
- _parseNumber: function() {
- var exponent = 0;
- var integer = 0;
- var frac = 1;
- var decimal = 0;
- var sign = 1;
- var expsign = 1;
- var startIndex = this._currentIndex;
-
- this._skipOptionalSpaces();
-
- // Read the sign.
- if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === "+") {
- this._currentIndex += 1;
- }
- else if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === "-") {
- this._currentIndex += 1;
- sign = -1;
- }
-
- if (
- this._currentIndex === this._endIndex ||
- (
- (this._string[this._currentIndex] < "0" || this._string[this._currentIndex] > "9") &&
- this._string[this._currentIndex] !== "."
- )
- ) {
- // The first character of a number must be one of [0-9+-.].
- return null;
- }
-
- // Read the integer part, build right-to-left.
- var startIntPartIndex = this._currentIndex;
-
- while (
- this._currentIndex < this._endIndex &&
- this._string[this._currentIndex] >= "0" &&
- this._string[this._currentIndex] <= "9"
- ) {
- this._currentIndex += 1; // Advance to first non-digit.
- }
-
- if (this._currentIndex !== startIntPartIndex) {
- var scanIntPartIndex = this._currentIndex - 1;
- var multiplier = 1;
-
- while (scanIntPartIndex >= startIntPartIndex) {
- integer += multiplier * (this._string[scanIntPartIndex] - "0");
- scanIntPartIndex -= 1;
- multiplier *= 10;
- }
- }
-
- // Read the decimals.
- if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === ".") {
- this._currentIndex += 1;
-
- // There must be a least one digit following the .
- if (
- this._currentIndex >= this._endIndex ||
- this._string[this._currentIndex] < "0" ||
- this._string[this._currentIndex] > "9"
- ) {
- return null;
+ };
+
+ const isIE = window.navigator.userAgent.indexOf("MSIE ") !== -1;
+
+ Source.prototype = {
+ parseSegment: function () {
+ const char = this._string[this._currentIndex];
+ let command = commandsMap[char] ? commandsMap[char] : null;
+
+ if (command === null) {
+ // Possibly an implicit command. Not allowed if this is the first command.
+ if (this._prevCommand === null) {
+ return null;
+ }
+
+ // Check for remaining coordinates in the current command.
+ if (
+ (char === "+" || char === "-" || char === "." || (char >= "0" && char <= "9")) &&
+ this._prevCommand !== "Z"
+ ) {
+ if (this._prevCommand === "M") {
+ command = "L";
+ } else if (this._prevCommand === "m") {
+ command = "l";
+ } else {
+ command = this._prevCommand;
+ }
+ } else {
+ command = null;
+ }
+
+ if (command === null) {
+ return null;
+ }
+ } else {
+ this._currentIndex += 1;
+ }
+
+ this._prevCommand = command;
+
+ let values = null;
+ const cmd = command.toUpperCase();
+
+ if (cmd === "H" || cmd === "V") {
+ values = [this._parseNumber()];
+ } else if (cmd === "M" || cmd === "L" || cmd === "T") {
+ values = [this._parseNumber(), this._parseNumber()];
+ } else if (cmd === "S" || cmd === "Q") {
+ values = [this._parseNumber(), this._parseNumber(), this._parseNumber(), this._parseNumber()];
+ } else if (cmd === "C") {
+ values = [
+ this._parseNumber(),
+ this._parseNumber(),
+ this._parseNumber(),
+ this._parseNumber(),
+ this._parseNumber(),
+ this._parseNumber(),
+ ];
+ } else if (cmd === "A") {
+ values = [
+ this._parseNumber(),
+ this._parseNumber(),
+ this._parseNumber(),
+ this._parseArcFlag(),
+ this._parseArcFlag(),
+ this._parseNumber(),
+ this._parseNumber(),
+ ];
+ } else if (cmd === "Z") {
+ this._skipOptionalSpaces();
+ values = [];
+ }
+
+ if (values === null || values.indexOf(null) >= 0) {
+ // Unknown command or known command with invalid values
+ return null;
+ }
+ return { type: command, values: values };
+ },
+
+ hasMoreData: function () {
+ return this._currentIndex < this._endIndex;
+ },
+
+ peekSegmentType: function () {
+ const char = this._string[this._currentIndex];
+ return commandsMap[char] ? commandsMap[char] : null;
+ },
+
+ initialCommandIsMoveTo: function () {
+ // If the path is empty it is still valid, so return true.
+ if (!this.hasMoreData()) {
+ return true;
+ }
+
+ const command = this.peekSegmentType();
+ // Path must start with moveTo.
+ return command === "M" || command === "m";
+ },
+
+ _isCurrentSpace: function () {
+ const char = this._string[this._currentIndex];
+ return (
+ char <= " " && (char === " " || char === "\n" || char === "\t" || char === "\r" || char === "\f")
+ );
+ },
+
+ _skipOptionalSpaces: function () {
+ while (this._currentIndex < this._endIndex && this._isCurrentSpace()) {
+ this._currentIndex += 1;
+ }
+
+ return this._currentIndex < this._endIndex;
+ },
+
+ _skipOptionalSpacesOrDelimiter: function () {
+ if (
+ this._currentIndex < this._endIndex &&
+ !this._isCurrentSpace() &&
+ this._string[this._currentIndex] !== ","
+ ) {
+ return false;
+ }
+
+ if (this._skipOptionalSpaces()) {
+ if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === ",") {
+ this._currentIndex += 1;
+ this._skipOptionalSpaces();
+ }
+ }
+ return this._currentIndex < this._endIndex;
+ },
+
+ // Parse a number from an SVG path. This very closely follows genericParseNumber(...) from
+ // Source/core/svg/SVGParserUtilities.cpp.
+ // Spec: http://www.w3.org/TR/SVG11/single-page.html#paths-PathDataBNF
+ _parseNumber: function () {
+ let exponent = 0;
+ let integer = 0;
+ let frac = 1;
+ let decimal = 0;
+ let sign = 1;
+ let expsign = 1;
+ const startIndex = this._currentIndex;
+
+ this._skipOptionalSpaces();
+
+ // Read the sign.
+ if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === "+") {
+ this._currentIndex += 1;
+ } else if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === "-") {
+ this._currentIndex += 1;
+ sign = -1;
+ }
+
+ if (
+ this._currentIndex === this._endIndex ||
+ ((this._string[this._currentIndex] < "0" || this._string[this._currentIndex] > "9") &&
+ this._string[this._currentIndex] !== ".")
+ ) {
+ // The first character of a number must be one of [0-9+-.].
+ return null;
+ }
+
+ // Read the integer part, build right-to-left.
+ const startIntPartIndex = this._currentIndex;
+
+ while (
+ this._currentIndex < this._endIndex &&
+ this._string[this._currentIndex] >= "0" &&
+ this._string[this._currentIndex] <= "9"
+ ) {
+ this._currentIndex += 1; // Advance to first non-digit.
+ }
+
+ if (this._currentIndex !== startIntPartIndex) {
+ let scanIntPartIndex = this._currentIndex - 1;
+ let multiplier = 1;
+
+ while (scanIntPartIndex >= startIntPartIndex) {
+ integer += multiplier * (this._string[scanIntPartIndex] - "0");
+ scanIntPartIndex -= 1;
+ multiplier *= 10;
+ }
+ }
+
+ // Read the decimals.
+ if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === ".") {
+ this._currentIndex += 1;
+
+ // There must be a least one digit following the .
+ if (
+ this._currentIndex >= this._endIndex ||
+ this._string[this._currentIndex] < "0" ||
+ this._string[this._currentIndex] > "9"
+ ) {
+ return null;
+ }
+
+ while (
+ this._currentIndex < this._endIndex &&
+ this._string[this._currentIndex] >= "0" &&
+ this._string[this._currentIndex] <= "9"
+ ) {
+ frac *= 10;
+ decimal += (this._string.charAt(this._currentIndex) - "0") / frac;
+ this._currentIndex += 1;
+ }
+ }
+
+ // Read the exponent part.
+ if (
+ this._currentIndex !== startIndex &&
+ this._currentIndex + 1 < this._endIndex &&
+ (this._string[this._currentIndex] === "e" || this._string[this._currentIndex] === "E") &&
+ this._string[this._currentIndex + 1] !== "x" &&
+ this._string[this._currentIndex + 1] !== "m"
+ ) {
+ this._currentIndex += 1;
+
+ // Read the sign of the exponent.
+ if (this._string[this._currentIndex] === "+") {
+ this._currentIndex += 1;
+ } else if (this._string[this._currentIndex] === "-") {
+ this._currentIndex += 1;
+ expsign = -1;
+ }
+
+ // There must be an exponent.
+ if (
+ this._currentIndex >= this._endIndex ||
+ this._string[this._currentIndex] < "0" ||
+ this._string[this._currentIndex] > "9"
+ ) {
+ return null;
+ }
+
+ while (
+ this._currentIndex < this._endIndex &&
+ this._string[this._currentIndex] >= "0" &&
+ this._string[this._currentIndex] <= "9"
+ ) {
+ exponent *= 10;
+ exponent += this._string[this._currentIndex] - "0";
+ this._currentIndex += 1;
+ }
+ }
+
+ let number = integer + decimal;
+ number *= sign;
+
+ if (exponent) {
+ number *= 10 ** (expsign * exponent);
+ }
+
+ if (startIndex === this._currentIndex) {
+ return null;
+ }
+
+ this._skipOptionalSpacesOrDelimiter();
+
+ return number;
+ },
+
+ _parseArcFlag: function () {
+ if (this._currentIndex >= this._endIndex) {
+ return null;
+ }
+
+ let flag = null;
+ const flagChar = this._string[this._currentIndex];
+
+ this._currentIndex += 1;
+
+ if (flagChar === "0") {
+ flag = 0;
+ } else if (flagChar === "1") {
+ flag = 1;
+ } else {
+ return null;
+ }
+
+ this._skipOptionalSpacesOrDelimiter();
+ return flag;
+ },
+ };
+
+ const parsePathDataString = (string) => {
+ if (!string || string.length === 0) return [];
+
+ const source = new Source(string);
+ const pathData = [];
+
+ if (source.initialCommandIsMoveTo()) {
+ while (source.hasMoreData()) {
+ const pathSeg = source.parseSegment();
+
+ if (pathSeg === null) {
+ break;
+ }
+
+ pathData.push(pathSeg);
+ }
}
-
- while (
- this._currentIndex < this._endIndex &&
- this._string[this._currentIndex] >= "0" &&
- this._string[this._currentIndex] <= "9"
- ) {
- frac *= 10;
- decimal += (this._string.charAt(this._currentIndex) - "0") / frac;
- this._currentIndex += 1;
+
+ return pathData;
+ };
+
+ const setAttribute = SVGPathElement.prototype.setAttribute;
+ const setAttributeNS = SVGPathElement.prototype.setAttributeNS;
+ const removeAttribute = SVGPathElement.prototype.removeAttribute;
+ const removeAttributeNS = SVGPathElement.prototype.removeAttributeNS;
+
+ const $cachedPathData = window.Symbol ? Symbol() : "__cachedPathData";
+ const $cachedNormalizedPathData = window.Symbol ? Symbol() : "__cachedNormalizedPathData";
+
+ // @info
+ // Get an array of corresponding cubic bezier curve parameters for given arc curve paramters.
+ const arcToCubicCurves = (
+ x1Raw,
+ y1Raw,
+ x2Raw,
+ y2Raw,
+ r1Raw,
+ r2Raw,
+ angle,
+ largeArcFlag,
+ sweepFlag,
+ _recursive,
+ ) => {
+ const degToRad = (degrees) => {
+ return (Math.PI * degrees) / 180;
+ };
+
+ const rotate = (x, y, angleRad) => {
+ const X = x * Math.cos(angleRad) - y * Math.sin(angleRad);
+ const Y = x * Math.sin(angleRad) + y * Math.cos(angleRad);
+ return { x: X, y: Y };
+ };
+
+ let x1 = x1Raw;
+ let y1 = y1Raw;
+ let x2 = x2Raw;
+ let y2 = y2Raw;
+ let r1 = r1Raw;
+ let r2 = r2Raw;
+
+ const angleRad = degToRad(angle);
+ let params = [];
+ let f1;
+ let f2;
+ let cx;
+ let cy;
+
+ if (_recursive) {
+ f1 = _recursive[0];
+ f2 = _recursive[1];
+ cx = _recursive[2];
+ cy = _recursive[3];
+ } else {
+ const p1 = rotate(x1, y1, -angleRad);
+ x1 = p1.x;
+ y1 = p1.y;
+
+ const p2 = rotate(x2, y2, -angleRad);
+ x2 = p2.x;
+ y2 = p2.y;
+
+ const x = (x1 - x2) / 2;
+ const y = (y1 - y2) / 2;
+ let h = (x * x) / (r1 * r1) + (y * y) / (r2 * r2);
+
+ if (h > 1) {
+ h = Math.sqrt(h);
+ r1 = h * r1;
+ r2 = h * r2;
+ }
+
+ let sign;
+
+ if (largeArcFlag === sweepFlag) {
+ sign = -1;
+ } else {
+ sign = 1;
+ }
+
+ const r1Pow = r1 * r1;
+ const r2Pow = r2 * r2;
+
+ const left = r1Pow * r2Pow - r1Pow * y * y - r2Pow * x * x;
+ const right = r1Pow * y * y + r2Pow * x * x;
+
+ const k = sign * Math.sqrt(Math.abs(left / right));
+
+ cx = (k * r1 * y) / r2 + (x1 + x2) / 2;
+ cy = (k * -r2 * x) / r1 + (y1 + y2) / 2;
+
+ f1 = Math.asin(Number.parseFloat(((y1 - cy) / r2).toFixed(9)));
+ f2 = Math.asin(Number.parseFloat(((y2 - cy) / r2).toFixed(9)));
+
+ if (x1 < cx) {
+ f1 = Math.PI - f1;
+ }
+ if (x2 < cx) {
+ f2 = Math.PI - f2;
+ }
+
+ if (f1 < 0) {
+ f1 = Math.PI * 2 + f1;
+ }
+ if (f2 < 0) {
+ f2 = Math.PI * 2 + f2;
+ }
+
+ if (sweepFlag && f1 > f2) {
+ f1 = f1 - Math.PI * 2;
+ }
+ if (!sweepFlag && f2 > f1) {
+ f2 = f2 - Math.PI * 2;
+ }
}
- }
-
- // Read the exponent part.
- if (
- this._currentIndex !== startIndex &&
- this._currentIndex + 1 < this._endIndex &&
- (this._string[this._currentIndex] === "e" || this._string[this._currentIndex] === "E") &&
- (this._string[this._currentIndex + 1] !== "x" && this._string[this._currentIndex + 1] !== "m")
- ) {
- this._currentIndex += 1;
-
- // Read the sign of the exponent.
- if (this._string[this._currentIndex] === "+") {
- this._currentIndex += 1;
+
+ let df = f2 - f1;
+
+ if (Math.abs(df) > (Math.PI * 120) / 180) {
+ const f2old = f2;
+ const x2old = x2;
+ const y2old = y2;
+
+ if (sweepFlag && f2 > f1) {
+ f2 = f1 + ((Math.PI * 120) / 180) * 1;
+ } else {
+ f2 = f1 + ((Math.PI * 120) / 180) * -1;
+ }
+
+ x2 = cx + r1 * Math.cos(f2);
+ y2 = cy + r2 * Math.sin(f2);
+ params = arcToCubicCurves(x2, y2, x2old, y2old, r1, r2, angle, 0, sweepFlag, [f2, f2old, cx, cy]);
}
- else if (this._string[this._currentIndex] === "-") {
- this._currentIndex += 1;
- expsign = -1;
+
+ df = f2 - f1;
+
+ const c1 = Math.cos(f1);
+ const s1 = Math.sin(f1);
+ const c2 = Math.cos(f2);
+ const s2 = Math.sin(f2);
+ const t = Math.tan(df / 4);
+ const hx = (4 / 3) * r1 * t;
+ const hy = (4 / 3) * r2 * t;
+
+ const m1 = [x1, y1];
+ const m2 = [x1 + hx * s1, y1 - hy * c1];
+ const m3 = [x2 + hx * s2, y2 - hy * c2];
+ const m4 = [x2, y2];
+
+ m2[0] = 2 * m1[0] - m2[0];
+ m2[1] = 2 * m1[1] - m2[1];
+
+ if (_recursive) {
+ return [m2, m3, m4].concat(params);
}
-
- // There must be an exponent.
- if (
- this._currentIndex >= this._endIndex ||
- this._string[this._currentIndex] < "0" ||
- this._string[this._currentIndex] > "9"
- ) {
- return null;
+
+ params = [m2, m3, m4].concat(params);
+
+ const curves = [];
+
+ for (let i = 0; i < params.length; i += 3) {
+ const r1 = rotate(params[i][0], params[i][1], angleRad);
+ const r2 = rotate(params[i + 1][0], params[i + 1][1], angleRad);
+ const r3 = rotate(params[i + 2][0], params[i + 2][1], angleRad);
+ curves.push([r1.x, r1.y, r2.x, r2.y, r3.x, r3.y]);
}
-
- while (
- this._currentIndex < this._endIndex &&
- this._string[this._currentIndex] >= "0" &&
- this._string[this._currentIndex] <= "9"
- ) {
- exponent *= 10;
- exponent += (this._string[this._currentIndex] - "0");
- this._currentIndex += 1;
+
+ return curves;
+ };
+
+ const clonePathData = (pathData) => {
+ return pathData.map((seg) => {
+ return { type: seg.type, values: Array.prototype.slice.call(seg.values) };
+ });
+ };
+
+ // @info
+ // Takes any path data, returns path data that consists only from absolute commands.
+ const absolutizePathData = (pathData) => {
+ const absolutizedPathData = [];
+
+ let currentX = null;
+ let currentY = null;
+
+ let subpathX = null;
+ let subpathY = null;
+
+ for (const seg of pathData) {
+ const type = seg.type;
+
+ if (type === "M") {
+ const x = seg.values[0];
+ const y = seg.values[1];
+
+ absolutizedPathData.push({ type: "M", values: [x, y] });
+
+ subpathX = x;
+ subpathY = y;
+
+ currentX = x;
+ currentY = y;
+ } else if (type === "m") {
+ const x = currentX + seg.values[0];
+ const y = currentY + seg.values[1];
+
+ absolutizedPathData.push({ type: "M", values: [x, y] });
+
+ subpathX = x;
+ subpathY = y;
+
+ currentX = x;
+ currentY = y;
+ } else if (type === "L") {
+ const x = seg.values[0];
+ const y = seg.values[1];
+
+ absolutizedPathData.push({ type: "L", values: [x, y] });
+
+ currentX = x;
+ currentY = y;
+ } else if (type === "l") {
+ const x = currentX + seg.values[0];
+ const y = currentY + seg.values[1];
+
+ absolutizedPathData.push({ type: "L", values: [x, y] });
+
+ currentX = x;
+ currentY = y;
+ } else if (type === "C") {
+ const x1 = seg.values[0];
+ const y1 = seg.values[1];
+ const x2 = seg.values[2];
+ const y2 = seg.values[3];
+ const x = seg.values[4];
+ const y = seg.values[5];
+
+ absolutizedPathData.push({ type: "C", values: [x1, y1, x2, y2, x, y] });
+
+ currentX = x;
+ currentY = y;
+ } else if (type === "c") {
+ const x1 = currentX + seg.values[0];
+ const y1 = currentY + seg.values[1];
+ const x2 = currentX + seg.values[2];
+ const y2 = currentY + seg.values[3];
+ const x = currentX + seg.values[4];
+ const y = currentY + seg.values[5];
+
+ absolutizedPathData.push({ type: "C", values: [x1, y1, x2, y2, x, y] });
+
+ currentX = x;
+ currentY = y;
+ } else if (type === "Q") {
+ const x1 = seg.values[0];
+ const y1 = seg.values[1];
+ const x = seg.values[2];
+ const y = seg.values[3];
+
+ absolutizedPathData.push({ type: "Q", values: [x1, y1, x, y] });
+
+ currentX = x;
+ currentY = y;
+ } else if (type === "q") {
+ const x1 = currentX + seg.values[0];
+ const y1 = currentY + seg.values[1];
+ const x = currentX + seg.values[2];
+ const y = currentY + seg.values[3];
+
+ absolutizedPathData.push({ type: "Q", values: [x1, y1, x, y] });
+
+ currentX = x;
+ currentY = y;
+ } else if (type === "A") {
+ const x = seg.values[5];
+ const y = seg.values[6];
+
+ absolutizedPathData.push({
+ type: "A",
+ values: [seg.values[0], seg.values[1], seg.values[2], seg.values[3], seg.values[4], x, y],
+ });
+
+ currentX = x;
+ currentY = y;
+ } else if (type === "a") {
+ const x = currentX + seg.values[5];
+ const y = currentY + seg.values[6];
+
+ absolutizedPathData.push({
+ type: "A",
+ values: [seg.values[0], seg.values[1], seg.values[2], seg.values[3], seg.values[4], x, y],
+ });
+
+ currentX = x;
+ currentY = y;
+ } else if (type === "H") {
+ const x = seg.values[0];
+ absolutizedPathData.push({ type: "H", values: [x] });
+ currentX = x;
+ } else if (type === "h") {
+ const x = currentX + seg.values[0];
+ absolutizedPathData.push({ type: "H", values: [x] });
+ currentX = x;
+ } else if (type === "V") {
+ const y = seg.values[0];
+ absolutizedPathData.push({ type: "V", values: [y] });
+ currentY = y;
+ } else if (type === "v") {
+ const y = currentY + seg.values[0];
+ absolutizedPathData.push({ type: "V", values: [y] });
+ currentY = y;
+ } else if (type === "S") {
+ const x2 = seg.values[0];
+ const y2 = seg.values[1];
+ const x = seg.values[2];
+ const y = seg.values[3];
+
+ absolutizedPathData.push({ type: "S", values: [x2, y2, x, y] });
+
+ currentX = x;
+ currentY = y;
+ } else if (type === "s") {
+ const x2 = currentX + seg.values[0];
+ const y2 = currentY + seg.values[1];
+ const x = currentX + seg.values[2];
+ const y = currentY + seg.values[3];
+
+ absolutizedPathData.push({ type: "S", values: [x2, y2, x, y] });
+
+ currentX = x;
+ currentY = y;
+ } else if (type === "T") {
+ const x = seg.values[0];
+ const y = seg.values[1];
+
+ absolutizedPathData.push({ type: "T", values: [x, y] });
+
+ currentX = x;
+ currentY = y;
+ } else if (type === "t") {
+ const x = currentX + seg.values[0];
+ const y = currentY + seg.values[1];
+
+ absolutizedPathData.push({ type: "T", values: [x, y] });
+
+ currentX = x;
+ currentY = y;
+ } else if (type === "Z" || type === "z") {
+ absolutizedPathData.push({ type: "Z", values: [] });
+
+ currentX = subpathX;
+ currentY = subpathY;
+ }
}
- }
-
- var number = integer + decimal;
- number *= sign;
-
- if (exponent) {
- number *= Math.pow(10, expsign * exponent);
- }
-
- if (startIndex === this._currentIndex) {
- return null;
- }
-
- this._skipOptionalSpacesOrDelimiter();
-
- return number;
- },
-
- _parseArcFlag: function() {
- if (this._currentIndex >= this._endIndex) {
- return null;
- }
-
- var flag = null;
- var flagChar = this._string[this._currentIndex];
-
- this._currentIndex += 1;
-
- if (flagChar === "0") {
- flag = 0;
- }
- else if (flagChar === "1") {
- flag = 1;
- }
- else {
- return null;
- }
-
- this._skipOptionalSpacesOrDelimiter();
- return flag;
- }
- };
-
- var parsePathDataString = function(string) {
- if (!string || string.length === 0) return [];
-
- var source = new Source(string);
- var pathData = [];
-
- if (source.initialCommandIsMoveTo()) {
- while (source.hasMoreData()) {
- var pathSeg = source.parseSegment();
-
- if (pathSeg === null) {
- break;
+
+ return absolutizedPathData;
+ };
+
+ // @info
+ // Takes path data that consists only from absolute commands, returns path data that consists only from
+ // "M", "L", "C" and "Z" commands.
+ const reducePathData = (pathData) => {
+ const reducedPathData = [];
+ let lastType = null;
+
+ let lastControlX = null;
+ let lastControlY = null;
+
+ let currentX = null;
+ let currentY = null;
+
+ let subpathX = null;
+ let subpathY = null;
+
+ for (const seg of pathData) {
+ if (seg.type === "M") {
+ const x = seg.values[0];
+ const y = seg.values[1];
+
+ reducedPathData.push({ type: "M", values: [x, y] });
+
+ subpathX = x;
+ subpathY = y;
+
+ currentX = x;
+ currentY = y;
+ } else if (seg.type === "C") {
+ const x1 = seg.values[0];
+ const y1 = seg.values[1];
+ const x2 = seg.values[2];
+ const y2 = seg.values[3];
+ const x = seg.values[4];
+ const y = seg.values[5];
+
+ reducedPathData.push({ type: "C", values: [x1, y1, x2, y2, x, y] });
+
+ lastControlX = x2;
+ lastControlY = y2;
+
+ currentX = x;
+ currentY = y;
+ } else if (seg.type === "L") {
+ const x = seg.values[0];
+ const y = seg.values[1];
+
+ reducedPathData.push({ type: "L", values: [x, y] });
+
+ currentX = x;
+ currentY = y;
+ } else if (seg.type === "H") {
+ const x = seg.values[0];
+
+ reducedPathData.push({ type: "L", values: [x, currentY] });
+
+ currentX = x;
+ } else if (seg.type === "V") {
+ const y = seg.values[0];
+
+ reducedPathData.push({ type: "L", values: [currentX, y] });
+
+ currentY = y;
+ } else if (seg.type === "S") {
+ const x2 = seg.values[0];
+ const y2 = seg.values[1];
+ const x = seg.values[2];
+ const y = seg.values[3];
+
+ let cx1;
+ let cy1;
+
+ if (lastType === "C" || lastType === "S") {
+ cx1 = currentX + (currentX - lastControlX);
+ cy1 = currentY + (currentY - lastControlY);
+ } else {
+ cx1 = currentX;
+ cy1 = currentY;
+ }
+
+ reducedPathData.push({ type: "C", values: [cx1, cy1, x2, y2, x, y] });
+
+ lastControlX = x2;
+ lastControlY = y2;
+
+ currentX = x;
+ currentY = y;
+ } else if (seg.type === "T") {
+ const x = seg.values[0];
+ const y = seg.values[1];
+
+ let x1;
+ let y1;
+
+ if (lastType === "Q" || lastType === "T") {
+ x1 = currentX + (currentX - lastControlX);
+ y1 = currentY + (currentY - lastControlY);
+ } else {
+ x1 = currentX;
+ y1 = currentY;
+ }
+
+ const cx1 = currentX + (2 * (x1 - currentX)) / 3;
+ const cy1 = currentY + (2 * (y1 - currentY)) / 3;
+ const cx2 = x + (2 * (x1 - x)) / 3;
+ const cy2 = y + (2 * (y1 - y)) / 3;
+
+ reducedPathData.push({ type: "C", values: [cx1, cy1, cx2, cy2, x, y] });
+
+ lastControlX = x1;
+ lastControlY = y1;
+
+ currentX = x;
+ currentY = y;
+ } else if (seg.type === "Q") {
+ const x1 = seg.values[0];
+ const y1 = seg.values[1];
+ const x = seg.values[2];
+ const y = seg.values[3];
+
+ const cx1 = currentX + (2 * (x1 - currentX)) / 3;
+ const cy1 = currentY + (2 * (y1 - currentY)) / 3;
+ const cx2 = x + (2 * (x1 - x)) / 3;
+ const cy2 = y + (2 * (y1 - y)) / 3;
+
+ reducedPathData.push({ type: "C", values: [cx1, cy1, cx2, cy2, x, y] });
+
+ lastControlX = x1;
+ lastControlY = y1;
+
+ currentX = x;
+ currentY = y;
+ } else if (seg.type === "A") {
+ const r1 = Math.abs(seg.values[0]);
+ const r2 = Math.abs(seg.values[1]);
+ const angle = seg.values[2];
+ const largeArcFlag = seg.values[3];
+ const sweepFlag = seg.values[4];
+ const x = seg.values[5];
+ const y = seg.values[6];
+
+ if (r1 === 0 || r2 === 0) {
+ reducedPathData.push({ type: "C", values: [currentX, currentY, x, y, x, y] });
+
+ currentX = x;
+ currentY = y;
+ } else {
+ if (currentX !== x || currentY !== y) {
+ const curves = arcToCubicCurves(
+ currentX,
+ currentY,
+ x,
+ y,
+ r1,
+ r2,
+ angle,
+ largeArcFlag,
+ sweepFlag,
+ );
+
+ for (const curve of curves) {
+ reducedPathData.push({ type: "C", values: curve });
+ }
+
+ currentX = x;
+ currentY = y;
+ }
+ }
+ } else if (seg.type === "Z") {
+ reducedPathData.push(seg);
+
+ currentX = subpathX;
+ currentY = subpathY;
+ }
+
+ lastType = seg.type;
}
- else {
- pathData.push(pathSeg);
+
+ return reducedPathData;
+ };
+
+ SVGPathElement.prototype.setAttribute = function (name, value) {
+ if (name === "d") {
+ this[$cachedPathData] = null;
+ this[$cachedNormalizedPathData] = null;
}
- }
- }
-
- return pathData;
- }
-
- var setAttribute = SVGPathElement.prototype.setAttribute;
- var setAttributeNS = SVGPathElement.prototype.setAttributeNS;
- var removeAttribute = SVGPathElement.prototype.removeAttribute;
- var removeAttributeNS = SVGPathElement.prototype.removeAttributeNS;
-
- var $cachedPathData = window.Symbol ? Symbol() : "__cachedPathData";
- var $cachedNormalizedPathData = window.Symbol ? Symbol() : "__cachedNormalizedPathData";
-
- // @info
- // Get an array of corresponding cubic bezier curve parameters for given arc curve paramters.
- var arcToCubicCurves = function(x1, y1, x2, y2, r1, r2, angle, largeArcFlag, sweepFlag, _recursive) {
- var degToRad = function(degrees) {
- return (Math.PI * degrees) / 180;
+
+ setAttribute.call(this, name, value);
};
-
- var rotate = function(x, y, angleRad) {
- var X = x * Math.cos(angleRad) - y * Math.sin(angleRad);
- var Y = x * Math.sin(angleRad) + y * Math.cos(angleRad);
- return {x: X, y: Y};
+
+ SVGPathElement.prototype.setAttributeNS = function (namespace, name, value) {
+ if (name === "d") {
+ let namespaceURI = "http://www.w3.org/2000/svg";
+
+ if (namespace) {
+ for (const attribute of this.ownerSVGElement.attributes) {
+ if (attribute.name === `xmlns:${namespace}`) {
+ namespaceURI = attribute.value;
+ }
+ }
+ }
+
+ if (namespaceURI === "http://www.w3.org/2000/svg") {
+ this[$cachedPathData] = null;
+ this[$cachedNormalizedPathData] = null;
+ }
+ }
+
+ setAttributeNS.call(this, namespace, name, value);
};
-
- var angleRad = degToRad(angle);
- var params = [];
- var f1, f2, cx, cy;
-
- if (_recursive) {
- f1 = _recursive[0];
- f2 = _recursive[1];
- cx = _recursive[2];
- cy = _recursive[3];
- }
- else {
- var p1 = rotate(x1, y1, -angleRad);
- x1 = p1.x;
- y1 = p1.y;
-
- var p2 = rotate(x2, y2, -angleRad);
- x2 = p2.x;
- y2 = p2.y;
-
- var x = (x1 - x2) / 2;
- var y = (y1 - y2) / 2;
- var h = (x * x) / (r1 * r1) + (y * y) / (r2 * r2);
-
- if (h > 1) {
- h = Math.sqrt(h);
- r1 = h * r1;
- r2 = h * r2;
- }
-
- var sign;
-
- if (largeArcFlag === sweepFlag) {
- sign = -1;
- }
- else {
- sign = 1;
- }
-
- var r1Pow = r1 * r1;
- var r2Pow = r2 * r2;
-
- var left = r1Pow * r2Pow - r1Pow * y * y - r2Pow * x * x;
- var right = r1Pow * y * y + r2Pow * x * x;
-
- var k = sign * Math.sqrt(Math.abs(left/right));
-
- cx = k * r1 * y / r2 + (x1 + x2) / 2;
- cy = k * -r2 * x / r1 + (y1 + y2) / 2;
-
- f1 = Math.asin(parseFloat(((y1 - cy) / r2).toFixed(9)));
- f2 = Math.asin(parseFloat(((y2 - cy) / r2).toFixed(9)));
-
- if (x1 < cx) {
- f1 = Math.PI - f1;
- }
- if (x2 < cx) {
- f2 = Math.PI - f2;
- }
-
- if (f1 < 0) {
- f1 = Math.PI * 2 + f1;
- }
- if (f2 < 0) {
- f2 = Math.PI * 2 + f2;
- }
-
- if (sweepFlag && f1 > f2) {
- f1 = f1 - Math.PI * 2;
- }
- if (!sweepFlag && f2 > f1) {
- f2 = f2 - Math.PI * 2;
- }
- }
-
- var df = f2 - f1;
-
- if (Math.abs(df) > (Math.PI * 120 / 180)) {
- var f2old = f2;
- var x2old = x2;
- var y2old = y2;
-
- if (sweepFlag && f2 > f1) {
- f2 = f1 + (Math.PI * 120 / 180) * (1);
- }
- else {
- f2 = f1 + (Math.PI * 120 / 180) * (-1);
- }
-
- x2 = cx + r1 * Math.cos(f2);
- y2 = cy + r2 * Math.sin(f2);
- params = arcToCubicCurves(x2, y2, x2old, y2old, r1, r2, angle, 0, sweepFlag, [f2, f2old, cx, cy]);
- }
-
- df = f2 - f1;
-
- var c1 = Math.cos(f1);
- var s1 = Math.sin(f1);
- var c2 = Math.cos(f2);
- var s2 = Math.sin(f2);
- var t = Math.tan(df / 4);
- var hx = 4 / 3 * r1 * t;
- var hy = 4 / 3 * r2 * t;
-
- var m1 = [x1, y1];
- var m2 = [x1 + hx * s1, y1 - hy * c1];
- var m3 = [x2 + hx * s2, y2 - hy * c2];
- var m4 = [x2, y2];
-
- m2[0] = 2 * m1[0] - m2[0];
- m2[1] = 2 * m1[1] - m2[1];
-
- if (_recursive) {
- return [m2, m3, m4].concat(params);
- }
- else {
- params = [m2, m3, m4].concat(params);
-
- var curves = [];
-
- for (var i = 0; i < params.length; i+=3) {
- var r1 = rotate(params[i][0], params[i][1], angleRad);
- var r2 = rotate(params[i+1][0], params[i+1][1], angleRad);
- var r3 = rotate(params[i+2][0], params[i+2][1], angleRad);
- curves.push([r1.x, r1.y, r2.x, r2.y, r3.x, r3.y]);
- }
-
- return curves;
- }
- };
-
- var clonePathData = function(pathData) {
- return pathData.map( function(seg) {
- return {type: seg.type, values: Array.prototype.slice.call(seg.values)}
- });
- };
-
- // @info
- // Takes any path data, returns path data that consists only from absolute commands.
- var absolutizePathData = function(pathData) {
- var absolutizedPathData = [];
-
- var currentX = null;
- var currentY = null;
-
- var subpathX = null;
- var subpathY = null;
-
- pathData.forEach( function(seg) {
- var type = seg.type;
-
- if (type === "M") {
- var x = seg.values[0];
- var y = seg.values[1];
-
- absolutizedPathData.push({type: "M", values: [x, y]});
-
- subpathX = x;
- subpathY = y;
-
- currentX = x;
- currentY = y;
- }
-
- else if (type === "m") {
- var x = currentX + seg.values[0];
- var y = currentY + seg.values[1];
-
- absolutizedPathData.push({type: "M", values: [x, y]});
-
- subpathX = x;
- subpathY = y;
-
- currentX = x;
- currentY = y;
- }
-
- else if (type === "L") {
- var x = seg.values[0];
- var y = seg.values[1];
-
- absolutizedPathData.push({type: "L", values: [x, y]});
-
- currentX = x;
- currentY = y;
- }
-
- else if (type === "l") {
- var x = currentX + seg.values[0];
- var y = currentY + seg.values[1];
-
- absolutizedPathData.push({type: "L", values: [x, y]});
-
- currentX = x;
- currentY = y;
- }
-
- else if (type === "C") {
- var x1 = seg.values[0];
- var y1 = seg.values[1];
- var x2 = seg.values[2];
- var y2 = seg.values[3];
- var x = seg.values[4];
- var y = seg.values[5];
-
- absolutizedPathData.push({type: "C", values: [x1, y1, x2, y2, x, y]});
-
- currentX = x;
- currentY = y;
- }
-
- else if (type === "c") {
- var x1 = currentX + seg.values[0];
- var y1 = currentY + seg.values[1];
- var x2 = currentX + seg.values[2];
- var y2 = currentY + seg.values[3];
- var x = currentX + seg.values[4];
- var y = currentY + seg.values[5];
-
- absolutizedPathData.push({type: "C", values: [x1, y1, x2, y2, x, y]});
-
- currentX = x;
- currentY = y;
- }
-
- else if (type === "Q") {
- var x1 = seg.values[0];
- var y1 = seg.values[1];
- var x = seg.values[2];
- var y = seg.values[3];
-
- absolutizedPathData.push({type: "Q", values: [x1, y1, x, y]});
-
- currentX = x;
- currentY = y;
- }
-
- else if (type === "q") {
- var x1 = currentX + seg.values[0];
- var y1 = currentY + seg.values[1];
- var x = currentX + seg.values[2];
- var y = currentY + seg.values[3];
-
- absolutizedPathData.push({type: "Q", values: [x1, y1, x, y]});
-
- currentX = x;
- currentY = y;
- }
-
- else if (type === "A") {
- var x = seg.values[5];
- var y = seg.values[6];
-
- absolutizedPathData.push({
- type: "A",
- values: [seg.values[0], seg.values[1], seg.values[2], seg.values[3], seg.values[4], x, y]
- });
-
- currentX = x;
- currentY = y;
- }
-
- else if (type === "a") {
- var x = currentX + seg.values[5];
- var y = currentY + seg.values[6];
-
- absolutizedPathData.push({
- type: "A",
- values: [seg.values[0], seg.values[1], seg.values[2], seg.values[3], seg.values[4], x, y]
- });
-
- currentX = x;
- currentY = y;
- }
-
- else if (type === "H") {
- var x = seg.values[0];
- absolutizedPathData.push({type: "H", values: [x]});
- currentX = x;
- }
-
- else if (type === "h") {
- var x = currentX + seg.values[0];
- absolutizedPathData.push({type: "H", values: [x]});
- currentX = x;
- }
-
- else if (type === "V") {
- var y = seg.values[0];
- absolutizedPathData.push({type: "V", values: [y]});
- currentY = y;
- }
-
- else if (type === "v") {
- var y = currentY + seg.values[0];
- absolutizedPathData.push({type: "V", values: [y]});
- currentY = y;
- }
-
- else if (type === "S") {
- var x2 = seg.values[0];
- var y2 = seg.values[1];
- var x = seg.values[2];
- var y = seg.values[3];
-
- absolutizedPathData.push({type: "S", values: [x2, y2, x, y]});
-
- currentX = x;
- currentY = y;
- }
-
- else if (type === "s") {
- var x2 = currentX + seg.values[0];
- var y2 = currentY + seg.values[1];
- var x = currentX + seg.values[2];
- var y = currentY + seg.values[3];
-
- absolutizedPathData.push({type: "S", values: [x2, y2, x, y]});
-
- currentX = x;
- currentY = y;
- }
-
- else if (type === "T") {
- var x = seg.values[0];
- var y = seg.values[1]
-
- absolutizedPathData.push({type: "T", values: [x, y]});
-
- currentX = x;
- currentY = y;
- }
-
- else if (type === "t") {
- var x = currentX + seg.values[0];
- var y = currentY + seg.values[1]
-
- absolutizedPathData.push({type: "T", values: [x, y]});
-
- currentX = x;
- currentY = y;
- }
-
- else if (type === "Z" || type === "z") {
- absolutizedPathData.push({type: "Z", values: []});
-
- currentX = subpathX;
- currentY = subpathY;
- }
- });
-
- return absolutizedPathData;
- };
-
- // @info
- // Takes path data that consists only from absolute commands, returns path data that consists only from
- // "M", "L", "C" and "Z" commands.
- var reducePathData = function(pathData) {
- var reducedPathData = [];
- var lastType = null;
-
- var lastControlX = null;
- var lastControlY = null;
-
- var currentX = null;
- var currentY = null;
-
- var subpathX = null;
- var subpathY = null;
-
- pathData.forEach( function(seg) {
- if (seg.type === "M") {
- var x = seg.values[0];
- var y = seg.values[1];
-
- reducedPathData.push({type: "M", values: [x, y]});
-
- subpathX = x;
- subpathY = y;
-
- currentX = x;
- currentY = y;
- }
-
- else if (seg.type === "C") {
- var x1 = seg.values[0];
- var y1 = seg.values[1];
- var x2 = seg.values[2];
- var y2 = seg.values[3];
- var x = seg.values[4];
- var y = seg.values[5];
-
- reducedPathData.push({type: "C", values: [x1, y1, x2, y2, x, y]});
-
- lastControlX = x2;
- lastControlY = y2;
-
- currentX = x;
- currentY = y;
- }
-
- else if (seg.type === "L") {
- var x = seg.values[0];
- var y = seg.values[1];
-
- reducedPathData.push({type: "L", values: [x, y]});
-
- currentX = x;
- currentY = y;
- }
-
- else if (seg.type === "H") {
- var x = seg.values[0];
-
- reducedPathData.push({type: "L", values: [x, currentY]});
-
- currentX = x;
- }
-
- else if (seg.type === "V") {
- var y = seg.values[0];
-
- reducedPathData.push({type: "L", values: [currentX, y]});
-
- currentY = y;
- }
-
- else if (seg.type === "S") {
- var x2 = seg.values[0];
- var y2 = seg.values[1];
- var x = seg.values[2];
- var y = seg.values[3];
-
- var cx1, cy1;
-
- if (lastType === "C" || lastType === "S") {
- cx1 = currentX + (currentX - lastControlX);
- cy1 = currentY + (currentY - lastControlY);
+
+ SVGPathElement.prototype.removeAttribute = (name, _value) => {
+ if (name === "d") {
+ this[$cachedPathData] = null;
+ this[$cachedNormalizedPathData] = null;
}
- else {
- cx1 = currentX;
- cy1 = currentY;
+
+ removeAttribute.call(this, name);
+ };
+
+ SVGPathElement.prototype.removeAttributeNS = (namespace, name) => {
+ if (name === "d") {
+ let namespaceURI = "http://www.w3.org/2000/svg";
+
+ if (namespace) {
+ for (const attribute of this.ownerSVGElement.attributes) {
+ if (attribute.name === `xmlns:${namespace}`) {
+ namespaceURI = attribute.value;
+ }
+ }
+ }
+
+ if (namespaceURI === "http://www.w3.org/2000/svg") {
+ this[$cachedPathData] = null;
+ this[$cachedNormalizedPathData] = null;
+ }
}
-
- reducedPathData.push({type: "C", values: [cx1, cy1, x2, y2, x, y]});
-
- lastControlX = x2;
- lastControlY = y2;
-
- currentX = x;
- currentY = y;
- }
-
- else if (seg.type === "T") {
- var x = seg.values[0];
- var y = seg.values[1];
-
- var x1, y1;
-
- if (lastType === "Q" || lastType === "T") {
- x1 = currentX + (currentX - lastControlX);
- y1 = currentY + (currentY - lastControlY);
+
+ removeAttributeNS.call(this, namespace, name);
+ };
+
+ SVGPathElement.prototype.getPathData = (options) => {
+ if (options?.normalize) {
+ if (this[$cachedNormalizedPathData]) {
+ return clonePathData(this[$cachedNormalizedPathData]);
+ }
+
+ let pathData;
+
+ if (this[$cachedPathData]) {
+ pathData = clonePathData(this[$cachedPathData]);
+ } else {
+ pathData = parsePathDataString(this.getAttribute("d") || "");
+ this[$cachedPathData] = clonePathData(pathData);
+ }
+
+ const normalizedPathData = reducePathData(absolutizePathData(pathData));
+ this[$cachedNormalizedPathData] = clonePathData(normalizedPathData);
+ return normalizedPathData;
}
- else {
- x1 = currentX;
- y1 = currentY;
+
+ if (this[$cachedPathData]) {
+ return clonePathData(this[$cachedPathData]);
}
-
- var cx1 = currentX + 2 * (x1 - currentX) / 3;
- var cy1 = currentY + 2 * (y1 - currentY) / 3;
- var cx2 = x + 2 * (x1 - x) / 3;
- var cy2 = y + 2 * (y1 - y) / 3;
-
- reducedPathData.push({type: "C", values: [cx1, cy1, cx2, cy2, x, y]});
-
- lastControlX = x1;
- lastControlY = y1;
-
- currentX = x;
- currentY = y;
- }
-
- else if (seg.type === "Q") {
- var x1 = seg.values[0];
- var y1 = seg.values[1];
- var x = seg.values[2];
- var y = seg.values[3];
-
- var cx1 = currentX + 2 * (x1 - currentX) / 3;
- var cy1 = currentY + 2 * (y1 - currentY) / 3;
- var cx2 = x + 2 * (x1 - x) / 3;
- var cy2 = y + 2 * (y1 - y) / 3;
-
- reducedPathData.push({type: "C", values: [cx1, cy1, cx2, cy2, x, y]});
-
- lastControlX = x1;
- lastControlY = y1;
-
- currentX = x;
- currentY = y;
- }
-
- else if (seg.type === "A") {
- var r1 = Math.abs(seg.values[0]);
- var r2 = Math.abs(seg.values[1]);
- var angle = seg.values[2];
- var largeArcFlag = seg.values[3];
- var sweepFlag = seg.values[4];
- var x = seg.values[5];
- var y = seg.values[6];
-
- if (r1 === 0 || r2 === 0) {
- reducedPathData.push({type: "C", values: [currentX, currentY, x, y, x, y]});
-
- currentX = x;
- currentY = y;
+
+ const pathData = parsePathDataString(this.getAttribute("d") || "");
+ this[$cachedPathData] = clonePathData(pathData);
+ return pathData;
+ };
+
+ SVGPathElement.prototype.setPathData = (pathData) => {
+ if (pathData.length === 0) {
+ if (isIE) {
+ // @bugfix https://github.com/mbostock/d3/issues/1737
+ this.setAttribute("d", "");
+ } else {
+ this.removeAttribute("d");
+ }
+ } else {
+ let d = "";
+
+ for (let i = 0, l = pathData.length; i < l; i += 1) {
+ const seg = pathData[i];
+
+ if (i > 0) {
+ d += " ";
+ }
+
+ d += seg.type;
+
+ if (seg.values && seg.values.length > 0) {
+ d += ` ${seg.values.join(" ")}`;
+ }
+ }
+
+ this.setAttribute("d", d);
}
- else {
- if (currentX !== x || currentY !== y) {
- var curves = arcToCubicCurves(currentX, currentY, x, y, r1, r2, angle, largeArcFlag, sweepFlag);
-
- curves.forEach( function(curve) {
- reducedPathData.push({type: "C", values: curve});
- });
-
- currentX = x;
- currentY = y;
- }
+ };
+
+ SVGRectElement.prototype.getPathData = (options) => {
+ const x = this.x.baseVal.value;
+ const y = this.y.baseVal.value;
+ const width = this.width.baseVal.value;
+ const height = this.height.baseVal.value;
+ let rx = this.hasAttribute("rx") ? this.rx.baseVal.value : this.ry.baseVal.value;
+ let ry = this.hasAttribute("ry") ? this.ry.baseVal.value : this.rx.baseVal.value;
+
+ if (rx > width / 2) {
+ rx = width / 2;
}
- }
-
- else if (seg.type === "Z") {
- reducedPathData.push(seg);
-
- currentX = subpathX;
- currentY = subpathY;
- }
-
- lastType = seg.type;
- });
-
- return reducedPathData;
- };
-
- SVGPathElement.prototype.setAttribute = function(name, value) {
- if (name === "d") {
- this[$cachedPathData] = null;
- this[$cachedNormalizedPathData] = null;
- }
-
- setAttribute.call(this, name, value);
- };
-
- SVGPathElement.prototype.setAttributeNS = function(namespace, name, value) {
- if (name === "d") {
- var namespaceURI = "http://www.w3.org/2000/svg";
-
- if (namespace) {
- for (var attribute of this.ownerSVGElement.attributes) {
- if (attribute.name === `xmlns:${namespace}`) {
- namespaceURI = attribute.value;
- }
+
+ if (ry > height / 2) {
+ ry = height / 2;
}
- }
-
- if (namespaceURI === "http://www.w3.org/2000/svg") {
- this[$cachedPathData] = null;
- this[$cachedNormalizedPathData] = null;
- }
- }
-
- setAttributeNS.call(this, namespace, name, value);
- };
-
- SVGPathElement.prototype.removeAttribute = function(name, value) {
- if (name === "d") {
- this[$cachedPathData] = null;
- this[$cachedNormalizedPathData] = null;
- }
-
- removeAttribute.call(this, name);
- };
-
- SVGPathElement.prototype.removeAttributeNS = function(namespace, name) {
- if (name === "d") {
- var namespaceURI = "http://www.w3.org/2000/svg";
-
- if (namespace) {
- for (var attribute of this.ownerSVGElement.attributes) {
- if (attribute.name === `xmlns:${namespace}`) {
- namespaceURI = attribute.value;
- }
+
+ let pathData = [
+ { type: "M", values: [x + rx, y] },
+ { type: "H", values: [x + width - rx] },
+ { type: "A", values: [rx, ry, 0, 0, 1, x + width, y + ry] },
+ { type: "V", values: [y + height - ry] },
+ { type: "A", values: [rx, ry, 0, 0, 1, x + width - rx, y + height] },
+ { type: "H", values: [x + rx] },
+ { type: "A", values: [rx, ry, 0, 0, 1, x, y + height - ry] },
+ { type: "V", values: [y + ry] },
+ { type: "A", values: [rx, ry, 0, 0, 1, x + rx, y] },
+ { type: "Z", values: [] },
+ ];
+
+ // Get rid of redundant "A" segs when either rx or ry is 0
+ pathData = pathData.filter((s) => {
+ return s.type === "A" && (s.values[0] === 0 || s.values[1] === 0);
+ });
+
+ if (options && options.normalize === true) {
+ pathData = reducePathData(pathData);
}
- }
-
- if (namespaceURI === "http://www.w3.org/2000/svg") {
- this[$cachedPathData] = null;
- this[$cachedNormalizedPathData] = null;
- }
- }
-
- removeAttributeNS.call(this, namespace, name);
- };
-
- SVGPathElement.prototype.getPathData = function(options) {
- if (options && options.normalize) {
- if (this[$cachedNormalizedPathData]) {
- return clonePathData(this[$cachedNormalizedPathData]);
- }
- else {
- var pathData;
-
- if (this[$cachedPathData]) {
- pathData = clonePathData(this[$cachedPathData]);
+
+ return pathData;
+ };
+
+ SVGCircleElement.prototype.getPathData = (options) => {
+ const cx = this.cx.baseVal.value;
+ const cy = this.cy.baseVal.value;
+ const r = this.r.baseVal.value;
+
+ let pathData = [
+ { type: "M", values: [cx + r, cy] },
+ { type: "A", values: [r, r, 0, 0, 1, cx, cy + r] },
+ { type: "A", values: [r, r, 0, 0, 1, cx - r, cy] },
+ { type: "A", values: [r, r, 0, 0, 1, cx, cy - r] },
+ { type: "A", values: [r, r, 0, 0, 1, cx + r, cy] },
+ { type: "Z", values: [] },
+ ];
+
+ if (options && options.normalize === true) {
+ pathData = reducePathData(pathData);
}
- else {
- pathData = parsePathDataString(this.getAttribute("d") || "");
- this[$cachedPathData] = clonePathData(pathData);
+
+ return pathData;
+ };
+
+ SVGEllipseElement.prototype.getPathData = (options) => {
+ const cx = this.cx.baseVal.value;
+ const cy = this.cy.baseVal.value;
+ const rx = this.rx.baseVal.value;
+ const ry = this.ry.baseVal.value;
+
+ let pathData = [
+ { type: "M", values: [cx + rx, cy] },
+ { type: "A", values: [rx, ry, 0, 0, 1, cx, cy + ry] },
+ { type: "A", values: [rx, ry, 0, 0, 1, cx - rx, cy] },
+ { type: "A", values: [rx, ry, 0, 0, 1, cx, cy - ry] },
+ { type: "A", values: [rx, ry, 0, 0, 1, cx + rx, cy] },
+ { type: "Z", values: [] },
+ ];
+
+ if (options && options.normalize === true) {
+ pathData = reducePathData(pathData);
}
-
- var normalizedPathData = reducePathData(absolutizePathData(pathData));
- this[$cachedNormalizedPathData] = clonePathData(normalizedPathData);
- return normalizedPathData;
- }
- }
- else {
- if (this[$cachedPathData]) {
- return clonePathData(this[$cachedPathData]);
- }
- else {
- var pathData = parsePathDataString(this.getAttribute("d") || "");
- this[$cachedPathData] = clonePathData(pathData);
+
return pathData;
- }
- }
- };
-
- SVGPathElement.prototype.setPathData = function(pathData) {
- if (pathData.length === 0) {
- if (isIE) {
- // @bugfix https://github.com/mbostock/d3/issues/1737
- this.setAttribute("d", "");
- }
- else {
- this.removeAttribute("d");
- }
- }
- else {
- var d = "";
-
- for (var i = 0, l = pathData.length; i < l; i += 1) {
- var seg = pathData[i];
-
- if (i > 0) {
- d += " ";
+ };
+
+ SVGLineElement.prototype.getPathData = () => {
+ return [
+ { type: "M", values: [this.x1.baseVal.value, this.y1.baseVal.value] },
+ { type: "L", values: [this.x2.baseVal.value, this.y2.baseVal.value] },
+ ];
+ };
+
+ SVGPolylineElement.prototype.getPathData = () => {
+ const pathData = [];
+
+ for (let i = 0; i < this.points.numberOfItems; i += 1) {
+ const point = this.points.getItem(i);
+
+ pathData.push({
+ type: i === 0 ? "M" : "L",
+ values: [point.x, point.y],
+ });
}
-
- d += seg.type;
-
- if (seg.values && seg.values.length > 0) {
- d += " " + seg.values.join(" ");
+
+ return pathData;
+ };
+
+ SVGPolygonElement.prototype.getPathData = () => {
+ const pathData = [];
+
+ for (let i = 0; i < this.points.numberOfItems; i += 1) {
+ const point = this.points.getItem(i);
+
+ pathData.push({
+ type: i === 0 ? "M" : "L",
+ values: [point.x, point.y],
+ });
}
- }
-
- this.setAttribute("d", d);
- }
- };
-
- SVGRectElement.prototype.getPathData = function(options) {
- var x = this.x.baseVal.value;
- var y = this.y.baseVal.value;
- var width = this.width.baseVal.value;
- var height = this.height.baseVal.value;
- var rx = this.hasAttribute("rx") ? this.rx.baseVal.value : this.ry.baseVal.value;
- var ry = this.hasAttribute("ry") ? this.ry.baseVal.value : this.rx.baseVal.value;
-
- if (rx > width / 2) {
- rx = width / 2;
- }
-
- if (ry > height / 2) {
- ry = height / 2;
- }
-
- var pathData = [
- {type: "M", values: [x+rx, y]},
- {type: "H", values: [x+width-rx]},
- {type: "A", values: [rx, ry, 0, 0, 1, x+width, y+ry]},
- {type: "V", values: [y+height-ry]},
- {type: "A", values: [rx, ry, 0, 0, 1, x+width-rx, y+height]},
- {type: "H", values: [x+rx]},
- {type: "A", values: [rx, ry, 0, 0, 1, x, y+height-ry]},
- {type: "V", values: [y+ry]},
- {type: "A", values: [rx, ry, 0, 0, 1, x+rx, y]},
- {type: "Z", values: []}
- ];
-
- // Get rid of redundant "A" segs when either rx or ry is 0
- pathData = pathData.filter(function(s) {
- return s.type === "A" && (s.values[0] === 0 || s.values[1] === 0) ? false : true;
- });
-
- if (options && options.normalize === true) {
- pathData = reducePathData(pathData);
- }
-
- return pathData;
- };
-
- SVGCircleElement.prototype.getPathData = function(options) {
- var cx = this.cx.baseVal.value;
- var cy = this.cy.baseVal.value;
- var r = this.r.baseVal.value;
-
- var pathData = [
- { type: "M", values: [cx + r, cy] },
- { type: "A", values: [r, r, 0, 0, 1, cx, cy+r] },
- { type: "A", values: [r, r, 0, 0, 1, cx-r, cy] },
- { type: "A", values: [r, r, 0, 0, 1, cx, cy-r] },
- { type: "A", values: [r, r, 0, 0, 1, cx+r, cy] },
- { type: "Z", values: [] }
- ];
-
- if (options && options.normalize === true) {
- pathData = reducePathData(pathData);
- }
-
- return pathData;
- };
-
- SVGEllipseElement.prototype.getPathData = function(options) {
- var cx = this.cx.baseVal.value;
- var cy = this.cy.baseVal.value;
- var rx = this.rx.baseVal.value;
- var ry = this.ry.baseVal.value;
-
- var pathData = [
- { type: "M", values: [cx + rx, cy] },
- { type: "A", values: [rx, ry, 0, 0, 1, cx, cy+ry] },
- { type: "A", values: [rx, ry, 0, 0, 1, cx-rx, cy] },
- { type: "A", values: [rx, ry, 0, 0, 1, cx, cy-ry] },
- { type: "A", values: [rx, ry, 0, 0, 1, cx+rx, cy] },
- { type: "Z", values: [] }
- ];
-
- if (options && options.normalize === true) {
- pathData = reducePathData(pathData);
- }
-
- return pathData;
- };
-
- SVGLineElement.prototype.getPathData = function() {
- return [
- { type: "M", values: [this.x1.baseVal.value, this.y1.baseVal.value] },
- { type: "L", values: [this.x2.baseVal.value, this.y2.baseVal.value] }
- ];
- };
-
- SVGPolylineElement.prototype.getPathData = function() {
- var pathData = [];
-
- for (var i = 0; i < this.points.numberOfItems; i += 1) {
- var point = this.points.getItem(i);
-
- pathData.push({
- type: (i === 0 ? "M" : "L"),
- values: [point.x, point.y]
- });
- }
-
- return pathData;
- };
-
- SVGPolygonElement.prototype.getPathData = function() {
- var pathData = [];
-
- for (var i = 0; i < this.points.numberOfItems; i += 1) {
- var point = this.points.getItem(i);
-
- pathData.push({
- type: (i === 0 ? "M" : "L"),
- values: [point.x, point.y]
- });
- }
-
- pathData.push({
- type: "Z",
- values: []
- });
-
- return pathData;
- };
+
+ pathData.push({
+ type: "Z",
+ values: [],
+ });
+
+ return pathData;
+ };
})();
- }
\ No newline at end of file
+}
diff --git a/web/scripts/preview-canvas.js b/web/scripts/preview-canvas.js
index 35cdc12..fc9e380 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 paddingPx = 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 - paddingPx[0] * 2;
+ const dst_h = this.h - paddingPx[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 paddingPx = 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 - paddingPx[0] * 2;
+ const dst_h = this.h - paddingPx[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 + paddingPx[0]), y, w, h);
}
draw_image_n_up(img, i, n, padding = [2, 2]) {
- padding = padding.map((x) => x * this.one_rem);
+ const paddingPx = 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 - paddingPx[0] * 2;
+ const dst_h = this.h - paddingPx[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 + paddingPx[0] / 2;
+ const x_min = this.w / 2 - n * (n_w / 2) + paddingPx[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..a0be1f2 100644
--- a/web/scripts/wasi.js
+++ b/web/scripts/wasi.js
@@ -25,11 +25,11 @@ export default class WASI {
return {
proc_exit() {},
- environ_get: (environ, buf) => {
+ 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 });
}
@@ -95,7 +95,7 @@ export default class WASI {
path_unlink_file() {},
fd_filestat_get() {},
- fd_fdstat_get: (fd, buf_ptr) => {
+ fd_fdstat_get: (_fd, _buf_ptr) => {
return WASI_ESUCCESS;
},
diff --git a/web/scripts/yak.js b/web/scripts/yak.js
index b6b5cf8..dfa2e67 100644
--- a/web/scripts/yak.js
+++ b/web/scripts/yak.js
@@ -45,7 +45,7 @@ async function ImageBitmap_from_Blob(blob, width = 1000, context = null) {
// Workaround for firefox- it doesn't set the image dimensions for SVG
// elements until they've been added to the DOM, so use the viewBox
// dimensions.
- if (image.width == 0 && context instanceof XMLDocument) {
+ if (image.width === 0 && context instanceof XMLDocument) {
const viewbox = context.documentElement.viewBox.baseVal;
image.width = viewbox.width;
image.height = viewbox.height;
@@ -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;
- if (image instanceof XMLDocument) {
- image = Blob_from_SVGDocument(image);
+ let imageToProcess = image;
+
+ if (imageToProcess instanceof XMLDocument) {
+ imageToProcess = Blob_from_SVGDocument(imageToProcess);
}
- if (image instanceof Blob) {
- return await ImageBitmap_from_Blob(image, width, context);
- } else {
- return await window.createImageBitmap(image);
+ if (imageToProcess instanceof Blob) {
+ return await ImageBitmap_from_Blob(imageToProcess, width, context);
}
+
+ return await window.createImageBitmap(imageToProcess);
}
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 strokeToUse = stroke ?? fill;
+ const fillToUse = 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 = fillToUse;
el.style.fillOpacity = "1";
}
if (!invisible_values.includes(current_stroke)) {
- el.style.stroke = stroke;
+ el.style.stroke = strokeToUse;
}
}
}
@@ -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;
@@ -201,27 +197,15 @@ export async function ImageBitmap_inverse_mask(
*/
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);
+ const dd0 = (p1[0] - 2 * p2[0] + p3[0]) ** 2 + (p1[1] - 2 * p2[1] + p3[1]) ** 2;
+ const dd1 = (p2[0] - 2 * p3[0] + p4[0]) ** 2 + (p2[1] - 2 * p3[1] + p4[1]) ** 2;
const dd = 6 * Math.sqrt(Math.max(dd0, dd1));
const e2 = 8 * delta < dd ? (8 * delta) / dd : 1;
const interval = Math.sqrt(e2);
for (let t = 0; t < 1; t += interval) {
- 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) +
- 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) +
- p4[1] * Math.pow(t, 3);
+ const x = p1[0] * (1 - t) ** 3 + 3 * p2[0] * (1 - t) ** 2 * t + 3 * p3[0] * (1 - t) * t ** 2 + p4[0] * t ** 3;
+ const y = p1[1] * (1 - t) ** 3 + 3 * p2[1] * (1 - t) ** 2 * t + 3 * p3[1] * (1 - t) * t ** 2 + p4[1] * t ** 3;
yield [x, y];
}
@@ -260,12 +244,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 +253,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() {
diff --git a/web/styles/styles.css b/web/styles/styles.css
index 997f9f4..2de229a 100644
--- a/web/styles/styles.css
+++ b/web/styles/styles.css
@@ -90,20 +90,21 @@ h6 {
/* Section: Links */
-a,
-a:focus,
-a:hover {
+a, a:focus {
color: var(--link-color);
text-decoration: var(--link-decoration);
}
-a:hover {
+a:hover, a:active {
color: var(--link-hover-color);
text-decoration: var(--link-hover-decoration);
}
a.tags,
-a.tags:hover {
+a.tags:hover,
+a.tags:active,
+a.tags:focus
+{
text-decoration: none;
}
@@ -112,7 +113,10 @@ a.button::after {
content: none;
}
-.navbar a, .navbar a:focus, .navbar a:hover {
+.navbar a,
+.navbar a:focus,
+.navbar a:hover,
+.navbar a:active {
text-decoration: none;
}
@@ -122,7 +126,9 @@ a.button::after {
background-color: var(--color-Violet);
}
-.button.is-primary:hover {
+.button.is-primary:hover,
+.button.is-primary:active,
+.button.is-primary:focus {
background-color: var(--color-Aquarelle);
}
@@ -132,7 +138,9 @@ a.button::after {
border-color: transparent;
}
-.button.is-alt:hover {
+.button.is-alt:hover,
+.button.is-alt:active,
+.button.is_alt:focus {
background-color: var(--color-Cupid);
}
@@ -163,7 +171,7 @@ body {
margin: 1rem auto;
}
-.content h1, .content h2 {
+.content h1, .content h2, .content h3 {
color: var(--color-Neptune);
}
@@ -179,7 +187,12 @@ body {
font-size: 1.5rem;
}
-.navbar.is-primary .navbar-brand > a.navbar-item:hover, .navbar.is-primary .navbar-end > a.navbar-item:hover {
+.navbar.is-primary .navbar-brand > a.navbar-item:hover,
+.navbar.is-primary .navbar-brand > a.navbar-item:active,
+.navbar.is-primary .navbar-brand > a.navbar-item:focus,
+.navbar.is-primary .navbar-menu a.navbar-item:hover,
+.navbar.is-primary .navbar-menu a.navbar-item:active,
+.navbar.is-primary .navbar-menu a.navbar-item:focus {
background-color: var(--color-Zinnia);
}
diff --git a/web/templates/layout.html b/web/templates/layout.html
index be33f08..ce38a67 100644
--- a/web/templates/layout.html
+++ b/web/templates/layout.html
@@ -6,6 +6,7 @@
Gingerbread
+
@@ -27,17 +28,37 @@
-
-