diff --git a/README.md b/README.md
index 19add6f..06f838e 100644
--- a/README.md
+++ b/README.md
@@ -4,9 +4,59 @@ framework for making qr codes
Blatantly inspired by [QRBTF](https://qrbtf.com) and [Anthony Fu's QR Toolkit](https://qrcode.antfu.me).
+## Examples
+
+I'm working on more examples.
+
+
+
+
+ Extending with noise
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Using external libs like Rough.js
+
+
+
+
+
+
+
+
+
+
+
+ Styles from QRBTF
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
## Features
- Customize data:
+
- encoding mode, version, error tolerance, mask pattern
- powered by [`fuqr`](https://github.com/zhengkyl/fuqr), my own Rust library imported as WASM. (i use windows, btw)
@@ -14,7 +64,7 @@ Blatantly inspired by [QRBTF](https://qrbtf.com) and [Anthony Fu's QR Toolkit](h
- Choose any preset, customize or even create a new one from scratch via code editor.
- Define arbitrary ui parameters in code
- Supports SVG and PNG (canvas)
- - All code runs *directly* in browser. There are no safeguards except that which browser vendors have bestowed upon us.
+ - All code runs _directly_ in browser. There are no safeguards except that which browser vendors have bestowed upon us.
## Use existing presets
@@ -23,49 +73,3 @@ Blatantly inspired by [QRBTF](https://qrbtf.com) and [Anthony Fu's QR Toolkit](h
## Customizable parameters defined in code

-
-## Examples
-
-I'm working on more examples.
-
-
-
-
- Extending with noise
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Styles from QRBTF
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/examples/drawing1.png b/examples/drawing1.png
new file mode 100644
index 0000000..a11a0b5
Binary files /dev/null and b/examples/drawing1.png differ
diff --git a/examples/drawing2.png b/examples/drawing2.png
new file mode 100644
index 0000000..9329af2
Binary files /dev/null and b/examples/drawing2.png differ
diff --git a/examples/drawing3.png b/examples/drawing3.png
new file mode 100644
index 0000000..06f493b
Binary files /dev/null and b/examples/drawing3.png differ
diff --git a/presets/Drawing.ts b/presets/Drawing.ts
new file mode 100644
index 0000000..3ff83a5
--- /dev/null
+++ b/presets/Drawing.ts
@@ -0,0 +1,369 @@
+import type { Params, RawParamsSchema } from "~/lib/params";
+import type { OutputQr } from "~/lib/QrContext";
+// @ts-expect-error not bundled
+import rough from "https://esm.sh/roughjs";
+
+export const paramsSchema = {
+ Margin: {
+ type: "number",
+ min: 0,
+ max: 10,
+ default: 2,
+ },
+ "Fill style": {
+ type: "Select",
+ options: [
+ "Hachure",
+ "Solid",
+ "Zigzag",
+ "Cross-hatch",
+ "Dots",
+ "Dashed",
+ "Zigzag-line",
+ ],
+ default: "Zigzag",
+ },
+ Fill: {
+ type: "Color",
+ default: "#ffffff",
+ },
+ "Fill weight": {
+ type: "number",
+ min: 0,
+ max: 10,
+ default: 2,
+ },
+ "Fill gap": {
+ type: "number",
+ min: 1,
+ max: 10,
+ default: 4,
+ },
+ Stroke: {
+ type: "Color",
+ default: "#ffffff",
+ },
+ "Stroke width": {
+ type: "number",
+ min: 0,
+ max: 10,
+ default: 1,
+ },
+ Invert: {
+ type: "boolean",
+ default: true,
+ },
+ Roughness: {
+ type: "number",
+ min: 0,
+ max: 10,
+ default: 1,
+ },
+ Bowing: {
+ type: "number",
+ min: 0,
+ max: 10,
+ default: 1,
+ },
+ Background: {
+ type: "Color",
+ default: "#000000",
+ },
+ Seed: {
+ type: "number",
+ min: 1,
+ max: 100,
+ default: 1,
+ },
+} satisfies RawParamsSchema;
+
+const Module = {
+ DataOFF: 0,
+ DataON: 1,
+ FinderOFF: 2,
+ FinderON: 3,
+ AlignmentOFF: 4,
+ AlignmentON: 5,
+ TimingOFF: 6,
+ TimingON: 7,
+ FormatOFF: 8,
+ FormatON: 9,
+ VersionOFF: 10,
+ VersionON: 11,
+ SeparatorOFF: 12,
+};
+
+function splitmix32(a: number) {
+ return function () {
+ a |= 0;
+ a = (a + 0x9e3779b9) | 0;
+ let t = a ^ (a >>> 16);
+ t = Math.imul(t, 0x21f0aaad);
+ t = t ^ (t >>> 15);
+ t = Math.imul(t, 0x735a2d97);
+ return ((t = t ^ (t >>> 15)) >>> 0) / 4294967296;
+ };
+}
+
+const domMock = {
+ ownerDocument: {
+ createElementNS: (_ns: string, tagName: string) => {
+ const children: any[] = [];
+ const attributes: any = {};
+ return {
+ tagName,
+ attributes,
+ setAttribute: (key: string, value: string) => (attributes[key] = value),
+ appendChild: (node: any) => children.push(node),
+ children,
+ };
+ },
+ },
+};
+
+export function renderSVG(qr: OutputQr, params: Params) {
+ const roughSVG = rough.svg(domMock, {
+ options: {
+ roughness: params["Roughness"],
+ bowing: params["Bowing"],
+ fillStyle: params["Fill style"].toLowerCase(),
+ fillWeight: params["Fill weight"],
+ fill: params["Fill weight"] === 0 ? "none" : params["Fill"],
+ strokeWidth: params["Stroke width"],
+ stroke: params["Stroke width"] === 0 ? "none" : params["Stroke"],
+ hachureGap: params["Fill gap"],
+ seed: params["Seed"],
+ fixedDecimalPlaceDigits: 2,
+ },
+ });
+
+ let matrix = qr.matrix as any;
+ let matrixWidth = qr.version * 4 + 17;
+
+ if (params["Invert"]) {
+ matrixWidth += 2;
+ matrix = [];
+ for (let y = 0; y < matrixWidth; y++) {
+ for (let x = 0; x < matrixWidth; x++) {
+ if (
+ x === 0 ||
+ y === 0 ||
+ x === matrixWidth - 1 ||
+ y === matrixWidth - 1
+ ) {
+ matrix.push(Module.DataOFF);
+ } else {
+ matrix.push(qr.matrix[(y - 1) * (matrixWidth - 2) + x - 1]);
+ }
+ }
+ }
+ }
+
+ const visited = new Uint16Array(matrixWidth * matrixWidth);
+ const unit = 10;
+ const margin = params["Margin"] * unit;
+ const size = matrixWidth * unit + 2 * margin;
+
+ let svg = ``;
+
+ svg += ` `;
+
+ const xMax = matrixWidth - 1;
+ const yMax = matrixWidth - 1;
+
+ let baseX: number;
+ let baseY: number;
+
+ const on = params["Invert"]
+ ? (x: number, y: number) => (matrix[y * matrixWidth + x] & 1) === 0
+ : (x: number, y: number) => (matrix[y * matrixWidth + x] & 1) === 1;
+
+ function goRight(x: number, y: number, shape: number, cw: boolean) {
+ let sx = x;
+
+ let vert = false;
+ visited[y * matrixWidth + x] = shape;
+ while (x < xMax) {
+ const right = on(x + 1, y);
+ const vertRight = y > 0 && on(x + 1, y - 1);
+ if (!right || vertRight) {
+ vert = right && vertRight;
+ break;
+ }
+ x++;
+ visited[y * matrixWidth + x] = shape;
+ }
+
+ paths[shape] += `h${(x - sx + 1) * unit}`;
+ if (vert) {
+ goUp(x + 1, y - 1, shape, cw);
+ } else {
+ goDown(x, y, shape, cw);
+ }
+ }
+
+ function goLeft(x: number, y: number, shape: number, cw: boolean) {
+ let sx = x;
+
+ let vert = false;
+ visited[y * matrixWidth + x] = shape;
+ while (x > 0) {
+ const left = on(x - 1, y);
+ const vertLeft = y < yMax && on(x - 1, y + 1);
+ if (!left || vertLeft) {
+ vert = left && vertLeft;
+ break;
+ }
+ x--;
+ visited[y * matrixWidth + x] = shape;
+ }
+ if (!cw && x === baseX && y === baseY) {
+ paths[shape] += "z";
+ return;
+ }
+
+ paths[shape] += `h${(x - sx - 1) * unit}`;
+ if (vert) {
+ goDown(x - 1, y + 1, shape, cw);
+ } else {
+ goUp(x, y, shape, cw);
+ }
+ }
+
+ function goUp(x: number, y: number, shape: number, cw: boolean) {
+ let sy = y;
+ let horz = false;
+ visited[y * matrixWidth + x] = shape;
+ while (y > 0) {
+ const up = on(x, y - 1);
+ const horzUp = x > 0 && on(x - 1, y - 1);
+ if (!up || horzUp) {
+ horz = up && horzUp;
+ break;
+ }
+ y--;
+ visited[y * matrixWidth + x] = shape;
+ }
+ if (cw && x === baseX && y === baseY) {
+ paths[shape] += "z";
+ return;
+ }
+
+ paths[shape] += `v${(y - sy - 1) * unit}`;
+ if (horz) {
+ goLeft(x - 1, y - 1, shape, cw);
+ } else {
+ goRight(x, y, shape, cw);
+ }
+ }
+
+ function goDown(x: number, y: number, shape: number, cw: boolean) {
+ let sy = y;
+ let horz = false;
+ visited[y * matrixWidth + x] = shape;
+ while (y < yMax) {
+ const down = on(x, y + 1);
+ const horzDown = x < xMax && on(x + 1, y + 1);
+ if (!down || horzDown) {
+ horz = down && horzDown;
+ break;
+ }
+ y++;
+ visited[y * matrixWidth + x] = shape;
+ }
+
+ paths[shape] += `v${(y - sy + 1) * unit}`;
+ if (horz) {
+ goRight(x + 1, y + 1, shape, cw);
+ } else {
+ goLeft(x, y, shape, cw);
+ }
+ }
+
+ const stack: [number, number][] = [];
+ for (let x = 0; x < matrixWidth; x++) {
+ if (!on(x, 0)) stack.push([x, 0]);
+ }
+ for (let y = 1; y < yMax; y++) {
+ if (!on(0, y)) stack.push([0, y]);
+ if (!on(xMax, y)) stack.push([xMax, y]);
+ }
+ for (let x = 0; x < matrixWidth; x++) {
+ if (!on(x, yMax)) stack.push([x, yMax]);
+ }
+
+ // recursion dfs limited to ~4000
+ // visit all whitespace connected to edges
+ function dfsOff() {
+ while (stack.length > 0) {
+ const [x, y] = stack.pop()!;
+ if (visited[y * matrixWidth + x]) continue;
+ visited[y * matrixWidth + x] = 1;
+ for (let dy = -1; dy <= 1; dy++) {
+ for (let dx = -1; dx <= 1; dx++) {
+ if (dy === 0 && dx === 0) continue;
+ let nx = x + dx;
+ let ny = y + dy;
+ if (nx < 0 || nx > xMax || ny < 0 || ny > yMax) continue;
+ if (on(nx, ny)) continue;
+ stack.push([nx, ny]);
+ }
+ }
+ }
+ }
+ dfsOff();
+
+ const paths = [""];
+ for (let y = 0; y < matrixWidth; y++) {
+ for (let x = 0; x < matrixWidth; x++) {
+ if (visited[y * matrixWidth + x]) continue;
+
+ if (!on(x, y)) {
+ const shape = visited[y * matrixWidth + x - 1];
+ paths[shape] += `M${x * unit},${y * unit}`;
+
+ baseY = y - 1;
+ baseX = x;
+ goDown(x - 1, y, shape, false);
+ stack.push([x, y]);
+ dfsOff();
+ continue;
+ }
+
+ if (y > 0 && on(x, y - 1) && visited[(y - 1) * matrixWidth + x]) {
+ visited[y * matrixWidth + x] = visited[(y - 1) * matrixWidth + x];
+ continue;
+ }
+ if (x > 0 && on(x - 1, y) && visited[y * matrixWidth + x - 1]) {
+ visited[y * matrixWidth + x] = visited[y * matrixWidth + x - 1];
+ continue;
+ }
+
+ paths.push(`M${x * unit},${y * unit}`);
+
+ baseY = y;
+ baseX = x;
+
+ goRight(x, y, paths.length - 1, true);
+ }
+ }
+
+ function domToString(node: any) {
+ const attrs = Object.entries(node.attributes)
+ .map(([key, value]) => `${key}="${value}"`)
+ .join(" ");
+ svg += `<${node.tagName} ${attrs}>`;
+ node.children.forEach(domToString);
+ svg += `${node.tagName}>`;
+ }
+
+ paths.forEach((path, i) => {
+ if (i === 0) return;
+ const g = roughSVG.path(path);
+ console.log(g);
+ domToString(g);
+ });
+
+ svg += ` `;
+ return svg;
+}
diff --git a/public/favicon.svg b/public/favicon.svg
index 0ecd2f7..d19521d 100644
--- a/public/favicon.svg
+++ b/public/favicon.svg
@@ -1,6 +1 @@
-
-
-
-
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/components/editor/QrEditor.tsx b/src/components/editor/QrEditor.tsx
index 3e6a09e..86b5a5b 100644
--- a/src/components/editor/QrEditor.tsx
+++ b/src/components/editor/QrEditor.tsx
@@ -70,6 +70,7 @@ export function Editor(props: Props) {
Circle: "",
Camo: "",
Neon: "",
+ Drawing: "",
Blocks: "",
Bubbles: "",
Alien: "",
diff --git a/src/components/preview/QrPreview.tsx b/src/components/preview/QrPreview.tsx
index 291ea84..f132b16 100644
--- a/src/components/preview/QrPreview.tsx
+++ b/src/components/preview/QrPreview.tsx
@@ -194,7 +194,7 @@ function RenderedQrCode() {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d")!;
// TODO allow adjust resolution/aspect ratio
- const size = (outputQr().version * 4 + 17) * 10;
+ const size = 300 //(outputQr().version * 4 + 17) * 10;
canvas.width = size;
canvas.height = size;
diff --git a/src/lib/presets.ts b/src/lib/presets.ts
index 34a5c97..8c7d2cf 100644
--- a/src/lib/presets.ts
+++ b/src/lib/presets.ts
@@ -3,6 +3,7 @@ import { Blocks } from "./presets/Blocks";
import { Bubbles } from "./presets/Bubbles";
import { Camo } from "./presets/Camo";
import { Circle } from "./presets/Circle";
+import { Drawing } from "./presets/Drawing";
import { Halftone } from "./presets/Halftone";
import { Minimal } from "./presets/Minimal";
import { Neon } from "./presets/Neon";
@@ -13,6 +14,7 @@ export const PRESET_CODE = {
Circle,
Camo,
Neon,
+ Drawing,
Halftone,
Minimal,
Blocks,
diff --git a/src/lib/presets/Drawing.ts b/src/lib/presets/Drawing.ts
new file mode 100644
index 0000000..8436807
--- /dev/null
+++ b/src/lib/presets/Drawing.ts
@@ -0,0 +1,367 @@
+export const Drawing = `import rough from "https://esm.sh/roughjs";
+
+export const paramsSchema = {
+ Margin: {
+ type: "number",
+ min: 0,
+ max: 10,
+ default: 2,
+ },
+ "Fill style": {
+ type: "Select",
+ options: [
+ "Hachure",
+ "Solid",
+ "Zigzag",
+ "Cross-hatch",
+ "Dots",
+ "Dashed",
+ "Zigzag-line",
+ ],
+ default: "Zigzag",
+ },
+ Fill: {
+ type: "Color",
+ default: "#ffffff",
+ },
+ "Fill weight": {
+ type: "number",
+ min: 0,
+ max: 10,
+ default: 2,
+ },
+ "Fill gap": {
+ type: "number",
+ min: 1,
+ max: 10,
+ default: 4,
+ },
+ Stroke: {
+ type: "Color",
+ default: "#ffffff",
+ },
+ "Stroke width": {
+ type: "number",
+ min: 0,
+ max: 10,
+ default: 1,
+ },
+ Invert: {
+ type: "boolean",
+ default: true,
+ },
+ Roughness: {
+ type: "number",
+ min: 0,
+ max: 10,
+ default: 1,
+ },
+ Bowing: {
+ type: "number",
+ min: 0,
+ max: 10,
+ default: 1,
+ },
+ Background: {
+ type: "Color",
+ default: "#000000",
+ },
+ Seed: {
+ type: "number",
+ min: 1,
+ max: 100,
+ default: 1,
+ },
+};
+
+const Module = {
+ DataOFF: 0,
+ DataON: 1,
+ FinderOFF: 2,
+ FinderON: 3,
+ AlignmentOFF: 4,
+ AlignmentON: 5,
+ TimingOFF: 6,
+ TimingON: 7,
+ FormatOFF: 8,
+ FormatON: 9,
+ VersionOFF: 10,
+ VersionON: 11,
+ SeparatorOFF: 12,
+};
+
+function splitmix32(a) {
+ return function () {
+ a |= 0;
+ a = (a + 0x9e3779b9) | 0;
+ let t = a ^ (a >>> 16);
+ t = Math.imul(t, 0x21f0aaad);
+ t = t ^ (t >>> 15);
+ t = Math.imul(t, 0x735a2d97);
+ return ((t = t ^ (t >>> 15)) >>> 0) / 4294967296;
+ };
+}
+
+const domMock = {
+ ownerDocument: {
+ createElementNS: (_ns, tagName) => {
+ const children = [];
+ const attributes = {};
+ return {
+ tagName,
+ attributes,
+ setAttribute: (key, value) => (attributes[key] = value),
+ appendChild: (node) => children.push(node),
+ children,
+ };
+ },
+ },
+};
+
+export function renderSVG(qr, params) {
+ const roughSVG = rough.svg(domMock, {
+ options: {
+ roughness: params["Roughness"],
+ bowing: params["Bowing"],
+ fillStyle: params["Fill style"].toLowerCase(),
+ fillWeight: params["Fill weight"],
+ fill: params["Fill weight"] === 0 ? "none" : params["Fill"],
+ strokeWidth: params["Stroke width"],
+ stroke: params["Stroke width"] === 0 ? "none" : params["Stroke"],
+ hachureGap: params["Fill gap"],
+ seed: params["Seed"],
+ fixedDecimalPlaceDigits: 2,
+ },
+ });
+
+ let matrix = qr.matrix;
+ let matrixWidth = qr.version * 4 + 17;
+
+ if (params["Invert"]) {
+ matrixWidth += 2;
+ matrix = [];
+ for (let y = 0; y < matrixWidth; y++) {
+ for (let x = 0; x < matrixWidth; x++) {
+ if (
+ x === 0 ||
+ y === 0 ||
+ x === matrixWidth - 1 ||
+ y === matrixWidth - 1
+ ) {
+ matrix.push(Module.DataOFF);
+ } else {
+ matrix.push(qr.matrix[(y - 1) * (matrixWidth - 2) + x - 1]);
+ }
+ }
+ }
+ }
+
+ const visited = new Uint16Array(matrixWidth * matrixWidth);
+ const unit = 10;
+ const margin = params["Margin"] * unit;
+ const size = matrixWidth * unit + 2 * margin;
+
+ let svg = \`\`;
+
+ svg += \` \`;
+
+ const xMax = matrixWidth - 1;
+ const yMax = matrixWidth - 1;
+
+ let baseX;
+ let baseY;
+
+ const on = params["Invert"]
+ ? (x, y) => (matrix[y * matrixWidth + x] & 1) === 0
+ : (x, y) => (matrix[y * matrixWidth + x] & 1) === 1;
+
+ function goRight(x, y, shape, cw) {
+ let sx = x;
+
+ let vert = false;
+ visited[y * matrixWidth + x] = shape;
+ while (x < xMax) {
+ const right = on(x + 1, y);
+ const vertRight = y > 0 && on(x + 1, y - 1);
+ if (!right || vertRight) {
+ vert = right && vertRight;
+ break;
+ }
+ x++;
+ visited[y * matrixWidth + x] = shape;
+ }
+
+ paths[shape] += \`h\${(x - sx + 1) * unit}\`;
+ if (vert) {
+ goUp(x + 1, y - 1, shape, cw);
+ } else {
+ goDown(x, y, shape, cw);
+ }
+ }
+
+ function goLeft(x, y, shape, cw) {
+ let sx = x;
+
+ let vert = false;
+ visited[y * matrixWidth + x] = shape;
+ while (x > 0) {
+ const left = on(x - 1, y);
+ const vertLeft = y < yMax && on(x - 1, y + 1);
+ if (!left || vertLeft) {
+ vert = left && vertLeft;
+ break;
+ }
+ x--;
+ visited[y * matrixWidth + x] = shape;
+ }
+ if (!cw && x === baseX && y === baseY) {
+ paths[shape] += "z";
+ return;
+ }
+
+ paths[shape] += \`h\${(x - sx - 1) * unit}\`;
+ if (vert) {
+ goDown(x - 1, y + 1, shape, cw);
+ } else {
+ goUp(x, y, shape, cw);
+ }
+ }
+
+ function goUp(x, y, shape, cw) {
+ let sy = y;
+ let horz = false;
+ visited[y * matrixWidth + x] = shape;
+ while (y > 0) {
+ const up = on(x, y - 1);
+ const horzUp = x > 0 && on(x - 1, y - 1);
+ if (!up || horzUp) {
+ horz = up && horzUp;
+ break;
+ }
+ y--;
+ visited[y * matrixWidth + x] = shape;
+ }
+ if (cw && x === baseX && y === baseY) {
+ paths[shape] += "z";
+ return;
+ }
+
+ paths[shape] += \`v\${(y - sy - 1) * unit}\`;
+ if (horz) {
+ goLeft(x - 1, y - 1, shape, cw);
+ } else {
+ goRight(x, y, shape, cw);
+ }
+ }
+
+ function goDown(x, y, shape, cw) {
+ let sy = y;
+ let horz = false;
+ visited[y * matrixWidth + x] = shape;
+ while (y < yMax) {
+ const down = on(x, y + 1);
+ const horzDown = x < xMax && on(x + 1, y + 1);
+ if (!down || horzDown) {
+ horz = down && horzDown;
+ break;
+ }
+ y++;
+ visited[y * matrixWidth + x] = shape;
+ }
+
+ paths[shape] += \`v\${(y - sy + 1) * unit}\`;
+ if (horz) {
+ goRight(x + 1, y + 1, shape, cw);
+ } else {
+ goLeft(x, y, shape, cw);
+ }
+ }
+
+ const stack = [];
+ for (let x = 0; x < matrixWidth; x++) {
+ if (!on(x, 0)) stack.push([x, 0]);
+ }
+ for (let y = 1; y < yMax; y++) {
+ if (!on(0, y)) stack.push([0, y]);
+ if (!on(xMax, y)) stack.push([xMax, y]);
+ }
+ for (let x = 0; x < matrixWidth; x++) {
+ if (!on(x, yMax)) stack.push([x, yMax]);
+ }
+
+ // recursion dfs limited to ~4000
+ // visit all whitespace connected to edges
+ function dfsOff() {
+ while (stack.length > 0) {
+ const [x, y] = stack.pop();
+ if (visited[y * matrixWidth + x]) continue;
+ visited[y * matrixWidth + x] = 1;
+ for (let dy = -1; dy <= 1; dy++) {
+ for (let dx = -1; dx <= 1; dx++) {
+ if (dy === 0 && dx === 0) continue;
+ let nx = x + dx;
+ let ny = y + dy;
+ if (nx < 0 || nx > xMax || ny < 0 || ny > yMax) continue;
+ if (on(nx, ny)) continue;
+ stack.push([nx, ny]);
+ }
+ }
+ }
+ }
+ dfsOff();
+
+ const paths = [""];
+ for (let y = 0; y < matrixWidth; y++) {
+ for (let x = 0; x < matrixWidth; x++) {
+ if (visited[y * matrixWidth + x]) continue;
+
+ if (!on(x, y)) {
+ const shape = visited[y * matrixWidth + x - 1];
+ paths[shape] += \`M\${x * unit},\${y * unit}\`;
+
+ baseY = y - 1;
+ baseX = x;
+ goDown(x - 1, y, shape, false);
+ stack.push([x, y]);
+ dfsOff();
+ continue;
+ }
+
+ if (y > 0 && on(x, y - 1) && visited[(y - 1) * matrixWidth + x]) {
+ visited[y * matrixWidth + x] = visited[(y - 1) * matrixWidth + x];
+ continue;
+ }
+ if (x > 0 && on(x - 1, y) && visited[y * matrixWidth + x - 1]) {
+ visited[y * matrixWidth + x] = visited[y * matrixWidth + x - 1];
+ continue;
+ }
+
+ paths.push(\`M\${x * unit},\${y * unit}\`);
+
+ baseY = y;
+ baseX = x;
+
+ goRight(x, y, paths.length - 1, true);
+ }
+ }
+
+ function domToString(node) {
+ const attrs = Object.entries(node.attributes)
+ .map(([key, value]) => \`\${key}="\${value}"\`)
+ .join(" ");
+ svg += \`<\${node.tagName} \${attrs}>\`;
+ node.children.forEach(domToString);
+ svg += \`\${node.tagName}>\`;
+ }
+
+ paths.forEach((path, i) => {
+ if (i === 0) return;
+ const g = roughSVG.path(path);
+ console.log(g);
+ domToString(g);
+ });
+
+ svg += \` \`;
+ return svg;
+}
+`