From 56b140cb7d51ec8ceb3faccad7980a3ac9370824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20Hauser?= Date: Thu, 15 May 2025 21:35:33 +0200 Subject: [PATCH 01/19] feat: add commits --- native/.gitignore | 1 + native/build.zig | 47 +++++++++++++++++---------------------- web/help.html | 31 ++++++++++++++++++-------- web/scripts/bulma.js | 22 ++++++++++++++++++ web/scripts/main.js | 16 ++++++------- web/styles/styles.css | 33 ++++++++++++++++++--------- web/templates/layout.html | 31 +++++++++++++++++++++----- 7 files changed, 122 insertions(+), 59 deletions(-) create mode 100644 web/scripts/bulma.js 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/web/help.html b/web/help.html index 4974ef9..d5f91fd 100644 --- a/web/help.html +++ b/web/help.html @@ -17,43 +17,56 @@

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.

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: +

    +
  1. Select a single text item (doesn't matter which, as long as it's text)
  2. +
  3. Click Edit->Select Same->Object Type or press Shift+Alt+A to select all text objects
  4. +
  5. Convert them all to paths by clicking Path->Object to Path or pressing Shift+Ctrl+C.
  6. +
+

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 +74,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/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/main.js b/web/scripts/main.js index 28932dc..df81440 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, 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 @@ - From e53ce79c32eed61b41b1c649db901c2578214606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20Hauser?= Date: Fri, 6 Dec 2024 11:37:12 +0100 Subject: [PATCH 02/19] chore: add biome, gitignore --- .vscode/settings.json | 13 +++++++++++++ biome.json | 20 ++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 biome.json 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..5d42c52 --- /dev/null +++ b/biome.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { "ignoreUnknown": false, "ignore": [] }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "lineWidth": 220, + "indentWidth": 4 + }, + "linter": { + "enabled": true, + "rules": { "recommended": true } + }, + "javascript": { "formatter": { "quoteStyle": "double" } } +} From ec6b88294d0bd43e0ae0f2b2856d59b019535a9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20Hauser?= Date: Wed, 14 May 2025 22:47:20 +0200 Subject: [PATCH 03/19] fix(path-data-polyfill): format --- web/scripts/path-data-polyfill.js | 2143 ++++++++++++++--------------- 1 file changed, 1016 insertions(+), 1127 deletions(-) diff --git a/web/scripts/path-data-polyfill.js b/web/scripts/path-data-polyfill.js index 2bb47c1..c5a3df7 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,1042 @@ // @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") { + (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(); - 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; - } - - 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; + }; + + 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") { + 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; + } + + 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; + } + } + + 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; + } else { + pathData.push(pathSeg); + } + } } - - 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 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; + }; + + 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 }; + }; + + 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 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; + + 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]); } - else { - pathData.push(pathSeg); + + 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; } - } - } - - 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; }; - - 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}; + + var clonePathData = function (pathData) { + return pathData.map(function (seg) { + return { type: seg.type, values: Array.prototype.slice.call(seg.values) }; + }); }; - - 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] + + // @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; + } }); - - 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] + + 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); + } 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") { + 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); + } else { + x1 = currentX; + y1 = currentY; + } + + 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; + } 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; + } + } + } else if (seg.type === "Z") { + reducedPathData.push(seg); + + currentX = subpathX; + currentY = subpathY; + } + + lastType = seg.type; }); - - 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); + + return reducedPathData; + }; + + SVGPathElement.prototype.setAttribute = function (name, value) { + if (name === "d") { + this[$cachedPathData] = null; + this[$cachedNormalizedPathData] = null; } - else { - cx1 = currentX; - cy1 = currentY; + + 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 (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); + + setAttributeNS.call(this, namespace, name, value); + }; + + SVGPathElement.prototype.removeAttribute = function (name, value) { + if (name === "d") { + this[$cachedPathData] = null; + this[$cachedNormalizedPathData] = null; } - else { - x1 = currentX; - y1 = currentY; + + 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; + } + } + } + + if (namespaceURI === "http://www.w3.org/2000/svg") { + this[$cachedPathData] = null; + this[$cachedNormalizedPathData] = null; + } } - - 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; + + 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]); + } else { + pathData = parsePathDataString(this.getAttribute("d") || ""); + this[$cachedPathData] = clonePathData(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; + } } - 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; - } + }; + + 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 += " "; + } + + d += seg.type; + + if (seg.values && seg.values.length > 0) { + d += " " + seg.values.join(" "); + } + } + + this.setAttribute("d", d); } - } - - 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; - } + }; + + 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 (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; - } + + if (ry > height / 2) { + ry = height / 2; } - } - - 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]); + + 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); } - else { - pathData = parsePathDataString(this.getAttribute("d") || ""); - this[$cachedPathData] = clonePathData(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); } - - 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 = 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], + }); } - - d += seg.type; - - if (seg.values && seg.values.length > 0) { - d += " " + seg.values.join(" "); + + 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], + }); } - } - - 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 +} From 03f52b0e51ccd9275947ee377e4ba0f23eb4169f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20Hauser?= Date: Wed, 14 May 2025 22:47:49 +0200 Subject: [PATCH 04/19] fix(path-data-polyfill): lint --- web/scripts/path-data-polyfill.js | 523 +++++++++++++++--------------- 1 file changed, 267 insertions(+), 256 deletions(-) diff --git a/web/scripts/path-data-polyfill.js b/web/scripts/path-data-polyfill.js index c5a3df7..f49f768 100644 --- a/web/scripts/path-data-polyfill.js +++ b/web/scripts/path-data-polyfill.js @@ -11,8 +11,8 @@ // @license // MIT License if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathData) { - (function () { - var commandsMap = { + (() => { + const commandsMap = { Z: "Z", M: "M", L: "L", @@ -35,7 +35,7 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa t: "t", }; - var Source = function (string) { + const Source = function (string) { this._string = string; this._currentIndex = 0; this._endIndex = this._string.length; @@ -43,12 +43,12 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa this._skipOptionalSpaces(); }; - var isIE = window.navigator.userAgent.indexOf("MSIE ") !== -1; + const isIE = window.navigator.userAgent.indexOf("MSIE ") !== -1; Source.prototype = { parseSegment: function () { - var char = this._string[this._currentIndex]; - var command = commandsMap[char] ? commandsMap[char] : null; + 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. @@ -78,8 +78,8 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa this._prevCommand = command; - var values = null; - var cmd = command.toUpperCase(); + let values = null; + const cmd = command.toUpperCase(); if (cmd === "H" || cmd === "V") { values = [this._parseNumber()]; @@ -99,9 +99,8 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa if (values === null || values.indexOf(null) >= 0) { // Unknown command or known command with invalid values return null; - } else { - return { type: command, values: values }; } + return { type: command, values: values }; }, hasMoreData: function () { @@ -109,7 +108,7 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa }, peekSegmentType: function () { - var char = this._string[this._currentIndex]; + const char = this._string[this._currentIndex]; return commandsMap[char] ? commandsMap[char] : null; }, @@ -119,13 +118,13 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa return true; } - var command = this.peekSegmentType(); + const command = this.peekSegmentType(); // Path must start with moveTo. return command === "M" || command === "m"; }, _isCurrentSpace: function () { - var char = this._string[this._currentIndex]; + const char = this._string[this._currentIndex]; return char <= " " && (char === " " || char === "\n" || char === "\t" || char === "\r" || char === "\f"); }, @@ -155,13 +154,13 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa // 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; + let exponent = 0; + let integer = 0; + let frac = 1; + let decimal = 0; + let sign = 1; + let expsign = 1; + const startIndex = this._currentIndex; this._skipOptionalSpaces(); @@ -179,15 +178,15 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa } // Read the integer part, build right-to-left. - var startIntPartIndex = this._currentIndex; + 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) { - var scanIntPartIndex = this._currentIndex - 1; - var multiplier = 1; + let scanIntPartIndex = this._currentIndex - 1; + let multiplier = 1; while (scanIntPartIndex >= startIntPartIndex) { integer += multiplier * (this._string[scanIntPartIndex] - "0"); @@ -242,11 +241,11 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa } } - var number = integer + decimal; + let number = integer + decimal; number *= sign; if (exponent) { - number *= Math.pow(10, expsign * exponent); + number *= 10 ** (expsign * exponent); } if (startIndex === this._currentIndex) { @@ -263,8 +262,8 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa return null; } - var flag = null; - var flagChar = this._string[this._currentIndex]; + let flag = null; + const flagChar = this._string[this._currentIndex]; this._currentIndex += 1; @@ -281,51 +280,61 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa }, }; - var parsePathDataString = function (string) { + const parsePathDataString = (string) => { if (!string || string.length === 0) return []; - var source = new Source(string); - var pathData = []; + const source = new Source(string); + const pathData = []; if (source.initialCommandIsMoveTo()) { while (source.hasMoreData()) { - var pathSeg = source.parseSegment(); + const pathSeg = source.parseSegment(); if (pathSeg === null) { break; - } else { - pathData.push(pathSeg); } + + pathData.push(pathSeg); } } return pathData; }; - var setAttribute = SVGPathElement.prototype.setAttribute; - var setAttributeNS = SVGPathElement.prototype.setAttributeNS; - var removeAttribute = SVGPathElement.prototype.removeAttribute; - var removeAttributeNS = SVGPathElement.prototype.removeAttributeNS; + const setAttribute = SVGPathElement.prototype.setAttribute; + const setAttributeNS = SVGPathElement.prototype.setAttributeNS; + const removeAttribute = SVGPathElement.prototype.removeAttribute; + const removeAttributeNS = SVGPathElement.prototype.removeAttributeNS; - var $cachedPathData = window.Symbol ? Symbol() : "__cachedPathData"; - var $cachedNormalizedPathData = window.Symbol ? Symbol() : "__cachedNormalizedPathData"; + 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. - var arcToCubicCurves = function (x1, y1, x2, y2, r1, r2, angle, largeArcFlag, sweepFlag, _recursive) { - var degToRad = function (degrees) { + const arcToCubicCurves = (x1Raw, y1Raw, x2Raw, y2Raw, r1Raw, r2Raw, angle, largeArcFlag, sweepFlag, _recursive) => { + const degToRad = (degrees) => { return (Math.PI * degrees) / 180; }; - 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); + 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 }; }; - var angleRad = degToRad(angle); - var params = []; - var f1, f2, cx, cy; + 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]; @@ -333,17 +342,17 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa cx = _recursive[2]; cy = _recursive[3]; } else { - var p1 = rotate(x1, y1, -angleRad); + const p1 = rotate(x1, y1, -angleRad); x1 = p1.x; y1 = p1.y; - var p2 = rotate(x2, y2, -angleRad); + const 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); + 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); @@ -351,7 +360,7 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa r2 = h * r2; } - var sign; + let sign; if (largeArcFlag === sweepFlag) { sign = -1; @@ -359,19 +368,19 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa sign = 1; } - var r1Pow = r1 * r1; - var r2Pow = r2 * r2; + const r1Pow = r1 * r1; + const r2Pow = r2 * r2; - var left = r1Pow * r2Pow - r1Pow * y * y - r2Pow * x * x; - var right = r1Pow * y * y + r2Pow * x * x; + const left = r1Pow * r2Pow - r1Pow * y * y - r2Pow * x * x; + const right = r1Pow * y * y + r2Pow * x * x; - var k = sign * Math.sqrt(Math.abs(left / right)); + 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(parseFloat(((y1 - cy) / r2).toFixed(9))); - f2 = Math.asin(parseFloat(((y2 - cy) / r2).toFixed(9))); + 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; @@ -395,12 +404,12 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa } } - var df = f2 - f1; + let df = f2 - f1; if (Math.abs(df) > (Math.PI * 120) / 180) { - var f2old = f2; - var x2old = x2; - var y2old = y2; + const f2old = f2; + const x2old = x2; + const y2old = y2; if (sweepFlag && f2 > f1) { f2 = f1 + ((Math.PI * 120) / 180) * 1; @@ -415,63 +424,63 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa 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; + 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; - var m1 = [x1, y1]; - var m2 = [x1 + hx * s1, y1 - hy * c1]; - var m3 = [x2 + hx * s2, y2 - hy * c2]; - var m4 = [x2, y2]; + 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); - } else { - params = [m2, m3, m4].concat(params); + } - var curves = []; + params = [m2, m3, m4].concat(params); - 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]); - } + const curves = []; - return 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]); } + + return curves; }; - var clonePathData = function (pathData) { - return pathData.map(function (seg) { + 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. - var absolutizePathData = function (pathData) { - var absolutizedPathData = []; + const absolutizePathData = (pathData) => { + const absolutizedPathData = []; - var currentX = null; - var currentY = null; + let currentX = null; + let currentY = null; - var subpathX = null; - var subpathY = null; + let subpathX = null; + let subpathY = null; - pathData.forEach(function (seg) { - var type = seg.type; + for (const seg of pathData) { + const type = seg.type; if (type === "M") { - var x = seg.values[0]; - var y = seg.values[1]; + const x = seg.values[0]; + const y = seg.values[1]; absolutizedPathData.push({ type: "M", values: [x, y] }); @@ -481,8 +490,8 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa currentX = x; currentY = y; } else if (type === "m") { - var x = currentX + seg.values[0]; - var y = currentY + seg.values[1]; + const x = currentX + seg.values[0]; + const y = currentY + seg.values[1]; absolutizedPathData.push({ type: "M", values: [x, y] }); @@ -492,68 +501,68 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa currentX = x; currentY = y; } else if (type === "L") { - var x = seg.values[0]; - var y = seg.values[1]; + 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") { - var x = currentX + seg.values[0]; - var y = currentY + seg.values[1]; + 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") { - 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]; + 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") { - 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]; + 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") { - var x1 = seg.values[0]; - var y1 = seg.values[1]; - var x = seg.values[2]; - var y = seg.values[3]; + 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") { - var x1 = currentX + seg.values[0]; - var y1 = currentY + seg.values[1]; - var x = currentX + seg.values[2]; - var y = currentY + seg.values[3]; + 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") { - var x = seg.values[5]; - var y = seg.values[6]; + const x = seg.values[5]; + const y = seg.values[6]; absolutizedPathData.push({ type: "A", @@ -563,8 +572,8 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa currentX = x; currentY = y; } else if (type === "a") { - var x = currentX + seg.values[5]; - var y = currentY + seg.values[6]; + const x = currentX + seg.values[5]; + const y = currentY + seg.values[6]; absolutizedPathData.push({ type: "A", @@ -574,52 +583,52 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa currentX = x; currentY = y; } else if (type === "H") { - var x = seg.values[0]; + const x = seg.values[0]; absolutizedPathData.push({ type: "H", values: [x] }); currentX = x; } else if (type === "h") { - var x = currentX + seg.values[0]; + const x = currentX + seg.values[0]; absolutizedPathData.push({ type: "H", values: [x] }); currentX = x; } else if (type === "V") { - var y = seg.values[0]; + const y = seg.values[0]; absolutizedPathData.push({ type: "V", values: [y] }); currentY = y; } else if (type === "v") { - var y = currentY + seg.values[0]; + const 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]; + 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") { - var x2 = currentX + seg.values[0]; - var y2 = currentY + seg.values[1]; - var x = currentX + seg.values[2]; - var y = currentY + seg.values[3]; + 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") { - var x = seg.values[0]; - var y = seg.values[1]; + 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") { - var x = currentX + seg.values[0]; - var y = currentY + seg.values[1]; + const x = currentX + seg.values[0]; + const y = currentY + seg.values[1]; absolutizedPathData.push({ type: "T", values: [x, y] }); @@ -631,7 +640,7 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa currentX = subpathX; currentY = subpathY; } - }); + } return absolutizedPathData; }; @@ -639,23 +648,23 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa // @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; + const reducePathData = (pathData) => { + const reducedPathData = []; + let lastType = null; - var lastControlX = null; - var lastControlY = null; + let lastControlX = null; + let lastControlY = null; - var currentX = null; - var currentY = null; + let currentX = null; + let currentY = null; - var subpathX = null; - var subpathY = null; + let subpathX = null; + let subpathY = null; - pathData.forEach(function (seg) { + for (const seg of pathData) { if (seg.type === "M") { - var x = seg.values[0]; - var y = seg.values[1]; + const x = seg.values[0]; + const y = seg.values[1]; reducedPathData.push({ type: "M", values: [x, y] }); @@ -665,12 +674,12 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa 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]; + 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] }); @@ -680,32 +689,33 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa currentX = x; currentY = y; } else if (seg.type === "L") { - var x = seg.values[0]; - var y = seg.values[1]; + 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") { - var x = seg.values[0]; + const x = seg.values[0]; reducedPathData.push({ type: "L", values: [x, currentY] }); currentX = x; } else if (seg.type === "V") { - var y = seg.values[0]; + const 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]; + const x2 = seg.values[0]; + const y2 = seg.values[1]; + const x = seg.values[2]; + const y = seg.values[3]; - var cx1, cy1; + let cx1; + let cy1; if (lastType === "C" || lastType === "S") { cx1 = currentX + (currentX - lastControlX); @@ -723,10 +733,11 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa currentX = x; currentY = y; } else if (seg.type === "T") { - var x = seg.values[0]; - var y = seg.values[1]; + const x = seg.values[0]; + const y = seg.values[1]; - var x1, y1; + let x1; + let y1; if (lastType === "Q" || lastType === "T") { x1 = currentX + (currentX - lastControlX); @@ -736,10 +747,10 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa y1 = currentY; } - 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; + 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] }); @@ -749,15 +760,15 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa 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]; + const x1 = seg.values[0]; + const y1 = seg.values[1]; + const x = seg.values[2]; + const 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; + 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] }); @@ -767,13 +778,13 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa 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]; + 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] }); @@ -782,11 +793,11 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa currentY = y; } else { if (currentX !== x || currentY !== y) { - var curves = arcToCubicCurves(currentX, currentY, x, y, r1, r2, angle, largeArcFlag, sweepFlag); + const curves = arcToCubicCurves(currentX, currentY, x, y, r1, r2, angle, largeArcFlag, sweepFlag); - curves.forEach(function (curve) { + for (const curve of curves) { reducedPathData.push({ type: "C", values: curve }); - }); + } currentX = x; currentY = y; @@ -800,7 +811,7 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa } lastType = seg.type; - }); + } return reducedPathData; }; @@ -816,10 +827,10 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa SVGPathElement.prototype.setAttributeNS = function (namespace, name, value) { if (name === "d") { - var namespaceURI = "http://www.w3.org/2000/svg"; + let namespaceURI = "http://www.w3.org/2000/svg"; if (namespace) { - for (var attribute of this.ownerSVGElement.attributes) { + for (const attribute of this.ownerSVGElement.attributes) { if (attribute.name === `xmlns:${namespace}`) { namespaceURI = attribute.value; } @@ -835,7 +846,7 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa setAttributeNS.call(this, namespace, name, value); }; - SVGPathElement.prototype.removeAttribute = function (name, value) { + SVGPathElement.prototype.removeAttribute = (name, _value) => { if (name === "d") { this[$cachedPathData] = null; this[$cachedNormalizedPathData] = null; @@ -844,12 +855,12 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa removeAttribute.call(this, name); }; - SVGPathElement.prototype.removeAttributeNS = function (namespace, name) { + SVGPathElement.prototype.removeAttributeNS = (namespace, name) => { if (name === "d") { - var namespaceURI = "http://www.w3.org/2000/svg"; + let namespaceURI = "http://www.w3.org/2000/svg"; if (namespace) { - for (var attribute of this.ownerSVGElement.attributes) { + for (const attribute of this.ownerSVGElement.attributes) { if (attribute.name === `xmlns:${namespace}`) { namespaceURI = attribute.value; } @@ -865,36 +876,36 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa removeAttributeNS.call(this, namespace, name); }; - SVGPathElement.prototype.getPathData = function (options) { - if (options && options.normalize) { + SVGPathElement.prototype.getPathData = (options) => { + if (options?.normalize) { if (this[$cachedNormalizedPathData]) { return clonePathData(this[$cachedNormalizedPathData]); - } else { - var pathData; + } - if (this[$cachedPathData]) { - pathData = clonePathData(this[$cachedPathData]); - } else { - pathData = parsePathDataString(this.getAttribute("d") || ""); - this[$cachedPathData] = clonePathData(pathData); - } + let pathData; - var normalizedPathData = reducePathData(absolutizePathData(pathData)); - this[$cachedNormalizedPathData] = clonePathData(normalizedPathData); - return normalizedPathData; - } - } else { if (this[$cachedPathData]) { - return clonePathData(this[$cachedPathData]); + pathData = clonePathData(this[$cachedPathData]); } else { - var pathData = parsePathDataString(this.getAttribute("d") || ""); + pathData = parsePathDataString(this.getAttribute("d") || ""); this[$cachedPathData] = clonePathData(pathData); - return pathData; } + + const normalizedPathData = reducePathData(absolutizePathData(pathData)); + this[$cachedNormalizedPathData] = clonePathData(normalizedPathData); + return normalizedPathData; + } + + if (this[$cachedPathData]) { + return clonePathData(this[$cachedPathData]); } + + const pathData = parsePathDataString(this.getAttribute("d") || ""); + this[$cachedPathData] = clonePathData(pathData); + return pathData; }; - SVGPathElement.prototype.setPathData = function (pathData) { + SVGPathElement.prototype.setPathData = (pathData) => { if (pathData.length === 0) { if (isIE) { // @bugfix https://github.com/mbostock/d3/issues/1737 @@ -903,10 +914,10 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa this.removeAttribute("d"); } } else { - var d = ""; + let d = ""; - for (var i = 0, l = pathData.length; i < l; i += 1) { - var seg = pathData[i]; + for (let i = 0, l = pathData.length; i < l; i += 1) { + const seg = pathData[i]; if (i > 0) { d += " "; @@ -915,7 +926,7 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa d += seg.type; if (seg.values && seg.values.length > 0) { - d += " " + seg.values.join(" "); + d += ` ${seg.values.join(" ")}`; } } @@ -923,13 +934,13 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa } }; - 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; + 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; @@ -939,7 +950,7 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa ry = height / 2; } - var pathData = [ + 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] }, @@ -953,8 +964,8 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa ]; // 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; + pathData = pathData.filter((s) => { + return s.type === "A" && (s.values[0] === 0 || s.values[1] === 0); }); if (options && options.normalize === true) { @@ -964,12 +975,12 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa 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; + SVGCircleElement.prototype.getPathData = (options) => { + const cx = this.cx.baseVal.value; + const cy = this.cy.baseVal.value; + const r = this.r.baseVal.value; - var pathData = [ + 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] }, @@ -985,13 +996,13 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa 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; + 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; - var pathData = [ + 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] }, @@ -1007,18 +1018,18 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa return pathData; }; - SVGLineElement.prototype.getPathData = function () { + 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 = function () { - var pathData = []; + SVGPolylineElement.prototype.getPathData = () => { + const pathData = []; - for (var i = 0; i < this.points.numberOfItems; i += 1) { - var point = this.points.getItem(i); + for (let i = 0; i < this.points.numberOfItems; i += 1) { + const point = this.points.getItem(i); pathData.push({ type: i === 0 ? "M" : "L", @@ -1029,11 +1040,11 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa return pathData; }; - SVGPolygonElement.prototype.getPathData = function () { - var pathData = []; + SVGPolygonElement.prototype.getPathData = () => { + const pathData = []; - for (var i = 0; i < this.points.numberOfItems; i += 1) { - var point = this.points.getItem(i); + for (let i = 0; i < this.points.numberOfItems; i += 1) { + const point = this.points.getItem(i); pathData.push({ type: i === 0 ? "M" : "L", From aa03575ef8f658e70d6b2b811a3126824682dcb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20Hauser?= Date: Wed, 14 May 2025 22:57:30 +0200 Subject: [PATCH 05/19] feat: exclude alpine from biome config --- biome.json | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/biome.json b/biome.json index 5d42c52..5e1f15e 100644 --- a/biome.json +++ b/biome.json @@ -5,7 +5,12 @@ "clientKind": "git", "useIgnoreFile": false }, - "files": { "ignoreUnknown": false, "ignore": [] }, + "files": { + "ignoreUnknown": false, + "ignore": [ + "web/scripts/alpine-3.10.4.js" + ] + }, "formatter": { "enabled": true, "indentStyle": "space", @@ -14,7 +19,13 @@ }, "linter": { "enabled": true, - "rules": { "recommended": true } + "rules": { + "recommended": true + } }, - "javascript": { "formatter": { "quoteStyle": "double" } } -} + "javascript": { + "formatter": { + "quoteStyle": "double" + } + } +} \ No newline at end of file From a2b9b458a0716b9bf6508298b13d7e924cd5fd62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20Hauser?= Date: Wed, 14 May 2025 22:57:52 +0200 Subject: [PATCH 06/19] fix(web): lint, format --- web/scripts/dragdrop.js | 9 ++-- web/scripts/libgingerbread.js | 7 ++- web/scripts/main.js | 91 +++++++++-------------------------- web/scripts/preview-canvas.js | 30 ++++++------ web/scripts/wasi.js | 18 +++---- web/scripts/yak.js | 79 +++++++++++------------------- web/scripts/zigwasm.js | 16 ++---- 7 files changed, 85 insertions(+), 165 deletions(-) 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..c279fe5 100644 --- a/web/scripts/libgingerbread.js +++ b/web/scripts/libgingerbread.js @@ -9,11 +9,11 @@ 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() { @@ -50,5 +50,4 @@ export class LibGingerbread { conversion_add_drill(x, y, d, scale_factor) { this.zig.exports.conversion_add_drill(x, y, d, scale_factor); } - } diff --git a/web/scripts/main.js b/web/scripts/main.js index df81440..3b30721 100644 --- a/web/scripts/main.js +++ b/web/scripts/main.js @@ -153,16 +153,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 +176,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(); } @@ -212,11 +212,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()); } @@ -231,26 +227,12 @@ class Design { cvs.clear(); - if ( - this.preview_layout === "front" || - this.preview_layout === "front-spread" || - this.preview_layout === "both" - ) { - await this.draw_layers( - ["EdgeCuts", "FCu", "FMask", "FSilkS", "Drill"], - "left" - ); + if (this.preview_layout === "front" || this.preview_layout === "front-spread" || this.preview_layout === "both") { + 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"); } } @@ -268,36 +250,24 @@ class Design { 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 - ); + gingerbread.conversion_add_raster_layer(layer.number, this.trace_scale_factor, imgdata); break; + } 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 - ); + gingerbread.conversion_add_poly_point(pt[0], pt[1], this.dpmm); } gingerbread.conversion_end_poly(layer.number, 1, false); } break; 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 - ); + gingerbread.conversion_add_drill(circle.cx.baseVal.value, circle.cy.baseVal.value, circle.r.baseVal.value * 2, this.dpmm); } break; default: @@ -307,10 +277,10 @@ class Design { 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,26 +322,16 @@ 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.bitmap = await yak.ImageBitmap_inverse_mask(this.bitmap, await this.design.edge_cuts.get_preview_bitmap(), this.color); } } return this.bitmap; } 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 +351,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 +390,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); 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..6879811 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) { @@ -88,12 +90,7 @@ 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 - ); + return new DOMParser().parseFromString(new XMLSerializer().serializeToString(doc.documentElement.cloneNode(false)), type); } /* Clones and transplants the given element into the destination document. */ @@ -114,21 +111,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 +135,30 @@ export function SVGElement_get_effective_fill_and_stroke(elm) { let stroke = ""; let e = elm; - while(e) { - if(fill == "") { + while (e) { + if (fill === "") { fill = e.style.fill; } - if(stroke == "") { + if (stroke === "") { stroke = e.style.stroke; } e = e.parentElement; } - if(fill == "") { + if (fill === "") { fill = "black"; } - if(stroke == "") { + if (stroke === "") { 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 +194,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 +241,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 +250,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..94e0e94 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() { @@ -51,7 +43,7 @@ export class ZigWASM { env: {}, }); wasi.setMemory(wasm_inst.exports.memory); - return new this(wasm_inst, wasi); + return new ZigWASM(wasm_inst, wasi); } get exports() { From f7efd7631d08514a668651902910b92fa34b5f7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20Hauser?= Date: Wed, 14 May 2025 23:04:56 +0200 Subject: [PATCH 07/19] fix(web): line width format like empty silkscreen --- biome.json | 2 +- web/scripts/path-data-polyfill.js | 95 ++++++++++++++++++++++++++----- web/scripts/yak.js | 5 +- web/scripts/zigwasm.js | 6 +- 4 files changed, 92 insertions(+), 16 deletions(-) diff --git a/biome.json b/biome.json index 5e1f15e..edb4498 100644 --- a/biome.json +++ b/biome.json @@ -14,7 +14,7 @@ "formatter": { "enabled": true, "indentStyle": "space", - "lineWidth": 220, + "lineWidth": 120, "indentWidth": 4 }, "linter": { diff --git a/web/scripts/path-data-polyfill.js b/web/scripts/path-data-polyfill.js index f49f768..077e307 100644 --- a/web/scripts/path-data-polyfill.js +++ b/web/scripts/path-data-polyfill.js @@ -57,7 +57,10 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa } // Check for remaining coordinates in the current command. - if ((char === "+" || char === "-" || char === "." || (char >= "0" && char <= "9")) && this._prevCommand !== "Z") { + if ( + (char === "+" || char === "-" || char === "." || (char >= "0" && char <= "9")) && + this._prevCommand !== "Z" + ) { if (this._prevCommand === "M") { command = "L"; } else if (this._prevCommand === "m") { @@ -88,9 +91,24 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa } 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()]; + 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()]; + values = [ + this._parseNumber(), + this._parseNumber(), + this._parseNumber(), + this._parseArcFlag(), + this._parseArcFlag(), + this._parseNumber(), + this._parseNumber(), + ]; } else if (cmd === "Z") { this._skipOptionalSpaces(); values = []; @@ -125,7 +143,9 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa _isCurrentSpace: function () { const char = this._string[this._currentIndex]; - return char <= " " && (char === " " || char === "\n" || char === "\t" || char === "\r" || char === "\f"); + return ( + char <= " " && (char === " " || char === "\n" || char === "\t" || char === "\r" || char === "\f") + ); }, _skipOptionalSpaces: function () { @@ -137,7 +157,11 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa }, _skipOptionalSpacesOrDelimiter: function () { - if (this._currentIndex < this._endIndex && !this._isCurrentSpace() && this._string[this._currentIndex] !== ",") { + if ( + this._currentIndex < this._endIndex && + !this._isCurrentSpace() && + this._string[this._currentIndex] !== "," + ) { return false; } @@ -172,7 +196,11 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa sign = -1; } - if (this._currentIndex === this._endIndex || ((this._string[this._currentIndex] < "0" || this._string[this._currentIndex] > "9") && this._string[this._currentIndex] !== ".")) { + 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; } @@ -180,7 +208,11 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa // 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") { + while ( + this._currentIndex < this._endIndex && + this._string[this._currentIndex] >= "0" && + this._string[this._currentIndex] <= "9" + ) { this._currentIndex += 1; // Advance to first non-digit. } @@ -200,11 +232,19 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa 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") { + 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") { + 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; @@ -230,11 +270,19 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa } // There must be an exponent. - if (this._currentIndex >= this._endIndex || this._string[this._currentIndex] < "0" || this._string[this._currentIndex] > "9") { + 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") { + 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; @@ -311,7 +359,18 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa // @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 arcToCubicCurves = ( + x1Raw, + y1Raw, + x2Raw, + y2Raw, + r1Raw, + r2Raw, + angle, + largeArcFlag, + sweepFlag, + _recursive, + ) => { const degToRad = (degrees) => { return (Math.PI * degrees) / 180; }; @@ -793,7 +852,17 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa currentY = y; } else { if (currentX !== x || currentY !== y) { - const curves = arcToCubicCurves(currentX, currentY, x, y, r1, r2, angle, largeArcFlag, sweepFlag); + const curves = arcToCubicCurves( + currentX, + currentY, + x, + y, + r1, + r2, + angle, + largeArcFlag, + sweepFlag, + ); for (const curve of curves) { reducedPathData.push({ type: "C", values: curve }); diff --git a/web/scripts/yak.js b/web/scripts/yak.js index 6879811..5960689 100644 --- a/web/scripts/yak.js +++ b/web/scripts/yak.js @@ -90,7 +90,10 @@ 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); + return new DOMParser().parseFromString( + new XMLSerializer().serializeToString(doc.documentElement.cloneNode(false)), + type, + ); } /* Clones and transplants the given element into the destination document. */ diff --git a/web/scripts/zigwasm.js b/web/scripts/zigwasm.js index 94e0e94..7524b99 100644 --- a/web/scripts/zigwasm.js +++ b/web/scripts/zigwasm.js @@ -18,7 +18,11 @@ class Ptr { } 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() { From 1ab6473ae3ca9037dda11a1d75b64e427b070f66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20Hauser?= Date: Wed, 14 May 2025 23:22:08 +0200 Subject: [PATCH 08/19] fix(web): yak, undefined, null check --- web/scripts/yak.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/scripts/yak.js b/web/scripts/yak.js index 5960689..dfa2e67 100644 --- a/web/scripts/yak.js +++ b/web/scripts/yak.js @@ -139,20 +139,20 @@ export function SVGElement_get_effective_fill_and_stroke(elm) { let e = elm; while (e) { - if (fill === "") { + 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"; } From 46ebc98d9a790fd8b8cb42c5916f344ad250d83f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20Hauser?= Date: Thu, 15 May 2025 16:13:06 +0200 Subject: [PATCH 09/19] fix: literal keys --- biome.json | 5 ++++- web/scripts/main.js | 14 +++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/biome.json b/biome.json index edb4498..e75d898 100644 --- a/biome.json +++ b/biome.json @@ -20,7 +20,10 @@ "linter": { "enabled": true, "rules": { - "recommended": true + "recommended": true, + "complexity": { + "useLiteralKeys": "info" + } } }, "javascript": { diff --git a/web/scripts/main.js b/web/scripts/main.js index 3b30721..96c843f 100644 --- a/web/scripts/main.js +++ b/web/scripts/main.js @@ -153,16 +153,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 +176,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(); } From 8ecbcbb8acdf2d98880384814bf78ae57e7a9347 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20Hauser?= Date: Wed, 14 May 2025 22:57:52 +0200 Subject: [PATCH 10/19] fix(web): lint, format --- web/scripts/main.js | 14 +++++++------- web/scripts/zigwasm.js | 6 +----- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/web/scripts/main.js b/web/scripts/main.js index 96c843f..3b30721 100644 --- a/web/scripts/main.js +++ b/web/scripts/main.js @@ -153,16 +153,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 +176,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(); } diff --git a/web/scripts/zigwasm.js b/web/scripts/zigwasm.js index 7524b99..94e0e94 100644 --- a/web/scripts/zigwasm.js +++ b/web/scripts/zigwasm.js @@ -18,11 +18,7 @@ class Ptr { } 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() { From b068f5102e4b3aeca8197de3fbad3f2adc0a23af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20Hauser?= Date: Wed, 14 May 2025 23:06:19 +0200 Subject: [PATCH 11/19] feat(web): add error handling, resilience if no image data is present --- web/scripts/libgingerbread.js | 24 ++++++++++--- web/scripts/main.js | 68 +++++++++++++++++++++++++++++++---- 2 files changed, 81 insertions(+), 11 deletions(-) diff --git a/web/scripts/libgingerbread.js b/web/scripts/libgingerbread.js index c279fe5..575c323 100644 --- a/web/scripts/libgingerbread.js +++ b/web/scripts/libgingerbread.js @@ -20,14 +20,30 @@ export class LibGingerbread { this.zig.exports.conversion_start(); } - conversion_add_raster_layer(layer, scale, image) { + conversion_add_raster_layer(layer, scale, imageData) { 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, + ); + } 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() { diff --git a/web/scripts/main.js b/web/scripts/main.js index 3b30721..ca318d7 100644 --- a/web/scripts/main.js +++ b/web/scripts/main.js @@ -227,7 +227,11 @@ class Design { cvs.clear(); - if (this.preview_layout === "front" || this.preview_layout === "front-spread" || this.preview_layout === "both") { + if ( + this.preview_layout === "front" || + this.preview_layout === "front-spread" || + this.preview_layout === "both" + ) { await this.draw_layers(["EdgeCuts", "FCu", "FMask", "FSilkS", "Drill"], "left"); } @@ -244,6 +248,10 @@ 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(); @@ -253,10 +261,29 @@ class Design { 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); + } 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) { @@ -265,16 +292,25 @@ class Design { 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); + gingerbread.conversion_add_drill( + circle.cx.baseVal.value, + circle.cy.baseVal.value, + circle.r.baseVal.value * 2, + this.dpmm, + ); } break; - default: + } + default: { throw `Unexpected layer type ${layer.type}`; + } } } + console.log("Conversion finished"); const footprint = gingerbread.conversion_finish(); if (method === "clipboard") { @@ -324,7 +360,11 @@ class Layer { if (!this.bitmap) { 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.bitmap = await yak.ImageBitmap_inverse_mask( + this.bitmap, + await this.design.edge_cuts.get_preview_bitmap(), + this.color, + ); } } return this.bitmap; @@ -400,3 +440,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}`); + } +}; From 9f21fe0bf8c4a4381f871527d63877249c039678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20Hauser?= Date: Wed, 14 May 2025 23:23:47 +0200 Subject: [PATCH 12/19] feat(web): add WASM init error handling --- web/scripts/zigwasm.js | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/web/scripts/zigwasm.js b/web/scripts/zigwasm.js index 94e0e94..23ec9bf 100644 --- a/web/scripts/zigwasm.js +++ b/web/scripts/zigwasm.js @@ -37,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 ZigWASM(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() { From 6ebece58a57487df756b2bfa092485b66a3f75a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20Hauser?= Date: Fri, 6 Dec 2024 11:37:35 +0100 Subject: [PATCH 13/19] feat(zig): add mirror option --- native/src/gingerbread.zig | 17 ++++++++++++++++- native/src/pcb.zig | 22 +++++++++++++++------- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/native/src/gingerbread.zig b/native/src/gingerbread.zig index e4e341a..e91d9b3 100644 --- a/native/src/gingerbread.zig +++ b/native/src/gingerbread.zig @@ -91,9 +91,20 @@ export fn conversion_start_poly() void { export fn conversion_add_poly_point( x: f64, y: f64, + layer_number: u32, scale_factor: 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()) catch @panic("memory"); } export fn conversion_end_poly(layer: u32, width: f32, fill: bool) void { @@ -115,3 +126,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..89cea84 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,28 +33,32 @@ 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) !void { + // if is back layer and mirror_back_layers is true, negate the x coordinate + const x = if (is_back_layer(layer_name) and mirror_back_layers) -pt.x else pt.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", .{ x * scale_factor, pt.y * scale_factor }); } -pub fn end_xx_poly(layer: []const u8, width: f64, fill: bool, writer: anytype) !void { +pub fn end_xx_poly(layer_name: []const u8, width: f64, fill: bool, writer: anytype) !void { try writer.writeAll(" )\n"); - try writer.print(" (layer \"{s}\")\n", .{layer}); + try writer.print(" (layer \"{s}\")\n", .{layer_name}); try writer.print(" (width {d:.3})\n", .{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, width: f64, fill: bool, writer: anytype) !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); } - try end_xx_poly(layer, width, fill, writer); + try end_xx_poly(layer_name, width, fill, writer); } pub fn polylist_to_footprint(polylist: PolyList, layer: []const u8, scale_factor: f64, writer: anytype) !void { From d449d31f2668aa75d255ab05709bae5f6116a746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20Hauser?= Date: Wed, 14 May 2025 23:08:40 +0200 Subject: [PATCH 14/19] feat(web): add mirror option --- web/index.html | 7 +++++++ web/scripts/libgingerbread.js | 8 ++++++-- web/scripts/main.js | 11 +++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) 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 @@ +
+ +
+