@arraypress/covers 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +76 -0
- package/README.md +296 -11
- package/package.json +33 -5
- package/recipes/brand.json +1143 -0
- package/schema/recipe.schema.json +7300 -770
- package/src/animate/index.d.ts +19 -0
- package/src/animate/index.js +116 -0
- package/src/builtins.js +4 -0
- package/src/effects/ascii.d.ts +7 -0
- package/src/effects/ascii.js +93 -0
- package/src/effects/buffer.d.ts +60 -0
- package/src/effects/buffer.js +472 -0
- package/src/effects/detail.d.ts +12 -0
- package/src/effects/detail.js +124 -0
- package/src/effects/digital.d.ts +14 -0
- package/src/effects/digital.js +216 -0
- package/src/effects/distort.d.ts +20 -0
- package/src/effects/distort.js +314 -0
- package/src/effects/dither.d.ts +8 -0
- package/src/effects/dither.js +147 -0
- package/src/effects/drop-shadow.d.ts +4 -0
- package/src/effects/drop-shadow.js +45 -0
- package/src/effects/extrude.d.ts +5 -0
- package/src/effects/extrude.js +55 -0
- package/src/effects/film.d.ts +11 -0
- package/src/effects/film.js +204 -0
- package/src/effects/glow.d.ts +8 -0
- package/src/effects/glow.js +56 -0
- package/src/effects/grade.d.ts +30 -0
- package/src/effects/grade.js +274 -0
- package/src/effects/halftone.d.ts +8 -0
- package/src/effects/halftone.js +95 -0
- package/src/effects/index.d.ts +125 -0
- package/src/effects/index.js +277 -0
- package/src/effects/ink.d.ts +12 -0
- package/src/effects/ink.js +70 -0
- package/src/effects/lens.d.ts +14 -0
- package/src/effects/lens.js +277 -0
- package/src/effects/light.d.ts +10 -0
- package/src/effects/light.js +59 -0
- package/src/effects/looks.d.ts +9 -0
- package/src/effects/looks.js +74 -0
- package/src/effects/mask.d.ts +52 -0
- package/src/effects/mask.js +314 -0
- package/src/effects/motion.d.ts +6 -0
- package/src/effects/motion.js +60 -0
- package/src/effects/newsprint.d.ts +6 -0
- package/src/effects/newsprint.js +89 -0
- package/src/effects/optics.d.ts +10 -0
- package/src/effects/optics.js +213 -0
- package/src/effects/options.d.ts +6 -0
- package/src/effects/options.js +106 -0
- package/src/effects/paint-presets.d.ts +10 -0
- package/src/effects/paint-presets.js +47 -0
- package/src/effects/paint.d.ts +49 -0
- package/src/effects/paint.js +66 -0
- package/src/effects/pixels.d.ts +24 -0
- package/src/effects/pixels.js +138 -0
- package/src/effects/polar.d.ts +10 -0
- package/src/effects/polar.js +265 -0
- package/src/effects/region.d.ts +8 -0
- package/src/effects/region.js +136 -0
- package/src/effects/round-border.d.ts +5 -0
- package/src/effects/round-border.js +62 -0
- package/src/effects/screen.d.ts +14 -0
- package/src/effects/screen.js +212 -0
- package/src/effects/sort.d.ts +8 -0
- package/src/effects/sort.js +125 -0
- package/src/effects/symmetry.d.ts +6 -0
- package/src/effects/symmetry.js +74 -0
- package/src/effects/tile.d.ts +7 -0
- package/src/effects/tile.js +55 -0
- package/src/effects/tone.d.ts +16 -0
- package/src/effects/tone.js +117 -0
- package/src/effects/transform.d.ts +35 -0
- package/src/effects/transform.js +127 -0
- package/src/effects/util.d.ts +16 -0
- package/src/effects/util.js +85 -0
- package/src/extract.js +4 -3
- package/src/fonts.d.ts +55 -0
- package/src/fonts.js +246 -53
- package/src/formats/cut.d.ts +10 -0
- package/src/formats/cut.js +77 -0
- package/src/formats/index.d.ts +6 -0
- package/src/formats/index.js +9 -0
- package/src/formats/presets.d.ts +41 -0
- package/src/formats/presets.js +89 -0
- package/src/formats/print.d.ts +25 -0
- package/src/formats/print.js +117 -0
- package/src/formats/zones.d.ts +10 -0
- package/src/formats/zones.js +64 -0
- package/src/geometry.js +1 -1
- package/src/icons.js +43 -9
- package/src/index.d.ts +205 -19
- package/src/index.js +12 -4
- package/src/inter-data.js +27 -0
- package/src/json-recipe.js +181 -23
- package/src/kit/index.d.ts +20 -0
- package/src/kit/index.js +58 -0
- package/src/layer-options.js +23 -4
- package/src/layers/barcode.d.ts +19 -0
- package/src/layers/barcode.js +63 -0
- package/src/layers/code128.d.ts +4 -0
- package/src/layers/code128.js +28 -0
- package/src/layers/expr.d.ts +6 -0
- package/src/layers/expr.js +121 -0
- package/src/layers/geometry.d.ts +6 -0
- package/src/layers/geometry.js +238 -0
- package/src/layers/index.d.ts +18 -0
- package/src/layers/index.js +15 -0
- package/src/layers/perforation.d.ts +14 -0
- package/src/layers/perforation.js +47 -0
- package/src/layers/repeat.d.ts +29 -0
- package/src/layers/repeat.js +90 -0
- package/src/layers/shape.d.ts +46 -0
- package/src/layers/shape.js +115 -0
- package/src/layers/soft.d.ts +5 -0
- package/src/layers/soft.js +43 -0
- package/src/layers/svg.d.ts +23 -0
- package/src/layers/svg.js +109 -0
- package/src/layers/table.d.ts +25 -0
- package/src/layers/table.js +80 -0
- package/src/layers/tear.d.ts +21 -0
- package/src/layers/tear.js +74 -0
- package/src/layers/texture.d.ts +17 -0
- package/src/layers/texture.js +106 -0
- package/src/layers/util.d.ts +8 -0
- package/src/layers/util.js +48 -0
- package/src/layers.js +125 -41
- package/src/lint/image-size.d.ts +4 -0
- package/src/lint/image-size.js +34 -0
- package/src/lint/index.d.ts +16 -0
- package/src/lint/index.js +127 -0
- package/src/lint/rules.d.ts +5 -0
- package/src/lint/rules.js +19 -0
- package/src/node.d.ts +2 -0
- package/src/node.js +20 -0
- package/src/platform/bytes.d.ts +22 -0
- package/src/platform/bytes.js +103 -0
- package/src/platform/compress.d.ts +12 -0
- package/src/platform/compress.js +81 -0
- package/src/platform/harfbuzz.d.ts +9 -0
- package/src/platform/harfbuzz.js +80 -0
- package/src/platform/index.d.ts +8 -0
- package/src/platform/index.js +13 -0
- package/src/platform/load.d.ts +6 -0
- package/src/platform/load.js +72 -0
- package/src/platform/no-harfbuzz.d.ts +2 -0
- package/src/platform/no-harfbuzz.js +11 -0
- package/src/platform/node.d.ts +26 -0
- package/src/platform/node.js +86 -0
- package/src/platform/settings.d.ts +28 -0
- package/src/platform/settings.js +51 -0
- package/src/png.js +77 -28
- package/src/print.js +8 -7
- package/src/raster/index.d.ts +24 -0
- package/src/raster/index.js +171 -0
- package/src/recipes.js +4 -3
- package/src/render/backdrop.d.ts +6 -0
- package/src/render/backdrop.js +55 -0
- package/src/render/content.d.ts +5 -0
- package/src/render/content.js +58 -0
- package/src/render/context.d.ts +82 -0
- package/src/render/grain.d.ts +7 -0
- package/src/render/grain.js +113 -0
- package/src/render/group.d.ts +23 -0
- package/src/render/group.js +67 -0
- package/src/render/key.d.ts +5 -0
- package/src/render/key.js +91 -0
- package/src/render/report.d.ts +5 -0
- package/src/render/report.js +19 -0
- package/src/render/tiles.d.ts +6 -0
- package/src/render/tiles.js +77 -0
- package/src/render/use.d.ts +27 -0
- package/src/render/use.js +73 -0
- package/src/render/wrap.d.ts +14 -0
- package/src/render/wrap.js +199 -0
- package/src/render.js +303 -212
- package/src/text.d.ts +29 -0
- package/src/text.js +22 -15
- package/src/type/auto-color.d.ts +9 -0
- package/src/type/auto-color.js +44 -0
- package/src/type/fallback.d.ts +11 -0
- package/src/type/fallback.js +92 -0
- package/src/type/glyphs.d.ts +24 -0
- package/src/type/glyphs.js +87 -0
- package/src/type/index.d.ts +5 -0
- package/src/type/index.js +10 -0
- package/src/type/kern.d.ts +4 -0
- package/src/type/kern.js +144 -0
- package/src/type/layout-ref.d.ts +10 -0
- package/src/type/layout-ref.js +74 -0
- package/src/type/line.d.ts +11 -0
- package/src/type/line.js +105 -0
- package/src/type/live.d.ts +6 -0
- package/src/type/live.js +39 -0
- package/src/type/name.d.ts +4 -0
- package/src/type/name.js +32 -0
- package/src/type/path.d.ts +18 -0
- package/src/type/path.js +89 -0
- package/src/type/pixel.d.ts +9 -0
- package/src/type/pixel.js +115 -0
- package/src/type/sfnt.d.ts +20 -0
- package/src/type/sfnt.js +258 -0
- package/src/type/spans.d.ts +33 -0
- package/src/type/spans.js +138 -0
- package/src/type/symbols.d.ts +7 -0
- package/src/type/symbols.js +89 -0
- package/src/type/typeset.d.ts +10 -0
- package/src/type/typeset.js +352 -0
- package/src/type/woff2.d.ts +6 -0
- package/src/type/woff2.js +301 -0
- package/src/version.js +4 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Byte helpers that work the same in Node, browsers and Workers (no
|
|
3
|
+
* `Buffer`). Where Node's `Buffer` is present it's used for speed; the
|
|
4
|
+
* output is identical either way.
|
|
5
|
+
*
|
|
6
|
+
* @module bytes
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const B = /** @type {any} */ (globalThis).Buffer;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Bytes as a `Uint8Array` view.
|
|
13
|
+
*
|
|
14
|
+
* @param {Uint8Array | ArrayBuffer | ArrayBufferView} v
|
|
15
|
+
* @returns {Uint8Array}
|
|
16
|
+
*/
|
|
17
|
+
export function u8(v) {
|
|
18
|
+
if (v instanceof Uint8Array) return v;
|
|
19
|
+
if (v instanceof ArrayBuffer) return new Uint8Array(v);
|
|
20
|
+
if (ArrayBuffer.isView(v)) return new Uint8Array(v.buffer, v.byteOffset, v.byteLength);
|
|
21
|
+
throw new TypeError('Expected bytes (Uint8Array or ArrayBuffer)');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** @param {unknown} v @returns {v is Uint8Array | ArrayBuffer} */
|
|
25
|
+
export const isBytes = (v) => v instanceof Uint8Array || v instanceof ArrayBuffer || ArrayBuffer.isView(v);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Base64-encode bytes.
|
|
29
|
+
*
|
|
30
|
+
* @param {Uint8Array | ArrayBuffer} bytes
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
export function toBase64(bytes) {
|
|
34
|
+
const b = u8(bytes);
|
|
35
|
+
if (B) return B.from(b.buffer, b.byteOffset, b.byteLength).toString('base64');
|
|
36
|
+
let out = '';
|
|
37
|
+
for (let i = 0; i < b.length; i += 0x8000) out += String.fromCharCode.apply(null, /** @type {any} */ (b.subarray(i, i + 0x8000)));
|
|
38
|
+
return btoa(out);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Decode base64.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} s
|
|
45
|
+
* @returns {Uint8Array}
|
|
46
|
+
*/
|
|
47
|
+
export function fromBase64(s) {
|
|
48
|
+
if (B) return new Uint8Array(B.from(s, 'base64'));
|
|
49
|
+
const bin = atob(s.replace(/\s+/g, ''));
|
|
50
|
+
const out = new Uint8Array(bin.length);
|
|
51
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** @param {Uint8Array} b @returns {string} Each byte as one char. */
|
|
56
|
+
export function latin1(b) {
|
|
57
|
+
let s = '';
|
|
58
|
+
for (let i = 0; i < b.length; i++) s += String.fromCharCode(b[i]);
|
|
59
|
+
return s;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** @param {Uint8Array | ArrayBuffer} b @returns {string} */
|
|
63
|
+
export const utf8 = (b) => new TextDecoder().decode(u8(b));
|
|
64
|
+
|
|
65
|
+
/** @param {string} s @returns {Uint8Array} */
|
|
66
|
+
export const encodeUtf8 = (s) => new TextEncoder().encode(s);
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Join byte arrays.
|
|
70
|
+
*
|
|
71
|
+
* @param {Uint8Array[]} parts
|
|
72
|
+
* @returns {Uint8Array}
|
|
73
|
+
*/
|
|
74
|
+
export function concat(parts) {
|
|
75
|
+
const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
|
|
76
|
+
let o = 0;
|
|
77
|
+
for (const p of parts) { out.set(p, o); o += p.length; }
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Decode a `data:` URI.
|
|
83
|
+
*
|
|
84
|
+
* @param {string} uri
|
|
85
|
+
* @returns {Uint8Array | undefined} Its bytes, or undefined when it isn't one.
|
|
86
|
+
*/
|
|
87
|
+
export function fromDataUri(uri) {
|
|
88
|
+
const m = /^data:([^,]*?),(.*)$/s.exec(uri);
|
|
89
|
+
if (!m) return undefined;
|
|
90
|
+
return /;base64$/i.test(m[1]) ? fromBase64(m[2]) : encodeUtf8(decodeURIComponent(m[2]));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Hex digest with Web Crypto (Node 20+, browsers, Workers).
|
|
95
|
+
*
|
|
96
|
+
* @param {'SHA-1' | 'SHA-256'} algo
|
|
97
|
+
* @param {Uint8Array | string} data
|
|
98
|
+
* @returns {Promise<string>}
|
|
99
|
+
*/
|
|
100
|
+
export async function hexDigest(algo, data) {
|
|
101
|
+
const buf = await globalThis.crypto.subtle.digest(algo, typeof data === 'string' ? encodeUtf8(data) : u8(data));
|
|
102
|
+
return Array.from(new Uint8Array(buf), (x) => x.toString(16).padStart(2, '0')).join('');
|
|
103
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// zlib on every platform.
|
|
2
|
+
|
|
3
|
+
/** zlib-deflate synchronously (Node), else undefined. */
|
|
4
|
+
export function deflateSync(data: Uint8Array): Uint8Array | undefined;
|
|
5
|
+
/** zlib-deflate (Node's zlib, else CompressionStream). */
|
|
6
|
+
export function deflate(data: Uint8Array): Promise<Uint8Array>;
|
|
7
|
+
/** zlib-inflate. */
|
|
8
|
+
export function inflate(data: Uint8Array): Promise<Uint8Array>;
|
|
9
|
+
/** Brotli-decompress synchronously (Node), else undefined. */
|
|
10
|
+
export function unbrotliSync(data: Uint8Array): Uint8Array | undefined;
|
|
11
|
+
/** Brotli-decompress (Node's zlib, else DecompressionStream('brotli') where there is one), else undefined. */
|
|
12
|
+
export function unbrotli(data: Uint8Array): Promise<Uint8Array | undefined>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* zlib deflate and inflate: Node's zlib, else `CompressionStream` and
|
|
3
|
+
* `DecompressionStream` (browsers, Workers). For PNG textures and WOFF.
|
|
4
|
+
*
|
|
5
|
+
* @module platform/compress
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { node } from './node.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* zlib-deflate synchronously, when the platform can (Node).
|
|
12
|
+
*
|
|
13
|
+
* @param {Uint8Array} data
|
|
14
|
+
* @returns {Uint8Array | undefined}
|
|
15
|
+
*/
|
|
16
|
+
export function deflateSync(data) {
|
|
17
|
+
const z = node('zlib');
|
|
18
|
+
return z ? z.deflateSync(data, { level: 9 }) : undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* zlib-deflate: Node's zlib, else `CompressionStream` (browsers, Workers).
|
|
23
|
+
* The two may differ in bytes but both are valid zlib streams.
|
|
24
|
+
*
|
|
25
|
+
* @param {Uint8Array} data
|
|
26
|
+
* @returns {Promise<Uint8Array>}
|
|
27
|
+
*/
|
|
28
|
+
export async function deflate(data) {
|
|
29
|
+
return deflateSync(data) ?? stream(data, new CompressionStream('deflate'));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* zlib-inflate (WOFF tables).
|
|
34
|
+
*
|
|
35
|
+
* @param {Uint8Array} data
|
|
36
|
+
* @returns {Promise<Uint8Array>}
|
|
37
|
+
*/
|
|
38
|
+
export async function inflate(data) {
|
|
39
|
+
const z = node('zlib');
|
|
40
|
+
return z ? z.inflateSync(data) : stream(data, new DecompressionStream('deflate'));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {Uint8Array} data
|
|
45
|
+
* @param {TransformStream} ts
|
|
46
|
+
* @returns {Promise<Uint8Array>}
|
|
47
|
+
*/
|
|
48
|
+
async function stream(data, ts) {
|
|
49
|
+
const out = new Response(new Blob([/** @type {any} */ (data)]).stream().pipeThrough(ts));
|
|
50
|
+
return new Uint8Array(await out.arrayBuffer());
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Brotli-decompress synchronously, when the platform can (Node's zlib).
|
|
55
|
+
*
|
|
56
|
+
* @param {Uint8Array} data
|
|
57
|
+
* @returns {Uint8Array | undefined}
|
|
58
|
+
*/
|
|
59
|
+
export function unbrotliSync(data) {
|
|
60
|
+
const z = node('zlib');
|
|
61
|
+
if (!z?.brotliDecompressSync) return undefined;
|
|
62
|
+
const out = z.brotliDecompressSync(data);
|
|
63
|
+
return new Uint8Array(out.buffer, out.byteOffset, out.byteLength);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Brotli-decompress (WOFF2): Node's zlib, else `DecompressionStream('brotli')`
|
|
68
|
+
* where the runtime has it. Undefined where neither can.
|
|
69
|
+
*
|
|
70
|
+
* @param {Uint8Array} data
|
|
71
|
+
* @returns {Promise<Uint8Array | undefined>}
|
|
72
|
+
*/
|
|
73
|
+
export async function unbrotli(data) {
|
|
74
|
+
const sync = unbrotliSync(data);
|
|
75
|
+
if (sync) return sync;
|
|
76
|
+
try {
|
|
77
|
+
return await stream(data, new DecompressionStream(/** @type {any} */ ('brotli')));
|
|
78
|
+
} catch {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// HarfBuzz, loaded once.
|
|
2
|
+
|
|
3
|
+
/** The harfbuzzjs module (its own types need Emscripten's; covers uses a handful of its classes). */
|
|
4
|
+
export type HarfBuzzModule = object;
|
|
5
|
+
|
|
6
|
+
/** harfbuzzjs, with its wasm from `configure({ harfbuzz })` when given. */
|
|
7
|
+
export function harfbuzz(): Promise<HarfBuzzModule>;
|
|
8
|
+
/** harfbuzzjs where it runs (and isn't switched off), else undefined: text then uses the JavaScript outliner. */
|
|
9
|
+
export function shaperModule(): Promise<HarfBuzzModule | undefined>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HarfBuzz, loaded once and lazily, with its WebAssembly from wherever
|
|
3
|
+
* `configure({ harfbuzz })` says. `#harfbuzz` is harfbuzzjs, except under
|
|
4
|
+
* the `workerd` / `worker` conditions, where it's a stub that fails, and
|
|
5
|
+
* text falls back to the JavaScript outliner.
|
|
6
|
+
*
|
|
7
|
+
* @module platform/harfbuzz
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { isBytes } from './bytes.js';
|
|
11
|
+
import { settings } from './settings.js';
|
|
12
|
+
import { loadBytes, asBytes } from './load.js';
|
|
13
|
+
|
|
14
|
+
/** @type {Promise<any> | undefined} */
|
|
15
|
+
let hbPromise;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Load harfbuzzjs. With `configure({ harfbuzz })` its WebAssembly comes from
|
|
19
|
+
* there (a URL, bytes, a compiled Module or a Response) instead of the file
|
|
20
|
+
* next to harfbuzzjs, which a bundler may not have copied.
|
|
21
|
+
*
|
|
22
|
+
* harfbuzzjs creates small wasm trampolines at runtime (Emscripten's
|
|
23
|
+
* `addFunction`), which Cloudflare Workers forbid; there text falls back to
|
|
24
|
+
* the built-in JavaScript outliner (`fonts/sfnt.js`).
|
|
25
|
+
*
|
|
26
|
+
* @returns {Promise<any>} The harfbuzzjs module.
|
|
27
|
+
*/
|
|
28
|
+
export function harfbuzz() {
|
|
29
|
+
hbPromise ??= (async () => {
|
|
30
|
+
const src = await settings.harfbuzz;
|
|
31
|
+
if (src == null) return import('#harfbuzz');
|
|
32
|
+
const W = /** @type {any} */ (WebAssembly);
|
|
33
|
+
const { instantiate, instantiateStreaming } = W;
|
|
34
|
+
/** @param {any} imports */
|
|
35
|
+
const ours = async (imports) => {
|
|
36
|
+
if (src instanceof WebAssembly.Module) return { module: src, instance: await instantiate(src, imports) };
|
|
37
|
+
const bytes = isBytes(src) || typeof src?.arrayBuffer === 'function' ? await asBytes(src) : await loadBytes(src, 'file');
|
|
38
|
+
return instantiate(bytes, imports);
|
|
39
|
+
};
|
|
40
|
+
let used = false;
|
|
41
|
+
// harfbuzzjs instantiates once, on import: hand it our module then.
|
|
42
|
+
W.instantiateStreaming = (/** @type {any} */ response, /** @type {any} */ imports) => {
|
|
43
|
+
if (used) return instantiateStreaming(response, imports);
|
|
44
|
+
used = true;
|
|
45
|
+
Promise.resolve(response).catch(() => {});
|
|
46
|
+
return ours(imports);
|
|
47
|
+
};
|
|
48
|
+
W.instantiate = (/** @type {any} */ source, /** @type {any} */ imports) => {
|
|
49
|
+
if (used || !(source instanceof Uint8Array || source instanceof ArrayBuffer)) return instantiate(source, imports);
|
|
50
|
+
used = true;
|
|
51
|
+
return ours(imports);
|
|
52
|
+
};
|
|
53
|
+
try {
|
|
54
|
+
return await import('#harfbuzz');
|
|
55
|
+
} finally {
|
|
56
|
+
W.instantiate = instantiate;
|
|
57
|
+
W.instantiateStreaming = instantiateStreaming;
|
|
58
|
+
}
|
|
59
|
+
})();
|
|
60
|
+
return hbPromise;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Whether outlines come from HarfBuzz: `shaper: 'harfbuzz'`, or `'auto'`
|
|
65
|
+
* where HarfBuzz loads (everywhere but Workers).
|
|
66
|
+
*
|
|
67
|
+
* @returns {Promise<any>} harfbuzzjs, or undefined for the JS outliner.
|
|
68
|
+
*/
|
|
69
|
+
export async function shaperModule() {
|
|
70
|
+
if (settings.shaper === 'js') return undefined;
|
|
71
|
+
try {
|
|
72
|
+
return await harfbuzz();
|
|
73
|
+
} catch (err) {
|
|
74
|
+
if (settings.shaper === 'harfbuzz') throw err;
|
|
75
|
+
hbPromise = Promise.reject(err);
|
|
76
|
+
hbPromise.catch(() => {});
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// The platform adapter.
|
|
2
|
+
export { node, installNode, isNode, FROM_DISK, readTextSync, packageFileSync } from './node.js';
|
|
3
|
+
export type { NodeModules } from './node.js';
|
|
4
|
+
export { settings, configure, registry } from './settings.js';
|
|
5
|
+
export type { Settings } from './settings.js';
|
|
6
|
+
export { loadBytes } from './load.js';
|
|
7
|
+
export { deflateSync, deflate, inflate, unbrotli, unbrotliSync } from './compress.js';
|
|
8
|
+
export { harfbuzz, shaperModule } from './harfbuzz.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The platform: everything that differs between Node, a browser and a
|
|
3
|
+
* Cloudflare Worker, behind one small adapter. Nothing here imports a
|
|
4
|
+
* `node:` module.
|
|
5
|
+
*
|
|
6
|
+
* @module platform
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export { node, installNode, isNode, FROM_DISK, readTextSync, packageFileSync } from './node.js';
|
|
10
|
+
export { settings, configure, registry } from './settings.js';
|
|
11
|
+
export { loadBytes } from './load.js';
|
|
12
|
+
export { deflateSync, deflate, inflate, unbrotli, unbrotliSync } from './compress.js';
|
|
13
|
+
export { harfbuzz, shaperModule } from './harfbuzz.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Loading bytes from anything.
|
|
2
|
+
|
|
3
|
+
/** Load an image, font, audio file or icon set. */
|
|
4
|
+
export function loadBytes(src: unknown, kind?: 'image' | 'font' | 'audio' | 'icons' | 'file'): Promise<Uint8Array>;
|
|
5
|
+
/** Bytes from what a loader returned (bytes, a Blob or a Response). */
|
|
6
|
+
export function asBytes(v: unknown): Promise<Uint8Array>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loading bytes from anything: bytes, Blobs and Responses, `data:` URIs,
|
|
3
|
+
* paths (Node), URLs (fetched where allowed), or a `configure({ load })`
|
|
4
|
+
* hook.
|
|
5
|
+
*
|
|
6
|
+
* @module platform/load
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { isBytes, u8, fromDataUri } from './bytes.js';
|
|
10
|
+
import { node, isNode } from './node.js';
|
|
11
|
+
import { settings } from './settings.js';
|
|
12
|
+
|
|
13
|
+
/** @returns {boolean} */
|
|
14
|
+
const networkAllowed = () => settings.network ?? !isNode();
|
|
15
|
+
|
|
16
|
+
/** @returns {typeof fetch} */
|
|
17
|
+
function fetcher() {
|
|
18
|
+
const f = settings.fetch ?? globalThis.fetch;
|
|
19
|
+
if (!f) throw new Error('No fetch available to load a URL: pass bytes, or configure({ fetch })');
|
|
20
|
+
return f;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Whatever a loader handed back, as bytes.
|
|
25
|
+
*
|
|
26
|
+
* @param {any} v
|
|
27
|
+
* @returns {Promise<Uint8Array>}
|
|
28
|
+
*/
|
|
29
|
+
async function asBytes(v) {
|
|
30
|
+
if (isBytes(v)) return u8(v);
|
|
31
|
+
if (typeof v?.arrayBuffer === 'function') {
|
|
32
|
+
if (typeof v.ok === 'boolean' && !v.ok) throw new Error(`Couldn't load ${v.url}: HTTP ${v.status}`);
|
|
33
|
+
return new Uint8Array(await v.arrayBuffer());
|
|
34
|
+
}
|
|
35
|
+
throw new TypeError('A loader must return bytes, an ArrayBuffer, a Blob or a Response');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Load bytes: an image, font, audio file or icon set.
|
|
40
|
+
*
|
|
41
|
+
* @param {any} src - Bytes, a Blob, a Response, a `data:` URI, a URL, or a path (Node).
|
|
42
|
+
* @param {'image' | 'font' | 'audio' | 'icons' | 'file'} [kind='file'] - What it is (for errors and the loader).
|
|
43
|
+
* @returns {Promise<Uint8Array>} The bytes (a Node Buffer when read from disk).
|
|
44
|
+
*/
|
|
45
|
+
export async function loadBytes(src, kind = 'file') {
|
|
46
|
+
if (isBytes(src)) return u8(src);
|
|
47
|
+
if (src && typeof src === 'object' && !(src instanceof URL)) return asBytes(src);
|
|
48
|
+
if (settings.load) {
|
|
49
|
+
const r = await settings.load(src, { kind });
|
|
50
|
+
if (r != null) return asBytes(r);
|
|
51
|
+
}
|
|
52
|
+
const s = String(src instanceof URL ? src.href : src);
|
|
53
|
+
const data = fromDataUri(s);
|
|
54
|
+
if (data) return data;
|
|
55
|
+
if (/^https?:/i.test(s)) {
|
|
56
|
+
if (!networkAllowed()) {
|
|
57
|
+
throw new TypeError(kind === 'image'
|
|
58
|
+
? 'Image src must be a local file or bytes: covers never fetch from the network (allow it with configure({ network: true }))'
|
|
59
|
+
: `${kind} "${s}" is a URL, and covers doesn't fetch from the network in Node unless asked: configure({ network: true }) or pass bytes`);
|
|
60
|
+
}
|
|
61
|
+
return asBytes(await fetcher()(s));
|
|
62
|
+
}
|
|
63
|
+
const fsp = node('fs/promises');
|
|
64
|
+
const isFileUrl = s.startsWith('file:');
|
|
65
|
+
if (fsp && (isFileUrl || !/^[a-z][\w+.-]*:/i.test(s))) return fsp.readFile(isFileUrl ? new URL(s) : s);
|
|
66
|
+
// A browser: a relative or absolute URL, against the page.
|
|
67
|
+
const base = /** @type {any} */ (globalThis).document?.baseURI ?? /** @type {any} */ (globalThis).location?.href;
|
|
68
|
+
if (base || /^[a-z][\w+.-]*:/i.test(s)) return asBytes(await fetcher()(new URL(s, base).href));
|
|
69
|
+
throw new Error(`Can't load ${kind} "${s}" here: pass bytes, or give covers a loader with configure({ load })`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { asBytes };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `#harfbuzz` resolves to in Cloudflare Workers (the `workerd` and
|
|
3
|
+
* `worker` export conditions): harfbuzzjs needs to compile WebAssembly
|
|
4
|
+
* trampolines at runtime, which Workers forbid, and its loader imports
|
|
5
|
+
* `node:module`. Importing this fails, so text falls back to the built-in
|
|
6
|
+
* JavaScript outliner (`sfnt.js`).
|
|
7
|
+
*
|
|
8
|
+
* @module no-harfbuzz
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
throw new Error('HarfBuzz is not available in Cloudflare Workers; covers draws text with its JavaScript outliner there');
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Node's built-ins, without importing node:.
|
|
2
|
+
|
|
3
|
+
/** Node modules covers uses. */
|
|
4
|
+
export interface NodeModules {
|
|
5
|
+
fs?: typeof import('node:fs');
|
|
6
|
+
fsp?: typeof import('node:fs/promises');
|
|
7
|
+
zlib?: typeof import('node:zlib');
|
|
8
|
+
path?: typeof import('node:path');
|
|
9
|
+
module?: typeof import('node:module');
|
|
10
|
+
url?: typeof import('node:url');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** The global `process`, where there is one. */
|
|
14
|
+
export const proc: NodeJS.Process | undefined;
|
|
15
|
+
/** This module was loaded from disk. */
|
|
16
|
+
export const FROM_DISK: boolean;
|
|
17
|
+
/** A Node built-in, when there is one. */
|
|
18
|
+
export function node<K extends 'fs' | 'fs/promises' | 'zlib' | 'path' | 'module' | 'url'>(name: K): K extends 'fs' ? typeof import('node:fs') : K extends 'fs/promises' ? typeof import('node:fs/promises') : K extends 'zlib' ? typeof import('node:zlib') : K extends 'path' ? typeof import('node:path') : K extends 'module' ? typeof import('node:module') : typeof import('node:url') | undefined;
|
|
19
|
+
/** Install Node's modules (what `@arraypress/covers/node` does). */
|
|
20
|
+
export function installNode(mods: NodeModules): void;
|
|
21
|
+
/** Whether this is Node with a file system. */
|
|
22
|
+
export function isNode(): boolean;
|
|
23
|
+
/** Read a text file synchronously (Node). */
|
|
24
|
+
export function readTextSync(path: string | URL): string;
|
|
25
|
+
/** A file shipped with the package, from disk (Node), or undefined. */
|
|
26
|
+
export function packageFileSync(rel: string): Uint8Array | undefined;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node's built-ins, found without importing `node:` (so bundles for
|
|
3
|
+
* browsers and Workers never see them): `process.getBuiltinModule` on Node
|
|
4
|
+
* 20.16+, or the modules `@arraypress/covers/node` installs.
|
|
5
|
+
*
|
|
6
|
+
* @module platform/node
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {object} NodeModules
|
|
11
|
+
* @property {any} [fs] - `node:fs`.
|
|
12
|
+
* @property {any} [fsp] - `node:fs/promises`.
|
|
13
|
+
* @property {any} [zlib] - `node:zlib`.
|
|
14
|
+
* @property {any} [path] - `node:path`.
|
|
15
|
+
* @property {any} [module] - `node:module`.
|
|
16
|
+
* @property {any} [url] - `node:url`.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** @type {NodeModules} */
|
|
20
|
+
let nodeModules = {};
|
|
21
|
+
|
|
22
|
+
/** The global `process`, where there is one. */
|
|
23
|
+
export const proc = /** @type {any} */ (globalThis).process;
|
|
24
|
+
/** Built-in lookup without a static import (Node 20.16+, Deno, Bun). */
|
|
25
|
+
const builtin = typeof proc?.getBuiltinModule === 'function' && proc.versions?.node ? proc.getBuiltinModule.bind(proc) : undefined;
|
|
26
|
+
/** This module was loaded from disk (Node, Deno, Bun), not a bundle or a page. */
|
|
27
|
+
export const FROM_DISK = String(import.meta.url).startsWith('file:');
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A Node built-in, when there is one.
|
|
31
|
+
*
|
|
32
|
+
* @param {'fs' | 'fs/promises' | 'zlib' | 'path' | 'module' | 'url'} name
|
|
33
|
+
* @returns {any}
|
|
34
|
+
*/
|
|
35
|
+
export function node(name) {
|
|
36
|
+
const key = name === 'fs/promises' ? 'fsp' : name;
|
|
37
|
+
const have = /** @type {any} */ (nodeModules)[key];
|
|
38
|
+
if (have) return have;
|
|
39
|
+
if (!builtin) return undefined;
|
|
40
|
+
try {
|
|
41
|
+
return (/** @type {any} */ (nodeModules)[key] = builtin(`node:${name}`));
|
|
42
|
+
} catch {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Install Node's modules (what `@arraypress/covers/node` does).
|
|
49
|
+
*
|
|
50
|
+
* @param {NodeModules} mods
|
|
51
|
+
* @returns {void}
|
|
52
|
+
*/
|
|
53
|
+
export function installNode(mods) {
|
|
54
|
+
nodeModules = { ...nodeModules, ...mods };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Whether this looks like Node with a file system we can read. */
|
|
58
|
+
export const isNode = () => Boolean(node('fs')) && Boolean(proc?.versions?.node);
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Read a text file synchronously (Node only; `loadRecipe`).
|
|
62
|
+
*
|
|
63
|
+
* @param {string | URL} path
|
|
64
|
+
* @returns {string}
|
|
65
|
+
*/
|
|
66
|
+
export function readTextSync(path) {
|
|
67
|
+
const fs = node('fs');
|
|
68
|
+
if (!fs) throw new Error('Reading a file synchronously needs Node. In a browser or Worker, fetch the JSON and pass it to defineRecipe().');
|
|
69
|
+
return fs.readFileSync(path, 'utf8');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Read a file that ships with this package (Node, from disk), or undefined.
|
|
74
|
+
*
|
|
75
|
+
* @param {string} rel - Relative to the package root (`'fonts/inter-latin-400.woff'`).
|
|
76
|
+
* @returns {Uint8Array | undefined}
|
|
77
|
+
*/
|
|
78
|
+
export function packageFileSync(rel) {
|
|
79
|
+
const fs = node('fs');
|
|
80
|
+
if (!fs || !FROM_DISK) return undefined;
|
|
81
|
+
try {
|
|
82
|
+
return fs.readFileSync(new URL(`../../${rel}`, import.meta.url));
|
|
83
|
+
} catch {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Global settings.
|
|
2
|
+
import type { FontSource } from '../index.js';
|
|
3
|
+
|
|
4
|
+
/** Global settings: how files load, where HarfBuzz comes from, how text is drawn. */
|
|
5
|
+
export interface Settings {
|
|
6
|
+
/** Load a file (an image, font, audio or icon set): return bytes, an ArrayBuffer, a Blob or a Response, or undefined to fall through. */
|
|
7
|
+
load?: (src: unknown, info: { kind: string }) => unknown;
|
|
8
|
+
/** Allow fetching http(s) URLs (default: yes in browsers and Workers, no in Node). */
|
|
9
|
+
network?: boolean;
|
|
10
|
+
fetch?: typeof fetch;
|
|
11
|
+
/** HarfBuzz's wasm: a URL, bytes, a compiled `WebAssembly.Module`, a `Response`, or a promise of one. */
|
|
12
|
+
harfbuzz?: unknown;
|
|
13
|
+
/** `'auto'` (HarfBuzz where it runs, else the JavaScript outliner), `'harfbuzz'`, `'js'`. */
|
|
14
|
+
shaper?: 'auto' | 'harfbuzz' | 'js';
|
|
15
|
+
text?: 'outline' | 'live';
|
|
16
|
+
/** Where icon sets load from outside Node: a URL template with `{prefix}`, or a function. */
|
|
17
|
+
icons?: string | ((prefix: string) => string);
|
|
18
|
+
/** Fonts for every cover. */
|
|
19
|
+
fonts?: FontSource[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
/** The current settings. */
|
|
24
|
+
export const settings: Settings;
|
|
25
|
+
/** Fonts registered for every cover. */
|
|
26
|
+
export const registry: FontSource[];
|
|
27
|
+
/** Configure loading, HarfBuzz and text rendering (merges with what's set). */
|
|
28
|
+
export function configure(options: Settings): void;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global settings: how files load, where HarfBuzz comes from, how text is
|
|
3
|
+
* drawn. Set with {@link configure}.
|
|
4
|
+
*
|
|
5
|
+
* @module platform/settings
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {object} Settings
|
|
10
|
+
* @property {((src: any, info: { kind: string }) => any) | undefined} load - A loader hook; return bytes (or a Response, Blob, ArrayBuffer), or undefined to fall through.
|
|
11
|
+
* @property {boolean | undefined} network - Allow fetching http(s) URLs.
|
|
12
|
+
* @property {typeof fetch | undefined} fetch - The fetch to use.
|
|
13
|
+
* @property {any} harfbuzz - HarfBuzz's wasm: a URL, bytes, a WebAssembly.Module, a Response, or a promise of one.
|
|
14
|
+
* @property {'auto' | 'harfbuzz' | 'js'} shaper - How text is shaped and outlined.
|
|
15
|
+
* @property {'outline' | 'live'} text - The default text rendering.
|
|
16
|
+
* @property {((prefix: string) => string) | string | undefined} icons - Where icon sets load from outside Node: a URL template with `{prefix}`, or a function.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** @type {Settings} */
|
|
20
|
+
export const settings = {
|
|
21
|
+
load: undefined,
|
|
22
|
+
network: undefined,
|
|
23
|
+
fetch: undefined,
|
|
24
|
+
harfbuzz: undefined,
|
|
25
|
+
shaper: 'auto',
|
|
26
|
+
text: 'outline',
|
|
27
|
+
icons: undefined,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Configure how covers loads files, fetches, shapes text and finds
|
|
32
|
+
* HarfBuzz. Settings are global and merge with what's set already.
|
|
33
|
+
*
|
|
34
|
+
* @param {Partial<Settings> & { fonts?: import('../index.js').FontSource[] }} options
|
|
35
|
+
* @returns {void}
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* // A browser app serving the wasm itself:
|
|
39
|
+
* configure({ harfbuzz: '/wasm/harfbuzz.wasm' });
|
|
40
|
+
* // A Worker: no wasm compilation at runtime, so text uses the JS outliner.
|
|
41
|
+
* configure({ shaper: 'js' });
|
|
42
|
+
*/
|
|
43
|
+
export function configure(options = {}) {
|
|
44
|
+
for (const k of /** @type {(keyof Settings)[]} */ (Object.keys(settings))) {
|
|
45
|
+
if (Object.hasOwn(options, k)) /** @type {any} */ (settings)[k] = /** @type {any} */ (options)[k];
|
|
46
|
+
}
|
|
47
|
+
if (options.fonts) for (const f of options.fonts) registry.push(f);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Fonts registered globally (`configure({ fonts })`, `registerFont()`). @type {import('../index.js').FontSource[]} */
|
|
51
|
+
export const registry = [];
|