@arraypress/covers 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +76 -0
- package/README.md +296 -11
- package/package.json +33 -5
- package/recipes/brand.json +1143 -0
- package/schema/recipe.schema.json +7300 -770
- package/src/animate/index.d.ts +19 -0
- package/src/animate/index.js +116 -0
- package/src/builtins.js +4 -0
- package/src/effects/ascii.d.ts +7 -0
- package/src/effects/ascii.js +93 -0
- package/src/effects/buffer.d.ts +60 -0
- package/src/effects/buffer.js +472 -0
- package/src/effects/detail.d.ts +12 -0
- package/src/effects/detail.js +124 -0
- package/src/effects/digital.d.ts +14 -0
- package/src/effects/digital.js +216 -0
- package/src/effects/distort.d.ts +20 -0
- package/src/effects/distort.js +314 -0
- package/src/effects/dither.d.ts +8 -0
- package/src/effects/dither.js +147 -0
- package/src/effects/drop-shadow.d.ts +4 -0
- package/src/effects/drop-shadow.js +45 -0
- package/src/effects/extrude.d.ts +5 -0
- package/src/effects/extrude.js +55 -0
- package/src/effects/film.d.ts +11 -0
- package/src/effects/film.js +204 -0
- package/src/effects/glow.d.ts +8 -0
- package/src/effects/glow.js +56 -0
- package/src/effects/grade.d.ts +30 -0
- package/src/effects/grade.js +274 -0
- package/src/effects/halftone.d.ts +8 -0
- package/src/effects/halftone.js +95 -0
- package/src/effects/index.d.ts +125 -0
- package/src/effects/index.js +277 -0
- package/src/effects/ink.d.ts +12 -0
- package/src/effects/ink.js +70 -0
- package/src/effects/lens.d.ts +14 -0
- package/src/effects/lens.js +277 -0
- package/src/effects/light.d.ts +10 -0
- package/src/effects/light.js +59 -0
- package/src/effects/looks.d.ts +9 -0
- package/src/effects/looks.js +74 -0
- package/src/effects/mask.d.ts +52 -0
- package/src/effects/mask.js +314 -0
- package/src/effects/motion.d.ts +6 -0
- package/src/effects/motion.js +60 -0
- package/src/effects/newsprint.d.ts +6 -0
- package/src/effects/newsprint.js +89 -0
- package/src/effects/optics.d.ts +10 -0
- package/src/effects/optics.js +213 -0
- package/src/effects/options.d.ts +6 -0
- package/src/effects/options.js +106 -0
- package/src/effects/paint-presets.d.ts +10 -0
- package/src/effects/paint-presets.js +47 -0
- package/src/effects/paint.d.ts +49 -0
- package/src/effects/paint.js +66 -0
- package/src/effects/pixels.d.ts +24 -0
- package/src/effects/pixels.js +138 -0
- package/src/effects/polar.d.ts +10 -0
- package/src/effects/polar.js +265 -0
- package/src/effects/region.d.ts +8 -0
- package/src/effects/region.js +136 -0
- package/src/effects/round-border.d.ts +5 -0
- package/src/effects/round-border.js +62 -0
- package/src/effects/screen.d.ts +14 -0
- package/src/effects/screen.js +212 -0
- package/src/effects/sort.d.ts +8 -0
- package/src/effects/sort.js +125 -0
- package/src/effects/symmetry.d.ts +6 -0
- package/src/effects/symmetry.js +74 -0
- package/src/effects/tile.d.ts +7 -0
- package/src/effects/tile.js +55 -0
- package/src/effects/tone.d.ts +16 -0
- package/src/effects/tone.js +117 -0
- package/src/effects/transform.d.ts +35 -0
- package/src/effects/transform.js +127 -0
- package/src/effects/util.d.ts +16 -0
- package/src/effects/util.js +85 -0
- package/src/extract.js +4 -3
- package/src/fonts.d.ts +55 -0
- package/src/fonts.js +246 -53
- package/src/formats/cut.d.ts +10 -0
- package/src/formats/cut.js +77 -0
- package/src/formats/index.d.ts +6 -0
- package/src/formats/index.js +9 -0
- package/src/formats/presets.d.ts +41 -0
- package/src/formats/presets.js +89 -0
- package/src/formats/print.d.ts +25 -0
- package/src/formats/print.js +117 -0
- package/src/formats/zones.d.ts +10 -0
- package/src/formats/zones.js +64 -0
- package/src/geometry.js +1 -1
- package/src/icons.js +43 -9
- package/src/index.d.ts +205 -19
- package/src/index.js +12 -4
- package/src/inter-data.js +27 -0
- package/src/json-recipe.js +181 -23
- package/src/kit/index.d.ts +20 -0
- package/src/kit/index.js +58 -0
- package/src/layer-options.js +23 -4
- package/src/layers/barcode.d.ts +19 -0
- package/src/layers/barcode.js +63 -0
- package/src/layers/code128.d.ts +4 -0
- package/src/layers/code128.js +28 -0
- package/src/layers/expr.d.ts +6 -0
- package/src/layers/expr.js +121 -0
- package/src/layers/geometry.d.ts +6 -0
- package/src/layers/geometry.js +238 -0
- package/src/layers/index.d.ts +18 -0
- package/src/layers/index.js +15 -0
- package/src/layers/perforation.d.ts +14 -0
- package/src/layers/perforation.js +47 -0
- package/src/layers/repeat.d.ts +29 -0
- package/src/layers/repeat.js +90 -0
- package/src/layers/shape.d.ts +46 -0
- package/src/layers/shape.js +115 -0
- package/src/layers/soft.d.ts +5 -0
- package/src/layers/soft.js +43 -0
- package/src/layers/svg.d.ts +23 -0
- package/src/layers/svg.js +109 -0
- package/src/layers/table.d.ts +25 -0
- package/src/layers/table.js +80 -0
- package/src/layers/tear.d.ts +21 -0
- package/src/layers/tear.js +74 -0
- package/src/layers/texture.d.ts +17 -0
- package/src/layers/texture.js +106 -0
- package/src/layers/util.d.ts +8 -0
- package/src/layers/util.js +48 -0
- package/src/layers.js +125 -41
- package/src/lint/image-size.d.ts +4 -0
- package/src/lint/image-size.js +34 -0
- package/src/lint/index.d.ts +16 -0
- package/src/lint/index.js +127 -0
- package/src/lint/rules.d.ts +5 -0
- package/src/lint/rules.js +19 -0
- package/src/node.d.ts +2 -0
- package/src/node.js +20 -0
- package/src/platform/bytes.d.ts +22 -0
- package/src/platform/bytes.js +103 -0
- package/src/platform/compress.d.ts +12 -0
- package/src/platform/compress.js +81 -0
- package/src/platform/harfbuzz.d.ts +9 -0
- package/src/platform/harfbuzz.js +80 -0
- package/src/platform/index.d.ts +8 -0
- package/src/platform/index.js +13 -0
- package/src/platform/load.d.ts +6 -0
- package/src/platform/load.js +72 -0
- package/src/platform/no-harfbuzz.d.ts +2 -0
- package/src/platform/no-harfbuzz.js +11 -0
- package/src/platform/node.d.ts +26 -0
- package/src/platform/node.js +86 -0
- package/src/platform/settings.d.ts +28 -0
- package/src/platform/settings.js +51 -0
- package/src/png.js +77 -28
- package/src/print.js +8 -7
- package/src/raster/index.d.ts +24 -0
- package/src/raster/index.js +171 -0
- package/src/recipes.js +4 -3
- package/src/render/backdrop.d.ts +6 -0
- package/src/render/backdrop.js +55 -0
- package/src/render/content.d.ts +5 -0
- package/src/render/content.js +58 -0
- package/src/render/context.d.ts +82 -0
- package/src/render/grain.d.ts +7 -0
- package/src/render/grain.js +113 -0
- package/src/render/group.d.ts +23 -0
- package/src/render/group.js +67 -0
- package/src/render/key.d.ts +5 -0
- package/src/render/key.js +91 -0
- package/src/render/report.d.ts +5 -0
- package/src/render/report.js +19 -0
- package/src/render/tiles.d.ts +6 -0
- package/src/render/tiles.js +77 -0
- package/src/render/use.d.ts +27 -0
- package/src/render/use.js +73 -0
- package/src/render/wrap.d.ts +14 -0
- package/src/render/wrap.js +199 -0
- package/src/render.js +303 -212
- package/src/text.d.ts +29 -0
- package/src/text.js +22 -15
- package/src/type/auto-color.d.ts +9 -0
- package/src/type/auto-color.js +44 -0
- package/src/type/fallback.d.ts +11 -0
- package/src/type/fallback.js +92 -0
- package/src/type/glyphs.d.ts +24 -0
- package/src/type/glyphs.js +87 -0
- package/src/type/index.d.ts +5 -0
- package/src/type/index.js +10 -0
- package/src/type/kern.d.ts +4 -0
- package/src/type/kern.js +144 -0
- package/src/type/layout-ref.d.ts +10 -0
- package/src/type/layout-ref.js +74 -0
- package/src/type/line.d.ts +11 -0
- package/src/type/line.js +105 -0
- package/src/type/live.d.ts +6 -0
- package/src/type/live.js +39 -0
- package/src/type/name.d.ts +4 -0
- package/src/type/name.js +32 -0
- package/src/type/path.d.ts +18 -0
- package/src/type/path.js +89 -0
- package/src/type/pixel.d.ts +9 -0
- package/src/type/pixel.js +115 -0
- package/src/type/sfnt.d.ts +20 -0
- package/src/type/sfnt.js +258 -0
- package/src/type/spans.d.ts +33 -0
- package/src/type/spans.js +138 -0
- package/src/type/symbols.d.ts +7 -0
- package/src/type/symbols.js +89 -0
- package/src/type/typeset.d.ts +10 -0
- package/src/type/typeset.js +352 -0
- package/src/type/woff2.d.ts +6 -0
- package/src/type/woff2.js +301 -0
- package/src/version.js +4 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-instance values for `repeat`: `{#…}` expressions over the instance's
|
|
3
|
+
* variables (`i`, `row`, `col`, `n`, `rows`, `cols`, `angle`, `x`, `y`,
|
|
4
|
+
* `odd`), with `+ - * / %`, parentheses, comparisons, `a ? b : c` and
|
|
5
|
+
* `min`, `max`, `abs`, `floor`, `round`, `sin`, `cos`, `pick(i, a, b, …)`.
|
|
6
|
+
* No other code runs: it's a small parser, not `eval`.
|
|
7
|
+
*
|
|
8
|
+
* @module layers/expr
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Functions an expression can call. */
|
|
12
|
+
const FNS = /** @type {Record<string, (...a: any[]) => any>} */ ({
|
|
13
|
+
min: Math.min, max: Math.max, abs: Math.abs, floor: Math.floor, ceil: Math.ceil, round: Math.round,
|
|
14
|
+
sin: (/** @type {number} */ d) => Math.sin((d * Math.PI) / 180), cos: (/** @type {number} */ d) => Math.cos((d * Math.PI) / 180),
|
|
15
|
+
pick: (/** @type {number} */ i, /** @type {any[]} */ ...xs) => xs[((Math.floor(i) % xs.length) + xs.length) % xs.length],
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
/** @param {string} src @returns {string[]} */
|
|
19
|
+
function tokens(src) {
|
|
20
|
+
const out = [];
|
|
21
|
+
const re = /\s*(?:(\d*\.?\d+(?:e[+-]?\d+)?)|([A-Za-z_]\w*)|('[^']*'|"[^"]*")|(<=|>=|==|!=|&&|\|\||[-+*/%()?:,<>!]))/gy;
|
|
22
|
+
let m;
|
|
23
|
+
let at = 0;
|
|
24
|
+
while (at < src.length && (m = re.exec(src))) {
|
|
25
|
+
out.push(m[0].trim());
|
|
26
|
+
at = re.lastIndex;
|
|
27
|
+
}
|
|
28
|
+
if (src.slice(at).trim()) throw new TypeError(`Can't read "${src.slice(at).trim()}" in {#${src}}`);
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Evaluate an expression.
|
|
34
|
+
*
|
|
35
|
+
* @param {string} src
|
|
36
|
+
* @param {Record<string, number | string | boolean>} vars
|
|
37
|
+
* @returns {any}
|
|
38
|
+
* @throws {TypeError} On an unknown name or bad syntax.
|
|
39
|
+
*/
|
|
40
|
+
export function evaluate(src, vars) {
|
|
41
|
+
const t = tokens(src);
|
|
42
|
+
let k = 0;
|
|
43
|
+
const peek = () => t[k];
|
|
44
|
+
const take = (/** @type {string} */ want) => { if (t[k] !== want) throw new TypeError(`Expected "${want}" in {#${src}}`); k++; };
|
|
45
|
+
/** @returns {any} */
|
|
46
|
+
const ternary = () => {
|
|
47
|
+
const c = or();
|
|
48
|
+
if (peek() !== '?') return c;
|
|
49
|
+
k++;
|
|
50
|
+
const a = ternary();
|
|
51
|
+
take(':');
|
|
52
|
+
const b = ternary();
|
|
53
|
+
return c ? a : b;
|
|
54
|
+
};
|
|
55
|
+
const or = () => { let v = and(); while (peek() === '||') { k++; const r = and(); v = v || r; } return v; };
|
|
56
|
+
const and = () => { let v = cmp(); while (peek() === '&&') { k++; const r = cmp(); v = v && r; } return v; };
|
|
57
|
+
const cmp = () => {
|
|
58
|
+
let v = add();
|
|
59
|
+
while (['<', '>', '<=', '>=', '==', '!='].includes(peek())) {
|
|
60
|
+
const op = t[k++];
|
|
61
|
+
const r = add();
|
|
62
|
+
v = op === '<' ? v < r : op === '>' ? v > r : op === '<=' ? v <= r : op === '>=' ? v >= r : op === '==' ? v === r : v !== r;
|
|
63
|
+
}
|
|
64
|
+
return v;
|
|
65
|
+
};
|
|
66
|
+
const add = () => { let v = mul(); while (peek() === '+' || peek() === '-') { const op = t[k++]; const r = mul(); v = op === '+' ? v + r : v - r; } return v; };
|
|
67
|
+
const mul = () => { let v = unary(); while (peek() === '*' || peek() === '/' || peek() === '%') { const op = t[k++]; const r = unary(); v = op === '*' ? v * r : op === '/' ? v / r : ((v % r) + r) % r; } return v; };
|
|
68
|
+
/** @returns {any} */
|
|
69
|
+
const unary = () => {
|
|
70
|
+
if (peek() === '-') { k++; return -unary(); }
|
|
71
|
+
if (peek() === '!') { k++; return !unary(); }
|
|
72
|
+
return atom();
|
|
73
|
+
};
|
|
74
|
+
const atom = () => {
|
|
75
|
+
const tok = t[k++];
|
|
76
|
+
if (tok === undefined) throw new TypeError(`Unexpected end of {#${src}}`);
|
|
77
|
+
if (tok === '(') { const v = ternary(); take(')'); return v; }
|
|
78
|
+
if (/^\d|^\./.test(tok)) return Number(tok);
|
|
79
|
+
if (/^['"]/.test(tok)) return tok.slice(1, -1);
|
|
80
|
+
if (peek() === '(') {
|
|
81
|
+
if (!Object.hasOwn(FNS, tok)) throw new TypeError(`Unknown function "${tok}" in {#${src}} (${Object.keys(FNS).join(', ')})`);
|
|
82
|
+
k++;
|
|
83
|
+
const args = [];
|
|
84
|
+
if (peek() !== ')') { args.push(ternary()); while (peek() === ',') { k++; args.push(ternary()); } }
|
|
85
|
+
take(')');
|
|
86
|
+
return FNS[tok](...args);
|
|
87
|
+
}
|
|
88
|
+
if (!Object.hasOwn(vars, tok)) throw new TypeError(`Unknown name "${tok}" in {#${src}} (${Object.keys(vars).join(', ')})`);
|
|
89
|
+
return vars[tok];
|
|
90
|
+
};
|
|
91
|
+
const v = ternary();
|
|
92
|
+
if (k < t.length) throw new TypeError(`Unexpected "${t[k]}" in {#${src}}`);
|
|
93
|
+
return v;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** A whole-value expression, or one inside a string. */
|
|
97
|
+
const WHOLE = /^\{#([^{}]+)\}$/;
|
|
98
|
+
const INSIDE = /\{#([^{}]+)\}/g;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* A value with its `{#…}` expressions evaluated (a whole-string expression
|
|
102
|
+
* keeps its type). Nested layers are left alone: an inner `repeat` fills
|
|
103
|
+
* its own.
|
|
104
|
+
*
|
|
105
|
+
* @param {any} v
|
|
106
|
+
* @param {Record<string, number | string | boolean>} vars
|
|
107
|
+
* @returns {any}
|
|
108
|
+
*/
|
|
109
|
+
export function fill(v, vars) {
|
|
110
|
+
if (typeof v === 'string') {
|
|
111
|
+
const whole = WHOLE.exec(v);
|
|
112
|
+
if (whole) return evaluate(whole[1], vars);
|
|
113
|
+
return v.includes('{#') ? v.replace(INSIDE, (_, e) => String(evaluate(e, vars))) : v;
|
|
114
|
+
}
|
|
115
|
+
if (Array.isArray(v)) return v.map((x) => fill(x, vars));
|
|
116
|
+
if (v && typeof v === 'object' && !(v instanceof Uint8Array)) {
|
|
117
|
+
if (v.type === 'repeat') return v;
|
|
118
|
+
return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, fill(x, vars)]));
|
|
119
|
+
}
|
|
120
|
+
return v;
|
|
121
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Shape geometry for the `shape` layer.
|
|
2
|
+
import type { ShapeSpec } from './shape.js';
|
|
3
|
+
import type { RenderContext } from '../render/context.js';
|
|
4
|
+
|
|
5
|
+
/** A shape's path data (or an opening element tag), bounds and transform. */
|
|
6
|
+
export function geometry(s: ShapeSpec, ctx: RenderContext): { d?: string; el?: string; transform?: string; box: { x: number; y: number; width: number; height: number } };
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shape geometry for the `shape` layer: path data (or an element) and
|
|
3
|
+
* bounds for each kind, and jittered edges for deckled, torn and zigzag
|
|
4
|
+
* paper.
|
|
5
|
+
*
|
|
6
|
+
* @module layers/geometry
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { attrs, num } from '../svg.js';
|
|
10
|
+
import { len } from '../geometry.js';
|
|
11
|
+
import { pos } from './util.js';
|
|
12
|
+
import { cutPath } from '../formats/cut.js';
|
|
13
|
+
import { invert, apply } from '../effects/transform.js';
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Seeded jitter along a polyline, for deckled, torn or zigzag edges.
|
|
18
|
+
*
|
|
19
|
+
* @param {[number, number]} a @param {[number, number]} b
|
|
20
|
+
* @param {{ kind: string, size: number, step: number }} e
|
|
21
|
+
* @param {import('../rng.js').Rng} rng
|
|
22
|
+
* @returns {[number, number][]} Points from a (exclusive) to b (inclusive).
|
|
23
|
+
*/
|
|
24
|
+
function edgePoints(a, b, e, rng) {
|
|
25
|
+
const L = Math.hypot(b[0] - a[0], b[1] - a[1]);
|
|
26
|
+
const n = Math.max(2, Math.round(L / e.step));
|
|
27
|
+
const nx = -(b[1] - a[1]) / L;
|
|
28
|
+
const ny = (b[0] - a[0]) / L;
|
|
29
|
+
const out = /** @type {[number, number][]} */ ([]);
|
|
30
|
+
for (let i = 1; i <= n; i++) {
|
|
31
|
+
const t = i / n;
|
|
32
|
+
let off = 0;
|
|
33
|
+
if (i < n) {
|
|
34
|
+
if (e.kind === 'zigzag') off = (i % 2 ? 1 : -1) * e.size / 2;
|
|
35
|
+
else if (e.kind === 'wave') off = Math.sin(t * n * Math.PI) * e.size / 2;
|
|
36
|
+
else if (e.kind === 'scallop') off = -Math.abs(Math.sin(t * n * Math.PI / 2)) * e.size;
|
|
37
|
+
else off = (rng.next() - 0.5) * e.size * (e.kind === 'torn' ? 2 : 1);
|
|
38
|
+
}
|
|
39
|
+
out.push([a[0] + (b[0] - a[0]) * t + nx * off, a[1] + (b[1] - a[1]) * t + ny * off]);
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* One shape's path data and bounds.
|
|
46
|
+
*
|
|
47
|
+
* @param {any} s - The shape.
|
|
48
|
+
* @param {import('../layers.js').RenderContext} ctx
|
|
49
|
+
* @returns {{ d?: string, el?: string, box: { x: number, y: number, width: number, height: number } }}
|
|
50
|
+
*/
|
|
51
|
+
export function geometry(s, ctx) {
|
|
52
|
+
const c = ctx.canvas;
|
|
53
|
+
const X = (/** @type {any} */ v, /** @type {any} */ f = 0) => pos(v ?? f, 'x', c);
|
|
54
|
+
const Y = (/** @type {any} */ v, /** @type {any} */ f = 0) => pos(v ?? f, 'y', c);
|
|
55
|
+
const L = (/** @type {any} */ v, /** @type {any} */ f = 0) => len(v, c, f);
|
|
56
|
+
const kind = s.kind ?? 'rect';
|
|
57
|
+
const P = (/** @type {number} */ x, /** @type {number} */ y) => `${num(x)} ${num(y)}`;
|
|
58
|
+
switch (kind) {
|
|
59
|
+
case 'rect': {
|
|
60
|
+
const x = X(s.x);
|
|
61
|
+
const y = Y(s.y);
|
|
62
|
+
const w = s.width != null ? X(s.width) : c.width - x;
|
|
63
|
+
const h = s.height != null ? Y(s.height) : c.height - y;
|
|
64
|
+
const box = { x, y, width: w, height: h };
|
|
65
|
+
if (s.edge) {
|
|
66
|
+
const e = typeof s.edge === 'string' ? { kind: s.edge } : s.edge;
|
|
67
|
+
const size = L(e.size, e.kind === 'deckle' ? '0.6vmin' : '1.2vmin');
|
|
68
|
+
const step = L(e.step, e.kind === 'zigzag' ? size : e.kind === 'deckle' ? '0.9vmin' : '2vmin');
|
|
69
|
+
const sides = new Set(e.sides ?? ['top', 'right', 'bottom', 'left']);
|
|
70
|
+
const corners = /** @type {[number, number][]} */ ([[x, y], [x + w, y], [x + w, y + h], [x, y + h]]);
|
|
71
|
+
const names = ['top', 'right', 'bottom', 'left'];
|
|
72
|
+
let d = `M${P(x, y)}`;
|
|
73
|
+
for (let k = 0; k < 4; k++) {
|
|
74
|
+
const a = corners[k];
|
|
75
|
+
const b = corners[(k + 1) % 4];
|
|
76
|
+
if (sides.has(names[k])) for (const q of edgePoints(a, b, { kind: e.kind, size, step }, ctx.rng)) d += `L${P(q[0], q[1])}`;
|
|
77
|
+
else d += `L${P(b[0], b[1])}`;
|
|
78
|
+
}
|
|
79
|
+
return { d: `${d}Z`, box };
|
|
80
|
+
}
|
|
81
|
+
return { el: `<rect${attrs({ x, y, width: w, height: h, rx: s.radius != null ? L(s.radius) : undefined })}`, box };
|
|
82
|
+
}
|
|
83
|
+
case 'circle': {
|
|
84
|
+
const cx = X(s.cx, '50%');
|
|
85
|
+
const cy = Y(s.cy, '50%');
|
|
86
|
+
const r = L(s.r, '20vmin');
|
|
87
|
+
return { el: `<circle${attrs({ cx, cy, r })}`, box: { x: cx - r, y: cy - r, width: 2 * r, height: 2 * r } };
|
|
88
|
+
}
|
|
89
|
+
case 'ellipse': {
|
|
90
|
+
const cx = X(s.cx, '50%');
|
|
91
|
+
const cy = Y(s.cy, '50%');
|
|
92
|
+
const rx = L(s.rx, '20vmin');
|
|
93
|
+
const ry = L(s.ry, '12vmin');
|
|
94
|
+
return { el: `<ellipse${attrs({ cx, cy, rx, ry })}`, box: { x: cx - rx, y: cy - ry, width: 2 * rx, height: 2 * ry } };
|
|
95
|
+
}
|
|
96
|
+
case 'line': {
|
|
97
|
+
const x1 = X(s.x1);
|
|
98
|
+
const y1 = Y(s.y1);
|
|
99
|
+
const x2 = X(s.x2, '100%');
|
|
100
|
+
const y2 = Y(s.y2, s.y1 ?? 0);
|
|
101
|
+
return { d: `M${P(x1, y1)}L${P(x2, y2)}`, box: { x: Math.min(x1, x2), y: Math.min(y1, y2), width: Math.abs(x2 - x1), height: Math.abs(y2 - y1) } };
|
|
102
|
+
}
|
|
103
|
+
case 'polygon':
|
|
104
|
+
case 'polyline': {
|
|
105
|
+
const pts = (s.points ?? []).map((/** @type {any} */ q) => [X(q[0]), Y(q[1])]);
|
|
106
|
+
const xs = pts.map((/** @type {number[]} */ q) => q[0]);
|
|
107
|
+
const ys = pts.map((/** @type {number[]} */ q) => q[1]);
|
|
108
|
+
return { d: pts.map((/** @type {number[]} */ q, /** @type {number} */ i) => `${i ? 'L' : 'M'}${P(q[0], q[1])}`).join('') + (kind === 'polygon' ? 'Z' : ''), box: { x: Math.min(...xs), y: Math.min(...ys), width: Math.max(...xs) - Math.min(...xs), height: Math.max(...ys) - Math.min(...ys) } };
|
|
109
|
+
}
|
|
110
|
+
case 'star':
|
|
111
|
+
case 'burst':
|
|
112
|
+
case 'scallop':
|
|
113
|
+
case 'rays':
|
|
114
|
+
case 'ring':
|
|
115
|
+
case 'sector': {
|
|
116
|
+
const cx = X(s.cx, '50%');
|
|
117
|
+
const cy = Y(s.cy, '50%');
|
|
118
|
+
const r = L(s.r, '20vmin');
|
|
119
|
+
const rot = ((s.rotate ?? 0) * Math.PI) / 180;
|
|
120
|
+
const box = { x: cx - r, y: cy - r, width: 2 * r, height: 2 * r };
|
|
121
|
+
const at = (/** @type {number} */ a, /** @type {number} */ rr) => P(cx + rr * Math.sin(a + rot), cy - rr * Math.cos(a + rot));
|
|
122
|
+
if (kind === 'ring') {
|
|
123
|
+
const r2 = L(s.inner, r * 0.6);
|
|
124
|
+
return { d: `M${P(cx - r, cy)}a${num(r)} ${num(r)} 0 1 0 ${num(2 * r)} 0a${num(r)} ${num(r)} 0 1 0 ${num(-2 * r)} 0ZM${P(cx - r2, cy)}a${num(r2)} ${num(r2)} 0 1 1 ${num(2 * r2)} 0a${num(r2)} ${num(r2)} 0 1 1 ${num(-2 * r2)} 0Z`, box };
|
|
125
|
+
}
|
|
126
|
+
if (kind === 'sector') {
|
|
127
|
+
// A pie slice, `start` to `end` degrees (0 at 12 o'clock, clockwise): quarter and half discs.
|
|
128
|
+
const a0 = ((s.start ?? 0) * Math.PI) / 180;
|
|
129
|
+
const a1 = ((s.end ?? 90) * Math.PI) / 180;
|
|
130
|
+
const large = Math.abs(a1 - a0) > Math.PI ? 1 : 0;
|
|
131
|
+
return { d: `M${P(cx, cy)}L${at(a0, r)}A${num(r)} ${num(r)} 0 ${large} 1 ${at(a1, r)}Z`, box };
|
|
132
|
+
}
|
|
133
|
+
if (kind === 'rays') {
|
|
134
|
+
const n = s.count ?? 24;
|
|
135
|
+
let d = '';
|
|
136
|
+
for (let i = 0; i < n; i += 2) {
|
|
137
|
+
const a0 = (i / n) * Math.PI * 2;
|
|
138
|
+
const a1 = ((i + 1) / n) * Math.PI * 2;
|
|
139
|
+
d += `M${P(cx, cy)}L${at(a0, r)}L${at(a1, r)}Z`;
|
|
140
|
+
}
|
|
141
|
+
return { d, box };
|
|
142
|
+
}
|
|
143
|
+
if (kind === 'scallop') {
|
|
144
|
+
const bumps = s.points ?? 12;
|
|
145
|
+
const depth = L(s.depth, r * 0.08);
|
|
146
|
+
let d = '';
|
|
147
|
+
for (let deg = 0; deg <= 360; deg += 2) {
|
|
148
|
+
const a = (deg * Math.PI) / 180;
|
|
149
|
+
d += `${deg ? 'L' : 'M'}${at(a, r - depth + depth * Math.cos(a * bumps))}`;
|
|
150
|
+
}
|
|
151
|
+
return { d: `${d}Z`, box };
|
|
152
|
+
}
|
|
153
|
+
const n = s.points ?? (kind === 'burst' ? 16 : 5);
|
|
154
|
+
const inner = s.inner != null ? (s.inner <= 1 ? r * s.inner : L(s.inner)) : r * (kind === 'burst' ? 0.8 : 0.42);
|
|
155
|
+
let d = '';
|
|
156
|
+
for (let i = 0; i < n * 2; i++) d += `${i ? 'L' : 'M'}${at((i / (n * 2)) * Math.PI * 2, i % 2 ? inner : r)}`;
|
|
157
|
+
return { d: `${d}Z`, box };
|
|
158
|
+
}
|
|
159
|
+
case 'arch': {
|
|
160
|
+
// A window: straight sides, a round top.
|
|
161
|
+
const x = X(s.x);
|
|
162
|
+
const y = Y(s.y);
|
|
163
|
+
const w = X(s.width, '40%');
|
|
164
|
+
const h = Y(s.height, '50%');
|
|
165
|
+
const r = w / 2;
|
|
166
|
+
// `open`: the arc and its sides only (a stroked arch).
|
|
167
|
+
return { d: `M${P(x, y + h)}V${num(y + r)}A${num(r)} ${num(r)} 0 0 1 ${P(x + w, y + r)}V${num(y + h)}${s.open ? '' : 'Z'}`, box: { x, y, width: w, height: h } };
|
|
168
|
+
}
|
|
169
|
+
case 'wave': {
|
|
170
|
+
const x1 = X(s.x1);
|
|
171
|
+
const x2 = X(s.x2, '100%');
|
|
172
|
+
const y = Y(s.y, '50%');
|
|
173
|
+
const amp = L(s.amplitude, '2vmin');
|
|
174
|
+
const wl = L(s.wavelength, '20vmin');
|
|
175
|
+
const ph = s.phase ?? 0;
|
|
176
|
+
let d = '';
|
|
177
|
+
for (let x = x1, i = 0; x <= x2 + 0.01; x += Math.max(1, wl / 32), i++) d += `${i ? 'L' : 'M'}${P(x, y + Math.sin(((x - x1) / wl) * Math.PI * 2 + ph) * amp)}`;
|
|
178
|
+
if (s.band) {
|
|
179
|
+
const bh = L(s.band);
|
|
180
|
+
let back = '';
|
|
181
|
+
for (let x = x2; x >= x1 - 0.01; x -= Math.max(1, wl / 32)) back += `L${P(x, y + bh + Math.sin(((x - x1) / wl) * Math.PI * 2 + ph) * amp)}`;
|
|
182
|
+
d += `${back}Z`;
|
|
183
|
+
}
|
|
184
|
+
return { d, box: { x: x1, y: y - amp, width: x2 - x1, height: 2 * amp } };
|
|
185
|
+
}
|
|
186
|
+
case 'path': {
|
|
187
|
+
// Path data, optionally drawn from a viewBox into a box (x, y, width, height).
|
|
188
|
+
const d = String(s.d ?? '');
|
|
189
|
+
if (s.viewBox) {
|
|
190
|
+
const [vx, vy, vw, vh] = s.viewBox;
|
|
191
|
+
const x = X(s.x);
|
|
192
|
+
const y = Y(s.y);
|
|
193
|
+
const w = s.width != null ? X(s.width) : vw;
|
|
194
|
+
const h = s.height != null ? Y(s.height) : (w / vw) * vh;
|
|
195
|
+
return { d, box: { x, y, width: w, height: h }, el: undefined, ...{ transform: `translate(${num(x)} ${num(y)}) scale(${num(w / vw, 5)} ${num(h / vh, 5)}) translate(${num(-vx)} ${num(-vy)})` } };
|
|
196
|
+
}
|
|
197
|
+
return { d, box: { x: 0, y: 0, width: c.width, height: c.height } };
|
|
198
|
+
}
|
|
199
|
+
case 'cut': {
|
|
200
|
+
// The print format's die-cut (the trim's corners or shape), in this layer's units.
|
|
201
|
+
return cutGeometry(/** @type {any} */ (ctx).cut, L(s.outset, 0), /** @type {any} */ (ctx).matrix, c);
|
|
202
|
+
}
|
|
203
|
+
default:
|
|
204
|
+
throw new TypeError(`Unknown shape kind "${kind}" (rect, circle, ellipse, line, polygon, polyline, star, burst, scallop, rays, ring, sector, arch, wave, path, cut)`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* The cut outline, from canvas px into the layer's units (through the
|
|
210
|
+
* inverse of everything drawn above it). Without a print format, the canvas.
|
|
211
|
+
*
|
|
212
|
+
* @param {{ x: number, y: number, width: number, height: number, cut?: any, mm?: number } | undefined} cut - Trim box and cut, canvas px.
|
|
213
|
+
* @param {number} outset - Grow (or, negative, shrink) the outline, in the layer's units.
|
|
214
|
+
* @param {import('../effects/transform.js').Affine | undefined} matrix - Layer units to canvas px.
|
|
215
|
+
* @param {{ width: number, height: number }} canvas
|
|
216
|
+
* @returns {{ d: string, box: { x: number, y: number, width: number, height: number } }}
|
|
217
|
+
*/
|
|
218
|
+
function cutGeometry(cut, outset, matrix, canvas) {
|
|
219
|
+
const inv = matrix ? invert(matrix) : undefined;
|
|
220
|
+
const k = matrix ? Math.sqrt(Math.abs(matrix[0] * matrix[3] - matrix[1] * matrix[2])) : 1;
|
|
221
|
+
const trim = cut ?? { x: 0, y: 0, width: canvas.width * k, height: canvas.height * k };
|
|
222
|
+
const o = outset * k;
|
|
223
|
+
const box = { x: trim.x - o, y: trim.y - o, width: trim.width + 2 * o, height: trim.height + 2 * o };
|
|
224
|
+
const segs = cutPath(cut?.cut, box, (cut?.mm ?? 1) * 1, o);
|
|
225
|
+
const at = (/** @type {number} */ x, /** @type {number} */ y) => (inv ? apply(inv, x, y) : [x, y]);
|
|
226
|
+
let d = '';
|
|
227
|
+
const xs = [];
|
|
228
|
+
const ys = [];
|
|
229
|
+
for (const seg of segs) {
|
|
230
|
+
d += seg.cmd;
|
|
231
|
+
for (let i = 0; i < seg.pts.length; i += 2) {
|
|
232
|
+
const [X, Y] = at(seg.pts[i], seg.pts[i + 1]);
|
|
233
|
+
xs.push(X); ys.push(Y);
|
|
234
|
+
d += `${i ? ' ' : ''}${num(X)} ${num(Y)}`;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return { d, box: { x: Math.min(...xs), y: Math.min(...ys), width: Math.max(...xs) - Math.min(...xs), height: Math.max(...ys) - Math.min(...ys) } };
|
|
238
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// The poster layers (1.1).
|
|
2
|
+
export { shape } from './shape.js';
|
|
3
|
+
export type { ShapeLayer, ShapeSpec } from './shape.js';
|
|
4
|
+
export { svg, sanitize, shapesOnly } from './svg.js';
|
|
5
|
+
export type { SvgLayer } from './svg.js';
|
|
6
|
+
export { table } from './table.js';
|
|
7
|
+
export type { TableLayer, TableCell } from './table.js';
|
|
8
|
+
export { barcode } from './barcode.js';
|
|
9
|
+
export type { BarcodeLayer } from './barcode.js';
|
|
10
|
+
export { code128 } from './code128.js';
|
|
11
|
+
export { perforation } from './perforation.js';
|
|
12
|
+
export type { PerforationLayer } from './perforation.js';
|
|
13
|
+
export { tear } from './tear.js';
|
|
14
|
+
export type { TearLayer } from './tear.js';
|
|
15
|
+
export { texture } from './texture.js';
|
|
16
|
+
export type { TextureLayer } from './texture.js';
|
|
17
|
+
export { expandRepeat } from './repeat.js';
|
|
18
|
+
export type { RepeatLayer } from './repeat.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The poster layers (1.1): one module each.
|
|
3
|
+
*
|
|
4
|
+
* @module layers
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { shape } from './shape.js';
|
|
8
|
+
export { svg, sanitize, shapesOnly } from './svg.js';
|
|
9
|
+
export { table } from './table.js';
|
|
10
|
+
export { barcode } from './barcode.js';
|
|
11
|
+
export { code128 } from './code128.js';
|
|
12
|
+
export { perforation } from './perforation.js';
|
|
13
|
+
export { tear } from './tear.js';
|
|
14
|
+
export { texture } from './texture.js';
|
|
15
|
+
export { expandRepeat } from './repeat.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// The `perforation` layer.
|
|
2
|
+
import type { Color, Length, BaseLayer, Anchor } from '../index.js';
|
|
3
|
+
import type { RenderContext, Markup } from '../render/context.js';
|
|
4
|
+
|
|
5
|
+
export interface PerforationLayer extends BaseLayer {
|
|
6
|
+
type: 'perforation';
|
|
7
|
+
kind?: 'holes' | 'dashes' | 'notches';
|
|
8
|
+
x?: Length | `${number}%`; x1?: Length | `${number}%`; y1?: Length | `${number}%`; x2?: Length | `${number}%`; y2?: Length | `${number}%`;
|
|
9
|
+
size?: Length; gap?: Length; inset?: Length; color?: Color; strokeWidth?: Length;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/** Draw a tear-off line. */
|
|
14
|
+
export function perforation(layer: PerforationLayer, ctx: RenderContext): Markup;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `perforation` layer.
|
|
3
|
+
*
|
|
4
|
+
* @module layers/perforation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { attrs, num } from '../svg.js';
|
|
8
|
+
import { len } from '../geometry.js';
|
|
9
|
+
import { toHex } from '../color.js';
|
|
10
|
+
import { pos, wrap } from './util.js';
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* `perforation` — a tear-off line: `holes` (punched dots), `dashes` or
|
|
15
|
+
* `notches` (half-circle bites at both ends), from `x1, y1` to `x2, y2`.
|
|
16
|
+
* In a layer's `knockout` it punches real holes through it.
|
|
17
|
+
*
|
|
18
|
+
* @param {any} layer
|
|
19
|
+
* @param {import('../layers.js').RenderContext} ctx
|
|
20
|
+
* @returns {import('../layers.js').Markup}
|
|
21
|
+
*/
|
|
22
|
+
export function perforation(layer, ctx) {
|
|
23
|
+
const x1 = pos(layer.x1 ?? layer.x ?? '50%', 'x', ctx.canvas);
|
|
24
|
+
const y1 = pos(layer.y1 ?? 0, 'y', ctx.canvas);
|
|
25
|
+
const x2 = pos(layer.x2 ?? layer.x ?? '50%', 'x', ctx.canvas);
|
|
26
|
+
const y2 = pos(layer.y2 ?? '100%', 'y', ctx.canvas);
|
|
27
|
+
const color = layer.color ? toHex(layer.color) : ctx.palette.text;
|
|
28
|
+
const kind = layer.kind ?? 'holes';
|
|
29
|
+
const L = Math.hypot(x2 - x1, y2 - y1);
|
|
30
|
+
const size = len(layer.size, ctx.canvas, kind === 'notches' ? '1.6vmin' : '0.25vmin');
|
|
31
|
+
const gap = len(layer.gap, ctx.canvas, kind === 'dashes' ? '0.8vmin' : '1.2vmin');
|
|
32
|
+
let body = '';
|
|
33
|
+
if (kind === 'dashes') {
|
|
34
|
+
body = `<path${attrs({ d: `M${num(x1)} ${num(y1)}L${num(x2)} ${num(y2)}`, stroke: color, 'stroke-width': len(layer.strokeWidth, ctx.canvas, '0.2vmin'), 'stroke-dasharray': `${num(gap)} ${num(gap * 0.8)}`, fill: 'none' })}/>`;
|
|
35
|
+
} else if (kind === 'notches') {
|
|
36
|
+
body = `<circle${attrs({ cx: x1, cy: y1, r: size, fill: color })}/><circle${attrs({ cx: x2, cy: y2, r: size, fill: color })}/>`;
|
|
37
|
+
} else {
|
|
38
|
+
const inset = len(layer.inset, ctx.canvas, gap);
|
|
39
|
+
const n = Math.max(1, Math.floor((L - 2 * inset) / gap));
|
|
40
|
+
for (let i = 0; i <= n; i++) {
|
|
41
|
+
const t = (inset + i * ((L - 2 * inset) / n)) / L;
|
|
42
|
+
body += `<circle${attrs({ cx: x1 + (x2 - x1) * t, cy: y1 + (y2 - y1) * t, r: size })}/>`;
|
|
43
|
+
}
|
|
44
|
+
body = `<g fill="${color}">${body}</g>`;
|
|
45
|
+
}
|
|
46
|
+
return { defs: '', body: wrap(layer, body) };
|
|
47
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// The `repeat` layer.
|
|
2
|
+
import type { Length, Layer, BaseLayer } from '../index.js';
|
|
3
|
+
|
|
4
|
+
/** `repeat`: a layer drawn many times (a grid, a ring, a line), with per-instance `{#…}` values. */
|
|
5
|
+
export interface RepeatLayer extends BaseLayer {
|
|
6
|
+
type: 'repeat';
|
|
7
|
+
/** The template (or `layers`, drawn as a group). */
|
|
8
|
+
layer?: Layer;
|
|
9
|
+
layers?: Layer[];
|
|
10
|
+
rows?: number; cols?: number;
|
|
11
|
+
count?: number;
|
|
12
|
+
/** `[dx, dy]` between instances (a grid's default divides the canvas). */
|
|
13
|
+
step?: [Length, Length];
|
|
14
|
+
x?: Length | `${number}%`; y?: Length | `${number}%`;
|
|
15
|
+
cx?: Length | `${number}%`; cy?: Length | `${number}%`; radius?: Length;
|
|
16
|
+
/** Degrees, 0 up, clockwise. */
|
|
17
|
+
start?: number; sweep?: number;
|
|
18
|
+
/** Turn each ring instance to its angle (default true). */
|
|
19
|
+
turn?: boolean;
|
|
20
|
+
/** Overrides for odd instances (a checkerboard on a grid). */
|
|
21
|
+
odd?: Record<string, unknown>;
|
|
22
|
+
/** Overrides taken in turn. */
|
|
23
|
+
cycle?: Record<string, unknown>[];
|
|
24
|
+
/** An expression (`{#…}`) that leaves an instance out when true. */
|
|
25
|
+
skip?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The instances of a repeat, as a group of placed copies. */
|
|
29
|
+
export function expandRepeat(layer: RepeatLayer, ctx: { width: number; height: number; canvas: { width: number; height: number } }): Layer;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `repeat`: one layer (or a stack) drawn many times, so tile grids, rings
|
|
3
|
+
* of marks and poster walls are data, not generator scripts.
|
|
4
|
+
*
|
|
5
|
+
* - A grid: `rows` × `cols`, `step: [dx, dy]` apart (default the canvas
|
|
6
|
+
* divided by the count), starting at `x`, `y`.
|
|
7
|
+
* - A ring: `count` round `cx`, `cy` at `radius`, from `start` degrees (0
|
|
8
|
+
* up, clockwise) over `sweep` (default 360), each turned to its angle
|
|
9
|
+
* (`turn: false` keeps them upright).
|
|
10
|
+
* - A line: `count` instances `step: [dx, dy]` apart.
|
|
11
|
+
*
|
|
12
|
+
* Each instance is the template (`layer`, or `layers` as a group) placed at
|
|
13
|
+
* its point, with `{#…}` expressions filled in from its variables: `i`,
|
|
14
|
+
* `n` (the count), `row`, `col`, `rows`, `cols`, `x`, `y`, `angle` and
|
|
15
|
+
* `odd` (a checkerboard on a grid, every other one otherwise). `odd` holds
|
|
16
|
+
* overrides for odd instances, `cycle` a list of overrides taken in turn,
|
|
17
|
+
* and `skip` an expression that leaves an instance out.
|
|
18
|
+
*
|
|
19
|
+
* @module layers/repeat
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { num } from '../svg.js';
|
|
23
|
+
import { len } from '../geometry.js';
|
|
24
|
+
import { pos } from './util.js';
|
|
25
|
+
import { fill, evaluate } from './expr.js';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The instances of a repeat, as a group of placed copies.
|
|
29
|
+
*
|
|
30
|
+
* @param {any} layer
|
|
31
|
+
* @param {{ width: number, height: number, canvas: { width: number, height: number } }} ctx
|
|
32
|
+
* @returns {any} A `group` layer.
|
|
33
|
+
*/
|
|
34
|
+
export function expandRepeat(layer, ctx) {
|
|
35
|
+
const template = layer.layer ?? { type: 'group', layers: layer.layers ?? [] };
|
|
36
|
+
const places = points(layer, ctx);
|
|
37
|
+
const grid = layer.rows != null || layer.cols != null;
|
|
38
|
+
const key = layer.seed ?? layer.id ?? 'repeat';
|
|
39
|
+
const layers = [];
|
|
40
|
+
places.forEach((p, i) => {
|
|
41
|
+
const odd = grid ? (p.row + p.col) % 2 === 1 : i % 2 === 1;
|
|
42
|
+
const vars = { i, n: places.length, row: p.row, col: p.col, rows: p.rows, cols: p.cols, x: p.x, y: p.y, angle: p.angle, odd };
|
|
43
|
+
if (layer.skip != null && evaluate(String(layer.skip).replace(/^\{#|\}$/g, ''), vars)) return;
|
|
44
|
+
let one = { ...template };
|
|
45
|
+
if (odd && layer.odd) one = { ...one, ...layer.odd };
|
|
46
|
+
if (layer.cycle?.length) one = { ...one, ...layer.cycle[i % layer.cycle.length] };
|
|
47
|
+
const turn = p.angle && layer.turn !== false ? ` rotate(${num(p.angle, 3)})` : '';
|
|
48
|
+
layers.push({ type: 'group', seed: `${key}#${i}`, transform: `translate(${num(p.x, 3)} ${num(p.y, 3)})${turn}`, layers: [fill(one, vars)] });
|
|
49
|
+
});
|
|
50
|
+
return { type: 'group', layers, opacity: layer.opacity, blend: layer.blend };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Where the instances go.
|
|
55
|
+
*
|
|
56
|
+
* @param {any} l
|
|
57
|
+
* @param {{ width: number, height: number, canvas: { width: number, height: number } }} ctx
|
|
58
|
+
* @returns {{ x: number, y: number, row: number, col: number, rows: number, cols: number, angle: number }[]}
|
|
59
|
+
*/
|
|
60
|
+
function points(l, ctx) {
|
|
61
|
+
const c = ctx.canvas;
|
|
62
|
+
const x0 = pos(l.x ?? 0, 'x', c);
|
|
63
|
+
const y0 = pos(l.y ?? 0, 'y', c);
|
|
64
|
+
if (l.rows != null || l.cols != null) {
|
|
65
|
+
const rows = Math.max(1, Math.round(l.rows ?? 1));
|
|
66
|
+
const cols = Math.max(1, Math.round(l.cols ?? 1));
|
|
67
|
+
const dx = l.step ? len(l.step[0], c) : (c.width - x0) / cols;
|
|
68
|
+
const dy = l.step ? len(l.step[1], c) : (c.height - y0) / rows;
|
|
69
|
+
const out = [];
|
|
70
|
+
for (let row = 0; row < rows; row++) for (let col = 0; col < cols; col++) out.push({ x: x0 + col * dx, y: y0 + row * dy, row, col, rows, cols, angle: 0 });
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
const count = Math.max(0, Math.round(l.count ?? 0));
|
|
74
|
+
if (l.radius != null) {
|
|
75
|
+
const cx = pos(l.cx ?? '50%', 'x', c);
|
|
76
|
+
const cy = pos(l.cy ?? '50%', 'y', c);
|
|
77
|
+
const r = len(l.radius, c);
|
|
78
|
+
const sweep = l.sweep ?? 360;
|
|
79
|
+
const start = l.start ?? 0;
|
|
80
|
+
// A full circle doesn't put the last on the first.
|
|
81
|
+
const stepA = count > 1 ? sweep / (Math.abs(sweep) >= 360 ? count : count - 1) : 0;
|
|
82
|
+
return Array.from({ length: count }, (_, i) => {
|
|
83
|
+
const angle = start + i * stepA;
|
|
84
|
+
const a = (angle * Math.PI) / 180;
|
|
85
|
+
return { x: cx + r * Math.sin(a), y: cy - r * Math.cos(a), row: 0, col: i, rows: 1, cols: count, angle };
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
const [dx, dy] = l.step ? [len(l.step[0], c), len(l.step[1], c)] : [0, 0];
|
|
89
|
+
return Array.from({ length: count }, (_, i) => ({ x: x0 + i * dx, y: y0 + i * dy, row: 0, col: i, rows: 1, cols: count, angle: 0 }));
|
|
90
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// The `shape` layer.
|
|
2
|
+
import type { Color, Length, BaseLayer, Anchor } from '../index.js';
|
|
3
|
+
import type { RenderContext, Markup } from '../render/context.js';
|
|
4
|
+
import type { Paint } from '../effects/paint.js';
|
|
5
|
+
|
|
6
|
+
/** One exact shape (see the README's `shape` table). */
|
|
7
|
+
export interface ShapeSpec {
|
|
8
|
+
kind?: 'rect' | 'circle' | 'ellipse' | 'line' | 'polygon' | 'polyline' | 'star' | 'burst' | 'scallop' | 'rays' | 'ring' | 'sector' | 'arch' | 'wave' | 'path' | 'cut';
|
|
9
|
+
x?: Length | `${number}%`; y?: Length | `${number}%`; width?: Length | `${number}%`; height?: Length | `${number}%`;
|
|
10
|
+
cx?: Length | `${number}%`; cy?: Length | `${number}%`; r?: Length; rx?: Length; ry?: Length;
|
|
11
|
+
x1?: Length | `${number}%`; y1?: Length | `${number}%`; x2?: Length | `${number}%`; y2?: Length | `${number}%`;
|
|
12
|
+
points?: number | [Length, Length][];
|
|
13
|
+
inner?: Length; count?: number; start?: number; end?: number; depth?: Length;
|
|
14
|
+
amplitude?: Length; wavelength?: Length; phase?: number; band?: Length;
|
|
15
|
+
d?: string; viewBox?: [number, number, number, number];
|
|
16
|
+
radius?: Length;
|
|
17
|
+
edge?: 'deckle' | 'torn' | 'zigzag' | 'wave' | 'scallop' | { kind: 'deckle' | 'torn' | 'zigzag' | 'wave' | 'scallop'; size?: Length; step?: Length; sides?: ('top' | 'right' | 'bottom' | 'left')[] };
|
|
18
|
+
rotate?: number;
|
|
19
|
+
/** Rotate about this point (px or `'%'`) instead of the centre. */
|
|
20
|
+
origin?: [Length | `${number}%`, Length | `${number}%`] | { x?: Length | `${number}%`; y?: Length | `${number}%` };
|
|
21
|
+
/** `arch`: the arc and its sides only (stroked). */
|
|
22
|
+
open?: boolean;
|
|
23
|
+
/** A blur radius, drawn padded: soft orbs with no edge fade or square edge. */
|
|
24
|
+
soft?: Length;
|
|
25
|
+
/** `cut`: grow (or shrink) the format's die-cut outline. */
|
|
26
|
+
outset?: Length;
|
|
27
|
+
/** Where the dash pattern starts. */
|
|
28
|
+
dashOffset?: Length;
|
|
29
|
+
fill?: Paint; stroke?: Paint; strokeWidth?: Length;
|
|
30
|
+
strokes?: { color: Color; width: Length; dash?: Length[] }[];
|
|
31
|
+
dash?: 'dashed' | 'dotted' | Length[];
|
|
32
|
+
linecap?: 'butt' | 'round' | 'square'; linejoin?: 'miter' | 'round' | 'bevel'; fillRule?: 'nonzero' | 'evenodd';
|
|
33
|
+
opacity?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ShapeLayer extends BaseLayer, Omit<ShapeSpec, 'opacity'> {
|
|
37
|
+
type: 'shape';
|
|
38
|
+
/** Several shapes in one layer (layer-level fill, stroke, … are their defaults). */
|
|
39
|
+
items?: ShapeSpec[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
/** Draw one shape, or `items` of them. */
|
|
44
|
+
export function shape(layer: ShapeLayer, ctx: RenderContext): Promise<Markup>;
|
|
45
|
+
/** A dash pattern as an SVG `stroke-dasharray`. */
|
|
46
|
+
export function dashArray(dash: 'dashed' | 'dotted' | Length[], width: number, ctx: RenderContext): string;
|