@arraypress/covers 0.0.0-stage → 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 +126 -0
- package/LICENSE +29 -0
- package/README.md +846 -2
- package/fonts/OFL.txt +93 -0
- package/fonts/inter-latin-400.woff +0 -0
- package/fonts/inter-latin-500.woff +0 -0
- package/fonts/inter-latin-600.woff +0 -0
- package/fonts/inter-latin-700.woff +0 -0
- package/package.json +124 -4
- package/recipes/aurora.json +132 -0
- package/recipes/bauhaus.json +113 -0
- package/recipes/bigType.json +103 -0
- package/recipes/brand.json +1143 -0
- package/recipes/contour.json +109 -0
- package/recipes/cutout.json +191 -0
- package/recipes/diagram.json +92 -0
- package/recipes/dither.json +170 -0
- package/recipes/duotone.json +75 -0
- package/recipes/gridTitle.json +136 -0
- package/recipes/iconTile.json +214 -0
- package/recipes/issue.json +221 -0
- package/recipes/mark.json +170 -0
- package/recipes/photo.json +156 -0
- package/recipes/riso.json +387 -0
- package/recipes/stripes.json +206 -0
- package/recipes/waveform.json +154 -0
- package/schema/recipe.schema.json +8822 -0
- package/src/animate/index.d.ts +19 -0
- package/src/animate/index.js +116 -0
- package/src/astro.d.ts +31 -0
- package/src/astro.js +86 -0
- package/src/builtins.js +4 -0
- package/src/color.js +246 -0
- package/src/diagram.js +248 -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 +91 -0
- package/src/flatten.js +234 -0
- package/src/fonts.d.ts +55 -0
- package/src/fonts.js +393 -0
- 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 +187 -0
- package/src/grid.js +41 -0
- package/src/handmade.js +253 -0
- package/src/icons.js +191 -0
- package/src/index.d.ts +1083 -0
- package/src/index.js +27 -0
- package/src/inter-data.js +27 -0
- package/src/json-recipe.js +616 -0
- package/src/kit/index.d.ts +20 -0
- package/src/kit/index.js +58 -0
- package/src/layer-options.js +56 -0
- 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 +1056 -0
- 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/palette.js +185 -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 +126 -0
- package/src/print.js +659 -0
- package/src/raster/index.d.ts +24 -0
- package/src/raster/index.js +171 -0
- package/src/recipes.js +79 -0
- 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 +553 -0
- package/src/rng.js +95 -0
- package/src/svg.js +69 -0
- package/src/text.d.ts +29 -0
- package/src/text.js +237 -0
- 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,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 = [];
|
package/src/png.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
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, deflate } from './platform/index.js';
|
|
10
|
+
import { concat, toBase64 } from './platform/bytes.js';
|
|
11
|
+
|
|
12
|
+
/** CRC-32 table (PNG's polynomial). */
|
|
13
|
+
const CRC = (() => {
|
|
14
|
+
const t = new Uint32Array(256);
|
|
15
|
+
for (let n = 0; n < 256; n++) {
|
|
16
|
+
let c = n;
|
|
17
|
+
for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
18
|
+
t[n] = c >>> 0;
|
|
19
|
+
}
|
|
20
|
+
return t;
|
|
21
|
+
})();
|
|
22
|
+
|
|
23
|
+
/** @param {Uint8Array} bytes @returns {number} */
|
|
24
|
+
function crc32(bytes) {
|
|
25
|
+
let c = 0xffffffff;
|
|
26
|
+
for (const b of bytes) c = CRC[(c ^ b) & 0xff] ^ (c >>> 8);
|
|
27
|
+
return (c ^ 0xffffffff) >>> 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** @param {string} type @param {Uint8Array} data @returns {Uint8Array} */
|
|
31
|
+
function chunk(type, data) {
|
|
32
|
+
const out = new Uint8Array(12 + data.length);
|
|
33
|
+
const v = new DataView(out.buffer);
|
|
34
|
+
v.setUint32(0, data.length);
|
|
35
|
+
for (let i = 0; i < 4; i++) out[4 + i] = type.charCodeAt(i);
|
|
36
|
+
out.set(data, 8);
|
|
37
|
+
v.setUint32(8 + data.length, crc32(out.subarray(4, 8 + data.length)));
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The filtered scanlines and header of a PNG.
|
|
43
|
+
*
|
|
44
|
+
* @param {Uint8Array} pixels @param {number} width @param {number} height @param {1 | 3 | 4} channels
|
|
45
|
+
* @returns {{ raw: Uint8Array, ihdr: Uint8Array }}
|
|
46
|
+
*/
|
|
47
|
+
function scanlines(pixels, width, height, channels) {
|
|
48
|
+
const stride = width * channels;
|
|
49
|
+
const raw = new Uint8Array((stride + 1) * height);
|
|
50
|
+
for (let y = 0; y < height; y++) {
|
|
51
|
+
raw[y * (stride + 1)] = 0;
|
|
52
|
+
raw.set(pixels.subarray(y * stride, y * stride + stride), y * (stride + 1) + 1);
|
|
53
|
+
}
|
|
54
|
+
const ihdr = new Uint8Array(13);
|
|
55
|
+
const v = new DataView(ihdr.buffer);
|
|
56
|
+
v.setUint32(0, width);
|
|
57
|
+
v.setUint32(4, height);
|
|
58
|
+
ihdr[8] = 8;
|
|
59
|
+
ihdr[9] = /** @type {Record<number, number>} */ ({ 1: 0, 3: 2, 4: 6 })[channels];
|
|
60
|
+
return { raw, ihdr };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const SIGNATURE = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* zlib "stored" blocks: valid, uncompressed. The synchronous fallback where
|
|
67
|
+
* the platform has no synchronous deflate (browsers, Workers).
|
|
68
|
+
*
|
|
69
|
+
* @param {Uint8Array} data
|
|
70
|
+
* @returns {Uint8Array}
|
|
71
|
+
*/
|
|
72
|
+
function stored(data) {
|
|
73
|
+
const parts = [new Uint8Array([0x78, 0x01])];
|
|
74
|
+
for (let i = 0; i < data.length || i === 0; i += 65535) {
|
|
75
|
+
const block = data.subarray(i, i + 65535);
|
|
76
|
+
const last = i + 65535 >= data.length ? 1 : 0;
|
|
77
|
+
parts.push(new Uint8Array([last, block.length & 255, block.length >> 8, ~block.length & 255, (~block.length >> 8) & 255]), block);
|
|
78
|
+
if (last) break;
|
|
79
|
+
}
|
|
80
|
+
let a = 1;
|
|
81
|
+
let b = 0;
|
|
82
|
+
for (const x of data) { a = (a + x) % 65521; b = (b + a) % 65521; }
|
|
83
|
+
const adler = new Uint8Array(4);
|
|
84
|
+
new DataView(adler.buffer).setUint32(0, ((b << 16) | a) >>> 0);
|
|
85
|
+
parts.push(adler);
|
|
86
|
+
return concat(parts);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Encode pixels as a PNG (synchronously: zlib in Node, stored blocks where
|
|
91
|
+
* there's no synchronous deflate; {@link encodePngAsync} compresses
|
|
92
|
+
* everywhere).
|
|
93
|
+
*
|
|
94
|
+
* @param {Uint8Array} pixels - Row-major samples.
|
|
95
|
+
* @param {number} width
|
|
96
|
+
* @param {number} height
|
|
97
|
+
* @param {1 | 3 | 4} [channels=4] - Grey, RGB or RGBA.
|
|
98
|
+
* @returns {Uint8Array} PNG bytes.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* encodePng(new Uint8Array([0, 255]), 2, 1, 1); // a black and a white pixel
|
|
102
|
+
*/
|
|
103
|
+
export function encodePng(pixels, width, height, channels = 4) {
|
|
104
|
+
const { raw, ihdr } = scanlines(pixels, width, height, channels);
|
|
105
|
+
return concat([SIGNATURE, chunk('IHDR', ihdr), chunk('IDAT', deflateSync(raw) ?? stored(raw)), chunk('IEND', new Uint8Array(0))]);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Encode pixels as a PNG, compressed on every platform (Node's zlib, else
|
|
110
|
+
* `CompressionStream`).
|
|
111
|
+
*
|
|
112
|
+
* @param {Uint8Array} pixels @param {number} width @param {number} height @param {1 | 3 | 4} [channels=4]
|
|
113
|
+
* @returns {Promise<Uint8Array>}
|
|
114
|
+
*/
|
|
115
|
+
export async function encodePngAsync(pixels, width, height, channels = 4) {
|
|
116
|
+
const { raw, ihdr } = scanlines(pixels, width, height, channels);
|
|
117
|
+
return concat([SIGNATURE, chunk('IHDR', ihdr), chunk('IDAT', await deflate(raw)), chunk('IEND', new Uint8Array(0))]);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* A PNG as a data URI.
|
|
122
|
+
*
|
|
123
|
+
* @param {Uint8Array} png
|
|
124
|
+
* @returns {string}
|
|
125
|
+
*/
|
|
126
|
+
export const pngUri = (png) => `data:image/png;base64,${toBase64(png)}`;
|