@arraypress/covers 1.0.0 → 1.3.1
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 +118 -0
- package/README.md +18 -524
- package/package.json +34 -5
- package/recipes/brand.json +1143 -0
- package/recipes/flyers/afterglow-motion.json +540 -0
- package/recipes/flyers/afterglow.json +70 -0
- package/recipes/flyers/arcfield-mirror-motion.json +397 -0
- package/recipes/flyers/arcfield-mirror.json +364 -0
- package/recipes/flyers/bauhaus-oktav.json +319 -0
- package/recipes/flyers/bauhaus-ritmo.json +735 -0
- package/recipes/flyers/bauhaus-volta.json +394 -0
- package/recipes/flyers/bloomfall.json +99 -0
- package/recipes/flyers/drag-solenne-motion.json +444 -0
- package/recipes/flyers/drag-solenne.json +426 -0
- package/recipes/flyers/fennick-patches.json +874 -0
- package/recipes/flyers/ferro-pass.json +398 -0
- package/recipes/flyers/ferrova.json +812 -0
- package/recipes/flyers/grid-rest-rhythm.json +125 -0
- package/recipes/flyers/groovy-honeycomb.json +816 -0
- package/recipes/flyers/groovy-marigold.json +479 -0
- package/recipes/flyers/groovy-peachfuzz.json +974 -0
- package/recipes/flyers/halden-orbit-motion.json +381 -0
- package/recipes/flyers/halden-orbit.json +356 -0
- package/recipes/flyers/halvard.json +120 -0
- package/recipes/flyers/kairo-weekender.json +696 -0
- package/recipes/flyers/linger-motion.json +518 -0
- package/recipes/flyers/linger.json +67 -0
- package/recipes/flyers/little-orchard.json +105 -0
- package/recipes/flyers/lofi-hexmoth.json +912 -0
- package/recipes/flyers/lofi-lowbit-motion.json +888 -0
- package/recipes/flyers/lofi-lowbit.json +880 -0
- package/recipes/flyers/lofi-nullpoint.json +3013 -0
- package/recipes/flyers/lumio-stack-motion.json +648 -0
- package/recipes/flyers/lumio-stack.json +438 -0
- package/recipes/flyers/magazine-cover.json +168 -0
- package/recipes/flyers/magazine-extended.json +269 -0
- package/recipes/flyers/magazine-knockout.json +40 -0
- package/recipes/flyers/northfold-varsity.json +618 -0
- package/recipes/flyers/open-studio.json +69 -0
- package/recipes/flyers/orrin-merch.json +715 -0
- package/recipes/flyers/oxyde.json +113 -0
- package/recipes/flyers/quiet-fields.json +610 -0
- package/recipes/flyers/scrapbook-halcyon.json +948 -0
- package/recipes/flyers/solenne-ticket.json +730 -0
- package/recipes/flyers/solvane-motion.json +576 -0
- package/recipes/flyers/solvane.json +98 -0
- package/recipes/flyers/subduct.json +99 -0
- package/recipes/flyers/sunvale-lineup.json +584 -0
- package/recipes/flyers/tidewell.json +91 -0
- package/recipes/flyers/tour-kairo.json +535 -0
- package/recipes/flyers/velora-stickers.json +885 -0
- package/recipes/flyers/wheatpaste-black.json +313 -0
- package/recipes/flyers/wheatpaste-mark.json +198 -0
- package/recipes/flyers/wheatpaste-torn.json +114 -0
- package/recipes/flyers/wheatpaste-wall.json +984 -0
- package/recipes/flyers/wheatpaste-words.json +233 -0
- package/recipes/release/aura.json +137 -0
- package/recipes/release/chrome.json +433 -0
- package/recipes/release/collage.json +372 -0
- package/recipes/release/duotone.json +113 -0
- package/recipes/release/film-still.json +102 -0
- package/recipes/release/headline.json +70 -0
- package/recipes/release/instant.json +170 -0
- package/recipes/release/label.json +364 -0
- package/recipes/release/liquid-glass.json +301 -0
- package/recipes/release/odd-tile-dark.json +688 -0
- package/recipes/release/odd-tile-dense.json +1056 -0
- package/recipes/release/odd-tile-shift.json +541 -0
- package/recipes/release/odd-tile-split.json +571 -0
- package/recipes/release/odd-tile.json +633 -0
- package/recipes/release/off-edge.json +96 -0
- package/recipes/release/plinth.json +794 -0
- package/recipes/release/riso.json +108 -0
- package/recipes/release/series/fluid-label.json +561 -0
- package/recipes/release/series/fluid-pixel.json +334 -0
- package/recipes/release/series/hairline.json +305 -0
- package/recipes/release/series/halo.json +282 -0
- package/recipes/release/series/mark-crop.json +135 -0
- package/recipes/release/series/mark-window.json +150 -0
- package/recipes/release/series/photo-label.json +475 -0
- package/recipes/release/series/smear-field.json +345 -0
- package/recipes/release/series/smear.json +202 -0
- package/recipes/release/series/spin.json +172 -0
- package/recipes/release/series/split-ghost.json +230 -0
- package/recipes/release/series/zoom.json +168 -0
- package/recipes/release/small-print.json +114 -0
- package/recipes/release/stack.json +156 -0
- package/recipes/release/swiss.json +117 -0
- package/schema/recipe.schema.json +7624 -827
- package/src/animate/index.d.ts +9 -0
- package/src/animate/index.js +11 -0
- package/src/animate/keyframes.d.ts +21 -0
- package/src/animate/keyframes.js +119 -0
- package/src/animate/marquee.d.ts +30 -0
- package/src/animate/marquee.js +93 -0
- package/src/animate/morph.d.ts +8 -0
- package/src/animate/morph.js +99 -0
- package/src/animate/motion.d.ts +59 -0
- package/src/animate/motion.js +63 -0
- package/src/animate/sheet.d.ts +19 -0
- package/src/animate/sheet.js +61 -0
- package/src/animate/timing.d.ts +28 -0
- package/src/animate/timing.js +146 -0
- package/src/animate/tracks.d.ts +18 -0
- package/src/animate/tracks.js +139 -0
- package/src/builtins.js +4 -0
- package/src/diagram.js +4 -3
- package/src/extract.js +6 -5
- package/src/fonts.d.ts +55 -0
- package/src/fonts.js +245 -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 +5 -35
- package/src/grid.js +1 -1
- package/src/handmade.js +9 -7
- package/src/icons.js +42 -9
- package/src/index.d.ts +232 -55
- package/src/index.js +14 -6
- package/src/inter-data.js +27 -0
- package/src/json-recipe.js +201 -27
- package/src/kit/index.d.ts +20 -0
- package/src/kit/index.js +58 -0
- package/src/layer-options.js +43 -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 +108 -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 +61 -0
- package/src/layers.js +129 -61
- 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/paint/index.d.ts +49 -0
- package/src/paint/index.js +66 -0
- package/src/paint/presets.d.ts +10 -0
- package/src/paint/presets.js +47 -0
- package/src/paint/tile.d.ts +7 -0
- package/src/paint/tile.js +55 -0
- package/src/palette.js +1 -1
- package/src/platform/files.d.ts +6 -0
- package/src/platform/files.js +41 -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 +15 -0
- package/src/platform/load.d.ts +6 -0
- package/src/platform/load.js +71 -0
- package/src/platform/no-harfbuzz.d.ts +2 -0
- package/src/platform/no-harfbuzz.js +11 -0
- package/src/platform/settings.d.ts +28 -0
- package/src/platform/settings.js +51 -0
- package/src/print.js +16 -13
- package/src/recipes.js +5 -4
- package/src/render/backdrop.d.ts +6 -0
- package/src/render/backdrop.js +55 -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 +90 -0
- package/src/render/report.d.ts +5 -0
- package/src/render/report.js +19 -0
- package/src/render/transform.d.ts +36 -0
- package/src/render/transform.js +139 -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 +144 -0
- package/src/render.js +343 -215
- package/src/text.d.ts +29 -0
- package/src/text.js +23 -16
- 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 +7 -0
- package/src/type/pixel.js +85 -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 +363 -0
- package/src/type/woff2.d.ts +6 -0
- package/src/type/woff2.js +301 -0
- package/src/version.js +4 -0
- package/src/color.js +0 -246
- package/src/png.js +0 -77
- package/src/rng.js +0 -95
- package/src/svg.js +0 -69
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Motion timing: presets, defaults, easing curves, and where a motion is at
|
|
3
|
+
* any time, with CSS animation semantics (so a still drawn at `t` matches
|
|
4
|
+
* what a browser shows at `t`).
|
|
5
|
+
*
|
|
6
|
+
* @module animate/timing
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { num } from '@logomarks/image-effects/svg';
|
|
10
|
+
|
|
11
|
+
/** Named motions: each is a motion's defaults, under whatever else you set. */
|
|
12
|
+
export const MOTION_PRESETS = Object.freeze({
|
|
13
|
+
drift: Object.freeze({ translate: ['3vmin', '-2vmin'], duration: 20, easing: 'inOut', direction: 'alternate' }),
|
|
14
|
+
spin: Object.freeze({ rotate: 360, duration: 30, easing: 'linear' }),
|
|
15
|
+
breathe: Object.freeze({ scale: 1.06, duration: 3, easing: 'inOut', direction: 'alternate' }),
|
|
16
|
+
blink: Object.freeze({ opacity: [1, 0, 1], duration: 1, easing: 'step' }),
|
|
17
|
+
marquee: Object.freeze({ marquee: true, duration: 10, easing: 'linear' }),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
/** Timing defaults. */
|
|
21
|
+
const DEFAULTS = { duration: 2, delay: 0, easing: 'linear', direction: 'normal', iterations: Infinity };
|
|
22
|
+
|
|
23
|
+
/** Directions, as CSS names them. */
|
|
24
|
+
const DIRECTIONS = ['normal', 'reverse', 'alternate', 'alternate-reverse'];
|
|
25
|
+
|
|
26
|
+
/** Named easing curves as cubic Béziers (CSS's), and the step. */
|
|
27
|
+
const CURVES = /** @type {Record<string, [number, number, number, number]>} */ ({
|
|
28
|
+
linear: [0, 0, 1, 1],
|
|
29
|
+
ease: [0.25, 0.1, 0.25, 1],
|
|
30
|
+
in: [0.42, 0, 1, 1],
|
|
31
|
+
out: [0, 0, 0.58, 1],
|
|
32
|
+
inOut: [0.42, 0, 0.58, 1],
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A layer's `motion` as a list of resolved motions: presets expanded,
|
|
37
|
+
* timing defaults filled in, `iterations: 'infinite'` as `Infinity`.
|
|
38
|
+
*
|
|
39
|
+
* @param {unknown} v - A motion, a preset name, or a list of them.
|
|
40
|
+
* @returns {import('./timing.js').ResolvedMotion[]}
|
|
41
|
+
* @throws {TypeError} On an unknown preset, direction or easing, or a duration that isn't positive.
|
|
42
|
+
*/
|
|
43
|
+
export function motionList(v) {
|
|
44
|
+
return (Array.isArray(v) ? v : [v]).filter((m) => m != null && m !== false).map((m) => {
|
|
45
|
+
const own = typeof m === 'string' ? { preset: m } : /** @type {any} */ (m);
|
|
46
|
+
const preset = own.preset == null ? {} : MOTION_PRESETS[/** @type {keyof typeof MOTION_PRESETS} */ (own.preset)];
|
|
47
|
+
if (!preset) throw new TypeError(`Unknown motion preset "${own.preset}" (${Object.keys(MOTION_PRESETS).join(', ')})`);
|
|
48
|
+
const r = { ...DEFAULTS, ...preset, ...own };
|
|
49
|
+
if (r.iterations === 'infinite') r.iterations = Infinity;
|
|
50
|
+
if (!(r.duration > 0)) throw new TypeError(`motion: duration must be a positive number of seconds, got ${r.duration}`);
|
|
51
|
+
if (!(r.iterations > 0)) throw new TypeError(`motion: iterations must be positive or "infinite", got ${r.iterations}`);
|
|
52
|
+
if (!DIRECTIONS.includes(r.direction)) throw new TypeError(`motion: unknown direction "${r.direction}" (${DIRECTIONS.join(', ')})`);
|
|
53
|
+
easing(r.easing);
|
|
54
|
+
return r;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* An easing as a function on 0–1.
|
|
60
|
+
*
|
|
61
|
+
* @param {import('./timing.js').Easing} e
|
|
62
|
+
* @returns {(x: number) => number}
|
|
63
|
+
* @throws {TypeError} On an unknown name.
|
|
64
|
+
*/
|
|
65
|
+
export function easing(e) {
|
|
66
|
+
if (e === 'step') return (x) => (x >= 1 ? 1 : 0);
|
|
67
|
+
const c = Array.isArray(e) ? e : CURVES[/** @type {string} */ (e)];
|
|
68
|
+
if (!c || c.length !== 4) throw new TypeError(`motion: unknown easing ${JSON.stringify(e)} (${[...Object.keys(CURVES), 'step'].join(', ')}, or [x1, y1, x2, y2])`);
|
|
69
|
+
return bezier(c[0], c[1], c[2], c[3]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* An easing as CSS writes it.
|
|
74
|
+
*
|
|
75
|
+
* @param {import('./timing.js').Easing} e
|
|
76
|
+
* @returns {string}
|
|
77
|
+
*/
|
|
78
|
+
export function cssEasing(e) {
|
|
79
|
+
if (e === 'step') return 'steps(1,end)';
|
|
80
|
+
if (e === 'linear') return 'linear';
|
|
81
|
+
const [a, b, c, d] = Array.isArray(e) ? e : CURVES[/** @type {string} */ (e)];
|
|
82
|
+
return `cubic-bezier(${[a, b, c, d].map((v) => num(v, 4)).join(',')})`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* A CSS cubic-bezier timing function, solved for y at x.
|
|
87
|
+
*
|
|
88
|
+
* @param {number} x1 @param {number} y1 @param {number} x2 @param {number} y2
|
|
89
|
+
* @returns {(x: number) => number}
|
|
90
|
+
*/
|
|
91
|
+
function bezier(x1, y1, x2, y2) {
|
|
92
|
+
const at = (/** @type {number} */ t, /** @type {number} */ a, /** @type {number} */ b) => 3 * a * t * (1 - t) ** 2 + 3 * b * t * t * (1 - t) + t ** 3;
|
|
93
|
+
return (x) => {
|
|
94
|
+
if (x <= 0) return 0;
|
|
95
|
+
if (x >= 1) return 1;
|
|
96
|
+
let lo = 0;
|
|
97
|
+
let hi = 1;
|
|
98
|
+
let t = x;
|
|
99
|
+
for (let i = 0; i < 40; i++) {
|
|
100
|
+
const v = at(t, x1, x2);
|
|
101
|
+
if (Math.abs(v - x) < 1e-7) break;
|
|
102
|
+
if (v < x) lo = t; else hi = t;
|
|
103
|
+
t = (lo + hi) / 2;
|
|
104
|
+
}
|
|
105
|
+
return at(t, y1, y2);
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* How far through its stops a motion is at `t` seconds (0–1, direction
|
|
111
|
+
* applied), as CSS plays it with `animation-fill-mode: both`: before its
|
|
112
|
+
* delay it holds the start, after its last iteration the end.
|
|
113
|
+
*
|
|
114
|
+
* @param {import('./timing.js').ResolvedMotion} m
|
|
115
|
+
* @param {number} t
|
|
116
|
+
* @returns {number}
|
|
117
|
+
*/
|
|
118
|
+
export function progress(m, t) {
|
|
119
|
+
const local = t - m.delay;
|
|
120
|
+
let iteration = 0;
|
|
121
|
+
let p = 0;
|
|
122
|
+
if (local >= m.duration * m.iterations) {
|
|
123
|
+
iteration = Math.ceil(m.iterations) - 1;
|
|
124
|
+
p = m.iterations - iteration;
|
|
125
|
+
} else if (local > 0) {
|
|
126
|
+
const whole = local / m.duration;
|
|
127
|
+
iteration = Math.floor(whole);
|
|
128
|
+
p = whole - iteration;
|
|
129
|
+
}
|
|
130
|
+
const back = m.direction === 'reverse'
|
|
131
|
+
|| (m.direction === 'alternate' && iteration % 2 === 1)
|
|
132
|
+
|| (m.direction === 'alternate-reverse' && iteration % 2 === 0);
|
|
133
|
+
return back ? 1 - p : p;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* A motion's CSS `animation` timing (everything but the name).
|
|
138
|
+
*
|
|
139
|
+
* @param {import('./timing.js').ResolvedMotion} m
|
|
140
|
+
* @param {{ linear?: boolean, delay?: number }} [o] - `linear`: the stops were sampled with the easing already applied.
|
|
141
|
+
* @returns {string}
|
|
142
|
+
*/
|
|
143
|
+
export function cssTiming(m, o = {}) {
|
|
144
|
+
const iterations = m.iterations === Infinity ? 'infinite' : num(m.iterations, 3);
|
|
145
|
+
return `${num(m.duration, 3)}s ${o.linear ? 'linear' : cssEasing(m.easing)} ${num(o.delay ?? m.delay, 3)}s ${iterations} ${m.direction} both`;
|
|
146
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// A motion's transform and opacity as tracks of stops.
|
|
2
|
+
import type { ResolvedMotion } from './timing.js';
|
|
3
|
+
|
|
4
|
+
/** One animated property: its stops (evenly spaced in time) and how a value is written. */
|
|
5
|
+
export interface Track<T = any> {
|
|
6
|
+
stops: T[];
|
|
7
|
+
/** Stops sampled along a path with the easing applied (CSS plays them linearly). */
|
|
8
|
+
sampled?: boolean;
|
|
9
|
+
/** A value as a CSS declaration, for keyframes. */
|
|
10
|
+
css(v: T): string;
|
|
11
|
+
/** A value as SVG attributes, for the still (`null` when it's the layer at rest). */
|
|
12
|
+
attr(v: T): Record<string, string> | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** A value at progress `p` (0–1) through evenly spaced stops, eased between each pair. */
|
|
16
|
+
export function atStops<T>(stops: T[], p: number, ease: (x: number) => number): T;
|
|
17
|
+
/** A motion's tracks, outermost first: translate (or path), rotate, scale, opacity. */
|
|
18
|
+
export function tracks(m: ResolvedMotion, canvas: { width: number; height: number }): Track[];
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A motion's transform and opacity as tracks: each is a list of stops (a
|
|
3
|
+
* value per stop, evenly spaced in time) and how one value is written, as a
|
|
4
|
+
* CSS declaration for keyframes and as SVG attributes for a still.
|
|
5
|
+
*
|
|
6
|
+
* - `translate`: `[x, y]` to move by (from where the layer is), or a list
|
|
7
|
+
* of points to visit (`[[0, 0], [x, y], …]`); px in the layer's grid, `'%'`
|
|
8
|
+
* of it, or lengths (`'4vw'`).
|
|
9
|
+
* - `path`: travel a path from where the layer is (`{ d }` or a text-path
|
|
10
|
+
* kind: `circle`, `ellipse`, `arc`, `wave`); the easing spans the whole path.
|
|
11
|
+
* - `rotate`: degrees to turn to, or a list of angles.
|
|
12
|
+
* - `scale`, `scaleX`, `scaleY`: a factor to grow to, or a list of factors.
|
|
13
|
+
* - `opacity`: 0–1 to fade to (multiplying the layer's own), or a list.
|
|
14
|
+
*
|
|
15
|
+
* Turns and scales are about `origin` (`[x, y]`, default the centre).
|
|
16
|
+
*
|
|
17
|
+
* @module animate/tracks
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { num } from '@logomarks/image-effects/svg';
|
|
21
|
+
import { textPathD, pathSampler } from '../type/path.js';
|
|
22
|
+
import { pos } from '../layers/util.js';
|
|
23
|
+
import { originOf } from '../render/transform.js';
|
|
24
|
+
import { interpolate } from './keyframes.js';
|
|
25
|
+
import { easing } from './timing.js';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A value at progress `p` (0–1) through evenly spaced stops, eased between
|
|
29
|
+
* each pair (as CSS eases between keyframes).
|
|
30
|
+
*
|
|
31
|
+
* @param {any[]} stops
|
|
32
|
+
* @param {number} p
|
|
33
|
+
* @param {(x: number) => number} ease
|
|
34
|
+
* @returns {any}
|
|
35
|
+
*/
|
|
36
|
+
export function atStops(stops, p, ease) {
|
|
37
|
+
if (stops.length === 1) return stops[0];
|
|
38
|
+
const x = Math.max(0, Math.min(1, p)) * (stops.length - 1);
|
|
39
|
+
const k = Math.min(Math.floor(x), stops.length - 2);
|
|
40
|
+
return interpolate(stops[k], stops[k + 1], ease(x - k));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A scalar option as stops: a list as given, one value as rest → value.
|
|
45
|
+
*
|
|
46
|
+
* @param {unknown} v @param {number} rest
|
|
47
|
+
* @returns {number[] | undefined}
|
|
48
|
+
*/
|
|
49
|
+
const scalar = (v, rest) => (v == null ? undefined : Array.isArray(v) ? v.map(Number) : [rest, Number(v)]);
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* A motion's tracks, outermost first: translate (or path), rotate, scale,
|
|
53
|
+
* opacity.
|
|
54
|
+
*
|
|
55
|
+
* @param {import('./timing.js').ResolvedMotion} m
|
|
56
|
+
* @param {{ width: number, height: number }} canvas - The layer's grid.
|
|
57
|
+
* @returns {import('./tracks.js').Track[]}
|
|
58
|
+
* @throws {TypeError} When `translate` and `path` are both set, or scale lists differ in length.
|
|
59
|
+
*/
|
|
60
|
+
export function tracks(m, canvas) {
|
|
61
|
+
/** @type {import('./tracks.js').Track[]} */
|
|
62
|
+
const out = [];
|
|
63
|
+
const px = (/** @type {any} */ p) => [pos(p[0] ?? 0, 'x', canvas), pos(p[1] ?? 0, 'y', canvas)];
|
|
64
|
+
if (m.translate != null && m.path != null) throw new TypeError('motion: give translate or path, not both');
|
|
65
|
+
if (m.translate != null) {
|
|
66
|
+
const list = Array.isArray(m.translate[0]) ? m.translate : [[0, 0], m.translate];
|
|
67
|
+
out.push(translateTrack(list.map(px), false));
|
|
68
|
+
} else if (m.path != null) {
|
|
69
|
+
out.push(translateTrack(pathStops(m.path, canvas, easing(m.easing)), true));
|
|
70
|
+
}
|
|
71
|
+
const [ox, oy] = originOf(m.origin, canvas);
|
|
72
|
+
const cssAbout = (/** @type {string} */ fn) => `translate(${num(ox)}px,${num(oy)}px) ${fn} translate(${num(-ox)}px,${num(-oy)}px)`;
|
|
73
|
+
const svgAbout = (/** @type {string} */ fn) => `translate(${num(ox)} ${num(oy)}) ${fn} translate(${num(-ox)} ${num(-oy)})`;
|
|
74
|
+
const rotate = scalar(m.rotate, 0);
|
|
75
|
+
if (rotate) {
|
|
76
|
+
out.push({
|
|
77
|
+
stops: rotate,
|
|
78
|
+
css: (a) => `transform:${cssAbout(`rotate(${num(a, 3)}deg)`)}`,
|
|
79
|
+
attr: (a) => (a ? { transform: `rotate(${num(a, 3)} ${num(ox)} ${num(oy)})` } : null),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
const s = scalar(m.scale, 1);
|
|
83
|
+
const sx = scalar(m.scaleX, 1);
|
|
84
|
+
const sy = scalar(m.scaleY, 1);
|
|
85
|
+
if (s || sx || sy) {
|
|
86
|
+
const n = Math.max(s?.length ?? 0, sx?.length ?? 0, sy?.length ?? 0);
|
|
87
|
+
if ([s, sx, sy].some((l) => l && l.length !== n)) throw new TypeError('motion: scale, scaleX and scaleY need the same number of stops');
|
|
88
|
+
const stops = Array.from({ length: n }, (_, i) => [(s?.[i] ?? 1) * (sx?.[i] ?? 1), (s?.[i] ?? 1) * (sy?.[i] ?? 1)]);
|
|
89
|
+
out.push({
|
|
90
|
+
stops,
|
|
91
|
+
css: ([a, b]) => `transform:${cssAbout(`scale(${num(a, 4)},${num(b, 4)})`)}`,
|
|
92
|
+
attr: ([a, b]) => (a === 1 && b === 1 ? null : { transform: svgAbout(`scale(${num(a, 4)} ${num(b, 4)})`) }),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
const opacity = scalar(m.opacity, 1);
|
|
96
|
+
if (opacity) {
|
|
97
|
+
out.push({
|
|
98
|
+
stops: opacity,
|
|
99
|
+
css: (o) => `opacity:${num(o, 3)}`,
|
|
100
|
+
attr: (o) => (o === 1 ? null : { opacity: num(Math.max(0, o), 3) }),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* A translate track.
|
|
108
|
+
*
|
|
109
|
+
* @param {[number, number][]} stops - px.
|
|
110
|
+
* @param {boolean} sampled - Stops sampled along a path (eased already).
|
|
111
|
+
* @returns {import('./tracks.js').Track}
|
|
112
|
+
*/
|
|
113
|
+
function translateTrack(stops, sampled) {
|
|
114
|
+
return {
|
|
115
|
+
stops,
|
|
116
|
+
sampled,
|
|
117
|
+
css: ([x, y]) => `transform:translate(${num(x)}px,${num(y)}px)`,
|
|
118
|
+
attr: ([x, y]) => (x || y ? { transform: `translate(${num(x)} ${num(y)})` } : null),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Points along a path, relative to its start, sampled evenly in time with
|
|
124
|
+
* the easing applied over the whole path.
|
|
125
|
+
*
|
|
126
|
+
* @param {any} path - `{ d }`, a text-path kind, or path data.
|
|
127
|
+
* @param {{ width: number, height: number }} canvas
|
|
128
|
+
* @param {(x: number) => number} ease
|
|
129
|
+
* @returns {[number, number][]}
|
|
130
|
+
*/
|
|
131
|
+
function pathStops(path, canvas, ease) {
|
|
132
|
+
const sampler = pathSampler(textPathD(path, canvas));
|
|
133
|
+
const n = Math.max(16, Math.min(240, Math.ceil(sampler.length / 4)));
|
|
134
|
+
const o = sampler.at(0);
|
|
135
|
+
return Array.from({ length: n + 1 }, (_, i) => {
|
|
136
|
+
const at = sampler.at(ease(i / n) * sampler.length);
|
|
137
|
+
return /** @type {[number, number]} */ ([at.x - o.x, at.y - o.y]);
|
|
138
|
+
});
|
|
139
|
+
}
|
package/src/builtins.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Generated by scripts/generate.mjs from recipes/*.json. Do not edit.
|
|
2
|
+
|
|
3
|
+
/** The built-in recipe documents, by name. */
|
|
4
|
+
export const BUILTIN_JSON = {"aurora":{"$schema":"../schema/recipe.schema.json","name":"aurora","description":"Full-bleed sweeps of the palette's colours, sometimes folded or streaked, with grain (the openai.com/news look). A title sets in the corner only when given.","layers":[{"id":"bg","type":"solid"},{"id":"mesh","type":"mesh","count":4,"blur":"11vmax","size":[0.45,0.8],"streak":1.8,"tone":{"if":"title","then":{"if":{"mode":"dark"},"then":{"l":[0.34,0.5],"c":[0.12,0.22]},"else":{"l":[0.66,0.82],"c":[0.13,0.2]}},"else":{"l":[0.55,0.85],"c":[0.12,0.22]}},"fold":{"if":"title","then":false,"else":{"pick":[false,false,true]}},"streaks":{"if":"title","then":0,"else":{"pick":[0,0,0,6]}},"hues":{"pick":[2,2,3]},"hueSpan":60},{"id":"grain","type":"grain","amount":0.09,"vary":{"pick":["edges",null]}},{"id":"title","type":"text","when":"title","content":"{title}","anchor":"bottom-left","weight":500,"size":{"mul":["@u",0.066]},"minSize":{"mul":["@u",0.048]},"lineHeight":1.08,"letterSpacing":-0.025,"maxLines":3,"label":{"if":"label","then":{"color":"@text","content":"{label}"}}}]},"bauhaus":{"$schema":"../schema/recipe.schema.json","name":"bauhaus","description":"A Bauhaus grid of quarter circles, half circles, circles, squares and triangles in the palette's colours and the page colour. A shared `series` keeps a family's grid rhythm while each cover's seed fills the cells.","params":{"series":null},"layers":[{"id":"bg","type":"solid"},{"id":"tiles","type":"tiles","series":"{series}","colors":[{"tone":["@accent",{"l":[0.5,0.75],"c":[0.12,0.22]}]},{"tone":["@colors.1",{"l":[0.5,0.75],"c":[0.12,0.22]}]},{"tone":["@colors.2",{"l":[0.5,0.75],"c":[0.12,0.22]}]},"@text"],"ground":"@bg","region":{"if":"title","then":{"x":0,"y":0,"width":1,"height":0.72},"else":{"x":0,"y":0,"width":1,"height":1}}},{"id":"title","type":"text","when":"title","content":"{title}","anchor":"bottom-left","weight":500,"size":{"mul":["@u",0.056]},"minSize":{"mul":["@u",0.042]},"lineHeight":1.08,"letterSpacing":-0.025,"maxLines":2,"label":{"if":"label","then":{"color":"@text","content":"{label}"}}}]},"bigType":{"$schema":"../schema/recipe.schema.json","name":"bigType","description":"The headline is the cover: large, tight, shrinking to fit four lines, with the label in the accent at the top.","vars":{"corner":{"pick":[[1,0],[0,0],[1,1],[0.5,0]]}},"layers":[{"id":"bg","type":"solid"},{"id":"wash","type":"radial","colors":[{"mix":["@bg","@accent",{"if":{"mode":"dark"},"then":0.16,"else":0.12}]},"@bg"],"x":"$corner.0","y":"$corner.1","radius":"110vmax"},{"id":"grain","type":"grain","amount":0.05},{"id":"label","type":"text","when":"label","content":"{label}","anchor":"top-left","size":{"mul":["@u",0.024]},"weight":600,"letterSpacing":0.09,"transform":"uppercase","color":"@accent","maxLines":1,"balance":false},{"id":"title","type":"text","when":"title","content":"{title}","anchor":"bottom-left","weight":600,"size":{"mul":["@u",0.13]},"minSize":{"mul":["@u",0.078]},"lineHeight":0.98,"letterSpacing":-0.045,"maxLines":4}]},"brand":{"$schema":"../schema/recipe.schema.json","name":"brand","description":"A brand on every format: avatars, headers and banners, posts and stories, business cards (front and back), letterhead, compliment slips, tickets, passes and stickers. kit() uses it by default. Options: name, tagline, mark (path markup on a 512 grid), photo, lines (contact or info lines), person and role (cards and passes), ticket (a ticket number), compliments.","pages":["front","back"],"params":{"tagline":""},"layers":[{"id":"bg","type":"solid","color":"@accent","when":{"not":{"any":[{"format":["letterhead","letterhead-us","compliment-slip","badge","lanyard-pass"]},{"all":[{"format":["business-card","business-card-us"]},{"page":"back"}]}]}}},{"id":"bg","type":"solid","color":"@bg","when":{"any":[{"format":["letterhead","letterhead-us","compliment-slip","badge","lanyard-pass"]},{"all":[{"format":["business-card","business-card-us"]},{"page":"back"}]}]}},{"id":"photo","type":"image","src":"{photo}","when":{"all":["photo",{"not":{"format":["avatar","x-avatar","youtube-avatar","sticker","sticker-square","business-card","business-card-us","letterhead","letterhead-us","compliment-slip","badge","lanyard-pass","ticket"]}}]},"effects":[{"kind":"duotone","colors":[{"mix":["@accent","#000000",0.6]},"@accent",{"mix":["@accent","#ffffff",0.65]}]}],"opacity":0.9},{"id":"scrim","type":"linear","angle":180,"colors":["@accent","@accent"],"opacities":[0,0.75],"when":{"all":["photo",{"not":{"format":["avatar","x-avatar","youtube-avatar","sticker","sticker-square","business-card","business-card-us","letterhead","letterhead-us","compliment-slip","badge","lanyard-pass","ticket"]}}]}},{"id":"mark","type":"icon","markup":"{mark}","anchor":"center","size":{"mul":[{"min":["@safeWidth","@safeHeight"]},0.86]},"color":"@onAccent","when":{"all":["mark",{"format":["avatar","x-avatar","youtube-avatar","sticker","sticker-square"]}]}},{"id":"initial","type":"text","content":{"upper":{"slice":["{name}",0,1]}},"anchor":"center","align":"center","size":{"mul":[{"min":["@safeWidth","@safeHeight"]},0.62]},"weight":700,"letterSpacing":0,"color":"@onAccent","when":{"all":[{"not":"mark"},"name",{"format":["avatar","x-avatar","youtube-avatar","sticker","sticker-square"]}]}},{"id":"mark","type":"icon","markup":"{mark}","anchor":"left","size":{"mul":["@safeHeight",0.6]},"color":"@onAccent","when":{"all":["mark",{"all":[{"not":{"format":["avatar","x-avatar","youtube-avatar","sticker","sticker-square","business-card","business-card-us","letterhead","letterhead-us","compliment-slip","badge","lanyard-pass","ticket"]}},{"aspect":"wide"}]}]}},{"id":"name","type":"text","content":"{name}","label":{"content":"{tagline}","color":{"mix":["@onAccent","@accent",0.3]}},"anchor":"left","weight":700,"letterSpacing":-0.03,"offset":{"if":"mark","then":{"x":{"mul":["@safeHeight",0.74]}},"else":{"x":0}},"maxWidth":{"if":"mark","then":{"sub":["@safeWidth",{"mul":["@safeHeight",0.9]}]},"else":{"mul":["@safeWidth",0.9]}},"size":{"min":[{"mul":["@safeHeight",0.3]},{"mul":["@safeWidth",0.09]}]},"minSize":{"mul":["@safeHeight",0.14]},"maxLines":2,"color":"@onAccent","when":{"all":["name",{"all":[{"not":{"format":["avatar","x-avatar","youtube-avatar","sticker","sticker-square","business-card","business-card-us","letterhead","letterhead-us","compliment-slip","badge","lanyard-pass","ticket"]}},{"aspect":"wide"}]}]}},{"id":"mark","type":"icon","markup":"{mark}","anchor":"center","offset":{"y":{"mul":["@safeHeight",-0.1]}},"size":{"mul":[{"min":["@safeWidth","@safeHeight"]},0.42]},"color":"@onAccent","when":{"all":["mark",{"all":[{"not":{"format":["avatar","x-avatar","youtube-avatar","sticker","sticker-square","business-card","business-card-us","letterhead","letterhead-us","compliment-slip","badge","lanyard-pass","ticket"]}},{"not":{"aspect":"wide"}}]}]}},{"id":"name","type":"text","content":"{name}","label":{"content":"{tagline}","color":{"mix":["@onAccent","@accent",0.3]}},"anchor":{"if":"mark","then":"bottom","else":"center"},"align":"center","weight":700,"letterSpacing":-0.03,"size":{"mul":["@safeWidth",0.1]},"minSize":{"mul":["@safeWidth",0.06]},"maxLines":2,"color":"@onAccent","when":{"all":["name",{"all":[{"not":{"format":["avatar","x-avatar","youtube-avatar","sticker","sticker-square","business-card","business-card-us","letterhead","letterhead-us","compliment-slip","badge","lanyard-pass","ticket"]}},{"not":{"aspect":"wide"}}]}]}},{"id":"mark","type":"icon","markup":"{mark}","anchor":"center","offset":{"y":{"mul":["@trimHeight",-0.08]}},"size":{"mul":["@trimHeight",0.4]},"color":"@onAccent","when":{"all":["mark",{"all":[{"format":["business-card","business-card-us"]},{"not":{"page":"back"}}]}]}},{"id":"name","type":"text","content":"{name}","anchor":"bottom","align":"center","weight":700,"letterSpacing":-0.02,"size":{"mul":["@trimHeight",0.1]},"maxLines":1,"color":"@onAccent","when":{"all":["name",{"all":[{"format":["business-card","business-card-us"]},{"not":{"page":"back"}}]}]}},{"id":"person","type":"text","content":"{opt.person}","label":"{opt.role}","anchor":"top-left","weight":700,"letterSpacing":-0.02,"size":{"mul":["@trimHeight",0.095]},"maxLines":1,"color":"@text","when":{"all":["opt.person",{"all":[{"format":["business-card","business-card-us"]},{"page":"back"}]}]}},{"id":"lines","type":"table","rows":"{lines}","x":"@safeX","y":{"mul":["@height",0.6]},"columns":[{"x":"@safeX"}],"size":{"mul":["@trimHeight",0.058]},"color":"@text","when":{"all":["lines",{"all":[{"format":["business-card","business-card-us"]},{"page":"back"}]}]}},{"id":"mark","type":"icon","markup":"{mark}","anchor":"bottom-right","size":{"mul":["@trimHeight",0.22]},"color":"@accent","when":{"all":["mark",{"all":[{"format":["business-card","business-card-us"]},{"page":"back"}]}]}},{"id":"mark","type":"icon","markup":"{mark}","anchor":"top-left","size":{"mul":["@trimWidth",0.085]},"color":"@accent","when":{"all":["mark",{"format":["letterhead","letterhead-us","compliment-slip"]}]}},{"id":"name","type":"text","content":"{name}","label":{"content":"{tagline}","color":"@muted"},"anchor":"top-right","align":"right","weight":700,"letterSpacing":-0.02,"size":{"mul":["@trimWidth",0.03]},"maxLines":1,"color":"@text","when":{"all":["name",{"format":["letterhead","letterhead-us","compliment-slip"]}]}},{"id":"compliments","type":"text","content":"{opt.compliments}","anchor":"left","weight":400,"size":{"mul":["@trimHeight",0.09]},"color":"@text","when":{"all":["opt.compliments",{"format":"compliment-slip"}]}},{"id":"rule","type":"shape","kind":"line","x1":"@safeX","x2":{"add":["@safeX","@safeWidth"]},"y1":{"sub":[{"add":["@safeY","@safeHeight"]},{"mul":["@trimHeight",{"if":{"format":"compliment-slip"},"then":0.16,"else":0.05}]}]},"stroke":"@accent","strokeWidth":{"mul":["@mm",0.3]},"when":{"format":["letterhead","letterhead-us","compliment-slip"]}},{"id":"lines","type":"table","rows":["{lines}"],"y":{"sub":[{"add":["@safeY","@safeHeight"]},{"mul":["@trimHeight",{"if":{"format":"compliment-slip"},"then":0.08,"else":0.025}]}]},"columns":[{"x":"@safeX"},{"x":"37%"},{"x":"68%"}],"size":{"mul":["@mm",2.6]},"color":"@muted","when":{"all":["lines",{"format":["letterhead","letterhead-us","compliment-slip"]}]}},{"id":"band","type":"shape","kind":"rect","x":0,"y":0,"width":"@width","height":{"mul":["@height",0.36]},"fill":"@accent","when":{"format":["badge","lanyard-pass"]}},{"id":"mark","type":"icon","markup":"{mark}","anchor":"top","size":{"mul":["@trimHeight",0.2]},"color":"@onAccent","when":{"all":["mark",{"format":["badge","lanyard-pass"]}]}},{"id":"name","type":"text","content":"{opt.person}","label":"{opt.role}","anchor":"center","align":"center","offset":{"y":{"mul":["@height",0.1]}},"weight":700,"letterSpacing":-0.03,"size":{"mul":["@trimWidth",0.12]},"maxLines":2,"color":"@text","when":{"all":["opt.person",{"format":["badge","lanyard-pass"]}]}},{"id":"footer","type":"text","content":"{name}","anchor":"bottom","align":"center","weight":600,"size":{"mul":["@trimWidth",0.055]},"letterSpacing":0.08,"transform":"uppercase","color":"@accent","when":{"all":["name",{"format":["badge","lanyard-pass"]}]}},{"id":"name","type":"text","content":"{name}","label":{"content":"{tagline}","color":{"mix":["@onAccent","@accent",0.3]}},"anchor":"left","weight":700,"letterSpacing":-0.03,"size":{"mul":["@trimHeight",0.24]},"maxWidth":{"mul":["@trimWidth",0.6]},"maxLines":2,"color":"@onAccent","when":{"all":["name",{"format":["ticket"]}]}},{"id":"perforation","type":"perforation","x":"74%","y1":"@bleed","y2":{"sub":["@height","@bleed"]},"color":"@onAccent","size":{"mul":["@mm",0.5]},"gap":{"mul":["@mm",2.2]},"when":{"format":["ticket"]}},{"id":"barcode","type":"barcode","value":{"upper":"{opt.ticket}"},"x":"79%","y":"28%","width":"16%","height":"34%","text":true,"color":"@onAccent","when":{"all":["opt.ticket",{"format":["ticket"]}]}},{"id":"lines","type":"table","rows":["{lines.0}"],"y":{"mul":["@height",0.82]},"columns":[{"x":{"add":["@safeX",0]}}],"size":{"mul":["@trimHeight",0.065]},"color":"@onAccent","when":{"all":["lines",{"format":["ticket"]}]}}]},"contour":{"$schema":"../schema/recipe.schema.json","name":"contour","description":"Topographic isolines of seeded noise on a flat field: thin even lines, an index contour every fifth line, one line in the accent.","params":{"levels":null},"vars":{"field":{"mix":["@bg","@accent",{"if":{"mode":"dark"},"then":0.08,"else":0.05}]}},"layers":[{"id":"bg","type":"solid","color":"$field"},{"id":"contour","type":"contour","levels":{"if":"levels","then":"{levels}","else":{"pick":[14,18,22,26,30]}},"color":"@text","opacity":0.42,"index":5,"scale":{"range":[1.8,3.2]}},{"id":"grain","type":"grain","amount":0.035},{"id":"title","type":"text","when":"title","content":"{title}","anchor":"bottom-left","weight":500,"size":{"mul":["@u",0.062]},"minSize":{"mul":["@u",0.046]},"lineHeight":1.08,"letterSpacing":-0.025,"maxLines":3,"label":{"if":"label","then":{"color":"@text","content":"{label}"}},"background":{"color":"$field","radius":0,"padding":[{"mul":["@u",0.03]},{"mul":["@u",0.035]}]}}]},"cutout":{"$schema":"../schema/recipe.schema.json","name":"cutout","description":"A flat matte field (or a hand-cut panel on the page colour) with cut-paper shapes and a hand-drawn icon or node graph.","params":{"panel":false,"shapes":null,"graph":false,"stroke":null},"vars":{"field":{"tone":["@accent",{"l":[0.52,0.64],"c":[0.05,0.11]}]},"count":{"if":{"shapes":null},"then":{"if":"panel","then":0,"else":3},"else":"{shapes}"},"ink":{"if":{"stroke":"light"},"then":"#faf9f5","else":{"if":{"stroke":"dark"},"then":"#141413","else":{"if":"panel","then":"#faf9f5","else":{"if":"graph","then":"#141413","else":{"if":{"shapes":0},"then":"#faf9f5","else":"#141413"}}}}},"type":{"readable":[{"if":"panel","then":"@bg","else":"$field"},4.5,"white"]}},"layers":[{"id":"bg","type":"solid","color":{"if":"panel","then":"@bg","else":"$field"}},{"id":"panel","type":"paper","when":"panel","items":[{"kind":"rect","x":0.5,"y":0.5,"width":{"mul":["@width",0.64]},"height":{"mul":["@height",0.6]}}],"region":{"x":0,"y":0,"width":1,"height":1},"color":"$field","wobble":0.3},{"id":"shapes","type":"paper","count":"$count","region":{"x":0.24,"y":0.22,"width":0.52,"height":0.56}},{"id":"icon","type":"scribble","when":{"not":"graph"},"icon":"{icon}","color":"$ink","size":{"if":"panel","then":"34vmin","else":"44vmin"},"stroke":"1.6vmin"},{"id":"graph","type":"scribble","when":"graph","graph":true,"color":"$ink","size":"52vmin","stroke":"1.7vmin"},{"id":"grain","type":"grain","amount":0.035},{"id":"title","type":"text","when":"title","content":"{title}","anchor":"bottom-left","weight":500,"size":{"mul":["@u",0.066]},"minSize":{"mul":["@u",0.048]},"lineHeight":1.08,"letterSpacing":-0.025,"maxLines":3,"label":{"if":"label","then":{"color":"$type","content":"{label}"}},"color":"$type"}]},"diagram":{"$schema":"../schema/recipe.schema.json","name":"diagram","description":"White line-art over the gradient field: one icon, or nodes in outlined tiles joined by arrows, with a dotted grid that breaks around them (the engineering-blog look).","params":{"nodes":null,"layout":null,"arrows":null},"layers":[{"id":"bg","type":"solid"},{"id":"mesh","type":"mesh","count":4,"blur":"11vmax","size":[0.45,0.8],"streak":1.8,"tone":{"l":[0.4,0.6],"c":[0.14,0.24]},"fold":{"pick":[false,false,{"sheen":0}]},"hueSpan":60},{"id":"diagram","type":"diagram","icon":"{icon}","nodes":"{nodes}","layout":"{layout}","arrows":"{arrows}","grid":true},{"id":"grain","type":"grain","amount":0.07},{"id":"title","type":"text","when":"title","content":"{title}","anchor":"bottom-left","weight":500,"size":{"mul":["@u",0.066]},"minSize":{"mul":["@u",0.048]},"lineHeight":1.08,"letterSpacing":-0.025,"maxLines":3,"label":{"if":"label","then":{"color":"#ffffff","content":"{label}"}},"color":"#ffffff"}]},"dither":{"$schema":"../schema/recipe.schema.json","name":"dither","description":"A soft field (noise, a gradient, or a lit sphere / arch / circle) rendered 1-bit at a visible pixel size: Bayer 4×4 / 8×8 or blue-ish noise, in two colours or three.","params":{"matrix":"bayer4","cell":"0.9vmin","source":null,"shape":null,"three":false},"vars":{"ink":{"tone":["@accent",{"if":{"mode":"dark"},"then":{"l":[0.62,0.85],"c":[0.1,0.22]},"else":{"l":[0.3,0.55],"c":[0.1,0.24]}}]},"colors":{"if":"three","then":["@bg",{"tone":["@colors.1",{"l":[0.55,0.75],"c":[0.1,0.2]}]},"$ink"],"else":["@bg","$ink"]}},"output":{"lossless":true},"layers":[{"id":"dither","type":"dither","colors":"$colors","source":{"if":"source","then":"{source}","else":{"pick":["field","linear","radial"]}},"shape":{"if":"shape","then":"{shape}","else":{"pick":["sphere","none","circle","arch","sphere"]}},"matrix":"{matrix}","cell":"{cell}","center":{"x":{"range":[0.4,0.6]},"y":{"range":[0.38,0.55]}},"size":"28vmin"},{"id":"title","type":"text","when":"title","content":"{title}","anchor":"bottom-left","weight":500,"size":{"mul":["@u",0.062]},"minSize":{"mul":["@u",0.046]},"lineHeight":1.08,"letterSpacing":-0.025,"maxLines":3,"label":{"if":"label","then":{"color":"@text","content":"{label}"}},"color":"@text","background":{"color":"@bg","radius":0,"padding":[{"mul":["@u",0.03]},{"mul":["@u",0.035]}]}}]},"duotone":{"$schema":"../schema/recipe.schema.json","name":"duotone","description":"A photo mapped to the palette's shadow and highlight colours, with grain, and a title over a scrim when given.","requires":["image"],"vars":{"shadow":{"if":{"mode":"dark"},"then":"@bg","else":"@text"}},"layers":[{"id":"image","type":"image","src":"{image}","duotone":true},{"id":"scrim","type":"linear","when":"title","angle":180,"colors":["$shadow","$shadow"],"opacities":[0,0.62]},{"id":"grain","type":"grain","amount":0.07},{"id":"title","type":"text","when":"title","content":"{title}","anchor":"bottom-left","weight":500,"size":{"mul":["@u",0.066]},"minSize":{"mul":["@u",0.048]},"lineHeight":1.08,"letterSpacing":-0.025,"maxLines":3,"label":{"if":"label","then":{"color":"#e9e9e9","content":"{label}"}},"color":"#ffffff"}]},"gridTitle":{"$schema":"../schema/recipe.schema.json","name":"gridTitle","description":"The aurora field under a dotted grid (four cells each way), grain, and an optional headline lower left.","layers":[{"id":"bg","type":"solid"},{"id":"mesh","type":"mesh","count":4,"blur":"11vmax","size":[0.45,0.8],"streak":1.8,"tone":{"if":"title","then":{"if":{"mode":"dark"},"then":{"l":[0.34,0.5],"c":[0.12,0.22]},"else":{"l":[0.66,0.82],"c":[0.13,0.2]}},"else":{"l":[0.55,0.85],"c":[0.12,0.22]}},"fold":{"if":"title","then":false,"else":{"pick":[false,false,true]}},"streaks":{"if":"title","then":0,"else":{"pick":[0,0,0,6]}},"hues":{"pick":[2,2,3]},"hueSpan":60},{"id":"grid","type":"pattern","kind":"grid","divisions":4,"stroke":"0.3vmin","dash":"dotted","color":"#ffffff","opacity":0.6},{"id":"grain","type":"grain","amount":0.08},{"id":"title","type":"text","when":"title","content":"{title}","anchor":"bottom-left","weight":500,"size":{"mul":["@u",0.066]},"minSize":{"mul":["@u",0.048]},"lineHeight":1.08,"letterSpacing":-0.025,"maxLines":3,"label":{"if":"label","then":{"color":"@text","content":"{label}"}}}]},"iconTile":{"$schema":"../schema/recipe.schema.json","name":"iconTile","description":"An icon on a raised tile over a soft tint of the accent, a glow and faint rings. With a title, the tile moves to the upper left.","vars":{"tint":{"mix":["@bg","@accent",{"if":{"mode":"dark"},"then":0.1,"else":0.07}]},"glow":{"if":"title","then":{"x":{"add":[0.22,{"range":[-0.12,0.12]}]},"y":{"add":[0.26,{"range":[-0.12,0.12]}]}},"else":{"x":{"add":[0.5,{"range":[-0.12,0.12]}]},"y":{"add":[0.5,{"range":[-0.12,0.12]}]}}}},"layers":[{"id":"bg","type":"solid","color":"$tint"},{"id":"glow","type":"radial","colors":[{"mix":["$tint","@accent",{"if":{"mode":"dark"},"then":0.32,"else":0.26}]},"$tint"],"x":"$glow.x","y":"$glow.y","radius":"52vmax"},{"id":"rings","type":"pattern","kind":"rings","size":{"if":"title","then":"6vmin","else":"7vmin"},"center":"$glow","opacity":{"if":{"mode":"dark"},"then":0.14,"else":0.16},"fade":"radial","color":{"if":{"mode":"dark"},"then":"@text","else":"@accent"}},{"id":"icon","type":"icon","name":"{icon}","path":{"if":{"not":"icon"},"then":"M12 2c.4 4.9 2.1 7.6 7 8.6l3 .4-3 .4c-4.9 1-6.6 3.7-7 8.6-.4-4.9-2.1-7.6-7-8.6L2 11l3-.4c4.9-1 6.6-3.7 7-8.6Z"},"viewBox":{"if":{"not":"icon"},"then":[0,0,24,24]},"size":{"mul":["@u",{"if":"title","then":0.075,"else":0.12}]},"anchor":{"if":"title","then":"top-left","else":"center"},"tile":{"color":{"if":{"mode":"dark"},"then":{"mix":["$tint","@text",0.09]},"else":{"mix":["$tint","#ffffff",0.85]}}}},{"id":"grain","type":"grain","amount":0.05},{"id":"title","type":"text","when":"title","content":"{title}","anchor":"bottom-left","weight":500,"size":{"mul":["@u",0.066]},"minSize":{"mul":["@u",0.048]},"lineHeight":1.08,"letterSpacing":-0.025,"maxLines":3,"label":"{label}"}]},"issue":{"$schema":"../schema/recipe.schema.json","name":"issue","description":"Type-led: a huge issue number in the accent, sized to the canvas and cropped at the bottom only, an optional No., a masthead line with the date, a hairline.","params":{"number":null,"masthead":null,"date":null,"prefix":true},"vars":{"number":{"if":"number","then":"{number}","else":{"int":[1,64]}},"numeral":{"readable":["@bg",3,"@accent"]},"size":{"min":[{"mul":["@height",1.04]},{"div":[{"mul":["@width",1.62]},{"length":"$number"}]}]},"bleed":{"add":[1,{"div":[{"mul":["$size",0.1]},"@height"]}]}},"layers":[{"id":"bg","type":"solid"},{"id":"masthead","type":"text","when":"masthead","content":"{masthead}","anchor":"top-left","size":{"mul":["@u",0.024]},"weight":600,"letterSpacing":0.08,"transform":"uppercase","maxLines":1,"balance":false,"color":"@text"},{"id":"date","type":"text","when":"date","content":"{date}","anchor":"top-right","size":{"mul":["@u",0.024]},"weight":500,"letterSpacing":0.04,"maxLines":1,"balance":false,"color":"@text"},{"id":"rule","type":"rule","y":{"if":{"any":["masthead","date"]},"then":0.13,"else":0.1}},{"id":"no","type":"text","when":{"not":{"prefix":false}},"content":"No.","anchor":"left","position":{"x":0.07,"y":{"if":"title","then":0.31,"else":0.27}},"size":{"mul":["@u",0.05]},"weight":500,"letterSpacing":-0.02,"color":"$numeral","maxLines":1,"balance":false},{"id":"number","type":"text","content":"$number","anchor":"bottom-left","position":{"x":0.055,"y":"$bleed"},"size":"$size","weight":600,"letterSpacing":-0.06,"lineHeight":1,"maxLines":1,"maxWidth":100000,"balance":false,"color":"$numeral"},{"id":"title","type":"text","when":"title","content":"{title}","anchor":{"if":{"aspect":"wide"},"then":"top-right","else":"top-left"},"weight":500,"size":{"mul":["@u",0.05]},"minSize":{"mul":["@u",0.04]},"lineHeight":1.08,"letterSpacing":-0.025,"maxLines":2,"label":{"if":"label","then":{"color":"@text","content":"{label}"}},"offset":{"y":"9vh"},"maxWidth":{"if":{"aspect":"wide"},"then":"40vw","else":"52vw"},"align":"left"}]},"mark":{"$schema":"../schema/recipe.schema.json","name":"mark","description":"One icon, centred, on the accent or an accent gradient: white whenever white clears 3:1 (WCAG 1.4.11, non-text) against everything under it, else near-black.","params":{"background":"solid","angle":135,"colors":null,"size":"24vmin","iconColor":"auto"},"vars":{"gradient":{"if":"colors","then":"{colors}","else":["@accent",{"mix":["@accent","@colors.1",0.5,"oklch"]}]},"ink":{"if":{"iconColor":"auto"},"then":{"if":{"background":"gradient"},"then":{"readable":[{"gradientUnder":["$gradient","{angle}","{size}"]},3,"white"]},"else":{"readable":["@accent",3,"white"]}},"else":{"if":{"iconColor":"white"},"then":"#ffffff","else":{"if":{"iconColor":"black"},"then":"#111111","else":"{iconColor}"}}},"type":{"if":{"background":"gradient"},"then":{"readable":["$gradient",4.5,"$ink"]},"else":{"readable":["@accent",4.5,"$ink"]}}},"layers":[{"id":"bg","type":"solid","when":{"background":"solid"},"color":"@accent"},{"id":"bg","type":"linear","when":{"background":"gradient"},"angle":"{angle}","colors":"$gradient"},{"id":"icon","type":"icon","name":"{icon}","path":{"if":{"not":"icon"},"then":"M12 2c.4 4.9 2.1 7.6 7 8.6l3 .4-3 .4c-4.9 1-6.6 3.7-7 8.6-.4-4.9-2.1-7.6-7-8.6L2 11l3-.4c4.9-1 6.6-3.7 7-8.6Z"},"viewBox":{"if":{"not":"icon"},"then":[0,0,24,24]},"size":"{size}","color":"$ink"},{"id":"grain","type":"grain","amount":0.03},{"id":"title","type":"text","when":"title","content":"{title}","anchor":"bottom-left","weight":500,"size":{"mul":["@u",0.066]},"minSize":{"mul":["@u",0.048]},"lineHeight":1.08,"letterSpacing":-0.025,"maxLines":3,"label":{"if":"label","then":{"color":"$type","content":"{label}"}},"color":"$type"}]},"photo":{"$schema":"../schema/recipe.schema.json","name":"photo","description":"A palette taken from a photo, laid out as full-bleed sweeps like aurora (the photo itself not shown), or `show: 'blur'` for the photo heavily blurred.","params":{"show":null},"requires":["image"],"vars":{"src":{"if":"image","then":"{image}","else":null}},"layers":[{"id":"bg","type":"solid"},{"id":"mesh","type":"mesh","when":{"not":{"show":"blur"}},"from":"$src","count":4,"blur":"11vmax","size":[0.45,0.8],"streak":1.8,"hues":3,"tone":{"if":"title","then":{"if":{"mode":"dark"},"then":{"l":[0.34,0.5],"c":[0.08,0.2]},"else":{"l":[0.66,0.82],"c":[0.08,0.18]}},"else":{"l":[0.5,0.85],"c":[0.08,0.2]}},"fold":{"if":"title","then":false,"else":{"pick":[false,false,true]}}},{"id":"photo","type":"image","when":{"show":"blur"},"src":"$src","blur":"7vmax"},{"id":"scrim","type":"solid","when":{"all":[{"show":"blur"},"title"]},"color":{"if":{"mode":"dark"},"then":"#000000","else":"#ffffff"},"opacity":0.4},{"id":"grain","type":"grain","amount":0.08},{"id":"title","type":"text","when":"title","content":"{title}","anchor":"bottom-left","weight":500,"size":{"mul":["@u",0.062]},"minSize":{"mul":["@u",0.046]},"lineHeight":1.08,"letterSpacing":-0.025,"maxLines":3,"label":{"if":"label","then":{"color":"@text","content":"{label}"}},"color":"@text"}]},"riso":{"$schema":"../schema/recipe.schema.json","name":"riso","description":"Two (or three) flat risograph inks overprinted on paper: big simple forms, slight misregistration, speckled or halftone texture, uneven density. Inks from the palette, or classic riso inks.","params":{"inks":null,"texture":"grain","third":false},"vars":{"ground":{"if":{"mode":"dark"},"then":"@bg","else":"#f6f1e7"},"blend":{"if":{"mode":"dark"},"then":"screen","else":"multiply"},"inks":{"if":"inks","then":"{inks}","else":[{"tone":["@accent",{"l":[0.5,0.72],"c":[0.14,0.26]}]},{"tone":["@colors.1",{"l":[0.5,0.72],"c":[0.14,0.26]}]},{"tone":["@colors.2",{"l":[0.5,0.72],"c":[0.14,0.26]}]}]},"comp":{"if":"title","then":{"pick":[[[{"kind":"circle","x":{"range":[0.4,0.6]},"y":0.32,"r":"24vmin"}],[{"kind":"band","y":0.42,"height":"16vmin","rotate":{"range":[-8,8]}}]],[[{"kind":"arch","x":{"range":[0.34,0.42]},"y":0.34,"width":"34vmin","height":"44vmin"}],[{"kind":"circle","x":{"range":[0.58,0.66]},"y":0.3,"r":"16vmin"}]],[[{"kind":"half","x":0.5,"y":0.5,"r":"32vmin"}],[{"kind":"circle","x":0.5,"y":0.3,"r":"14vmin"}]]]},"else":{"pick":[[[{"kind":"circle","x":{"range":[0.36,0.5]},"y":{"range":[0.38,0.5]},"r":"34vmin"}],[{"kind":"band","y":{"range":[0.56,0.68]},"height":"26vmin","rotate":{"range":[-14,14]}}]],[[{"kind":"arch","x":{"range":[0.32,0.42]},"y":0.54,"width":"46vmin","height":"66vmin"}],[{"kind":"circle","x":{"range":[0.6,0.68]},"y":{"range":[0.34,0.44]},"r":"22vmin"}]],[[{"kind":"half","x":0.5,"y":{"range":[0.6,0.7]},"r":"42vmin"}],[{"kind":"circle","x":{"range":[0.42,0.58]},"y":{"range":[0.34,0.42]},"r":"19vmin"}]],[[{"kind":"ring","x":{"range":[0.4,0.48]},"y":0.48,"r":"30vmin","thickness":"12vmin"}],[{"kind":"circle","x":{"range":[0.56,0.64]},"y":{"range":[0.5,0.58]},"r":"24vmin"}]]]}}},"layers":[{"id":"bg","type":"solid","color":"$ground"},{"id":"inkA","type":"ink","color":"$inks.0","shapes":"$comp.0","texture":"{texture}","blend":"$blend"},{"id":"inkB","type":"ink","color":"$inks.1","shapes":"$comp.1","texture":"{texture}","blend":"$blend"},{"id":"inkC","type":"ink","when":"third","color":"$inks.2","shapes":[{"kind":"circle","x":{"range":[0.2,0.8]},"y":{"range":[0.2,0.4]},"r":"7vmin"}],"texture":"{texture}","blend":"$blend"},{"id":"grain","type":"grain","amount":0.05},{"id":"title","type":"text","when":"title","content":"{title}","anchor":"bottom-left","weight":500,"size":{"mul":["@u",0.062]},"minSize":{"mul":["@u",0.046]},"lineHeight":1.08,"letterSpacing":-0.025,"maxLines":3,"label":{"if":"label","then":{"color":"@text","content":"{label}"}},"color":"@text"}]},"stripes":{"$schema":"../schema/recipe.schema.json","name":"stripes","description":"A tonal band of tapering stripes down one side, a disc riding its edge (with an optional icon), and the title on the open side (the claude.com/resources look).","params":{"side":"right"},"vars":{"spotY":{"add":[{"if":"title","then":0.36,"else":0.5},{"range":[-0.06,0.06]}]},"steps":{"int":[5,8]},"taper":{"range":[0.72,0.86]},"ramp":{"ramp":["@accent","@bg","$steps",0.88]},"weights":{"taper":["$taper","$steps"]},"edge":{"if":{"side":"right"},"then":{"sub":[1,0.46]},"else":0.46},"spot":{"mul":[{"min":["@width","@height"]},0.34]}},"layers":[{"id":"bg","type":"solid"},{"id":"stripes","type":"stripes","direction":"vertical","region":{"if":{"side":"right"},"then":{"x":"$edge","y":0,"width":0.46,"height":1},"else":{"x":0,"y":0,"width":0.46,"height":1}},"widths":{"if":{"side":"right"},"then":{"reverse":"$weights"},"else":"$weights"},"colors":{"if":{"side":"right"},"then":{"reverse":"$ramp"},"else":"$ramp"}},{"id":"spot","type":"shapes","items":[{"kind":"circle","x":"$edge","y":"$spotY","size":"$spot","color":"@text"}]},{"id":"icon","type":"icon","when":"icon","name":"{icon}","size":{"mul":["$spot",0.4]},"position":{"x":"$edge","y":"$spotY"},"color":"@bg"},{"id":"grain","type":"grain","amount":0.06},{"id":"title","type":"text","when":"title","content":"{title}","anchor":{"if":{"side":"right"},"then":"bottom-left","else":"bottom-right"},"weight":500,"size":{"mul":["@u",0.058]},"minSize":{"mul":["@u",0.044]},"lineHeight":1.08,"letterSpacing":-0.025,"maxLines":4,"label":"{label}","maxWidth":{"sub":[{"mul":["@width",{"sub":[1,0.46]}]},{"mul":["@u",0.12]}]}}]},"waveform":{"$schema":"../schema/recipe.schema.json","name":"waveform","description":"An episode's waveform (peaks you pass, or audio decoded at build) as bars, mirrored bars, a line or dots, on a flat or toned field, with an optional duration and label. Without peaks or audio it draws seeded demo peaks.","params":{"peaks":null,"audio":null,"duration":null,"style":null,"field":"flat"},"vars":{"field":{"if":{"field":"toned"},"then":{"tone":["@accent",{"if":{"mode":"dark"},"then":{"l":[0.3,0.4],"c":[0.06,0.12]},"else":{"l":[0.88,0.94],"c":[0.03,0.06]}}]},"else":"@bg"},"wave":{"readable":["$field",3,"@accent"]},"type":{"readable":["$field",4.5,"@text"]}},"layers":[{"id":"bg","type":"solid","color":"$field"},{"id":"wave","type":"waveform","peaks":"{peaks}","audio":"{audio}","style":{"if":"style","then":"{style}","else":{"pick":["mirrored","bars","line","dots","mirrored"]}},"color":"$wave","duration":"{duration}","caption":{"if":{"any":["duration","audio"]},"then":true,"else":null},"captionColor":"$type","region":{"if":"title","then":{"x":0.07,"y":0.42,"width":0.86,"height":0.3},"else":{"x":0.07,"y":0.3,"width":0.86,"height":0.4}}},{"id":"grain","type":"grain","amount":0.04},{"id":"title","type":"text","when":"title","content":"{title}","anchor":"top-left","weight":500,"size":{"mul":["@u",0.062]},"minSize":{"mul":["@u",0.046]},"lineHeight":1.08,"letterSpacing":-0.025,"maxLines":3,"label":{"if":"label","then":{"color":"$type","content":"{label}"}},"color":"$type"}]}};
|
package/src/diagram.js
CHANGED
|
@@ -19,9 +19,10 @@
|
|
|
19
19
|
* @module diagram
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import { attrs, num
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
22
|
+
import { attrs, num } from '@logomarks/image-effects/svg';
|
|
23
|
+
import { layerAttrs } from './layers/util.js';
|
|
24
|
+
import { len } from '@logomarks/image-effects/length';
|
|
25
|
+
import { toHex } from '@logomarks/image-effects/color';
|
|
25
26
|
import { resolveIcon, parseSvgIcon } from './icons.js';
|
|
26
27
|
import { gridPath } from './grid.js';
|
|
27
28
|
|
package/src/extract.js
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* @module extract
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import { toOklch, fromOklch, toHex } from '
|
|
9
|
-
import { createRng } from '
|
|
7
|
+
import { loadBytes } from './platform/index.js';
|
|
8
|
+
import { toOklch, fromOklch, toHex } from '@logomarks/image-effects/color';
|
|
9
|
+
import { createRng } from '@logomarks/image-effects/rng';
|
|
10
10
|
|
|
11
11
|
/** @type {Map<string, Promise<string[]>>} */
|
|
12
12
|
const cache = new Map();
|
|
@@ -35,11 +35,12 @@ export function extractPalette(src, options = {}) {
|
|
|
35
35
|
const job = (async () => {
|
|
36
36
|
let sharp;
|
|
37
37
|
try {
|
|
38
|
-
|
|
38
|
+
const name = 'sharp';
|
|
39
|
+
sharp = (await import(/* @vite-ignore */ name)).default;
|
|
39
40
|
} catch {
|
|
40
41
|
throw new Error('Extracting a palette from a photo needs sharp: npm install sharp');
|
|
41
42
|
}
|
|
42
|
-
const bytes =
|
|
43
|
+
const bytes = await loadBytes(src, 'image');
|
|
43
44
|
const { data } = await sharp(bytes).rotate().resize(64, 64, { fit: 'cover' }).removeAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
44
45
|
/** @type {[number, number, number][]} */
|
|
45
46
|
const pts = [];
|
package/src/fonts.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Fonts: loading, decoding and picking a face.
|
|
2
|
+
import type { FontSource } from './index.js';
|
|
3
|
+
import type { HarfBuzzModule } from './platform/harfbuzz.js';
|
|
4
|
+
|
|
5
|
+
/** A shaped glyph, font units. */
|
|
6
|
+
export interface RunGlyph {
|
|
7
|
+
gid: number;
|
|
8
|
+
cluster: number;
|
|
9
|
+
xAdvance: number;
|
|
10
|
+
xOffset: number;
|
|
11
|
+
yOffset: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** A loaded face, ready to shape with (HarfBuzz, or the JavaScript reader in Workers). */
|
|
15
|
+
export interface LoadedFont {
|
|
16
|
+
name: string;
|
|
17
|
+
weight: number;
|
|
18
|
+
style: string;
|
|
19
|
+
/** The harfbuzzjs Font, or the JavaScript face. */
|
|
20
|
+
font: object;
|
|
21
|
+
upem: number;
|
|
22
|
+
ascender: number;
|
|
23
|
+
descender: number;
|
|
24
|
+
capHeight: number;
|
|
25
|
+
/** Parsed outline commands by glyph id. */
|
|
26
|
+
paths: Map<number, (string | number)[]>;
|
|
27
|
+
/** Shape one line. */
|
|
28
|
+
shapeRun: (text: string) => RunGlyph[];
|
|
29
|
+
/** A glyph's outline, font units, y up. */
|
|
30
|
+
glyphToPath: (gid: number) => string;
|
|
31
|
+
axes: Record<string, { min: number; default: number; max: number }>;
|
|
32
|
+
variations: Record<string, number>;
|
|
33
|
+
source: FontSource;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** HarfBuzz, or undefined where it can't run (Workers) or is switched off. */
|
|
37
|
+
export function harfbuzz(): Promise<HarfBuzzModule | undefined>;
|
|
38
|
+
/** The bundled Inter faces (bytes from disk in Node, else loaded on first use). */
|
|
39
|
+
export function bundledFonts(): FontSource[];
|
|
40
|
+
/** A cover's fonts, then the registered ones, then Inter. */
|
|
41
|
+
export function fontPool(own: FontSource[]): FontSource[];
|
|
42
|
+
/** Register a font for every cover. */
|
|
43
|
+
export function registerFont(font: FontSource): void;
|
|
44
|
+
/** A font source's bytes. */
|
|
45
|
+
export function fontBytes(source: FontSource): Promise<Uint8Array>;
|
|
46
|
+
/** WOFF or sfnt bytes as sfnt, synchronously (Node). */
|
|
47
|
+
export function decodeFont(input: Uint8Array | ArrayBuffer): Uint8Array;
|
|
48
|
+
/** WOFF or sfnt bytes as sfnt, on any platform. */
|
|
49
|
+
export function decodeFontAsync(input: Uint8Array | ArrayBuffer): Promise<Uint8Array>;
|
|
50
|
+
/** Load a face, optionally at a variation (`{ wght: 650, wdth: 80 }`). */
|
|
51
|
+
export function loadFont(source: FontSource, variations?: Record<string, number>): Promise<LoadedFont>;
|
|
52
|
+
/** The closest face for a family, weight and style (a variable range matches inside it). */
|
|
53
|
+
export function pickFont(sources: FontSource[], family: string, weight: number, style?: string): FontSource;
|
|
54
|
+
/** A layer's variation settings (`wght` follows the weight on a variable font). */
|
|
55
|
+
export function variationsFor(source: FontSource, weight: number, axes: Record<string, number> | undefined): Record<string, number> | undefined;
|