kopia lustrzana https://github.com/zhengkyl/qrframe
roughjs preset
rodzic
1d2ba4b5b2
commit
81d704e034
98
README.md
98
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.
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th colspan="3">Extending with noise</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<img src="./examples/circle.svg" width="300"/>
|
||||
</td>
|
||||
<td>
|
||||
<img src="./examples/camo.svg" width="300"/>
|
||||
</td>
|
||||
<td>
|
||||
<img src="./examples/neon.svg" width="300"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="3">Using external libs like <a href="https://roughjs.com/">Rough.js</a></th>
|
||||
</tr>
|
||||
<td>
|
||||
<img src="./examples/drawing1.png"/>
|
||||
</td>
|
||||
<td>
|
||||
<img src="./examples/drawing2.png"/>
|
||||
</td>
|
||||
<td>
|
||||
<img src="./examples/drawing3.png"/>
|
||||
</td>
|
||||
<tr>
|
||||
<th colspan="3">Styles from <a href="https://qrbtf.com">QRBTF</a></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<img src="./examples/blocks.svg" width="300"/>
|
||||
</td>
|
||||
<td>
|
||||
<img src="./examples/bubbles.svg" width="300"/>
|
||||
</td>
|
||||
<td>
|
||||
<img src="./examples/alien.svg" width="300"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## 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.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="3">Extending with noise</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<img src="./examples/circle.svg" width="300"/>
|
||||
</td>
|
||||
<td>
|
||||
<img src="./examples/camo.svg" width="300"/>
|
||||
</td>
|
||||
<td>
|
||||
<img src="./examples/neon.svg" width="300"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="3">Styles from <a href="https://qrbtf.com">QRBTF</a></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<img src="./examples/blocks.svg" width="300"/>
|
||||
</td>
|
||||
<td>
|
||||
<img src="./examples/bubbles.svg" width="300"/>
|
||||
</td>
|
||||
<td>
|
||||
<img src="./examples/alien.svg" width="300"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 69 KiB |
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 46 KiB |
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 80 KiB |
|
@ -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<typeof paramsSchema>) {
|
||||
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 xmlns="http://www.w3.org/2000/svg" viewBox="${-margin} ${-margin} ${size} ${size}">`;
|
||||
|
||||
svg += `<rect x="${-margin}" y="${-margin}" width="${size}" height="${size}" fill="${params["Background"]}"/>`;
|
||||
|
||||
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 += `</svg>`;
|
||||
return svg;
|
||||
}
|
|
@ -1,6 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
|
||||
<path d="M2 1v6c1 1 2 0 2-1l1-2c1 0 3 0 2-2H4L2 1ZM17 1l-3 1 2 2c2 0 0 1 1 2-1 1 1 2 2 1V3l-1-2h-1zM18 12c-2 0-2 2-2 3v2c-1-1-3 0-3 1l3 1h2l1-3v-3l-1-1zM3 12l-1 2v4h6c0-2-1-2-2-2l-2-1-1-3Z" style="fill:#000"/>
|
||||
<path d="m15 5-4-1H6L4 7v4l1 3 3 2h7c2-1 2-3 2-5l-1-3-1-3z" style="fill:#fff"/>
|
||||
<path d="M10 5 8 6 6 7v7h2l4 1 2-1 1-2V7l-2-2h-3ZM8 8h4l1 4H8V8zm4 4z" style="fill:#000"/>
|
||||
<path d="M12 9H9l1 2h2V9z" style="fill:#000"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 7 7"><path d="M0,0h7v7h-7zM2,1v1h-1v3h1v1h4v-4h-1v-1zM2,2h3v2h-1v1h-2z" style="fill:#000"/></svg>
|
Przed Szerokość: | Wysokość: | Rozmiar: 511 B Po Szerokość: | Wysokość: | Rozmiar: 150 B |
|
@ -70,6 +70,7 @@ export function Editor(props: Props) {
|
|||
Circle: "",
|
||||
Camo: "",
|
||||
Neon: "",
|
||||
Drawing: "",
|
||||
Blocks: "",
|
||||
Bubbles: "",
|
||||
Alien: "",
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 xmlns="http://www.w3.org/2000/svg" viewBox="\${-margin} \${-margin} \${size} \${size}">\`;
|
||||
|
||||
svg += \`<rect x="\${-margin}" y="\${-margin}" width="\${size}" height="\${size}" fill="\${params["Background"]}"/>\`;
|
||||
|
||||
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 += \`</svg>\`;
|
||||
return svg;
|
||||
}
|
||||
`
|
Ładowanie…
Reference in New Issue