From c84ace30a20df968c36f06f2e8c07b0793310655 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Sat, 8 Aug 2026 19:36:49 -0700 Subject: [PATCH 1/2] sketch: replace Rough.js with rough-go --- .prettierignore | 1 - README.md | 3 + THIRD_PARTY_NOTICES.txt | 33 + ci/release/changelogs/next.md | 1 + d2js/js/THIRD_PARTY_NOTICES.txt | 33 + d2renderers/d2sketch/NOTICE.txt | 4 + d2renderers/d2sketch/rough.js | 1673 ------------------------------- d2renderers/d2sketch/setup.js | 23 - d2renderers/d2sketch/sketch.go | 417 +++----- d2renderers/d2svg/d2svg.go | 63 +- go.mod | 1 + go.sum | 2 + 12 files changed, 247 insertions(+), 2007 deletions(-) create mode 100644 d2renderers/d2sketch/NOTICE.txt delete mode 100644 d2renderers/d2sketch/rough.js delete mode 100644 d2renderers/d2sketch/setup.js diff --git a/.prettierignore b/.prettierignore index dfe5c5ee6e..783ee17f8e 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,6 +2,5 @@ d2layouts/d2elklayout/setup.js d2renderers/d2latex/mathjax.js d2renderers/d2latex/polyfills.js d2renderers/d2latex/setup.js -d2renderers/d2sketch/rough.js lib/png/generate_png.js d2js diff --git a/README.md b/README.md index b14137f365..1d0d7164ae 100644 --- a/README.md +++ b/README.md @@ -206,6 +206,9 @@ D2 intends to integrate with a variety of layout engines, e.g. `dot`, as well as single-purpose layout types like sequence diagrams. You can choose whichever layout engine you like and works best for the diagram you're making. +Sketch-mode rendering uses [rough-go](https://github.com/d2lang/rough-go), a native Go +port of Rough.js 4.0.4. + ## Comparison For a comparison against other popular text-to-diagram tools, see diff --git a/THIRD_PARTY_NOTICES.txt b/THIRD_PARTY_NOTICES.txt index 99f4bb07af..8457ed3d91 100644 --- a/THIRD_PARTY_NOTICES.txt +++ b/THIRD_PARTY_NOTICES.txt @@ -36,3 +36,36 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +Rough-go +https://github.com/d2lang/rough-go + +Rough-go is a native Go port of Rough.js 4.0.4. The port is based on: + + Rough.js 4.0.4 + https://github.com/rough-stuff/rough + commit 35302b8f1119ad004b4550f9a4d4162d3d845b0a + +MIT License + +Copyright (c) 2019 Preet Shihn +Copyright (c) 2026 rough-go contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index fcc6bdc41a..f40e926c86 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -5,6 +5,7 @@ #### Improvements 🧹 - d2dagre: replace the embedded JavaScript runtime with the native Go Dagro port without changing layout output +- d2sketch: replace the embedded Rough.js runtime with the native Go rough-go port without changing sketch output - d2ascii: - sql_table and uml class shapes are supported [#2623](https://github.com/d2lang/d2/pull/2623) - newlines are handled [#2626](https://github.com/d2lang/d2/pull/2626) diff --git a/d2js/js/THIRD_PARTY_NOTICES.txt b/d2js/js/THIRD_PARTY_NOTICES.txt index 99f4bb07af..8457ed3d91 100644 --- a/d2js/js/THIRD_PARTY_NOTICES.txt +++ b/d2js/js/THIRD_PARTY_NOTICES.txt @@ -36,3 +36,36 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +Rough-go +https://github.com/d2lang/rough-go + +Rough-go is a native Go port of Rough.js 4.0.4. The port is based on: + + Rough.js 4.0.4 + https://github.com/rough-stuff/rough + commit 35302b8f1119ad004b4550f9a4d4162d3d845b0a + +MIT License + +Copyright (c) 2019 Preet Shihn +Copyright (c) 2026 rough-go contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/d2renderers/d2sketch/NOTICE.txt b/d2renderers/d2sketch/NOTICE.txt new file mode 100644 index 0000000000..5ceedefc35 --- /dev/null +++ b/d2renderers/d2sketch/NOTICE.txt @@ -0,0 +1,4 @@ +The native sketch renderer uses rough-go, a Go port of Rough.js 4.0.4. + +Source and license details are included in the repository-root +THIRD_PARTY_NOTICES.txt and in https://github.com/d2lang/rough-go. diff --git a/d2renderers/d2sketch/rough.js b/d2renderers/d2sketch/rough.js deleted file mode 100644 index b19e8c7d72..0000000000 --- a/d2renderers/d2sketch/rough.js +++ /dev/null @@ -1,1673 +0,0 @@ -/*eslint-disable */ -// This is a slightly modified version of rough.js for D2 -// -// rough.js is from https://github.com/rough-stuff/rough. -// Attribution for this file is as follows: -// -// MIT License -// -// Copyright (c) 2019 Preet Shihn -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// -const t = "http://www.w3.org/2000/svg"; -function e(t, e, s) { - if (t && t.length) { - const [n, i] = e, - a = (Math.PI / 180) * s, - o = Math.cos(a), - h = Math.sin(a); - t.forEach((t) => { - const [e, s] = t; - (t[0] = (e - n) * o - (s - i) * h + n), (t[1] = (e - n) * h + (s - i) * o + i); - }); - } -} -function s(t) { - const e = t[0], - s = t[1]; - return Math.sqrt(Math.pow(e[0] - s[0], 2) + Math.pow(e[1] - s[1], 2)); -} -function n(t, e) { - return t.type === e; -} -const i = { - A: 7, - a: 7, - C: 6, - c: 6, - H: 1, - h: 1, - L: 2, - l: 2, - M: 2, - m: 2, - Q: 4, - q: 4, - S: 4, - s: 4, - T: 4, - t: 2, - V: 1, - v: 1, - Z: 0, - z: 0, -}; -class a { - constructor(t) { - (this.COMMAND = 0), - (this.NUMBER = 1), - (this.EOD = 2), - (this.segments = []), - this.parseData(t), - this.processPoints(); - } - tokenize(t) { - const e = new Array(); - - for (; "" !== t; ) { - const a = t.match(/^([ \t\r\n,]+)/); - const b = t.match(/^([aAcChHlLmMqQsStTvVzZ])/); - const c = t.match(/^(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)/); - if (a) t = t.substr(a[0].length); - else if (b) - (e[e.length] = { type: this.COMMAND, text: b[0] }), (t = t.substr(b[0].length)); - else { - if (!c) return []; - (e[e.length] = { type: this.NUMBER, text: `${parseFloat(c[0])}` }), - (t = t.substr(c[0].length)); - } - } - return (e[e.length] = { type: this.EOD, text: "" }), e; - } - parseData(t) { - const e = this.tokenize(t); - let s = 0, - a = e[s], - o = "BOD"; - for (this.segments = new Array(); !n(a, this.EOD); ) { - let h; - const r = new Array(); - if ("BOD" === o) { - if ("M" !== a.text && "m" !== a.text) return void this.parseData("M0,0" + t); - s++, (h = i[a.text]), (o = a.text); - } else n(a, this.NUMBER) ? (h = i[o]) : (s++, (h = i[a.text]), (o = a.text)); - if (s + h < e.length) { - for (let t = s; t < s + h; t++) { - const s = e[t]; - if (!n(s, this.NUMBER)) - return void console.error("Param not a number: " + o + "," + s.text); - r[r.length] = +s.text; - } - if ("number" != typeof i[o]) return void console.error("Bad segment: " + o); - { - const t = { key: o, data: r }; - this.segments.push(t), - (s += h), - (a = e[s]), - "M" === o && (o = "L"), - "m" === o && (o = "l"); - } - } else console.error("Path data ended short"); - } - } - get closed() { - if (void 0 === this._closed) { - this._closed = !1; - for (const t of this.segments) "z" === t.key.toLowerCase() && (this._closed = !0); - } - return this._closed; - } - processPoints() { - let t = null, - e = [0, 0]; - for (let s = 0; s < this.segments.length; s++) { - const n = this.segments[s]; - switch (n.key) { - case "M": - case "L": - case "T": - n.point = [n.data[0], n.data[1]]; - break; - case "m": - case "l": - case "t": - n.point = [n.data[0] + e[0], n.data[1] + e[1]]; - break; - case "H": - n.point = [n.data[0], e[1]]; - break; - case "h": - n.point = [n.data[0] + e[0], e[1]]; - break; - case "V": - n.point = [e[0], n.data[0]]; - break; - case "v": - n.point = [e[0], n.data[0] + e[1]]; - break; - case "z": - case "Z": - t && (n.point = [t[0], t[1]]); - break; - case "C": - n.point = [n.data[4], n.data[5]]; - break; - case "c": - n.point = [n.data[4] + e[0], n.data[5] + e[1]]; - break; - case "S": - n.point = [n.data[2], n.data[3]]; - break; - case "s": - n.point = [n.data[2] + e[0], n.data[3] + e[1]]; - break; - case "Q": - n.point = [n.data[2], n.data[3]]; - break; - case "q": - n.point = [n.data[2] + e[0], n.data[3] + e[1]]; - break; - case "A": - n.point = [n.data[5], n.data[6]]; - break; - case "a": - n.point = [n.data[5] + e[0], n.data[6] + e[1]]; - } - ("m" !== n.key && "M" !== n.key) || (t = null), - n.point && ((e = n.point), t || (t = n.point)), - ("z" !== n.key && "Z" !== n.key) || (t = null); - } - } -} -class o { - constructor(t) { - (this._position = [0, 0]), - (this._first = null), - (this.bezierReflectionPoint = null), - (this.quadReflectionPoint = null), - (this.parsed = new a(t)); - } - get segments() { - return this.parsed.segments; - } - get closed() { - return this.parsed.closed; - } - get linearPoints() { - if (!this._linearPoints) { - const t = []; - let e = []; - for (const s of this.parsed.segments) { - const n = s.key.toLowerCase(); - (("m" !== n && "z" !== n) || (e.length && (t.push(e), (e = [])), "z" !== n)) && - s.point && - e.push(s.point); - } - e.length && (t.push(e), (e = [])), (this._linearPoints = t); - } - return this._linearPoints; - } - get first() { - return this._first; - } - set first(t) { - this._first = t; - } - setPosition(t, e) { - (this._position = [t, e]), this._first || (this._first = [t, e]); - } - get position() { - return this._position; - } - get x() { - return this._position[0]; - } - get y() { - return this._position[1]; - } -} -class h { - constructor(t, e, s, n, i, a) { - if ( - ((this._segIndex = 0), - (this._numSegs = 0), - (this._rx = 0), - (this._ry = 0), - (this._sinPhi = 0), - (this._cosPhi = 0), - (this._C = [0, 0]), - (this._theta = 0), - (this._delta = 0), - (this._T = 0), - (this._from = t), - t[0] === e[0] && t[1] === e[1]) - ) - return; - const o = Math.PI / 180; - (this._rx = Math.abs(s[0])), - (this._ry = Math.abs(s[1])), - (this._sinPhi = Math.sin(n * o)), - (this._cosPhi = Math.cos(n * o)); - const h = (this._cosPhi * (t[0] - e[0])) / 2 + (this._sinPhi * (t[1] - e[1])) / 2, - r = (-this._sinPhi * (t[0] - e[0])) / 2 + (this._cosPhi * (t[1] - e[1])) / 2; - let c = 0; - const l = - this._rx * this._rx * this._ry * this._ry - - this._rx * this._rx * r * r - - this._ry * this._ry * h * h; - if (l < 0) { - const t = Math.sqrt(1 - l / (this._rx * this._rx * this._ry * this._ry)); - (this._rx = this._rx * t), (this._ry = this._ry * t), (c = 0); - } else - c = - (i === a ? -1 : 1) * - Math.sqrt(l / (this._rx * this._rx * r * r + this._ry * this._ry * h * h)); - const u = (c * this._rx * r) / this._ry, - p = (-c * this._ry * h) / this._rx; - (this._C = [0, 0]), - (this._C[0] = this._cosPhi * u - this._sinPhi * p + (t[0] + e[0]) / 2), - (this._C[1] = this._sinPhi * u + this._cosPhi * p + (t[1] + e[1]) / 2), - (this._theta = this.calculateVectorAngle( - 1, - 0, - (h - u) / this._rx, - (r - p) / this._ry - )); - let d = this.calculateVectorAngle( - (h - u) / this._rx, - (r - p) / this._ry, - (-h - u) / this._rx, - (-r - p) / this._ry - ); - !a && d > 0 ? (d -= 2 * Math.PI) : a && d < 0 && (d += 2 * Math.PI), - (this._numSegs = Math.ceil(Math.abs(d / (Math.PI / 2)))), - (this._delta = d / this._numSegs), - (this._T = - ((8 / 3) * Math.sin(this._delta / 4) * Math.sin(this._delta / 4)) / - Math.sin(this._delta / 2)); - } - getNextSegment() { - if (this._segIndex === this._numSegs) return null; - const t = Math.cos(this._theta), - e = Math.sin(this._theta), - s = this._theta + this._delta, - n = Math.cos(s), - i = Math.sin(s), - a = [ - this._cosPhi * this._rx * n - this._sinPhi * this._ry * i + this._C[0], - this._sinPhi * this._rx * n + this._cosPhi * this._ry * i + this._C[1], - ], - o = [ - this._from[0] + - this._T * (-this._cosPhi * this._rx * e - this._sinPhi * this._ry * t), - this._from[1] + - this._T * (-this._sinPhi * this._rx * e + this._cosPhi * this._ry * t), - ], - h = [ - a[0] + this._T * (this._cosPhi * this._rx * i + this._sinPhi * this._ry * n), - a[1] + this._T * (this._sinPhi * this._rx * i - this._cosPhi * this._ry * n), - ]; - return ( - (this._theta = s), - (this._from = [a[0], a[1]]), - this._segIndex++, - { cp1: o, cp2: h, to: a } - ); - } - calculateVectorAngle(t, e, s, n) { - const i = Math.atan2(e, t), - a = Math.atan2(n, s); - return a >= i ? a - i : 2 * Math.PI - (i - a); - } -} -class r { - constructor(t, e) { - (this.sets = t), (this.closed = e); - } - fit(t) { - const e = []; - for (const s of this.sets) { - const n = s.length; - let i = Math.floor(t * n); - if (i < 5) { - if (n <= 5) continue; - i = 5; - } - e.push(this.reduce(s, i)); - } - let s = ""; - for (const t of e) { - for (let e = 0; e < t.length; e++) { - const n = t[e]; - s += 0 === e ? "M" + n[0] + "," + n[1] : "L" + n[0] + "," + n[1]; - } - this.closed && (s += "z "); - } - return s; - } - reduce(t, e) { - if (t.length <= e) return t; - const n = t.slice(0); - for (; n.length > e; ) { - let t = -1, - e = -1; - for (let i = 1; i < n.length - 1; i++) { - const a = s([n[i - 1], n[i]]), - o = s([n[i], n[i + 1]]), - h = s([n[i - 1], n[i + 1]]), - r = (a + o + h) / 2, - c = Math.sqrt(r * (r - a) * (r - o) * (r - h)); - (t < 0 || c < t) && ((t = c), (e = i)); - } - if (!(e > 0)) break; - n.splice(e, 1); - } - return n; - } -} -function c(t, s) { - const n = [0, 0], - i = Math.round(s.hachureAngle + 90); - i && e(t, n, i); - const a = (function (t, e) { - const s = [...t]; - s[0].join(",") !== s[s.length - 1].join(",") && s.push([s[0][0], s[0][1]]); - const n = []; - if (s && s.length > 2) { - let t = e.hachureGap; - t < 0 && (t = 4 * e.strokeWidth), (t = Math.max(t, 0.1)); - const i = []; - for (let t = 0; t < s.length - 1; t++) { - const e = s[t], - n = s[t + 1]; - if (e[1] !== n[1]) { - const t = Math.min(e[1], n[1]); - i.push({ - ymin: t, - ymax: Math.max(e[1], n[1]), - x: t === e[1] ? e[0] : n[0], - islope: (n[0] - e[0]) / (n[1] - e[1]), - }); - } - } - if ( - (i.sort((t, e) => - t.ymin < e.ymin - ? -1 - : t.ymin > e.ymin - ? 1 - : t.x < e.x - ? -1 - : t.x > e.x - ? 1 - : t.ymax === e.ymax - ? 0 - : (t.ymax - e.ymax) / Math.abs(t.ymax - e.ymax) - ), - !i.length) - ) - return n; - let a = [], - o = i[0].ymin; - for (; a.length || i.length; ) { - if (i.length) { - let t = -1; - for (let e = 0; e < i.length && !(i[e].ymin > o); e++) t = e; - i.splice(0, t + 1).forEach((t) => { - a.push({ s: o, edge: t }); - }); - } - if ( - ((a = a.filter((t) => !(t.edge.ymax <= o))), - a.sort((t, e) => - t.edge.x === e.edge.x - ? 0 - : (t.edge.x - e.edge.x) / Math.abs(t.edge.x - e.edge.x) - ), - a.length > 1) - ) - for (let t = 0; t < a.length; t += 2) { - const e = t + 1; - if (e >= a.length) break; - const s = a[t].edge, - i = a[e].edge; - n.push([ - [Math.round(s.x), o], - [Math.round(i.x), o], - ]); - } - (o += t), - a.forEach((e) => { - e.edge.x = e.edge.x + t * e.edge.islope; - }); - } - } - return n; - })(t, s); - return ( - i && - (e(t, n, -i), - (function (t, s, n) { - const i = []; - t.forEach((t) => i.push(...t)), e(i, s, n); - })(a, n, -i)), - a - ); -} -class l { - constructor(t) { - this.helper = t; - } - fillPolygon(t, e) { - return this._fillPolygon(t, e); - } - _fillPolygon(t, e, s = !1) { - const n = c(t, e); - return { type: "fillSketch", ops: this.renderLines(n, e, s) }; - } - renderLines(t, e, s) { - let n = [], - i = null; - for (const a of t) - (n = n.concat(this.helper.doubleLineOps(a[0][0], a[0][1], a[1][0], a[1][1], e))), - s && - i && - (n = n.concat(this.helper.doubleLineOps(i[0], i[1], a[0][0], a[0][1], e))), - (i = a[1]); - return n; - } -} -class u extends l { - fillPolygon(t, e) { - return this._fillPolygon(t, e, !0); - } -} -class p extends l { - fillPolygon(t, e) { - const s = this._fillPolygon(t, e), - n = Object.assign({}, e, { hachureAngle: e.hachureAngle + 90 }), - i = this._fillPolygon(t, n); - return (s.ops = s.ops.concat(i.ops)), s; - } -} -class d { - constructor(t) { - this.helper = t; - } - fillPolygon(t, e) { - const s = c( - t, - (e = Object.assign({}, e, { - curveStepCount: 4, - hachureAngle: 0, - roughness: 1, - })) - ); - return this.dotsOnLines(s, e); - } - dotsOnLines(t, e) { - let n = [], - i = e.hachureGap; - i < 0 && (i = 4 * e.strokeWidth), (i = Math.max(i, 0.1)); - let a = e.fillWeight; - a < 0 && (a = e.strokeWidth / 2); - for (const o of t) { - const t = s(o) / i, - h = Math.ceil(t) - 1, - r = Math.atan((o[1][1] - o[0][1]) / (o[1][0] - o[0][0])); - for (let t = 0; t < h; t++) { - const s = i * (t + 1), - h = s * Math.sin(r), - c = s * Math.cos(r), - l = [o[0][0] - c, o[0][1] + h], - u = this.helper.randOffsetWithRange(l[0] - i / 4, l[0] + i / 4, e), - p = this.helper.randOffsetWithRange(l[1] - i / 4, l[1] + i / 4, e), - d = this.helper.ellipse(u, p, a, a, e); - n = n.concat(d.ops); - } - } - return { type: "fillSketch", ops: n }; - } -} -class f { - constructor(t) { - this.helper = t; - } - fillPolygon(t, e) { - const s = c(t, e); - return { type: "fillSketch", ops: this.dashedLine(s, e) }; - } - dashedLine(t, e) { - const n = - e.dashOffset < 0 - ? e.hachureGap < 0 - ? 4 * e.strokeWidth - : e.hachureGap - : e.dashOffset, - i = - e.dashGap < 0 ? (e.hachureGap < 0 ? 4 * e.strokeWidth : e.hachureGap) : e.dashGap; - let a = []; - return ( - t.forEach((t) => { - const o = s(t), - h = Math.floor(o / (n + i)), - r = (o + i - h * (n + i)) / 2; - let c = t[0], - l = t[1]; - c[0] > l[0] && ((c = t[1]), (l = t[0])); - const u = Math.atan((l[1] - c[1]) / (l[0] - c[0])); - for (let t = 0; t < h; t++) { - const s = t * (n + i), - o = s + n, - h = [ - c[0] + s * Math.cos(u) + r * Math.cos(u), - c[1] + s * Math.sin(u) + r * Math.sin(u), - ], - l = [ - c[0] + o * Math.cos(u) + r * Math.cos(u), - c[1] + o * Math.sin(u) + r * Math.sin(u), - ]; - a = a.concat(this.helper.doubleLineOps(h[0], h[1], l[0], l[1], e)); - } - }), - a - ); - } -} -class g { - constructor(t) { - this.helper = t; - } - fillPolygon(t, e) { - const s = e.hachureGap < 0 ? 4 * e.strokeWidth : e.hachureGap, - n = e.zigzagOffset < 0 ? s : e.zigzagOffset, - i = c(t, (e = Object.assign({}, e, { hachureGap: s + n }))); - return { type: "fillSketch", ops: this.zigzagLines(i, n, e) }; - } - zigzagLines(t, e, n) { - let i = []; - return ( - t.forEach((t) => { - const a = s(t), - o = Math.round(a / (2 * e)); - let h = t[0], - r = t[1]; - h[0] > r[0] && ((h = t[1]), (r = t[0])); - const c = Math.atan((r[1] - h[1]) / (r[0] - h[0])); - for (let t = 0; t < o; t++) { - const s = 2 * t * e, - a = 2 * (t + 1) * e, - o = Math.sqrt(2 * Math.pow(e, 2)), - r = [h[0] + s * Math.cos(c), h[1] + s * Math.sin(c)], - l = [h[0] + a * Math.cos(c), h[1] + a * Math.sin(c)], - u = [ - r[0] + o * Math.cos(c + Math.PI / 4), - r[1] + o * Math.sin(c + Math.PI / 4), - ]; - (i = i.concat(this.helper.doubleLineOps(r[0], r[1], u[0], u[1], n))), - (i = i.concat(this.helper.doubleLineOps(u[0], u[1], l[0], l[1], n))); - } - }), - i - ); - } -} -const y = {}; -class _ { - constructor(t) { - this.seed = t; - } - next() { - return this.seed - ? ((Math.pow(2, 31) - 1) & (this.seed = Math.imul(48271, this.seed))) / - Math.pow(2, 31) - : Math.random(); - } -} -const M = { - randOffset: function (t, e) { - return z(t, e); - }, - randOffsetWithRange: function (t, e, s) { - return C(t, e, s); - }, - ellipse: function (t, e, s, n, i) { - const a = P(s, n, i); - return w(t, e, i, a).opset; - }, - doubleLineOps: function (t, e, s, n, i) { - return A(t, e, s, n, i); - }, -}; -function x(t, e, s, n, i) { - return { type: "path", ops: A(t, e, s, n, i) }; -} -function m(t, e, s) { - const n = (t || []).length; - if (n > 2) { - let i = []; - for (let e = 0; e < n - 1; e++) - i = i.concat(A(t[e][0], t[e][1], t[e + 1][0], t[e + 1][1], s)); - return ( - e && (i = i.concat(A(t[n - 1][0], t[n - 1][1], t[0][0], t[0][1], s))), - { type: "path", ops: i } - ); - } - return 2 === n ? x(t[0][0], t[0][1], t[1][0], t[1][1], s) : { type: "path", ops: [] }; -} -function k(t, e, s, n, i) { - return (function (t, e) { - return m(t, !0, e); - })( - [ - [t, e], - [t + s, e], - [t + s, e + n], - [t, e + n], - ], - i - ); -} -function b(t, e) { - const s = W(t, 1 * (1 + 0.2 * e.roughness), e), - n = W(t, 1.5 * (1 + 0.22 * e.roughness), e); - return { type: "path", ops: s.concat(n) }; -} -function P(t, e, s) { - const n = Math.sqrt( - 2 * Math.PI * Math.sqrt((Math.pow(t / 2, 2) + Math.pow(e / 2, 2)) / 2) - ), - i = Math.max(s.curveStepCount, (s.curveStepCount / Math.sqrt(200)) * n), - a = (2 * Math.PI) / i; - let o = Math.abs(t / 2), - h = Math.abs(e / 2); - const r = 1 - s.curveFitting; - return (o += z(o * r, s)), (h += z(h * r, s)), { increment: a, rx: o, ry: h }; -} -function w(t, e, s, n) { - const [i, a] = D( - n.increment, - t, - e, - n.rx, - n.ry, - 1, - n.increment * C(0.1, C(0.4, 1, s), s), - s - ), - [o] = D(n.increment, t, e, n.rx, n.ry, 1.5, 0, s), - h = R(i, null, s), - r = R(o, null, s); - return { estimatedPoints: a, opset: { type: "path", ops: h.concat(r) } }; -} -function v(t, e, s, n, i, a, o, h, r) { - const c = t, - l = e; - let u = Math.abs(s / 2), - p = Math.abs(n / 2); - (u += z(0.01 * u, r)), (p += z(0.01 * p, r)); - let d = i, - f = a; - for (; d < 0; ) (d += 2 * Math.PI), (f += 2 * Math.PI); - f - d > 2 * Math.PI && ((d = 0), (f = 2 * Math.PI)); - const g = (2 * Math.PI) / r.curveStepCount, - y = Math.min(g / 2, (f - d) / 2), - _ = I(y, c, l, u, p, d, f, 1, r), - M = I(y, c, l, u, p, d, f, 1.5, r); - let x = _.concat(M); - return ( - o && - (h - ? ((x = x.concat(A(c, l, c + u * Math.cos(d), l + p * Math.sin(d), r))), - (x = x.concat(A(c, l, c + u * Math.cos(f), l + p * Math.sin(f), r)))) - : (x.push({ op: "lineTo", data: [c, l] }), - x.push({ - op: "lineTo", - data: [c + u * Math.cos(d), l + p * Math.sin(d)], - }))), - { type: "path", ops: x } - ); -} -function S(t, e) { - const s = []; - if (t.length) { - const n = e.maxRandomnessOffset || 0, - i = t.length; - if (i > 2) { - s.push({ op: "move", data: [t[0][0] + z(n, e), t[0][1] + z(n, e)] }); - for (let a = 1; a < i; a++) - s.push({ op: "lineTo", data: [t[a][0] + z(n, e), t[a][1] + z(n, e)] }); - } - } - return { type: "fillPath", ops: s }; -} -function O(t, e) { - return (function (t, e) { - let s = t.fillStyle || "hachure"; - if (!y[s]) - switch (s) { - case "zigzag": - y[s] || (y[s] = new u(e)); - break; - case "cross-hatch": - y[s] || (y[s] = new p(e)); - break; - case "dots": - y[s] || (y[s] = new d(e)); - break; - case "dashed": - y[s] || (y[s] = new f(e)); - break; - case "zigzag-line": - y[s] || (y[s] = new g(e)); - break; - case "hachure": - default: - (s = "hachure"), y[s] || (y[s] = new l(e)); - } - return y[s]; - })(e, M).fillPolygon(t, e); -} -function T(t) { - return t.randomizer || (t.randomizer = new _(t.seed || 0)), t.randomizer.next(); -} -function C(t, e, s) { - return s.roughness * s.roughnessGain * (T(s) * (e - t) + t); -} -function z(t, e) { - return C(-t, t, e); -} -function A(t, e, s, n, i) { - const a = E(t, e, s, n, i, !0, !1), - o = E(t, e, s, n, i, !0, !0); - return a.concat(o); -} -function E(t, e, s, n, i, a, o) { - const h = Math.pow(t - s, 2) + Math.pow(e - n, 2), - r = Math.sqrt(h); - i.roughnessGain = r < 200 ? 1 : r > 500 ? 0.4 : -0.0016668 * r + 1.233334; - let c = i.maxRandomnessOffset || 0; - c * c * 100 > h && (c = r / 10); - const l = c / 2, - u = 0.2 + 0.2 * T(i); - let p = (i.bowing * i.maxRandomnessOffset * (n - e)) / 200, - d = (i.bowing * i.maxRandomnessOffset * (t - s)) / 200; - (p = z(p, i)), (d = z(d, i)); - const f = [], - g = () => z(l, i), - y = () => z(c, i); - return ( - a && - (o - ? f.push({ op: "move", data: [t + g(), e + g()] }) - : f.push({ op: "move", data: [t + z(c, i), e + z(c, i)] })), - o - ? f.push({ - op: "bcurveTo", - data: [ - p + t + (s - t) * u + g(), - d + e + (n - e) * u + g(), - p + t + 2 * (s - t) * u + g(), - d + e + 2 * (n - e) * u + g(), - s + g(), - n + g(), - ], - }) - : f.push({ - op: "bcurveTo", - data: [ - p + t + (s - t) * u + y(), - d + e + (n - e) * u + y(), - p + t + 2 * (s - t) * u + y(), - d + e + 2 * (n - e) * u + y(), - s + y(), - n + y(), - ], - }), - f - ); -} -function W(t, e, s) { - const n = []; - n.push([t[0][0] + z(e, s), t[0][1] + z(e, s)]), - n.push([t[0][0] + z(e, s), t[0][1] + z(e, s)]); - for (let i = 1; i < t.length; i++) - n.push([t[i][0] + z(e, s), t[i][1] + z(e, s)]), - i === t.length - 1 && n.push([t[i][0] + z(e, s), t[i][1] + z(e, s)]); - return R(n, null, s); -} -function R(t, e, s) { - const n = t.length; - let i = []; - if (n > 3) { - const a = [], - o = 1 - s.curveTightness; - i.push({ op: "move", data: [t[1][0], t[1][1]] }); - for (let e = 1; e + 2 < n; e++) { - const s = t[e]; - (a[0] = [s[0], s[1]]), - (a[1] = [ - s[0] + (o * t[e + 1][0] - o * t[e - 1][0]) / 6, - s[1] + (o * t[e + 1][1] - o * t[e - 1][1]) / 6, - ]), - (a[2] = [ - t[e + 1][0] + (o * t[e][0] - o * t[e + 2][0]) / 6, - t[e + 1][1] + (o * t[e][1] - o * t[e + 2][1]) / 6, - ]), - (a[3] = [t[e + 1][0], t[e + 1][1]]), - i.push({ - op: "bcurveTo", - data: [a[1][0], a[1][1], a[2][0], a[2][1], a[3][0], a[3][1]], - }); - } - if (e && 2 === e.length) { - const t = s.maxRandomnessOffset; - i.push({ op: "lineTo", data: [e[0] + z(t, s), e[1] + z(t, s)] }); - } - } else - 3 === n - ? (i.push({ op: "move", data: [t[1][0], t[1][1]] }), - i.push({ - op: "bcurveTo", - data: [t[1][0], t[1][1], t[2][0], t[2][1], t[2][0], t[2][1]], - })) - : 2 === n && (i = i.concat(A(t[0][0], t[0][1], t[1][0], t[1][1], s))); - return i; -} -function D(t, e, s, n, i, a, o, h) { - const r = [], - c = [], - l = z(0.5, h) - Math.PI / 2; - c.push([ - z(a, h) + e + 0.9 * n * Math.cos(l - t), - z(a, h) + s + 0.9 * i * Math.sin(l - t), - ]); - for (let o = l; o < 2 * Math.PI + l - 0.01; o += t) { - const t = [z(a, h) + e + n * Math.cos(o), z(a, h) + s + i * Math.sin(o)]; - r.push(t), c.push(t); - } - return ( - c.push([ - z(a, h) + e + n * Math.cos(l + 2 * Math.PI + 0.5 * o), - z(a, h) + s + i * Math.sin(l + 2 * Math.PI + 0.5 * o), - ]), - c.push([ - z(a, h) + e + 0.98 * n * Math.cos(l + o), - z(a, h) + s + 0.98 * i * Math.sin(l + o), - ]), - c.push([ - z(a, h) + e + 0.9 * n * Math.cos(l + 0.5 * o), - z(a, h) + s + 0.9 * i * Math.sin(l + 0.5 * o), - ]), - [c, r] - ); -} -function I(t, e, s, n, i, a, o, h, r) { - const c = a + z(0.1, r), - l = []; - l.push([ - z(h, r) + e + 0.9 * n * Math.cos(c - t), - z(h, r) + s + 0.9 * i * Math.sin(c - t), - ]); - for (let a = c; a <= o; a += t) - l.push([z(h, r) + e + n * Math.cos(a), z(h, r) + s + i * Math.sin(a)]); - return ( - l.push([e + n * Math.cos(o), s + i * Math.sin(o)]), - l.push([e + n * Math.cos(o), s + i * Math.sin(o)]), - R(l, null, r) - ); -} -function q(t, e, s, n, i, a, o, h) { - const r = [], - c = [h.maxRandomnessOffset || 1, (h.maxRandomnessOffset || 1) + 0.5]; - let l = [0, 0]; - for (let u = 0; u < 2; u++) - 0 === u - ? r.push({ op: "move", data: [o.x, o.y] }) - : r.push({ op: "move", data: [o.x + z(c[0], h), o.y + z(c[0], h)] }), - (l = [i + z(c[u], h), a + z(c[u], h)]), - r.push({ - op: "bcurveTo", - data: [ - t + z(c[u], h), - e + z(c[u], h), - s + z(c[u], h), - n + z(c[u], h), - l[0], - l[1], - ], - }); - return o.setPosition(l[0], l[1]), r; -} -function $(t, e, s, n) { - let i = []; - switch (e.key) { - case "M": - case "m": { - const s = "m" === e.key; - if (e.data.length >= 2) { - let a = +e.data[0], - o = +e.data[1]; - s && ((a += t.x), (o += t.y)); - const h = 1 * (n.maxRandomnessOffset || 0); - (a += z(h, n)), - (o += z(h, n)), - t.setPosition(a, o), - i.push({ op: "move", data: [a, o] }); - } - break; - } - case "L": - case "l": { - const s = "l" === e.key; - if (e.data.length >= 2) { - let a = +e.data[0], - o = +e.data[1]; - s && ((a += t.x), (o += t.y)), - (i = i.concat(A(t.x, t.y, a, o, n))), - t.setPosition(a, o); - } - break; - } - case "H": - case "h": { - const s = "h" === e.key; - if (e.data.length) { - let a = +e.data[0]; - s && (a += t.x), (i = i.concat(A(t.x, t.y, a, t.y, n))), t.setPosition(a, t.y); - } - break; - } - case "V": - case "v": { - const s = "v" === e.key; - if (e.data.length) { - let a = +e.data[0]; - s && (a += t.y), (i = i.concat(A(t.x, t.y, t.x, a, n))), t.setPosition(t.x, a); - } - break; - } - case "Z": - case "z": - t.first && - ((i = i.concat(A(t.x, t.y, t.first[0], t.first[1], n))), - t.setPosition(t.first[0], t.first[1]), - (t.first = null)); - break; - case "C": - case "c": { - const s = "c" === e.key; - if (e.data.length >= 6) { - let a = +e.data[0], - o = +e.data[1], - h = +e.data[2], - r = +e.data[3], - c = +e.data[4], - l = +e.data[5]; - s && ((a += t.x), (h += t.x), (c += t.x), (o += t.y), (r += t.y), (l += t.y)); - const u = q(a, o, h, r, c, l, t, n); - (i = i.concat(u)), (t.bezierReflectionPoint = [c + (c - h), l + (l - r)]); - } - break; - } - case "S": - case "s": { - const a = "s" === e.key; - if (e.data.length >= 4) { - let o = +e.data[0], - h = +e.data[1], - r = +e.data[2], - c = +e.data[3]; - a && ((o += t.x), (r += t.x), (h += t.y), (c += t.y)); - let l = o, - u = h; - const p = s ? s.key : ""; - let d = null; - ("c" !== p && "C" !== p && "s" !== p && "S" !== p) || - (d = t.bezierReflectionPoint), - d && ((l = d[0]), (u = d[1])); - const f = q(l, u, o, h, r, c, t, n); - (i = i.concat(f)), (t.bezierReflectionPoint = [r + (r - o), c + (c - h)]); - } - break; - } - case "Q": - case "q": { - const s = "q" === e.key; - if (e.data.length >= 4) { - let a = +e.data[0], - o = +e.data[1], - h = +e.data[2], - r = +e.data[3]; - s && ((a += t.x), (h += t.x), (o += t.y), (r += t.y)); - const c = 1 * (1 + 0.2 * n.roughness), - l = 1.5 * (1 + 0.22 * n.roughness); - i.push({ op: "move", data: [t.x + z(c, n), t.y + z(c, n)] }); - let u = [h + z(c, n), r + z(c, n)]; - i.push({ - op: "qcurveTo", - data: [a + z(c, n), o + z(c, n), u[0], u[1]], - }), - i.push({ op: "move", data: [t.x + z(l, n), t.y + z(l, n)] }), - (u = [h + z(l, n), r + z(l, n)]), - i.push({ - op: "qcurveTo", - data: [a + z(l, n), o + z(l, n), u[0], u[1]], - }), - t.setPosition(u[0], u[1]), - (t.quadReflectionPoint = [h + (h - a), r + (r - o)]); - } - break; - } - case "T": - case "t": { - const a = "t" === e.key; - if (e.data.length >= 2) { - let o = +e.data[0], - h = +e.data[1]; - a && ((o += t.x), (h += t.y)); - let r = o, - c = h; - const l = s ? s.key : ""; - let u = null; - ("q" !== l && "Q" !== l && "t" !== l && "T" !== l) || (u = t.quadReflectionPoint), - u && ((r = u[0]), (c = u[1])); - const p = 1 * (1 + 0.2 * n.roughness), - d = 1.5 * (1 + 0.22 * n.roughness); - i.push({ op: "move", data: [t.x + z(p, n), t.y + z(p, n)] }); - let f = [o + z(p, n), h + z(p, n)]; - i.push({ - op: "qcurveTo", - data: [r + z(p, n), c + z(p, n), f[0], f[1]], - }), - i.push({ op: "move", data: [t.x + z(d, n), t.y + z(d, n)] }), - (f = [o + z(d, n), h + z(d, n)]), - i.push({ - op: "qcurveTo", - data: [r + z(d, n), c + z(d, n), f[0], f[1]], - }), - t.setPosition(f[0], f[1]), - (t.quadReflectionPoint = [o + (o - r), h + (h - c)]); - } - break; - } - case "A": - case "a": { - const s = "a" === e.key; - if (e.data.length >= 7) { - const a = +e.data[0], - o = +e.data[1], - r = +e.data[2], - c = +e.data[3], - l = +e.data[4]; - let u = +e.data[5], - p = +e.data[6]; - if ((s && ((u += t.x), (p += t.y)), u === t.x && p === t.y)) break; - if (0 === a || 0 === o) (i = i.concat(A(t.x, t.y, u, p, n))), t.setPosition(u, p); - else - for (let e = 0; e < 1; e++) { - const e = new h([t.x, t.y], [u, p], [a, o], r, !!c, !!l); - let s = e.getNextSegment(); - for (; s; ) { - const a = q(s.cp1[0], s.cp1[1], s.cp2[0], s.cp2[1], s.to[0], s.to[1], t, n); - (i = i.concat(a)), (s = e.getNextSegment()); - } - } - } - break; - } - } - return i; -} -const N = "undefined" != typeof self, - L = "none"; -class B { - constructor(t, e) { - (this.defaultOptions = { - maxRandomnessOffset: 2, - roughness: 1, - bowing: 1, - stroke: "#000", - strokeWidth: 1, - curveTightness: 0, - curveFitting: 0.95, - curveStepCount: 9, - fillStyle: "hachure", - fillWeight: -1, - hachureAngle: -41, - hachureGap: -1, - dashOffset: -1, - dashGap: -1, - zigzagOffset: -1, - seed: 0, - roughnessGain: 1, - }), - (this.config = t || {}), - (this.surface = e), - this.config.options && (this.defaultOptions = this._options(this.config.options)); - } - static newSeed() { - return Math.floor(Math.random() * Math.pow(2, 31)); - } - _options(t) { - return t ? Object.assign({}, this.defaultOptions, t) : this.defaultOptions; - } - _drawable(t, e, s) { - return { shape: t, sets: e || [], options: s || this.defaultOptions }; - } - line(t, e, s, n, i) { - const a = this._options(i); - return this._drawable("line", [x(t, e, s, n, a)], a); - } - rectangle(t, e, s, n, i) { - const a = this._options(i), - o = [], - h = k(t, e, s, n, a); - if (a.fill) { - const i = [ - [t, e], - [t + s, e], - [t + s, e + n], - [t, e + n], - ]; - "solid" === a.fillStyle ? o.push(S(i, a)) : o.push(O(i, a)); - } - return a.stroke !== L && o.push(h), this._drawable("rectangle", o, a); - } - ellipse(t, e, s, n, i) { - const a = this._options(i), - o = [], - h = P(s, n, a), - r = w(t, e, a, h); - if (a.fill) - if ("solid" === a.fillStyle) { - const s = w(t, e, a, h).opset; - (s.type = "fillPath"), o.push(s); - } else o.push(O(r.estimatedPoints, a)); - return a.stroke !== L && o.push(r.opset), this._drawable("ellipse", o, a); - } - circle(t, e, s, n) { - const i = this.ellipse(t, e, s, s, n); - return (i.shape = "circle"), i; - } - linearPath(t, e) { - const s = this._options(e); - return this._drawable("linearPath", [m(t, !1, s)], s); - } - arc(t, e, s, n, i, a, o = !1, h) { - const r = this._options(h), - c = [], - l = v(t, e, s, n, i, a, o, !0, r); - if (o && r.fill) - if ("solid" === r.fillStyle) { - const o = v(t, e, s, n, i, a, !0, !1, r); - (o.type = "fillPath"), c.push(o); - } else - c.push( - (function (t, e, s, n, i, a, o) { - const h = t, - r = e; - let c = Math.abs(s / 2), - l = Math.abs(n / 2); - (c += z(0.01 * c, o)), (l += z(0.01 * l, o)); - let u = i, - p = a; - for (; u < 0; ) (u += 2 * Math.PI), (p += 2 * Math.PI); - p - u > 2 * Math.PI && ((u = 0), (p = 2 * Math.PI)); - const d = (p - u) / o.curveStepCount, - f = []; - for (let t = u; t <= p; t += d) - f.push([h + c * Math.cos(t), r + l * Math.sin(t)]); - return ( - f.push([h + c * Math.cos(p), r + l * Math.sin(p)]), f.push([h, r]), O(f, o) - ); - })(t, e, s, n, i, a, r) - ); - return r.stroke !== L && c.push(l), this._drawable("arc", c, r); - } - curve(t, e) { - const s = this._options(e); - return this._drawable("curve", [b(t, s)], s); - } - polygon(t, e) { - const s = this._options(e), - n = [], - i = m(t, !0, s); - return ( - s.fill && ("solid" === s.fillStyle ? n.push(S(t, s)) : n.push(O(t, s))), - s.stroke !== L && n.push(i), - this._drawable("polygon", n, s) - ); - } - path(t, e) { - const s = this._options(e), - n = []; - if (!t) return this._drawable("path", n, s); - const i = (function (t, e) { - t = (t || "").replace(/\n/g, " ").replace(/(-\s)/g, "-").replace("/(\s\s)/g", " "); - let s = new o(t); - if (e.simplification) { - const t = new r(s.linearPoints, s.closed).fit(e.simplification); - s = new o(t); - } - let n = []; - const i = s.segments || []; - for (let t = 0; t < i.length; t++) { - const a = $(s, i[t], t > 0 ? i[t - 1] : null, e); - a && a.length && (n = n.concat(a)); - } - return { type: "path", ops: n }; - })(t, s); - if (s.fill) - if ("solid" === s.fillStyle) { - const e = { type: "path2Dfill", path: t, ops: [] }; - n.push(e); - } else { - const e = this.computePathSize(t), - i = O( - [ - [0, 0], - [e[0], 0], - [e[0], e[1]], - [0, e[1]], - ], - s - ); - (i.type = "path2Dpattern"), (i.size = e), (i.path = t), n.push(i); - } - return s.stroke !== L && n.push(i), this._drawable("path", n, s); - } - computePathSize(e) { - let s = [0, 0]; - if (N && self.document) - try { - const n = self.document.createElementNS(t, "svg"); - n.setAttribute("width", "0"), n.setAttribute("height", "0"); - const i = self.document.createElementNS(t, "path"); - i.setAttribute("d", e), n.appendChild(i), self.document.body.appendChild(n); - const a = i.getBBox(); - a && ((s[0] = a.width || 0), (s[1] = a.height || 0)), - self.document.body.removeChild(n); - } catch (t) {} - const n = this.getCanvasSize(); - return s[0] * s[1] || (s = n), s; - } - getCanvasSize() { - const t = (t) => - t && "object" == typeof t && t.baseVal && t.baseVal.value - ? t.baseVal.value - : t || 100; - return this.surface ? [t(this.surface.width), t(this.surface.height)] : [100, 100]; - } - opsToPath(t) { - let e = ""; - for (const s of t.ops) { - const t = s.data; - switch (s.op) { - case "move": - e += `M${t[0]} ${t[1]} `; - break; - case "bcurveTo": - e += `C${t[0]} ${t[1]}, ${t[2]} ${t[3]}, ${t[4]} ${t[5]} `; - break; - case "qcurveTo": - e += `Q${t[0]} ${t[1]}, ${t[2]} ${t[3]} `; - break; - case "lineTo": - e += `L${t[0]} ${t[1]} `; - } - } - return e.trim(); - } - toPaths(t) { - const e = t.sets || [], - s = t.options || this.defaultOptions, - n = []; - for (const t of e) { - let e = null; - switch (t.type) { - case "path": - e = { - d: this.opsToPath(t), - stroke: s.stroke, - strokeWidth: s.strokeWidth, - fill: L, - }; - break; - case "fillPath": - e = { - d: this.opsToPath(t), - stroke: L, - strokeWidth: 0, - fill: s.fill || L, - }; - break; - case "fillSketch": - e = this.fillSketch(t, s); - break; - case "path2Dfill": - e = { d: t.path || "", stroke: L, strokeWidth: 0, fill: s.fill || L }; - break; - case "path2Dpattern": { - const n = t.size, - i = { - x: 0, - y: 0, - width: 1, - height: 1, - viewBox: `0 0 ${Math.round(n[0])} ${Math.round(n[1])}`, - patternUnits: "objectBoundingBox", - path: this.fillSketch(t, s), - }; - e = { d: t.path, stroke: L, strokeWidth: 0, pattern: i }; - break; - } - } - e && n.push(e); - } - return n; - } - fillSketch(t, e) { - let s = e.fillWeight; - return ( - s < 0 && (s = e.strokeWidth / 2), - { d: this.opsToPath(t), stroke: e.fill || L, strokeWidth: s, fill: L } - ); - } -} -const G = "undefined" != typeof document; -class V { - constructor(t, e) { - (this.canvas = t), - (this.ctx = this.canvas.getContext("2d")), - (this.gen = new B(e, this.canvas)); - } - draw(t) { - const e = t.sets || [], - s = t.options || this.getDefaultOptions(), - n = this.ctx; - for (const t of e) - switch (t.type) { - case "path": - n.save(), - (n.strokeStyle = "none" === s.stroke ? "transparent" : s.stroke), - (n.lineWidth = s.strokeWidth), - this._drawToContext(n, t), - n.restore(); - break; - case "fillPath": - n.save(), (n.fillStyle = s.fill || ""), this._drawToContext(n, t), n.restore(); - break; - case "fillSketch": - this.fillSketch(n, t, s); - break; - case "path2Dfill": { - this.ctx.save(), (this.ctx.fillStyle = s.fill || ""); - const e = new Path2D(t.path); - this.ctx.fill(e), this.ctx.restore(); - break; - } - case "path2Dpattern": { - const e = this.canvas.ownerDocument || (G && document); - if (e) { - const n = t.size, - i = e.createElement("canvas"), - a = i.getContext("2d"), - o = this.computeBBox(t.path); - o && (o.width || o.height) - ? ((i.width = this.canvas.width), - (i.height = this.canvas.height), - a.translate(o.x || 0, o.y || 0)) - : ((i.width = n[0]), (i.height = n[1])), - this.fillSketch(a, t, s), - this.ctx.save(), - (this.ctx.fillStyle = this.ctx.createPattern(i, "repeat")); - const h = new Path2D(t.path); - this.ctx.fill(h), this.ctx.restore(); - } else console.error("Pattern fill fail: No defs"); - break; - } - } - } - computeBBox(e) { - if (G) - try { - const s = document.createElementNS(t, "svg"); - s.setAttribute("width", "0"), s.setAttribute("height", "0"); - const n = self.document.createElementNS(t, "path"); - n.setAttribute("d", e), s.appendChild(n), document.body.appendChild(s); - const i = n.getBBox(); - return document.body.removeChild(s), i; - } catch (t) {} - return null; - } - fillSketch(t, e, s) { - let n = s.fillWeight; - n < 0 && (n = s.strokeWidth / 2), - t.save(), - (t.strokeStyle = s.fill || ""), - (t.lineWidth = n), - this._drawToContext(t, e), - t.restore(); - } - _drawToContext(t, e) { - t.beginPath(); - for (const s of e.ops) { - const e = s.data; - switch (s.op) { - case "move": - t.moveTo(e[0], e[1]); - break; - case "bcurveTo": - t.bezierCurveTo(e[0], e[1], e[2], e[3], e[4], e[5]); - break; - case "qcurveTo": - t.quadraticCurveTo(e[0], e[1], e[2], e[3]); - break; - case "lineTo": - t.lineTo(e[0], e[1]); - } - } - "fillPath" === e.type ? t.fill() : t.stroke(); - } - get generator() { - return this.gen; - } - getDefaultOptions() { - return this.gen.defaultOptions; - } - line(t, e, s, n, i) { - const a = this.gen.line(t, e, s, n, i); - return this.draw(a), a; - } - rectangle(t, e, s, n, i) { - const a = this.gen.rectangle(t, e, s, n, i); - return this.draw(a), a; - } - ellipse(t, e, s, n, i) { - const a = this.gen.ellipse(t, e, s, n, i); - return this.draw(a), a; - } - circle(t, e, s, n) { - const i = this.gen.circle(t, e, s, n); - return this.draw(i), i; - } - linearPath(t, e) { - const s = this.gen.linearPath(t, e); - return this.draw(s), s; - } - polygon(t, e) { - const s = this.gen.polygon(t, e); - return this.draw(s), s; - } - arc(t, e, s, n, i, a, o = !1, h) { - const r = this.gen.arc(t, e, s, n, i, a, o, h); - return this.draw(r), r; - } - curve(t, e) { - const s = this.gen.curve(t, e); - return this.draw(s), s; - } - path(t, e) { - const s = this.gen.path(t, e); - return this.draw(s), s; - } -} -const j = "undefined" != typeof document; -class Q { - constructor(t, e) { - (this.svg = t), (this.gen = new B(e, this.svg)); - } - get defs() { - const e = this.svg.ownerDocument || (j && document); - if (e && !this._defs) { - const s = e.createElementNS(t, "defs"); - this.svg.firstChild - ? this.svg.insertBefore(s, this.svg.firstChild) - : this.svg.appendChild(s), - (this._defs = s); - } - return this._defs || null; - } - draw(e) { - const s = e.sets || [], - n = e.options || this.getDefaultOptions(), - i = this.svg.ownerDocument || window.document, - a = i.createElementNS(t, "g"); - for (const e of s) { - let s = null; - switch (e.type) { - case "path": - (s = i.createElementNS(t, "path")), - s.setAttribute("d", this.opsToPath(e)), - (s.style.stroke = n.stroke), - (s.style.strokeWidth = n.strokeWidth + ""), - (s.style.fill = "none"); - break; - case "fillPath": - (s = i.createElementNS(t, "path")), - s.setAttribute("d", this.opsToPath(e)), - (s.style.stroke = "none"), - (s.style.strokeWidth = "0"), - (s.style.fill = n.fill || ""); - break; - case "fillSketch": - s = this.fillSketch(i, e, n); - break; - case "path2Dfill": - (s = i.createElementNS(t, "path")), - s.setAttribute("d", e.path || ""), - (s.style.stroke = "none"), - (s.style.strokeWidth = "0"), - (s.style.fill = n.fill || ""); - break; - case "path2Dpattern": - if (this.defs) { - const a = e.size, - o = i.createElementNS(t, "pattern"), - h = `rough-${Math.floor( - Math.random() * (Number.MAX_SAFE_INTEGER || 999999) - )}`; - o.setAttribute("id", h), - o.setAttribute("x", "0"), - o.setAttribute("y", "0"), - o.setAttribute("width", "1"), - o.setAttribute("height", "1"), - o.setAttribute("height", "1"), - o.setAttribute("viewBox", `0 0 ${Math.round(a[0])} ${Math.round(a[1])}`), - o.setAttribute("patternUnits", "objectBoundingBox"); - const r = this.fillSketch(i, e, n); - o.appendChild(r), - this.defs.appendChild(o), - (s = i.createElementNS(t, "path")), - s.setAttribute("d", e.path || ""), - (s.style.stroke = "none"), - (s.style.strokeWidth = "0"), - (s.style.fill = `url(#${h})`); - } else console.error("Pattern fill fail: No defs"); - } - s && a.appendChild(s); - } - return a; - } - fillSketch(e, s, n) { - let i = n.fillWeight; - i < 0 && (i = n.strokeWidth / 2); - const a = e.createElementNS(t, "path"); - return ( - a.setAttribute("d", this.opsToPath(s)), - (a.style.stroke = n.fill || ""), - (a.style.strokeWidth = i + ""), - (a.style.fill = "none"), - a - ); - } - get generator() { - return this.gen; - } - getDefaultOptions() { - return this.gen.defaultOptions; - } - opsToPath(t) { - return this.gen.opsToPath(t); - } - line(t, e, s, n, i) { - const a = this.gen.line(t, e, s, n, i); - return this.draw(a); - } - rectangle(t, e, s, n, i) { - const a = this.gen.rectangle(t, e, s, n, i); - return this.draw(a); - } - ellipse(t, e, s, n, i) { - const a = this.gen.ellipse(t, e, s, n, i); - return this.draw(a); - } - circle(t, e, s, n) { - const i = this.gen.circle(t, e, s, n); - return this.draw(i); - } - linearPath(t, e) { - const s = this.gen.linearPath(t, e); - return this.draw(s); - } - polygon(t, e) { - const s = this.gen.polygon(t, e); - return this.draw(s); - } - arc(t, e, s, n, i, a, o = !1, h) { - const r = this.gen.arc(t, e, s, n, i, a, o, h); - return this.draw(r); - } - curve(t, e) { - const s = this.gen.curve(t, e); - return this.draw(s); - } - path(t, e) { - const s = this.gen.path(t, e); - return this.draw(s); - } -} -var rough = { - canvas: (t, e) => new V(t, e), - svg: (t, e) => new Q(t, e), - generator: (t, e) => new B(t, e), - newSeed: () => B.newSeed(), -}; diff --git a/d2renderers/d2sketch/setup.js b/d2renderers/d2sketch/setup.js deleted file mode 100644 index 8814fbf359..0000000000 --- a/d2renderers/d2sketch/setup.js +++ /dev/null @@ -1,23 +0,0 @@ -const root = { - ownerDocument: { - createElementNS: (ns, tagName) => { - const children = []; - const attrs = {}; - const style = {}; - return { - style, - tagName, - attrs, - setAttribute: (key, value) => (attrs[key] = value), - appendChild: (node) => children.push(node), - children, - }; - }, - }, -}; -const rc = rough.svg(root, { seed: 1 }); -let node; - -if (typeof globalThis !== "undefined") { - globalThis.rc = rc; -} diff --git a/d2renderers/d2sketch/sketch.go b/d2renderers/d2sketch/sketch.go index b9f49bbb99..69e9bace25 100644 --- a/d2renderers/d2sketch/sketch.go +++ b/d2renderers/d2sketch/sketch.go @@ -2,7 +2,6 @@ package d2sketch import ( "bytes" - "encoding/json" "fmt" "regexp" "strings" @@ -13,27 +12,15 @@ import ( "github.com/d2lang/d2/d2themes" "github.com/d2lang/d2/lib/color" "github.com/d2lang/d2/lib/geo" - "github.com/d2lang/d2/lib/jsrunner" "github.com/d2lang/d2/lib/label" "github.com/d2lang/d2/lib/svg" + rough "github.com/d2lang/rough-go" "github.com/d2lang/util-go/go2" ) -//go:embed rough.js -var roughJS string - -//go:embed setup.js -var setupJS string - //go:embed streaks.txt var streaks string -var baseRoughProps = `fillWeight: 2.0, -hachureGap: 16, -fillStyle: "solid", -bowing: 2, -seed: 1,` - var floatRE = regexp.MustCompile(`(\d+)\.(\d+)`) const ( @@ -41,14 +28,21 @@ const ( FG_COLOR = color.N1 ) -func LoadJS(runner jsrunner.JSRunner) error { - if _, err := runner.RunString(roughJS); err != nil { - return err - } - if _, err := runner.RunString(setupJS); err != nil { - return err +func newGenerator() *rough.Generator { + return rough.NewGenerator(&rough.Config{Options: &rough.Options{Seed: rough.Float64(1)}}) +} + +func baseOptions(strokeWidth float64) *rough.Options { + return &rough.Options{ + Fill: rough.String("#000"), + Stroke: rough.String("#000"), + StrokeWidth: rough.Float64(strokeWidth), + FillWeight: rough.Float64(2), + HachureGap: rough.Float64(16), + FillStyle: rough.String("solid"), + Bowing: rough.Float64(2), + Seed: rough.Float64(1), } - return nil } // DefineFillPatterns adds reusable patterns that are overlayed on shapes with @@ -73,14 +67,9 @@ func defineFillPattern(buf *bytes.Buffer, source, diagramHash string, luminanceC } } -func Rect(r jsrunner.JSRunner, shape d2target.Shape, diagramHash string) (string, error) { - js := fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %d, { - fill: "#000", - stroke: "#000", - strokeWidth: %d, - %s - });`, shape.Width, shape.Height, shape.StrokeWidth, baseRoughProps) - paths, err := computeRoughPathData(r, js) +func Rect(shape d2target.Shape, diagramHash string) (string, error) { + g := newGenerator() + paths, err := computeRoughPathData(g, g.Rectangle(0, 0, float64(shape.Width), float64(shape.Height), baseOptions(float64(shape.StrokeWidth)))) if err != nil { return "", err } @@ -119,24 +108,16 @@ func Rect(r jsrunner.JSRunner, shape d2target.Shape, diagramHash string) (string return output, nil } -func DoubleRect(r jsrunner.JSRunner, shape d2target.Shape, diagramHash string) (string, error) { - jsBigRect := fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %d, { - fill: "#000", - stroke: "#000", - strokeWidth: %d, - %s - });`, shape.Width, shape.Height, shape.StrokeWidth, baseRoughProps) - pathsBigRect, err := computeRoughPathData(r, jsBigRect) +func DoubleRect(shape d2target.Shape, diagramHash string) (string, error) { + g := newGenerator() + pathsBigRect, err := computeRoughPathData(g, g.Rectangle(0, 0, float64(shape.Width), float64(shape.Height), baseOptions(float64(shape.StrokeWidth)))) if err != nil { return "", err } - jsSmallRect := fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %d, { - fill: "#000", - stroke: "#000", - strokeWidth: %d, - %s - });`, shape.Width-d2target.INNER_BORDER_OFFSET*2, shape.Height-d2target.INNER_BORDER_OFFSET*2, shape.StrokeWidth, baseRoughProps) - pathsSmallRect, err := computeRoughPathData(r, jsSmallRect) + pathsSmallRect, err := computeRoughPathData(g, g.Rectangle(0, 0, + float64(shape.Width-d2target.INNER_BORDER_OFFSET*2), + float64(shape.Height-d2target.INNER_BORDER_OFFSET*2), + baseOptions(float64(shape.StrokeWidth)))) if err != nil { return "", err } @@ -194,14 +175,11 @@ func DoubleRect(r jsrunner.JSRunner, shape d2target.Shape, diagramHash string) ( return output, nil } -func Oval(r jsrunner.JSRunner, shape d2target.Shape, diagramHash string) (string, error) { - js := fmt.Sprintf(`node = rc.ellipse(%d, %d, %d, %d, { - fill: "#000", - stroke: "#000", - strokeWidth: %d, - %s - });`, shape.Width/2, shape.Height/2, shape.Width, shape.Height, shape.StrokeWidth, baseRoughProps) - paths, err := computeRoughPathData(r, js) +func Oval(shape d2target.Shape, diagramHash string) (string, error) { + g := newGenerator() + paths, err := computeRoughPathData(g, g.Ellipse( + float64(shape.Width/2), float64(shape.Height/2), + float64(shape.Width), float64(shape.Height), baseOptions(float64(shape.StrokeWidth)))) if err != nil { return "", err } @@ -243,24 +221,19 @@ func Oval(r jsrunner.JSRunner, shape d2target.Shape, diagramHash string) (string return output, nil } -func DoubleOval(r jsrunner.JSRunner, shape d2target.Shape, diagramHash string) (string, error) { - jsBigCircle := fmt.Sprintf(`node = rc.ellipse(%d, %d, %d, %d, { - fill: "#000", - stroke: "#000", - strokeWidth: %d, - %s - });`, shape.Width/2, shape.Height/2, shape.Width, shape.Height, shape.StrokeWidth, baseRoughProps) - jsSmallCircle := fmt.Sprintf(`node = rc.ellipse(%d, %d, %d, %d, { - fill: "#000", - stroke: "#000", - strokeWidth: %d, - %s - });`, shape.Width/2, shape.Height/2, shape.Width-d2target.INNER_BORDER_OFFSET*2, shape.Height-d2target.INNER_BORDER_OFFSET*2, shape.StrokeWidth, baseRoughProps) - pathsBigCircle, err := computeRoughPathData(r, jsBigCircle) +func DoubleOval(shape d2target.Shape, diagramHash string) (string, error) { + g := newGenerator() + pathsBigCircle, err := computeRoughPathData(g, g.Ellipse( + float64(shape.Width/2), float64(shape.Height/2), + float64(shape.Width), float64(shape.Height), baseOptions(float64(shape.StrokeWidth)))) if err != nil { return "", err } - pathsSmallCircle, err := computeRoughPathData(r, jsSmallCircle) + pathsSmallCircle, err := computeRoughPathData(g, g.Ellipse( + float64(shape.Width/2), float64(shape.Height/2), + float64(shape.Width-d2target.INNER_BORDER_OFFSET*2), + float64(shape.Height-d2target.INNER_BORDER_OFFSET*2), + baseOptions(float64(shape.StrokeWidth)))) if err != nil { return "", err } @@ -321,16 +294,11 @@ func DoubleOval(r jsrunner.JSRunner, shape d2target.Shape, diagramHash string) ( } // TODO need to personalize this per shape like we do in Terrastruct app -func Paths(r jsrunner.JSRunner, shape d2target.Shape, diagramHash string, paths []string) (string, error) { +func Paths(shape d2target.Shape, diagramHash string, paths []string) (string, error) { output := "" for _, path := range paths { - js := fmt.Sprintf(`node = rc.path("%s", { - fill: "#000", - stroke: "#000", - strokeWidth: %d, - %s - });`, path, shape.StrokeWidth, baseRoughProps) - sketchPaths, err := computeRoughPathData(r, js) + g := newGenerator() + sketchPaths, err := computeRoughPathData(g, g.Path(path, baseOptions(float64(shape.StrokeWidth)))) if err != nil { return "", err } @@ -370,7 +338,7 @@ func Paths(r jsrunner.JSRunner, shape d2target.Shape, diagramHash string, paths return output, nil } -func Connection(r jsrunner.JSRunner, connection d2target.Connection, path, attrs string) (string, error) { +func Connection(connection d2target.Connection, path, attrs string) (string, error) { animatedClass := "" if connection.Animated { animatedClass = " animated-connection" @@ -415,8 +383,11 @@ func Connection(r jsrunner.JSRunner, connection d2target.Connection, path, attrs } } else { roughness := 0.5 - js := fmt.Sprintf(`node = rc.path("%s", {roughness: %f, seed: 1});`, path, roughness) - paths, err := computeRoughPathData(r, js) + g := newGenerator() + paths, err := computeRoughPathData(g, g.Path(path, &rough.Options{ + Roughness: rough.Float64(roughness), + Seed: rough.Float64(1), + })) if err != nil { return "", err } @@ -438,15 +409,10 @@ func Connection(r jsrunner.JSRunner, connection d2target.Connection, path, attrs } // TODO cleanup -func Table(r jsrunner.JSRunner, shape d2target.Shape) (string, error) { +func Table(shape d2target.Shape) (string, error) { output := "" - js := fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %d, { - fill: "#000", - stroke: "#000", - strokeWidth: %d, - %s - });`, shape.Width, shape.Height, shape.StrokeWidth, baseRoughProps) - paths, err := computeRoughPathData(r, js) + g := newGenerator() + paths, err := computeRoughPathData(g, g.Rectangle(0, 0, float64(shape.Width), float64(shape.Height), baseOptions(float64(shape.StrokeWidth)))) if err != nil { return "", err } @@ -469,11 +435,7 @@ func Table(r jsrunner.JSRunner, shape d2target.Shape) (string, error) { rowHeight := box.Height / float64(1+len(shape.SQLTable.Columns)) headerBox := geo.NewBox(box.TopLeft, box.Width, rowHeight) - js = fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %f, { - fill: "#000", - %s - });`, shape.Width, rowHeight, baseRoughProps) - paths, err = computeRoughPathData(r, js) + paths, err = computeRoughPathData(g, g.Rectangle(0, 0, float64(shape.Width), rowHeight, baseOptions(1))) if err != nil { return "", err } @@ -551,10 +513,10 @@ func Table(r jsrunner.JSRunner, shape d2target.Shape) (string, error) { rowBox.TopLeft.Y += rowHeight - js = fmt.Sprintf(`node = rc.line(%f, %f, %f, %f, { - %s - });`, rowBox.TopLeft.X, rowBox.TopLeft.Y, rowBox.TopLeft.X+rowBox.Width, rowBox.TopLeft.Y, baseRoughProps) - paths, err = computeRoughPathData(r, js) + paths, err = computeRoughPathData(g, g.Line( + rowBox.TopLeft.X, rowBox.TopLeft.Y, + rowBox.TopLeft.X+rowBox.Width, rowBox.TopLeft.Y, + baseOptions(1))) if err != nil { return "", err } @@ -580,15 +542,10 @@ func Table(r jsrunner.JSRunner, shape d2target.Shape) (string, error) { return output, nil } -func Class(r jsrunner.JSRunner, shape d2target.Shape) (string, error) { +func Class(shape d2target.Shape) (string, error) { output := "" - js := fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %d, { - fill: "#000", - stroke: "#000", - strokeWidth: %d, - %s - });`, shape.Width, shape.Height, shape.StrokeWidth, baseRoughProps) - paths, err := computeRoughPathData(r, js) + g := newGenerator() + paths, err := computeRoughPathData(g, g.Rectangle(0, 0, float64(shape.Width), float64(shape.Height), baseOptions(float64(shape.StrokeWidth)))) if err != nil { return "", err } @@ -612,11 +569,7 @@ func Class(r jsrunner.JSRunner, shape d2target.Shape) (string, error) { rowHeight := box.Height / float64(2+len(shape.Class.Fields)+len(shape.Class.Methods)) headerBox := geo.NewBox(box.TopLeft, box.Width, 2*rowHeight) - js = fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %f, { - fill: "#000", - %s - });`, shape.Width, headerBox.Height, baseRoughProps) - paths, err = computeRoughPathData(r, js) + paths, err = computeRoughPathData(g, g.Rectangle(0, 0, float64(shape.Width), headerBox.Height, baseOptions(1))) if err != nil { return "", err } @@ -668,10 +621,10 @@ func Class(r jsrunner.JSRunner, shape d2target.Shape) (string, error) { rowBox.TopLeft.Y += rowHeight } - js = fmt.Sprintf(`node = rc.line(%f, %f, %f, %f, { -%s - });`, rowBox.TopLeft.X, rowBox.TopLeft.Y, rowBox.TopLeft.X+rowBox.Width, rowBox.TopLeft.Y, baseRoughProps) - paths, err = computeRoughPathData(r, js) + paths, err = computeRoughPathData(g, g.Line( + rowBox.TopLeft.X, rowBox.TopLeft.Y, + rowBox.TopLeft.X+rowBox.Width, rowBox.TopLeft.Y, + baseOptions(1))) if err != nil { return "", err } @@ -731,22 +684,24 @@ func classRow(shape d2target.Shape, box *geo.Box, prefix, nameText, typeText str return output } -func computeRoughPathData(r jsrunner.JSRunner, js string) ([]string, error) { - if _, err := r.RunString(js); err != nil { - return nil, err - } - roughPaths, err := extractRoughPaths(r) - if err != nil { - return nil, err - } - return extractPathData(roughPaths) +func computeRoughPathData(g *rough.Generator, drawable rough.Drawable) ([]string, error) { + return extractPathData(computeRoughPaths(g, drawable)) } -func computeRoughPaths(r jsrunner.JSRunner, js string) ([]roughPath, error) { - if _, err := r.RunString(js); err != nil { - return nil, err +func computeRoughPaths(g *rough.Generator, drawable rough.Drawable) []roughPath { + pathInfos := g.ToPaths(drawable) + paths := make([]roughPath, 0, len(pathInfos)) + for _, path := range pathInfos { + paths = append(paths, roughPath{ + Attrs: attrs{D: truncatePathData(path.D)}, + Style: style{ + Stroke: path.Stroke, + StrokeWidth: rough.NumberString(path.StrokeWidth), + Fill: path.Fill, + }, + }) } - return extractRoughPaths(r) + return paths } type attrs struct { @@ -772,30 +727,14 @@ func (rp roughPath) StyleCSS() string { return style } -func extractRoughPaths(r jsrunner.JSRunner) ([]roughPath, error) { - val, err := r.RunString("JSON.stringify(node.children, null, ' ')") - if err != nil { - return nil, err - } - - var roughPaths []roughPath - err = json.Unmarshal([]byte(val.String()), &roughPaths) - if err != nil { - return nil, err - } - +func truncatePathData(pathData string) string { // we want to have a fixed precision to the decimals in the path data - for i := range roughPaths { - // truncate all floats in path to only use up to 6 decimal places - roughPaths[i].Attrs.D = floatRE.ReplaceAllStringFunc(roughPaths[i].Attrs.D, func(floatStr string) string { - i := strings.Index(floatStr, ".") - decimalLen := len(floatStr) - i - 1 - end := i + go2.Min(decimalLen, 6) - return floatStr[:end+1] - }) - } - - return roughPaths, nil + return floatRE.ReplaceAllStringFunc(pathData, func(floatStr string) string { + i := strings.Index(floatStr, ".") + decimalLen := len(floatStr) - i - 1 + end := i + go2.Min(decimalLen, 6) + return floatStr[:end+1] + }) } func extractPathData(roughPaths []roughPath) ([]string, error) { @@ -806,133 +745,72 @@ func extractPathData(roughPaths []roughPath) ([]string, error) { return paths, nil } -func ArrowheadJS(r jsrunner.JSRunner, arrowhead d2target.Arrowhead, stroke string, strokeWidth int) (arrowJS, extraJS string) { +func arrowOptions(stroke string, strokeWidth int, seed float64) *rough.Options { + return &rough.Options{ + StrokeWidth: rough.Float64(float64(strokeWidth)), + Stroke: rough.String(stroke), + Seed: rough.Float64(seed), + } +} + +func solidArrowOptions(stroke, fill string, strokeWidth int, fillWeight, seed float64) *rough.Options { + o := arrowOptions(stroke, strokeWidth, seed) + o.Fill = rough.String(fill) + o.FillStyle = rough.String("solid") + if fillWeight >= 0 { + o.FillWeight = rough.Float64(fillWeight) + } + return o +} + +func arrowheadDrawable(g *rough.Generator, arrowhead d2target.Arrowhead, stroke string, strokeWidth int) (arrow rough.Drawable, extra *rough.Drawable, ok bool) { // Note: selected each seed that looks the good for consistent renders switch arrowhead { case d2target.ArrowArrowhead: - arrowJS = fmt.Sprintf( - `node = rc.linearPath(%s, { strokeWidth: %d, stroke: "%s", seed: 3 })`, - `[[-10, -4], [0, 0], [-10, 4]]`, - strokeWidth, - stroke, - ) + arrow = g.LinearPath([]rough.Point{{-10, -4}, {0, 0}, {-10, 4}}, arrowOptions(stroke, strokeWidth, 3)) case d2target.TriangleArrowhead: - arrowJS = fmt.Sprintf( - `node = rc.polygon(%s, { strokeWidth: %d, stroke: "%s", fill: "%s", fillStyle: "solid", seed: 2 })`, - `[[-10, -4], [0, 0], [-10, 4]]`, - strokeWidth, - stroke, - stroke, - ) + arrow = g.Polygon([]rough.Point{{-10, -4}, {0, 0}, {-10, 4}}, solidArrowOptions(stroke, stroke, strokeWidth, -1, 2)) case d2target.UnfilledTriangleArrowhead: - arrowJS = fmt.Sprintf( - `node = rc.polygon(%s, { strokeWidth: %d, stroke: "%s", fill: "%s", fillStyle: "solid", seed: 2 })`, - `[[-10, -4], [0, 0], [-10, 4]]`, - strokeWidth, - stroke, - BG_COLOR, - ) + arrow = g.Polygon([]rough.Point{{-10, -4}, {0, 0}, {-10, 4}}, solidArrowOptions(stroke, BG_COLOR, strokeWidth, -1, 2)) case d2target.DiamondArrowhead: - arrowJS = fmt.Sprintf( - `node = rc.polygon(%s, { strokeWidth: %d, stroke: "%s", fill: "%s", fillStyle: "solid", seed: 1 })`, - `[[-20, 0], [-10, 5], [0, 0], [-10, -5], [-20, 0]]`, - strokeWidth, - stroke, - BG_COLOR, - ) + arrow = g.Polygon([]rough.Point{{-20, 0}, {-10, 5}, {0, 0}, {-10, -5}, {-20, 0}}, solidArrowOptions(stroke, BG_COLOR, strokeWidth, -1, 1)) case d2target.FilledDiamondArrowhead: - arrowJS = fmt.Sprintf( - `node = rc.polygon(%s, { strokeWidth: %d, stroke: "%s", fill: "%s", fillStyle: "zigzag", fillWeight: 4, seed: 1 })`, - `[[-20, 0], [-10, 5], [0, 0], [-10, -5], [-20, 0]]`, - strokeWidth, - stroke, - stroke, - ) + o := solidArrowOptions(stroke, stroke, strokeWidth, 4, 1) + o.FillStyle = rough.String("zigzag") + arrow = g.Polygon([]rough.Point{{-20, 0}, {-10, 5}, {0, 0}, {-10, -5}, {-20, 0}}, o) case d2target.CrossArrowhead: - arrowJS = fmt.Sprintf( - `node = rc.linearPath(%s, { strokeWidth: %d, stroke: "%s", seed: 3 })`, - `[[-6, -6], [6, 6], [0, 0], [-6, 6], [0, 0], [6, -6]]`, - strokeWidth, - stroke, - ) + arrow = g.LinearPath([]rough.Point{{-6, -6}, {6, 6}, {0, 0}, {-6, 6}, {0, 0}, {6, -6}}, arrowOptions(stroke, strokeWidth, 3)) case d2target.CfManyRequired: - arrowJS = fmt.Sprintf( - // TODO why does fillStyle: "zigzag" error with path - `node = rc.path(%s, { strokeWidth: %d, stroke: "%s", fill: "%s", fillStyle: "solid", fillWeight: 4, seed: 2 })`, - `"M-15,-10 -15,10 M0,10 -15,0 M0,-10 -15,0"`, - strokeWidth, - stroke, - stroke, - ) + arrow = g.Path("M-15,-10 -15,10 M0,10 -15,0 M0,-10 -15,0", solidArrowOptions(stroke, stroke, strokeWidth, 4, 2)) case d2target.CfMany: - arrowJS = fmt.Sprintf( - `node = rc.path(%s, { strokeWidth: %d, stroke: "%s", fill: "%s", fillStyle: "solid", fillWeight: 4, seed: 8 })`, - `"M0,10 -15,0 M0,-10 -15,0"`, - strokeWidth, - stroke, - stroke, - ) - extraJS = fmt.Sprintf( - `node = rc.circle(-20, 0, 8, { strokeWidth: %d, stroke: "%s", fill: "%s", fillStyle: "solid", fillWeight: 1, seed: 4 })`, - strokeWidth, - stroke, - BG_COLOR, - ) + arrow = g.Path("M0,10 -15,0 M0,-10 -15,0", solidArrowOptions(stroke, stroke, strokeWidth, 4, 8)) + d := g.Circle(-20, 0, 8, solidArrowOptions(stroke, BG_COLOR, strokeWidth, 1, 4)) + extra = &d case d2target.CfOneRequired: - arrowJS = fmt.Sprintf( - `node = rc.path(%s, { strokeWidth: %d, stroke: "%s", fill: "%s", fillStyle: "solid", fillWeight: 4, seed: 2 })`, - `"M-15,-10 -15,10 M-10,-10 -10,10"`, - strokeWidth, - stroke, - stroke, - ) + arrow = g.Path("M-15,-10 -15,10 M-10,-10 -10,10", solidArrowOptions(stroke, stroke, strokeWidth, 4, 2)) case d2target.CfOne: - arrowJS = fmt.Sprintf( - `node = rc.path(%s, { strokeWidth: %d, stroke: "%s", fill: "%s", fillStyle: "solid", fillWeight: 4, seed: 3 })`, - `"M-10,-10 -10,10"`, - strokeWidth, - stroke, - stroke, - ) - extraJS = fmt.Sprintf( - `node = rc.circle(-20, 0, 8, { strokeWidth: %d, stroke: "%s", fill: "%s", fillStyle: "solid", fillWeight: 1, seed: 5 })`, - strokeWidth, - stroke, - BG_COLOR, - ) + arrow = g.Path("M-10,-10 -10,10", solidArrowOptions(stroke, stroke, strokeWidth, 4, 3)) + d := g.Circle(-20, 0, 8, solidArrowOptions(stroke, BG_COLOR, strokeWidth, 1, 5)) + extra = &d case d2target.CircleArrowhead: - arrowJS = fmt.Sprintf( - `node = rc.circle(-2, -1, 8, { strokeWidth: %d, stroke: "%s", fill: "%s", fillStyle: "solid", fillWeight: 1, seed: 5 })`, - strokeWidth, - stroke, - BG_COLOR, - ) + arrow = g.Circle(-2, -1, 8, solidArrowOptions(stroke, BG_COLOR, strokeWidth, 1, 5)) case d2target.BoxArrowhead: - arrowJS = fmt.Sprintf( - `node = rc.polygon(%s, { strokeWidth: %d, stroke: "%s", fill: "%s", fillStyle: "solid", seed: 1})`, - `[[0, -10], [0, 10], [-20, 10], [-20, -10]]`, - strokeWidth, - stroke, - BG_COLOR, - ) + arrow = g.Polygon([]rough.Point{{0, -10}, {0, 10}, {-20, 10}, {-20, -10}}, solidArrowOptions(stroke, BG_COLOR, strokeWidth, -1, 1)) case d2target.FilledBoxArrowhead: - arrowJS = fmt.Sprintf( - `node = rc.polygon(%s, { strokeWidth: %d, stroke: "%s", fill: "%s", fillStyle: "solid", seed: 1})`, - `[[0, -10], [0, 10], [-20, 10], [-20, -10]]`, - strokeWidth, - stroke, - stroke, - ) + arrow = g.Polygon([]rough.Point{{0, -10}, {0, 10}, {-20, 10}, {-20, -10}}, solidArrowOptions(stroke, stroke, strokeWidth, -1, 1)) + default: + return rough.Drawable{}, nil, false } - return + return arrow, extra, true } -func Arrowheads(r jsrunner.JSRunner, connection d2target.Connection, srcAdj, dstAdj *geo.Point) (string, error) { +func Arrowheads(connection d2target.Connection, srcAdj, dstAdj *geo.Point) (string, error) { arrowPaths := []string{} if connection.SrcArrow != d2target.NoArrowhead { - arrowJS, extraJS := ArrowheadJS(r, connection.SrcArrow, connection.Stroke, connection.StrokeWidth) - if arrowJS == "" { + g := newGenerator() + arrow, extra, ok := arrowheadDrawable(g, connection.SrcArrow, connection.Stroke, connection.StrokeWidth) + if !ok { return "", nil } @@ -944,16 +822,9 @@ func Arrowheads(r jsrunner.JSRunner, connection d2target.Connection, srcAdj, dst startingSegment.Start.X+srcAdj.X, startingSegment.Start.Y+srcAdj.Y, angle, ) - roughPaths, err := computeRoughPaths(r, arrowJS) - if err != nil { - return "", err - } - if extraJS != "" { - extraPaths, err := computeRoughPaths(r, extraJS) - if err != nil { - return "", err - } - roughPaths = append(roughPaths, extraPaths...) + roughPaths := computeRoughPaths(g, arrow) + if extra != nil { + roughPaths = append(roughPaths, computeRoughPaths(g, *extra)...) } pathEl := d2themes.NewThemableElement("path", nil) @@ -969,8 +840,9 @@ func Arrowheads(r jsrunner.JSRunner, connection d2target.Connection, srcAdj, dst } if connection.DstArrow != d2target.NoArrowhead { - arrowJS, extraJS := ArrowheadJS(r, connection.DstArrow, connection.Stroke, connection.StrokeWidth) - if arrowJS == "" { + g := newGenerator() + arrow, extra, ok := arrowheadDrawable(g, connection.DstArrow, connection.Stroke, connection.StrokeWidth) + if !ok { return "", nil } @@ -983,16 +855,9 @@ func Arrowheads(r jsrunner.JSRunner, connection d2target.Connection, srcAdj, dst endingSegment.End.X+dstAdj.X, endingSegment.End.Y+dstAdj.Y, angle, ) - roughPaths, err := computeRoughPaths(r, arrowJS) - if err != nil { - return "", err - } - if extraJS != "" { - extraPaths, err := computeRoughPaths(r, extraJS) - if err != nil { - return "", err - } - roughPaths = append(roughPaths, extraPaths...) + roughPaths := computeRoughPaths(g, arrow) + if extra != nil { + roughPaths = append(roughPaths, computeRoughPaths(g, *extra)...) } pathEl := d2themes.NewThemableElement("path", nil) diff --git a/d2renderers/d2svg/d2svg.go b/d2renderers/d2svg/d2svg.go index 76582282f9..7c6caed0bf 100644 --- a/d2renderers/d2svg/d2svg.go +++ b/d2renderers/d2svg/d2svg.go @@ -31,7 +31,6 @@ import ( "github.com/d2lang/d2/d2themes/d2themescatalog" "github.com/d2lang/d2/lib/color" "github.com/d2lang/d2/lib/geo" - "github.com/d2lang/d2/lib/jsrunner" "github.com/d2lang/d2/lib/label" "github.com/d2lang/d2/lib/shape" "github.com/d2lang/d2/lib/svg" @@ -377,7 +376,7 @@ func renderLegendShapeIcon(s d2target.Shape, x, y int, diagramHash string, theme finalBuf := &bytes.Buffer{} fmt.Fprintf(finalBuf, ``, x, y, 1.0/sizeFactor) - _, err := drawShape(buf, appendixBuf, diagramHash, iconShape, nil, theme) + _, err := drawShape(buf, appendixBuf, diagramHash, iconShape, false, theme) if err != nil { return "", err } @@ -426,7 +425,7 @@ func renderLegendConnectionIcon(c d2target.Connection, x, y int, theme *d2themes fmt.Fprintf(finalBuf, ``, x, y, 1.0/sizeFactor) - _, err := drawConnection(buf, legendHash, legendConn, markers, idToShape, nil, theme) + _, err := drawConnection(buf, legendHash, legendConn, markers, idToShape, false, theme) if err != nil { return "", err } @@ -1003,7 +1002,7 @@ func makeBorderLabelMask(labelPosition label.Position, labelTL *geo.Point, label ) } -func drawConnection(writer io.Writer, diagramHash string, connection d2target.Connection, markers map[string]struct{}, idToShape map[string]d2target.Shape, jsRunner jsrunner.JSRunner, inlineTheme *d2themes.Theme) (labelMask string, _ error) { +func drawConnection(writer io.Writer, diagramHash string, connection d2target.Connection, markers map[string]struct{}, idToShape map[string]d2target.Shape, sketch bool, inlineTheme *d2themes.Theme) (labelMask string, _ error) { opacityStyle := "" if connection.Opacity != 1.0 { opacityStyle = fmt.Sprintf(" style='opacity:%f'", connection.Opacity) @@ -1099,15 +1098,15 @@ func drawConnection(writer io.Writer, diagramHash string, connection d2target.Co path := pathData(connection, srcAdj, dstAdj) mask := fmt.Sprintf(`mask="url(#%s)"`, diagramHash) - if jsRunner != nil { - out, err := d2sketch.Connection(jsRunner, connection, path, mask) + if sketch { + out, err := d2sketch.Connection(connection, path, mask) if err != nil { return "", err } fmt.Fprint(writer, out) // render sketch arrowheads separately - arrowPaths, err := d2sketch.Arrowheads(jsRunner, connection, srcAdj, dstAdj) + arrowPaths, err := d2sketch.Arrowheads(connection, srcAdj, dstAdj) if err != nil { return "", err } @@ -1599,7 +1598,7 @@ func render3DHexagon(diagramHash string, targetShape d2target.Shape, inlineTheme return borderMask + mainShapeRendered + renderedSides + renderedBorder } -func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape d2target.Shape, jsRunner jsrunner.JSRunner, inlineTheme *d2themes.Theme) (labelMask string, err error) { +func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape d2target.Shape, sketch bool, inlineTheme *d2themes.Theme) (labelMask string, err error) { closingTag := "" if targetShape.Link != "" { @@ -1669,8 +1668,8 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape switch targetShape.Type { case d2target.ShapeClass: - if jsRunner != nil { - out, err := d2sketch.Class(jsRunner, targetShape) + if sketch { + out, err := d2sketch.Class(targetShape) if err != nil { return "", err } @@ -1686,8 +1685,8 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape fmt.Fprint(writer, closingTag) return labelMask, nil case d2target.ShapeSQLTable: - if jsRunner != nil { - out, err := d2sketch.Table(jsRunner, targetShape) + if sketch { + out, err := d2sketch.Table(targetShape) if err != nil { return "", err } @@ -1707,8 +1706,8 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape if targetShape.Multiple { fmt.Fprint(writer, renderDoubleOval(multipleTL, width, height, fill, "", stroke, style, inlineTheme)) } - if jsRunner != nil { - out, err := d2sketch.DoubleOval(jsRunner, targetShape, diagramHash) + if sketch { + out, err := d2sketch.DoubleOval(targetShape, diagramHash) if err != nil { return "", err } @@ -1720,8 +1719,8 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape if targetShape.Multiple { fmt.Fprint(writer, renderOval(multipleTL, width, height, fill, "", stroke, style, inlineTheme)) } - if jsRunner != nil { - out, err := d2sketch.Oval(jsRunner, targetShape, diagramHash) + if sketch { + out, err := d2sketch.Oval(targetShape, diagramHash) if err != nil { return "", err } @@ -1768,8 +1767,8 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape el.Rx = borderRadius fmt.Fprint(writer, el.Render()) } - if jsRunner != nil { - out, err := d2sketch.Rect(jsRunner, targetShape, diagramHash) + if sketch { + out, err := d2sketch.Rect(targetShape, diagramHash) if err != nil { return "", err } @@ -1817,8 +1816,8 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape el.Rx = borderRadius fmt.Fprint(writer, el.Render()) } - if jsRunner != nil { - out, err := d2sketch.DoubleRect(jsRunner, targetShape, diagramHash) + if sketch { + out, err := d2sketch.DoubleRect(targetShape, diagramHash) if err != nil { return "", err } @@ -1865,8 +1864,8 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape } } - if jsRunner != nil { - out, err := d2sketch.Paths(jsRunner, targetShape, diagramHash, s.GetSVGPathData()) + if sketch { + out, err := d2sketch.Paths(targetShape, diagramHash, s.GetSVGPathData()) if err != nil { return "", err } @@ -1897,8 +1896,8 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape } } - if jsRunner != nil { - out, err := d2sketch.Paths(jsRunner, targetShape, diagramHash, s.GetSVGPathData()) + if sketch { + out, err := d2sketch.Paths(targetShape, diagramHash, s.GetSVGPathData()) if err != nil { return "", err } @@ -1988,7 +1987,7 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape ) if labelPosition.IsBorder() { - if jsRunner != nil { + if sketch { labelMask = makeBorderLabelMask(labelPosition, labelTL, targetShape.LabelWidth, targetShape.LabelHeight, box, targetShape.StrokeWidth, 1.0, tl) } else { labelMask = makeBorderLabelMask(labelPosition, labelTL, targetShape.LabelWidth, targetShape.LabelHeight, box, targetShape.StrokeWidth, 1.0, nil) @@ -2731,7 +2730,7 @@ func appendOnTrigger(buf *bytes.Buffer, source string, triggers []string, newCon var DEFAULT_DARK_THEME *int64 = nil // no theme selected func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) { - var jsRunner jsrunner.JSRunner + sketch := false pad := DEFAULT_PADDING themeID := d2themescatalog.NeutralDefault.ID darkThemeID := DEFAULT_DARK_THEME @@ -2741,11 +2740,7 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) { pad = int(*opts.Pad) } if opts.Sketch != nil && *opts.Sketch { - jsRunner = jsrunner.NewJSRunner() - err := d2sketch.LoadJS(jsRunner) - if err != nil { - return nil, err - } + sketch = true } if opts.ThemeID != nil { themeID = *opts.ThemeID @@ -2829,7 +2824,7 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) { } for _, obj := range allObjects { if c, is := obj.(d2target.Connection); is { - labelMask, err := drawConnection(buf, isolatedDiagramHash, c, markers, idToShape, jsRunner, inlineTheme) + labelMask, err := drawConnection(buf, isolatedDiagramHash, c, markers, idToShape, sketch, inlineTheme) if err != nil { return nil, err } @@ -2837,7 +2832,7 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) { labelMasks = append(labelMasks, labelMask) } } else if s, is := obj.(d2target.Shape); is { - labelMask, err := drawShape(buf, appendixItemBuf, diagramHash, s, jsRunner, inlineTheme) + labelMask, err := drawShape(buf, appendixItemBuf, diagramHash, s, sketch, inlineTheme) if err != nil { return nil, err } else if labelMask != "" { @@ -2976,7 +2971,7 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) { fmt.Fprintf(upperBuf, ``, css) } - if jsRunner != nil { + if sketch { d2sketch.DefineFillPatterns(upperBuf, diagramHash) } } diff --git a/go.mod b/go.mod index 65192cc4fb..5e47d2dd14 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/andybalholm/brotli v1.2.0 github.com/coder/websocket v1.8.12 github.com/d2lang/dagro v0.1.0 + github.com/d2lang/rough-go v0.0.0-20260809023058-67cb01f42b09 github.com/d2lang/util-go v0.2.0 github.com/dop251/goja v0.0.0-20240927123429-241b342198c2 github.com/dsoprea/go-exif/v3 v3.0.1 diff --git a/go.sum b/go.sum index 0985344506..8183c1b875 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,8 @@ github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/d2lang/dagro v0.1.0 h1:517f9fzr6p1eN5TdUXjUGy9DKZnHhPIScWOsmN0ot4w= github.com/d2lang/dagro v0.1.0/go.mod h1:GYboiXSkXU1tuj7jYRXD8+lvHJBBZk3SAbfVT6qJsLU= +github.com/d2lang/rough-go v0.0.0-20260809023058-67cb01f42b09 h1:qSoM5xHxKW+DQAd/M+sHbwjHR5OOGkxZ8X0ft5G9360= +github.com/d2lang/rough-go v0.0.0-20260809023058-67cb01f42b09/go.mod h1:jBrT0USd0UYX1c61RU2T8Uj8OLMp4NKdWpe7SEpZJ/4= github.com/d2lang/util-go v0.2.0 h1:X9+F4j2S0453cG3EpA5bm9KBEV4ZAanxH+5ELA2yPDk= github.com/d2lang/util-go v0.2.0/go.mod h1:9t8cKlmsQw6UlChU4HQE9tZr8efjKIGDHkDdDYUeJy0= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= From 4142d8b315d6f3260660b6224a0534a5d93ade3c Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Sat, 8 Aug 2026 19:56:52 -0700 Subject: [PATCH 2/2] sketch: update rough-go for cross-architecture parity --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5e47d2dd14..04845a053e 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/andybalholm/brotli v1.2.0 github.com/coder/websocket v1.8.12 github.com/d2lang/dagro v0.1.0 - github.com/d2lang/rough-go v0.0.0-20260809023058-67cb01f42b09 + github.com/d2lang/rough-go v0.0.0-20260809025506-62dc0c1adf11 github.com/d2lang/util-go v0.2.0 github.com/dop251/goja v0.0.0-20240927123429-241b342198c2 github.com/dsoprea/go-exif/v3 v3.0.1 diff --git a/go.sum b/go.sum index 8183c1b875..a3681e1d65 100644 --- a/go.sum +++ b/go.sum @@ -20,8 +20,8 @@ github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/d2lang/dagro v0.1.0 h1:517f9fzr6p1eN5TdUXjUGy9DKZnHhPIScWOsmN0ot4w= github.com/d2lang/dagro v0.1.0/go.mod h1:GYboiXSkXU1tuj7jYRXD8+lvHJBBZk3SAbfVT6qJsLU= -github.com/d2lang/rough-go v0.0.0-20260809023058-67cb01f42b09 h1:qSoM5xHxKW+DQAd/M+sHbwjHR5OOGkxZ8X0ft5G9360= -github.com/d2lang/rough-go v0.0.0-20260809023058-67cb01f42b09/go.mod h1:jBrT0USd0UYX1c61RU2T8Uj8OLMp4NKdWpe7SEpZJ/4= +github.com/d2lang/rough-go v0.0.0-20260809025506-62dc0c1adf11 h1:v6b7Mof2QFPqUDgx6EBM0s4y+t9T1VBbi3VVIKZHVsI= +github.com/d2lang/rough-go v0.0.0-20260809025506-62dc0c1adf11/go.mod h1:jBrT0USd0UYX1c61RU2T8Uj8OLMp4NKdWpe7SEpZJ/4= github.com/d2lang/util-go v0.2.0 h1:X9+F4j2S0453cG3EpA5bm9KBEV4ZAanxH+5ELA2yPDk= github.com/d2lang/util-go v0.2.0/go.mod h1:9t8cKlmsQw6UlChU4HQE9tZr8efjKIGDHkDdDYUeJy0= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=