@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,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WOFF2 to a bare sfnt, in JavaScript: the Brotli stream decompressed (by
|
|
3
|
+
* the platform: Node's zlib, or a `DecompressionStream('brotli')`), then
|
|
4
|
+
* the transformed `glyf`, `loca` and `hmtx` tables rebuilt (W3C WOFF2 §5).
|
|
5
|
+
* Font collections aren't supported.
|
|
6
|
+
*
|
|
7
|
+
* @module type/woff2
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** The 63 known table tags, by index (WOFF2 §5.1). */
|
|
11
|
+
const KNOWN = ['cmap', 'head', 'hhea', 'hmtx', 'maxp', 'name', 'OS/2', 'post', 'cvt ', 'fpgm', 'glyf', 'loca', 'prep', 'CFF ', 'VORG', 'EBDT', 'EBLC', 'gasp', 'hdmx', 'kern', 'LTSH', 'PCLT', 'VDMX', 'vhea', 'vmtx', 'BASE', 'GDEF', 'GPOS', 'GSUB', 'EBSC', 'JSTF', 'MATH', 'CBDT', 'CBLC', 'COLR', 'CPAL', 'SVG ', 'sbix', 'acnt', 'avar', 'bdat', 'bloc', 'bsln', 'cvar', 'fdsc', 'feat', 'fmtx', 'fvar', 'gvar', 'hsty', 'just', 'lcar', 'mort', 'morx', 'opbd', 'prop', 'trak', 'Zapf', 'Silf', 'Glat', 'Gloc', 'Feat', 'Sill'];
|
|
12
|
+
|
|
13
|
+
/** A cursor over bytes. */
|
|
14
|
+
class Reader {
|
|
15
|
+
/** @param {Uint8Array} b @param {number} [at=0] */
|
|
16
|
+
constructor(b, at = 0) { this.b = b; this.v = new DataView(b.buffer, b.byteOffset, b.byteLength); this.at = at; }
|
|
17
|
+
u8() { return this.b[this.at++]; }
|
|
18
|
+
u16() { const x = this.v.getUint16(this.at); this.at += 2; return x; }
|
|
19
|
+
i16() { const x = this.v.getInt16(this.at); this.at += 2; return x; }
|
|
20
|
+
u32() { const x = this.v.getUint32(this.at); this.at += 4; return x; }
|
|
21
|
+
/** UIntBase128. */
|
|
22
|
+
base128() { let x = 0; for (let i = 0; i < 5; i++) { const c = this.u8(); x = x * 128 + (c & 0x7f); if (!(c & 0x80)) return x; } throw new TypeError('WOFF2: bad UIntBase128'); }
|
|
23
|
+
/** 255UInt16. */
|
|
24
|
+
u255() { const c = this.u8(); return c === 253 ? this.u16() : c === 255 ? this.u8() + 253 : c === 254 ? this.u8() + 506 : c; }
|
|
25
|
+
/** @param {number} n */
|
|
26
|
+
bytes(n) { const out = this.b.subarray(this.at, this.at + n); this.at += n; return out; }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A WOFF2 file's header and table directory, and where its Brotli stream is.
|
|
31
|
+
*
|
|
32
|
+
* @param {Uint8Array} buf
|
|
33
|
+
* @returns {{ flavor: number, tables: { tag: string, origLength: number, length: number, transformed: boolean, data: Uint8Array }[], payload: Uint8Array }}
|
|
34
|
+
* @throws {TypeError} On a collection or damaged data.
|
|
35
|
+
*/
|
|
36
|
+
export function woff2Directory(buf) {
|
|
37
|
+
try {
|
|
38
|
+
return directory(buf);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
if (err instanceof RangeError) throw new TypeError('WOFF2: the font data is damaged or cut short');
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** @param {Uint8Array} buf @returns {ReturnType<typeof woff2Directory>} */
|
|
46
|
+
function directory(buf) {
|
|
47
|
+
const r = new Reader(buf);
|
|
48
|
+
if (r.u32() !== 0x774f4632) throw new TypeError('Not a WOFF2 font');
|
|
49
|
+
const flavor = r.u32();
|
|
50
|
+
if (flavor === 0x74746366) throw new TypeError('WOFF2 font collections are not supported');
|
|
51
|
+
r.u32();
|
|
52
|
+
const count = r.u16();
|
|
53
|
+
r.u16(); r.u32();
|
|
54
|
+
const compressedSize = r.u32();
|
|
55
|
+
r.at = 48;
|
|
56
|
+
const tables = [];
|
|
57
|
+
for (let i = 0; i < count; i++) {
|
|
58
|
+
const flags = r.u8();
|
|
59
|
+
const tag = (flags & 0x3f) === 63 ? String.fromCharCode(...r.bytes(4)) : KNOWN[flags & 0x3f];
|
|
60
|
+
const version = flags >> 6;
|
|
61
|
+
const origLength = r.base128();
|
|
62
|
+
const transformed = tag === 'glyf' || tag === 'loca' ? version === 0 : version !== 0;
|
|
63
|
+
const length = transformed ? r.base128() : origLength;
|
|
64
|
+
tables.push({ tag, origLength, length, transformed, data: /** @type {Uint8Array} */ (new Uint8Array(0)) });
|
|
65
|
+
}
|
|
66
|
+
return { flavor, tables, payload: buf.subarray(r.at, r.at + compressedSize) };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* WOFF2 bytes as sfnt.
|
|
71
|
+
*
|
|
72
|
+
* @param {Uint8Array} buf - WOFF2 bytes.
|
|
73
|
+
* @param {Uint8Array | undefined} stream - The decompressed Brotli stream (see {@link woff2Directory}'s `payload`); undefined where nothing could decompress it.
|
|
74
|
+
* @returns {Uint8Array}
|
|
75
|
+
* @throws {TypeError} On a collection, damaged data, or no Brotli decoder.
|
|
76
|
+
*/
|
|
77
|
+
export function woff2ToSfnt(buf, stream) {
|
|
78
|
+
const { flavor, tables } = woff2Directory(buf);
|
|
79
|
+
if (!stream) throw new TypeError('WOFF2 needs Brotli, which this runtime lacks: convert the font to .ttf (scripts/woff2-to-ttf.mjs) or use render: "live"');
|
|
80
|
+
let at = 0;
|
|
81
|
+
for (const t of tables) { t.data = stream.subarray(at, at + t.length); at += t.length; }
|
|
82
|
+
const byTag = new Map(tables.map((t) => [t.tag, t]));
|
|
83
|
+
const glyf = byTag.get('glyf');
|
|
84
|
+
if (glyf?.transformed) {
|
|
85
|
+
const { glyf: g, loca, long } = rebuildGlyf(glyf.data);
|
|
86
|
+
glyf.data = g;
|
|
87
|
+
const l = byTag.get('loca');
|
|
88
|
+
if (l) l.data = loca;
|
|
89
|
+
// The rebuilt glyf outgrew short offsets: the head says long ones now.
|
|
90
|
+
const head = byTag.get('head');
|
|
91
|
+
if (long && head) {
|
|
92
|
+
head.data = new Uint8Array(head.data);
|
|
93
|
+
new DataView(head.data.buffer).setInt16(50, 1);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const hmtx = byTag.get('hmtx');
|
|
97
|
+
if (hmtx?.transformed) hmtx.data = rebuildHmtx(hmtx.data, byTag);
|
|
98
|
+
return assemble(flavor, tables);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The transformed `glyf` table back to `glyf` and `loca` (WOFF2 §5.1–5.3).
|
|
103
|
+
* Glyphs are written plainly (a word a delta), so the table can outgrow
|
|
104
|
+
* the font's short `loca` (offsets to 128 KB): then `loca` is long, and
|
|
105
|
+
* `long` says so (the `head` must agree).
|
|
106
|
+
*
|
|
107
|
+
* @param {Uint8Array} data
|
|
108
|
+
* @returns {{ glyf: Uint8Array, loca: Uint8Array, long: boolean }}
|
|
109
|
+
*/
|
|
110
|
+
function rebuildGlyf(data) {
|
|
111
|
+
const h = new Reader(data);
|
|
112
|
+
h.u16();
|
|
113
|
+
const options = h.u16();
|
|
114
|
+
const numGlyphs = h.u16();
|
|
115
|
+
const indexFormat = h.u16();
|
|
116
|
+
const sizes = Array.from({ length: 7 }, () => h.u32());
|
|
117
|
+
let at = h.at;
|
|
118
|
+
const streams = sizes.map((n) => { const s = new Reader(data.subarray(at, at + n)); at += n; return s; });
|
|
119
|
+
const [nContours, nPoints, flagStream, glyphStream, composite, bboxStream, instructions] = streams;
|
|
120
|
+
const bitmapLength = ((numGlyphs + 31) >> 5) << 2;
|
|
121
|
+
const bboxBitmap = bboxStream.bytes(bitmapLength);
|
|
122
|
+
void options;
|
|
123
|
+
|
|
124
|
+
/** @type {Uint8Array[]} */
|
|
125
|
+
const out = [];
|
|
126
|
+
const offsets = [0];
|
|
127
|
+
let total = 0;
|
|
128
|
+
for (let i = 0; i < numGlyphs; i++) {
|
|
129
|
+
const n = nContours.i16();
|
|
130
|
+
const explicit = (bboxBitmap[i >> 3] >> (7 - (i & 7))) & 1;
|
|
131
|
+
let glyph = new Uint8Array(0);
|
|
132
|
+
if (n === -1) {
|
|
133
|
+
// A composite: its component records, as they are; an explicit bbox.
|
|
134
|
+
const start = composite.at;
|
|
135
|
+
let more = true;
|
|
136
|
+
let hasInstructions = false;
|
|
137
|
+
while (more) {
|
|
138
|
+
const flags = composite.u16();
|
|
139
|
+
composite.u16();
|
|
140
|
+
composite.at += (flags & 0x0001 ? 4 : 2) + (flags & 0x0008 ? 2 : flags & 0x0040 ? 4 : flags & 0x0080 ? 8 : 0);
|
|
141
|
+
more = Boolean(flags & 0x0020);
|
|
142
|
+
if (flags & 0x0100) hasInstructions = true;
|
|
143
|
+
}
|
|
144
|
+
const records = composite.b.subarray(start, composite.at);
|
|
145
|
+
const bbox = [bboxStream.i16(), bboxStream.i16(), bboxStream.i16(), bboxStream.i16()];
|
|
146
|
+
const ilen = hasInstructions ? glyphStream.u255() : 0;
|
|
147
|
+
const ins = instructions.bytes(ilen);
|
|
148
|
+
glyph = new Uint8Array(10 + records.length + (hasInstructions ? 2 + ilen : 0));
|
|
149
|
+
const v = new DataView(glyph.buffer);
|
|
150
|
+
v.setInt16(0, -1);
|
|
151
|
+
bbox.forEach((x, k) => v.setInt16(2 + k * 2, x));
|
|
152
|
+
glyph.set(records, 10);
|
|
153
|
+
if (hasInstructions) { v.setUint16(10 + records.length, ilen); glyph.set(ins, 12 + records.length); }
|
|
154
|
+
} else if (n > 0) {
|
|
155
|
+
const ends = [];
|
|
156
|
+
let points = 0;
|
|
157
|
+
for (let c = 0; c < n; c++) { points += nPoints.u255(); ends.push(points - 1); }
|
|
158
|
+
const flags = flagStream.bytes(points);
|
|
159
|
+
const xs = new Int32Array(points);
|
|
160
|
+
const ys = new Int32Array(points);
|
|
161
|
+
const on = new Uint8Array(points);
|
|
162
|
+
let x = 0;
|
|
163
|
+
let y = 0;
|
|
164
|
+
for (let p = 0; p < points; p++) {
|
|
165
|
+
const [dx, dy] = triplet(flags[p] & 0x7f, glyphStream);
|
|
166
|
+
on[p] = flags[p] >> 7 ? 0 : 1;
|
|
167
|
+
x += dx; y += dy;
|
|
168
|
+
xs[p] = x; ys[p] = y;
|
|
169
|
+
}
|
|
170
|
+
const ilen = glyphStream.u255();
|
|
171
|
+
const ins = instructions.bytes(ilen);
|
|
172
|
+
const bbox = explicit ? [bboxStream.i16(), bboxStream.i16(), bboxStream.i16(), bboxStream.i16()] : [Math.min(...xs), Math.min(...ys), Math.max(...xs), Math.max(...ys)];
|
|
173
|
+
// Written plainly: a flag a point, every delta a word.
|
|
174
|
+
glyph = new Uint8Array(10 + 2 * n + 2 + ilen + points + 4 * points);
|
|
175
|
+
const v = new DataView(glyph.buffer);
|
|
176
|
+
v.setInt16(0, n);
|
|
177
|
+
bbox.forEach((b, k) => v.setInt16(2 + k * 2, b));
|
|
178
|
+
let o = 10;
|
|
179
|
+
for (const e of ends) { v.setUint16(o, e); o += 2; }
|
|
180
|
+
v.setUint16(o, ilen); o += 2;
|
|
181
|
+
glyph.set(ins, o); o += ilen;
|
|
182
|
+
for (let p = 0; p < points; p++) glyph[o++] = on[p];
|
|
183
|
+
for (let p = 0; p < points; p++) { v.setInt16(o, xs[p] - (p ? xs[p - 1] : 0)); o += 2; }
|
|
184
|
+
for (let p = 0; p < points; p++) { v.setInt16(o, ys[p] - (p ? ys[p - 1] : 0)); o += 2; }
|
|
185
|
+
}
|
|
186
|
+
const padded = (glyph.length + 3) & ~3;
|
|
187
|
+
const g = new Uint8Array(padded);
|
|
188
|
+
g.set(glyph);
|
|
189
|
+
out.push(g);
|
|
190
|
+
total += padded;
|
|
191
|
+
offsets.push(total);
|
|
192
|
+
}
|
|
193
|
+
const glyf = new Uint8Array(total);
|
|
194
|
+
let o = 0;
|
|
195
|
+
for (const g of out) { glyf.set(g, o); o += g.length; }
|
|
196
|
+
const long = Boolean(indexFormat) || total > 0x1fffe;
|
|
197
|
+
const loca = new Uint8Array((numGlyphs + 1) * (long ? 4 : 2));
|
|
198
|
+
const lv = new DataView(loca.buffer);
|
|
199
|
+
offsets.forEach((off, k) => (long ? lv.setUint32(k * 4, off) : lv.setUint16(k * 2, off / 2)));
|
|
200
|
+
return { glyf, loca, long: long && !indexFormat };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* One point's delta from the glyph stream (WOFF2 §5.2 triplet encoding).
|
|
205
|
+
*
|
|
206
|
+
* @param {number} flag - 0–127.
|
|
207
|
+
* @param {Reader} s
|
|
208
|
+
* @returns {[number, number]}
|
|
209
|
+
*/
|
|
210
|
+
function triplet(flag, s) {
|
|
211
|
+
const sign = (/** @type {number} */ f, /** @type {number} */ v) => (f & 1 ? v : -v);
|
|
212
|
+
if (flag < 10) return [0, sign(flag, ((flag & 14) << 7) + s.u8())];
|
|
213
|
+
if (flag < 20) return [sign(flag, (((flag - 10) & 14) << 7) + s.u8()), 0];
|
|
214
|
+
if (flag < 84) {
|
|
215
|
+
const b0 = flag - 20;
|
|
216
|
+
const b1 = s.u8();
|
|
217
|
+
return [sign(flag, 1 + (b0 & 0x30) + (b1 >> 4)), sign(flag >> 1, 1 + ((b0 & 0x0c) << 2) + (b1 & 0x0f))];
|
|
218
|
+
}
|
|
219
|
+
if (flag < 120) {
|
|
220
|
+
const b0 = flag - 84;
|
|
221
|
+
const a = s.u8();
|
|
222
|
+
const b = s.u8();
|
|
223
|
+
return [sign(flag, 1 + (Math.floor(b0 / 12) << 8) + a), sign(flag >> 1, 1 + (((b0 % 12) >> 2) << 8) + b)];
|
|
224
|
+
}
|
|
225
|
+
if (flag < 124) {
|
|
226
|
+
const a = s.u8();
|
|
227
|
+
const b2 = s.u8();
|
|
228
|
+
const c = s.u8();
|
|
229
|
+
return [sign(flag, (a << 4) + (b2 >> 4)), sign(flag >> 1, ((b2 & 0x0f) << 8) + c)];
|
|
230
|
+
}
|
|
231
|
+
const a = s.u16();
|
|
232
|
+
const b = s.u16();
|
|
233
|
+
return [sign(flag, a), sign(flag >> 1, b)];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* The transformed `hmtx` table back (WOFF2 §5.4): left side bearings from
|
|
238
|
+
* the glyphs' xMin where they were left out.
|
|
239
|
+
*
|
|
240
|
+
* @param {Uint8Array} data
|
|
241
|
+
* @param {Map<string, { data: Uint8Array }>} byTag
|
|
242
|
+
* @returns {Uint8Array}
|
|
243
|
+
*/
|
|
244
|
+
function rebuildHmtx(data, byTag) {
|
|
245
|
+
const r = new Reader(data);
|
|
246
|
+
const flags = r.u8();
|
|
247
|
+
const hhea = /** @type {any} */ (byTag.get('hhea')).data;
|
|
248
|
+
const maxp = /** @type {any} */ (byTag.get('maxp')).data;
|
|
249
|
+
const head = /** @type {any} */ (byTag.get('head')).data;
|
|
250
|
+
const metrics = new DataView(hhea.buffer, hhea.byteOffset).getUint16(34);
|
|
251
|
+
const numGlyphs = new DataView(maxp.buffer, maxp.byteOffset).getUint16(4);
|
|
252
|
+
const longLoca = new DataView(head.buffer, head.byteOffset).getInt16(50) === 1;
|
|
253
|
+
const glyf = /** @type {any} */ (byTag.get('glyf')).data;
|
|
254
|
+
const loca = /** @type {any} */ (byTag.get('loca')).data;
|
|
255
|
+
const lv = new DataView(loca.buffer, loca.byteOffset, loca.byteLength);
|
|
256
|
+
const gv = new DataView(glyf.buffer, glyf.byteOffset, glyf.byteLength);
|
|
257
|
+
const xMin = (/** @type {number} */ g) => {
|
|
258
|
+
const a = longLoca ? lv.getUint32(g * 4) : lv.getUint16(g * 2) * 2;
|
|
259
|
+
const b = longLoca ? lv.getUint32(g * 4 + 4) : lv.getUint16(g * 2 + 2) * 2;
|
|
260
|
+
return b > a ? gv.getInt16(a + 2) : 0;
|
|
261
|
+
};
|
|
262
|
+
const adv = Array.from({ length: metrics }, () => r.u16());
|
|
263
|
+
const lsb = Array.from({ length: numGlyphs }, (_, g) => (g < metrics ? (flags & 1 ? xMin(g) : r.i16()) : 0));
|
|
264
|
+
for (let g = metrics; g < numGlyphs; g++) lsb[g] = flags & 2 ? xMin(g) : r.i16();
|
|
265
|
+
const out = new Uint8Array(metrics * 4 + (numGlyphs - metrics) * 2);
|
|
266
|
+
const v = new DataView(out.buffer);
|
|
267
|
+
for (let g = 0; g < metrics; g++) { v.setUint16(g * 4, adv[g]); v.setInt16(g * 4 + 2, lsb[g]); }
|
|
268
|
+
for (let g = metrics; g < numGlyphs; g++) v.setInt16(metrics * 4 + (g - metrics) * 2, lsb[g]);
|
|
269
|
+
return out;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Tables into an sfnt file (sorted directory, checksums, 4-byte alignment).
|
|
274
|
+
*
|
|
275
|
+
* @param {number} flavor
|
|
276
|
+
* @param {{ tag: string, data: Uint8Array }[]} tables
|
|
277
|
+
* @returns {Uint8Array}
|
|
278
|
+
*/
|
|
279
|
+
function assemble(flavor, tables) {
|
|
280
|
+
const sorted = [...tables].sort((a, b) => (a.tag < b.tag ? -1 : a.tag > b.tag ? 1 : 0));
|
|
281
|
+
const n = sorted.length;
|
|
282
|
+
let pow = 1;
|
|
283
|
+
let log = 0;
|
|
284
|
+
while (pow * 2 <= n) { pow *= 2; log++; }
|
|
285
|
+
const header = 12 + 16 * n;
|
|
286
|
+
const total = sorted.reduce((s, t) => s + ((t.data.length + 3) & ~3), header);
|
|
287
|
+
const out = new Uint8Array(total);
|
|
288
|
+
const v = new DataView(out.buffer);
|
|
289
|
+
v.setUint32(0, flavor); v.setUint16(4, n); v.setUint16(6, pow * 16); v.setUint16(8, log); v.setUint16(10, n * 16 - pow * 16);
|
|
290
|
+
let at = header;
|
|
291
|
+
sorted.forEach((t, i) => {
|
|
292
|
+
out.set(t.data, at);
|
|
293
|
+
let sum = 0;
|
|
294
|
+
for (let k = 0; k < t.data.length; k += 4) sum = (sum + (((out[at + k] << 24) | (out[at + k + 1] << 16) | (out[at + k + 2] << 8) | out[at + k + 3]) >>> 0)) >>> 0;
|
|
295
|
+
const rec = 12 + i * 16;
|
|
296
|
+
for (let k = 0; k < 4; k++) out[rec + k] = t.tag.charCodeAt(k);
|
|
297
|
+
v.setUint32(rec + 4, sum); v.setUint32(rec + 8, at); v.setUint32(rec + 12, t.data.length);
|
|
298
|
+
at += (t.data.length + 3) & ~3;
|
|
299
|
+
});
|
|
300
|
+
return out;
|
|
301
|
+
}
|
package/src/version.js
ADDED
package/src/color.js
DELETED
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Colour parsing and the few colour-space moves palettes need.
|
|
3
|
-
*
|
|
4
|
-
* Everything inside the library is a lowercase `#rrggbb` string. Input can
|
|
5
|
-
* be hex (3, 4, 6 or 8 digits), `rgb()`/`rgba()` or `hsl()`/`hsla()`; alpha
|
|
6
|
-
* is dropped, since a cover's layers carry their own `opacity`.
|
|
7
|
-
*
|
|
8
|
-
* Mixing and contrast come from `@arraypress/color-utils`; this module adds
|
|
9
|
-
* OKLCH, so derived palettes can rotate hue and set lightness perceptually
|
|
10
|
-
* instead of in sRGB (where "the same lightness" means very different
|
|
11
|
-
* things for yellow and blue).
|
|
12
|
-
*
|
|
13
|
-
* @module color
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { hexToRgb, rgbToHex, contrastRatio } from '@arraypress/color-utils';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Parse a CSS colour to `#rrggbb`.
|
|
20
|
-
*
|
|
21
|
-
* @param {string} input - `#abc`, `#aabbcc`, `#aabbccdd`, `rgb(…)`, `rgba(…)`, `hsl(…)` or `hsla(…)`.
|
|
22
|
-
* @returns {string} Lowercase `#rrggbb`.
|
|
23
|
-
* @throws {TypeError} When the string isn't a colour this function understands.
|
|
24
|
-
*
|
|
25
|
-
* @example
|
|
26
|
-
* toHex('rgb(37 99 235)'); // '#2563eb'
|
|
27
|
-
* toHex('#FA0'); // '#ffaa00'
|
|
28
|
-
* toHex('hsl(220 80% 50%)'); // '#1a5ee6'
|
|
29
|
-
*/
|
|
30
|
-
export function toHex(input) {
|
|
31
|
-
if (typeof input !== 'string') throw new TypeError(`Expected a colour string, got ${typeof input}`);
|
|
32
|
-
const s = input.trim().toLowerCase();
|
|
33
|
-
|
|
34
|
-
const hex = s.match(/^#?([0-9a-f]{3,8})$/);
|
|
35
|
-
if (hex && [3, 4, 6, 8].includes(hex[1].length)) {
|
|
36
|
-
let h = hex[1];
|
|
37
|
-
if (h.length <= 4) h = h.split('').map((c) => c + c).join('');
|
|
38
|
-
return '#' + h.slice(0, 6);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const fn = s.match(/^(rgba?|hsla?)\(([^)]*)\)$/);
|
|
42
|
-
if (fn) {
|
|
43
|
-
const parts = fn[2].split(/[\s,/]+/).filter(Boolean);
|
|
44
|
-
if (parts.length >= 3) {
|
|
45
|
-
if (fn[1].startsWith('rgb')) {
|
|
46
|
-
const [r, g, b] = parts.slice(0, 3).map((p) => (p.endsWith('%') ? parseFloat(p) * 2.55 : parseFloat(p)));
|
|
47
|
-
if ([r, g, b].every(Number.isFinite)) return rgbToHex(r, g, b);
|
|
48
|
-
} else {
|
|
49
|
-
const h = parseFloat(parts[0]);
|
|
50
|
-
const sat = parseFloat(parts[1]) / 100;
|
|
51
|
-
const l = parseFloat(parts[2]) / 100;
|
|
52
|
-
if ([h, sat, l].every(Number.isFinite)) return hslToHex(h, sat, l);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
throw new TypeError(`Unrecognised colour: "${input}"`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* HSL to `#rrggbb`.
|
|
61
|
-
*
|
|
62
|
-
* @param {number} h - Hue in degrees.
|
|
63
|
-
* @param {number} s - Saturation, 0–1.
|
|
64
|
-
* @param {number} l - Lightness, 0–1.
|
|
65
|
-
* @returns {string} `#rrggbb`.
|
|
66
|
-
*/
|
|
67
|
-
function hslToHex(h, s, l) {
|
|
68
|
-
const k = (n) => (n + h / 30) % 12;
|
|
69
|
-
const a = s * Math.min(l, 1 - l);
|
|
70
|
-
const f = (n) => l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
|
|
71
|
-
return rgbToHex(f(0) * 255, f(8) * 255, f(4) * 255);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/** @param {number} c - sRGB channel 0–1. @returns {number} Linear channel. */
|
|
75
|
-
const toLinear = (c) => (c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);
|
|
76
|
-
/** @param {number} c - Linear channel. @returns {number} sRGB channel 0–1. */
|
|
77
|
-
const toGamma = (c) => (c <= 0.0031308 ? 12.92 * c : 1.055 * c ** (1 / 2.4) - 0.055);
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* `#rrggbb` to OKLCH.
|
|
81
|
-
*
|
|
82
|
-
* @param {string} color - Any colour {@link toHex} accepts.
|
|
83
|
-
* @returns {{ l: number, c: number, h: number }} Lightness 0–1, chroma (~0–0.37), hue in degrees.
|
|
84
|
-
*
|
|
85
|
-
* @example
|
|
86
|
-
* toOklch('#2563eb'); // { l: 0.546, c: 0.215, h: 262.9 }
|
|
87
|
-
*/
|
|
88
|
-
export function toOklch(color) {
|
|
89
|
-
const { r, g, b } = hexToRgb(toHex(color));
|
|
90
|
-
const [lr, lg, lb] = [r, g, b].map((v) => toLinear(v / 255));
|
|
91
|
-
const l = Math.cbrt(0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb);
|
|
92
|
-
const m = Math.cbrt(0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb);
|
|
93
|
-
const s = Math.cbrt(0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb);
|
|
94
|
-
const L = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s;
|
|
95
|
-
const A = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s;
|
|
96
|
-
const B = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s;
|
|
97
|
-
const h = (Math.atan2(B, A) * 180) / Math.PI;
|
|
98
|
-
return { l: L, c: Math.hypot(A, B), h: h < 0 ? h + 360 : h };
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* OKLCH to `#rrggbb`. Out-of-gamut colours are brought in by reducing
|
|
103
|
-
* chroma (keeping lightness and hue), not by clipping channels, so a
|
|
104
|
-
* derived colour keeps the hue it was asked for.
|
|
105
|
-
*
|
|
106
|
-
* @param {{ l: number, c: number, h: number }} lch - Lightness 0–1, chroma, hue in degrees.
|
|
107
|
-
* @returns {string} `#rrggbb`.
|
|
108
|
-
*
|
|
109
|
-
* @example
|
|
110
|
-
* fromOklch({ l: 0.7, c: 0.12, h: 30 }); // a soft coral
|
|
111
|
-
*/
|
|
112
|
-
export function fromOklch({ l, c, h }) {
|
|
113
|
-
const L = Math.max(0, Math.min(1, l));
|
|
114
|
-
let chroma = Math.max(0, c);
|
|
115
|
-
for (let i = 0; i < 24; i++) {
|
|
116
|
-
const rgb = oklabToRgb(L, chroma * Math.cos((h * Math.PI) / 180), chroma * Math.sin((h * Math.PI) / 180));
|
|
117
|
-
if (rgb.every((v) => v >= -0.0005 && v <= 1.0005) || chroma < 0.001) {
|
|
118
|
-
return toRgbHex(rgb);
|
|
119
|
-
}
|
|
120
|
-
chroma *= 0.9;
|
|
121
|
-
}
|
|
122
|
-
return toRgbHex(oklabToRgb(L, 0, 0));
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Clamp 0–1 sRGB channels and write them as `#rrggbb`.
|
|
127
|
-
*
|
|
128
|
-
* @param {[number, number, number]} rgb
|
|
129
|
-
* @returns {string}
|
|
130
|
-
*/
|
|
131
|
-
function toRgbHex([r, g, b]) {
|
|
132
|
-
const c = (/** @type {number} */ v) => Math.max(0, Math.min(1, v)) * 255;
|
|
133
|
-
return rgbToHex(c(r), c(g), c(b));
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* OKLab to gamma-encoded sRGB channels (0–1, possibly out of range).
|
|
138
|
-
*
|
|
139
|
-
* @param {number} L @param {number} A @param {number} B
|
|
140
|
-
* @returns {[number, number, number]} sRGB channels.
|
|
141
|
-
*/
|
|
142
|
-
function oklabToRgb(L, A, B) {
|
|
143
|
-
const l = (L + 0.3963377774 * A + 0.2158037573 * B) ** 3;
|
|
144
|
-
const m = (L - 0.1055613458 * A - 0.0638541728 * B) ** 3;
|
|
145
|
-
const s = (L - 0.0894841775 * A - 1.291485548 * B) ** 3;
|
|
146
|
-
return [
|
|
147
|
-
toGamma(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s),
|
|
148
|
-
toGamma(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s),
|
|
149
|
-
toGamma(-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s),
|
|
150
|
-
];
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Mix two colours in OKLab (perceptually even, no muddy midpoints).
|
|
155
|
-
*
|
|
156
|
-
* @param {string} a - First colour.
|
|
157
|
-
* @param {string} b - Second colour.
|
|
158
|
-
* @param {number} t - 0 gives `a`, 1 gives `b`.
|
|
159
|
-
* @returns {string} `#rrggbb`.
|
|
160
|
-
*
|
|
161
|
-
* @example
|
|
162
|
-
* mixOklab('#ffffff', '#2563eb', 0.1); // a pale blue tint
|
|
163
|
-
*/
|
|
164
|
-
export function mixOklab(a, b, t) {
|
|
165
|
-
const p = toLab(a);
|
|
166
|
-
const q = toLab(b);
|
|
167
|
-
const rgb = oklabToRgb(p[0] + (q[0] - p[0]) * t, p[1] + (q[1] - p[1]) * t, p[2] + (q[2] - p[2]) * t);
|
|
168
|
-
return toRgbHex(rgb);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/** @param {string} color @returns {[number, number, number]} OKLab. */
|
|
172
|
-
function toLab(color) {
|
|
173
|
-
const { l, c, h } = toOklch(color);
|
|
174
|
-
return [l, c * Math.cos((h * Math.PI) / 180), c * Math.sin((h * Math.PI) / 180)];
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Mix two colours in OKLCH: lightness and chroma linearly, hue the short
|
|
179
|
-
* way round. Unlike OKLab, a mix of two far-apart hues stays vivid instead
|
|
180
|
-
* of passing through grey (orange → blue goes by way of pink, not mud).
|
|
181
|
-
*
|
|
182
|
-
* @param {string} a - First colour.
|
|
183
|
-
* @param {string} b - Second colour.
|
|
184
|
-
* @param {number} t - 0 gives `a`, 1 gives `b`.
|
|
185
|
-
* @returns {string} `#rrggbb`.
|
|
186
|
-
*
|
|
187
|
-
* @example
|
|
188
|
-
* mixOklch('#c9502a', '#a9bdf2', 0.5); // a clear rose, not a muddy mauve
|
|
189
|
-
*/
|
|
190
|
-
export function mixOklch(a, b, t) {
|
|
191
|
-
const p = toOklch(a);
|
|
192
|
-
const q = toOklch(b);
|
|
193
|
-
// A near-grey has no meaningful hue: take the other colour's.
|
|
194
|
-
const ph = p.c < 0.02 ? q.h : p.h;
|
|
195
|
-
const qh = q.c < 0.02 ? p.h : q.h;
|
|
196
|
-
const dh = ((qh - ph + 540) % 360) - 180;
|
|
197
|
-
return fromOklch({ l: p.l + (q.l - p.l) * t, c: p.c + (q.c - p.c) * t, h: (ph + dh * t + 360) % 360 });
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/** Near-black used wherever white doesn't read. */
|
|
201
|
-
export const NEAR_BLACK = '#111111';
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* A colour for a mark on one or more backgrounds: `prefer` when it clears
|
|
205
|
-
* `ratio` against every one of them, else whichever of white and
|
|
206
|
-
* near-black has the better worst case.
|
|
207
|
-
*
|
|
208
|
-
* For icons and other non-text marks WCAG 1.4.11 asks 3:1 (text needs
|
|
209
|
-
* 4.5:1), so `onBackground(bg, 3, 'white')` keeps white on mid-tone fills
|
|
210
|
-
* where a text rule would flip to black.
|
|
211
|
-
*
|
|
212
|
-
* @param {string | string[]} backgrounds - The colour(s) under the mark.
|
|
213
|
-
* @param {number} [ratio=3] - Contrast to clear.
|
|
214
|
-
* @param {'white' | 'black' | string} [prefer='white'] - The colour to use when it clears `ratio`.
|
|
215
|
-
* @returns {string} `#rrggbb`.
|
|
216
|
-
*
|
|
217
|
-
* @example
|
|
218
|
-
* onBackground('#e0533d', 3, 'white'); // '#ffffff' (3.6:1)
|
|
219
|
-
* onBackground('#ffe14d', 3, 'white'); // '#111111'
|
|
220
|
-
*/
|
|
221
|
-
export function onBackground(backgrounds, ratio = 3, prefer = 'white') {
|
|
222
|
-
const bgs = [].concat(/** @type {any} */ (backgrounds)).map(toHex);
|
|
223
|
-
const want = prefer === 'white' ? '#ffffff' : prefer === 'black' ? NEAR_BLACK : toHex(prefer);
|
|
224
|
-
const worst = (/** @type {string} */ c) => Math.min(...bgs.map((b) => contrastRatio(c, b)));
|
|
225
|
-
if (worst(want) >= ratio) return want;
|
|
226
|
-
return worst('#ffffff') >= worst(NEAR_BLACK) ? '#ffffff' : NEAR_BLACK;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* The colour of an eased multi-stop gradient at `t` (0–1), the way the
|
|
231
|
-
* `linear` and `radial` layers draw it (smoothstep between stops, in OKLab).
|
|
232
|
-
*
|
|
233
|
-
* @param {string[]} colors - Two or more stops, evenly spaced.
|
|
234
|
-
* @param {number} t - Position along the gradient.
|
|
235
|
-
* @param {boolean} [ease=true] - Ease between stops.
|
|
236
|
-
* @returns {string} `#rrggbb`.
|
|
237
|
-
*/
|
|
238
|
-
export function gradientAt(colors, t, ease = true) {
|
|
239
|
-
const list = colors.map(toHex);
|
|
240
|
-
if (list.length === 1) return list[0];
|
|
241
|
-
const n = list.length - 1;
|
|
242
|
-
const x = Math.max(0, Math.min(1, t)) * n;
|
|
243
|
-
const i = Math.min(n - 1, Math.floor(x));
|
|
244
|
-
const f = x - i;
|
|
245
|
-
return mixOklab(list[i], list[i + 1], ease ? f * f * (3 - 2 * f) : f);
|
|
246
|
-
}
|
package/src/png.js
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A minimal PNG encoder (8-bit greyscale, RGB or RGBA; no filtering), so
|
|
3
|
-
* layers that are pixels by nature (dithers, print textures) can embed
|
|
4
|
-
* themselves in an SVG without sharp.
|
|
5
|
-
*
|
|
6
|
-
* @module png
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { deflateSync } from 'node:zlib';
|
|
10
|
-
|
|
11
|
-
/** CRC-32 table (PNG's polynomial). */
|
|
12
|
-
const CRC = (() => {
|
|
13
|
-
const t = new Uint32Array(256);
|
|
14
|
-
for (let n = 0; n < 256; n++) {
|
|
15
|
-
let c = n;
|
|
16
|
-
for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
17
|
-
t[n] = c >>> 0;
|
|
18
|
-
}
|
|
19
|
-
return t;
|
|
20
|
-
})();
|
|
21
|
-
|
|
22
|
-
/** @param {Uint8Array} bytes @returns {number} */
|
|
23
|
-
function crc32(bytes) {
|
|
24
|
-
let c = 0xffffffff;
|
|
25
|
-
for (const b of bytes) c = CRC[(c ^ b) & 0xff] ^ (c >>> 8);
|
|
26
|
-
return (c ^ 0xffffffff) >>> 0;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** @param {string} type @param {Uint8Array} data @returns {Buffer} */
|
|
30
|
-
function chunk(type, data) {
|
|
31
|
-
const out = Buffer.alloc(12 + data.length);
|
|
32
|
-
out.writeUInt32BE(data.length, 0);
|
|
33
|
-
out.write(type, 4, 'latin1');
|
|
34
|
-
Buffer.from(data).copy(out, 8);
|
|
35
|
-
out.writeUInt32BE(crc32(out.subarray(4, 8 + data.length)), 8 + data.length);
|
|
36
|
-
return out;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Encode pixels as a PNG.
|
|
41
|
-
*
|
|
42
|
-
* @param {Uint8Array} pixels - Row-major samples.
|
|
43
|
-
* @param {number} width
|
|
44
|
-
* @param {number} height
|
|
45
|
-
* @param {1 | 3 | 4} [channels=4] - Grey, RGB or RGBA.
|
|
46
|
-
* @returns {Buffer} PNG bytes.
|
|
47
|
-
*
|
|
48
|
-
* @example
|
|
49
|
-
* encodePng(new Uint8Array([0, 255]), 2, 1, 1); // a black and a white pixel
|
|
50
|
-
*/
|
|
51
|
-
export function encodePng(pixels, width, height, channels = 4) {
|
|
52
|
-
const stride = width * channels;
|
|
53
|
-
const raw = Buffer.alloc((stride + 1) * height);
|
|
54
|
-
for (let y = 0; y < height; y++) {
|
|
55
|
-
raw[y * (stride + 1)] = 0;
|
|
56
|
-
Buffer.from(pixels.buffer, pixels.byteOffset + y * stride, stride).copy(raw, y * (stride + 1) + 1);
|
|
57
|
-
}
|
|
58
|
-
const ihdr = Buffer.alloc(13);
|
|
59
|
-
ihdr.writeUInt32BE(width, 0);
|
|
60
|
-
ihdr.writeUInt32BE(height, 4);
|
|
61
|
-
ihdr[8] = 8;
|
|
62
|
-
ihdr[9] = { 1: 0, 3: 2, 4: 6 }[channels];
|
|
63
|
-
return Buffer.concat([
|
|
64
|
-
Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
|
|
65
|
-
chunk('IHDR', ihdr),
|
|
66
|
-
chunk('IDAT', deflateSync(raw, { level: 9 })),
|
|
67
|
-
chunk('IEND', new Uint8Array(0)),
|
|
68
|
-
]);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* A PNG as a data URI.
|
|
73
|
-
*
|
|
74
|
-
* @param {Buffer} png
|
|
75
|
-
* @returns {string}
|
|
76
|
-
*/
|
|
77
|
-
export const pngUri = (png) => `data:image/png;base64,${png.toString('base64')}`;
|