@arraypress/covers 1.1.0 → 1.4.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 +60 -0
- package/README.md +18 -809
- package/package.json +3 -1
- 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-poster.json +91 -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/logofolio.json +60 -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/pixel-mosaic.json +58 -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 +638 -69
- package/src/animate/index.d.ts +9 -19
- package/src/animate/index.js +6 -111
- 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/diagram.js +4 -3
- package/src/extract.js +2 -2
- package/src/fonts.js +1 -2
- package/src/geometry.js +4 -34
- package/src/grid.js +1 -1
- package/src/handmade.js +9 -7
- package/src/icons.js +1 -2
- package/src/index.d.ts +47 -54
- package/src/index.js +5 -5
- package/src/inter-data.js +1 -1
- package/src/json-recipe.js +28 -7
- package/src/layer-options.js +26 -4
- package/src/layers/barcode.js +3 -3
- package/src/layers/geometry.js +5 -5
- package/src/layers/index.d.ts +6 -0
- package/src/layers/index.js +3 -0
- package/src/layers/mark-geometry.d.ts +15 -0
- package/src/layers/mark-geometry.js +46 -0
- package/src/layers/marks.d.ts +25 -0
- package/src/layers/marks.js +102 -0
- package/src/layers/mosaic.d.ts +32 -0
- package/src/layers/mosaic.js +155 -0
- package/src/layers/perforation.js +3 -3
- package/src/layers/repeat.js +2 -2
- package/src/layers/shape.d.ts +2 -2
- package/src/layers/shape.js +5 -5
- package/src/layers/soft.js +2 -2
- package/src/layers/svg.js +2 -3
- package/src/layers/table.js +3 -3
- package/src/layers/tear.d.ts +1 -1
- package/src/layers/tear.js +4 -4
- package/src/layers/texture.js +3 -3
- package/src/layers/util.js +15 -2
- package/src/layers.js +17 -32
- package/src/{effects/paint.js → paint/index.js} +5 -5
- package/src/{effects/paint-presets.d.ts → paint/presets.d.ts} +1 -1
- package/src/{effects/paint-presets.js → paint/presets.js} +1 -1
- package/src/{effects → paint}/tile.d.ts +1 -1
- package/src/{effects → paint}/tile.js +4 -4
- 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.js +1 -1
- package/src/platform/index.d.ts +3 -3
- package/src/platform/index.js +6 -4
- package/src/platform/load.js +1 -2
- package/src/print.js +9 -7
- package/src/recipes.js +1 -1
- package/src/render/backdrop.js +2 -2
- package/src/render/grain.js +1 -1
- package/src/render/group.js +3 -3
- package/src/render/key.js +1 -2
- package/src/{effects → render}/transform.d.ts +2 -1
- package/src/{effects → render}/transform.js +17 -5
- package/src/render/use.d.ts +1 -1
- package/src/render/use.js +2 -2
- package/src/render/wrap.js +5 -60
- package/src/render.js +55 -18
- package/src/text.js +1 -1
- package/src/type/auto-color.js +1 -1
- package/src/type/fallback.js +1 -1
- package/src/type/glyphs.js +2 -2
- package/src/type/line.js +1 -1
- package/src/type/live.js +1 -1
- package/src/type/path.js +2 -2
- package/src/type/pixel.d.ts +0 -2
- package/src/type/pixel.js +3 -33
- package/src/type/spans.d.ts +5 -1
- package/src/type/spans.js +28 -5
- package/src/type/symbols.d.ts +1 -1
- package/src/type/symbols.js +20 -2
- package/src/type/typeset.js +25 -10
- package/src/version.js +1 -1
- package/src/color.js +0 -246
- package/src/effects/ascii.d.ts +0 -7
- package/src/effects/ascii.js +0 -93
- package/src/effects/buffer.d.ts +0 -60
- package/src/effects/buffer.js +0 -472
- package/src/effects/detail.d.ts +0 -12
- package/src/effects/detail.js +0 -124
- package/src/effects/digital.d.ts +0 -14
- package/src/effects/digital.js +0 -216
- package/src/effects/distort.d.ts +0 -20
- package/src/effects/distort.js +0 -314
- package/src/effects/dither.d.ts +0 -8
- package/src/effects/dither.js +0 -147
- package/src/effects/drop-shadow.d.ts +0 -4
- package/src/effects/drop-shadow.js +0 -45
- package/src/effects/extrude.d.ts +0 -5
- package/src/effects/extrude.js +0 -55
- package/src/effects/film.d.ts +0 -11
- package/src/effects/film.js +0 -204
- package/src/effects/glow.d.ts +0 -8
- package/src/effects/glow.js +0 -56
- package/src/effects/grade.d.ts +0 -30
- package/src/effects/grade.js +0 -274
- package/src/effects/halftone.d.ts +0 -8
- package/src/effects/halftone.js +0 -95
- package/src/effects/index.d.ts +0 -125
- package/src/effects/index.js +0 -277
- package/src/effects/ink.d.ts +0 -12
- package/src/effects/ink.js +0 -70
- package/src/effects/lens.d.ts +0 -14
- package/src/effects/lens.js +0 -277
- package/src/effects/light.d.ts +0 -10
- package/src/effects/light.js +0 -59
- package/src/effects/looks.d.ts +0 -9
- package/src/effects/looks.js +0 -74
- package/src/effects/mask.d.ts +0 -52
- package/src/effects/mask.js +0 -314
- package/src/effects/motion.d.ts +0 -6
- package/src/effects/motion.js +0 -60
- package/src/effects/newsprint.d.ts +0 -6
- package/src/effects/newsprint.js +0 -89
- package/src/effects/optics.d.ts +0 -10
- package/src/effects/optics.js +0 -213
- package/src/effects/options.d.ts +0 -6
- package/src/effects/options.js +0 -106
- package/src/effects/pixels.d.ts +0 -24
- package/src/effects/pixels.js +0 -138
- package/src/effects/polar.d.ts +0 -10
- package/src/effects/polar.js +0 -265
- package/src/effects/region.d.ts +0 -8
- package/src/effects/region.js +0 -136
- package/src/effects/round-border.d.ts +0 -5
- package/src/effects/round-border.js +0 -62
- package/src/effects/screen.d.ts +0 -14
- package/src/effects/screen.js +0 -212
- package/src/effects/sort.d.ts +0 -8
- package/src/effects/sort.js +0 -125
- package/src/effects/symmetry.d.ts +0 -6
- package/src/effects/symmetry.js +0 -74
- package/src/effects/tone.d.ts +0 -16
- package/src/effects/tone.js +0 -117
- package/src/effects/util.d.ts +0 -16
- package/src/effects/util.js +0 -85
- package/src/platform/bytes.d.ts +0 -22
- package/src/platform/bytes.js +0 -103
- package/src/platform/compress.d.ts +0 -12
- package/src/platform/compress.js +0 -81
- package/src/platform/node.d.ts +0 -26
- package/src/platform/node.js +0 -86
- package/src/png.js +0 -126
- package/src/raster/index.d.ts +0 -24
- package/src/raster/index.js +0 -171
- package/src/render/content.d.ts +0 -5
- package/src/render/content.js +0 -58
- package/src/render/tiles.d.ts +0 -6
- package/src/render/tiles.js +0 -77
- package/src/rng.js +0 -95
- package/src/svg.js +0 -69
- /package/src/{effects/paint.d.ts → paint/index.d.ts} +0 -0
package/src/platform/bytes.d.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
// Byte helpers without Buffer.
|
|
2
|
-
|
|
3
|
-
/** Bytes as a Uint8Array view. */
|
|
4
|
-
export function u8(v: Uint8Array | ArrayBuffer | ArrayBufferView): Uint8Array;
|
|
5
|
-
/** Whether a value is bytes. */
|
|
6
|
-
export function isBytes(v: unknown): v is Uint8Array | ArrayBuffer;
|
|
7
|
-
/** Base64-encode bytes. */
|
|
8
|
-
export function toBase64(bytes: Uint8Array | ArrayBuffer): string;
|
|
9
|
-
/** Decode base64. */
|
|
10
|
-
export function fromBase64(s: string): Uint8Array;
|
|
11
|
-
/** Each byte as one character. */
|
|
12
|
-
export function latin1(b: Uint8Array): string;
|
|
13
|
-
/** Decode UTF-8. */
|
|
14
|
-
export function utf8(b: Uint8Array | ArrayBuffer): string;
|
|
15
|
-
/** Encode UTF-8. */
|
|
16
|
-
export function encodeUtf8(s: string): Uint8Array;
|
|
17
|
-
/** Join byte arrays. */
|
|
18
|
-
export function concat(parts: Uint8Array[]): Uint8Array;
|
|
19
|
-
/** A `data:` URI's bytes, or undefined when it isn't one. */
|
|
20
|
-
export function fromDataUri(uri: string): Uint8Array | undefined;
|
|
21
|
-
/** A hex digest with Web Crypto. */
|
|
22
|
-
export function hexDigest(algo: 'SHA-1' | 'SHA-256', data: Uint8Array | string): Promise<string>;
|
package/src/platform/bytes.js
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
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>;
|
package/src/platform/compress.js
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
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
|
-
}
|
package/src/platform/node.d.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
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;
|
package/src/platform/node.js
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
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
|
-
}
|
package/src/png.js
DELETED
|
@@ -1,126 +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, 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)}`;
|
package/src/raster/index.d.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
// Rasterising in the browser.
|
|
2
|
-
|
|
3
|
-
/** Browser raster options. */
|
|
4
|
-
export interface RasterizeOptions {
|
|
5
|
-
/** Default `'png'`. */
|
|
6
|
-
format?: 'png' | 'jpeg' | 'webp';
|
|
7
|
-
/** 0–1 (JPEG, WebP). Default 0.9. */
|
|
8
|
-
quality?: number;
|
|
9
|
-
/** Output width or height, px (the other follows), or a `scale`. */
|
|
10
|
-
width?: number;
|
|
11
|
-
height?: number;
|
|
12
|
-
scale?: number;
|
|
13
|
-
/** A colour under the art (JPEG defaults to the page colour). */
|
|
14
|
-
background?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** Rasterise an SVG string in a browser page. */
|
|
18
|
-
export function rasterize(svg: string, options?: RasterizeOptions): Promise<Blob>;
|
|
19
|
-
/** RGBA pixels of an SVG at a size: sharp in Node, a canvas in a page, undefined elsewhere. */
|
|
20
|
-
export function pixelsOf(svg: string, w: number, h: number, sharp?: unknown): Promise<Uint8Array | undefined>;
|
|
21
|
-
/** Encode RGBA pixels for embedding: JPEG where opaque (unless `lossless`), else PNG. */
|
|
22
|
-
export function encodePixels(data: Uint8Array | Uint8ClampedArray, w: number, h: number, o?: { opaque?: boolean; lossless?: boolean; quality?: number }, sharp?: unknown): Promise<{ mime: 'image/jpeg' | 'image/png'; bytes: Uint8Array }>;
|
|
23
|
-
/** Pixels through a real JPEG encoder and back; undefined where none is available. */
|
|
24
|
-
export function jpegRoundTrip(data: Uint8ClampedArray, w: number, h: number, quality: number, subsample: boolean, sharp?: unknown): Promise<Uint8ClampedArray | undefined>;
|
package/src/raster/index.js
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Rasterising without sharp: an SVG drawn into a canvas in the browser,
|
|
3
|
-
* encoded as PNG, JPEG or WebP. `renderImage()` stays the Node path (sharp,
|
|
4
|
-
* with its grain and padded-blur handling); this is the browser one.
|
|
5
|
-
*
|
|
6
|
-
* The SVG is decoded by the browser as an `<img>` (so it needs a page:
|
|
7
|
-
* `document` and `Image`), drawn onto an `OffscreenCanvas` when there is
|
|
8
|
-
* one (else a `<canvas>`), and encoded with `convertToBlob` / `toBlob`.
|
|
9
|
-
*
|
|
10
|
-
* @module raster
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
const MIMES = /** @type {const} */ ({ png: 'image/png', jpeg: 'image/jpeg', webp: 'image/webp' });
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Decode an SVG string into a drawable image (browser).
|
|
17
|
-
*
|
|
18
|
-
* @param {string} svg
|
|
19
|
-
* @returns {Promise<HTMLImageElement>}
|
|
20
|
-
*/
|
|
21
|
-
async function decodeSvg(svg) {
|
|
22
|
-
const g = /** @type {any} */ (globalThis);
|
|
23
|
-
if (typeof g.Image !== 'function' || typeof g.document === 'undefined') {
|
|
24
|
-
throw new Error('rasterize() needs a browser page (Image and canvas). In Node use renderImage(); in a Worker return the SVG, or rasterise it with a wasm renderer.');
|
|
25
|
-
}
|
|
26
|
-
const url = URL.createObjectURL(new Blob([svg], { type: 'image/svg+xml' }));
|
|
27
|
-
try {
|
|
28
|
-
const img = new g.Image();
|
|
29
|
-
img.decoding = 'sync';
|
|
30
|
-
img.src = url;
|
|
31
|
-
await img.decode();
|
|
32
|
-
return img;
|
|
33
|
-
} finally {
|
|
34
|
-
// Revoked after decode: the decoded image keeps its pixels.
|
|
35
|
-
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* A 2D canvas of a size.
|
|
41
|
-
*
|
|
42
|
-
* @param {number} w @param {number} h
|
|
43
|
-
* @returns {{ canvas: any, ctx: CanvasRenderingContext2D }}
|
|
44
|
-
*/
|
|
45
|
-
function canvasOf(w, h) {
|
|
46
|
-
const g = /** @type {any} */ (globalThis);
|
|
47
|
-
const canvas = typeof g.OffscreenCanvas === 'function' ? new g.OffscreenCanvas(w, h) : Object.assign(g.document.createElement('canvas'), { width: w, height: h });
|
|
48
|
-
return { canvas, ctx: canvas.getContext('2d') };
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Rasterise an SVG in the browser.
|
|
53
|
-
*
|
|
54
|
-
* @param {string} svg - An SVG document (from `renderSvg`).
|
|
55
|
-
* @param {{ format?: 'png' | 'jpeg' | 'webp', quality?: number, width?: number, height?: number, scale?: number, background?: string }} [options]
|
|
56
|
-
* @returns {Promise<Blob>}
|
|
57
|
-
*
|
|
58
|
-
* @example
|
|
59
|
-
* const blob = await rasterize(await renderSvg(cover), { format: 'webp', quality: 0.9 });
|
|
60
|
-
* img.src = URL.createObjectURL(blob);
|
|
61
|
-
*/
|
|
62
|
-
export async function rasterize(svg, options = {}) {
|
|
63
|
-
const format = options.format ?? 'png';
|
|
64
|
-
if (!Object.hasOwn(MIMES, format)) throw new TypeError(`Unknown format "${format}" (png, jpeg, webp)`);
|
|
65
|
-
const vw = Number(/\bwidth="([\d.]+)"/.exec(svg)?.[1] ?? 1200);
|
|
66
|
-
const vh = Number(/\bheight="([\d.]+)"/.exec(svg)?.[1] ?? 1200);
|
|
67
|
-
const scale = options.scale ?? (options.width ? options.width / vw : options.height ? options.height / vh : 1);
|
|
68
|
-
const w = Math.max(1, Math.round(vw * scale));
|
|
69
|
-
const h = Math.max(1, Math.round(vh * scale));
|
|
70
|
-
const img = await decodeSvg(svg);
|
|
71
|
-
const { canvas, ctx } = canvasOf(w, h);
|
|
72
|
-
if (format === 'jpeg' || options.background) {
|
|
73
|
-
ctx.fillStyle = options.background ?? /fill="(#[0-9a-f]{6})"/i.exec(svg)?.[1] ?? '#ffffff';
|
|
74
|
-
ctx.fillRect(0, 0, w, h);
|
|
75
|
-
}
|
|
76
|
-
ctx.drawImage(img, 0, 0, w, h);
|
|
77
|
-
const type = MIMES[format];
|
|
78
|
-
const quality = options.quality ?? (format === 'png' ? undefined : 0.9);
|
|
79
|
-
if (typeof canvas.convertToBlob === 'function') return canvas.convertToBlob({ type, quality });
|
|
80
|
-
return new Promise((resolve, reject) => canvas.toBlob((/** @type {Blob | null} */ b) => (b ? resolve(b) : reject(new Error('Canvas encoding failed'))), type, quality));
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* RGBA pixels of an SVG at a size: sharp in Node, a canvas in a page.
|
|
85
|
-
* Undefined where neither is available (a Worker).
|
|
86
|
-
*
|
|
87
|
-
* @param {string} svg
|
|
88
|
-
* @param {number} w
|
|
89
|
-
* @param {number} h
|
|
90
|
-
* @param {any} [sharp] - The sharp module, when loaded.
|
|
91
|
-
* @returns {Promise<Uint8Array | undefined>}
|
|
92
|
-
*/
|
|
93
|
-
export async function pixelsOf(svg, w, h, sharp) {
|
|
94
|
-
if (sharp) {
|
|
95
|
-
const out = await sharp(new TextEncoder().encode(svg)).resize(w, h, { fit: 'fill' }).ensureAlpha().raw().toBuffer();
|
|
96
|
-
return new Uint8Array(out.buffer, out.byteOffset, out.byteLength);
|
|
97
|
-
}
|
|
98
|
-
const g = /** @type {any} */ (globalThis);
|
|
99
|
-
if (typeof g.Image !== 'function' || typeof g.document === 'undefined') return undefined;
|
|
100
|
-
const img = await decodeSvg(svg);
|
|
101
|
-
const { ctx } = canvasOf(w, h);
|
|
102
|
-
ctx.drawImage(img, 0, 0, w, h);
|
|
103
|
-
return new Uint8Array(ctx.getImageData(0, 0, w, h).data.buffer);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Encode RGBA pixels for embedding: JPEG where they're opaque (smaller),
|
|
108
|
-
* PNG where they aren't or where `lossless` (flat colour, crisp cells).
|
|
109
|
-
* sharp in Node, a canvas in a page, else the in-library PNG encoder.
|
|
110
|
-
*
|
|
111
|
-
* @param {Uint8Array | Uint8ClampedArray} data - RGBA, unpremultiplied.
|
|
112
|
-
* @param {number} w
|
|
113
|
-
* @param {number} h
|
|
114
|
-
* @param {{ opaque?: boolean, lossless?: boolean, quality?: number }} [o]
|
|
115
|
-
* @param {any} [sharp]
|
|
116
|
-
* @returns {Promise<{ mime: 'image/jpeg' | 'image/png', bytes: Uint8Array }>}
|
|
117
|
-
*/
|
|
118
|
-
export async function encodePixels(data, w, h, o = {}, sharp) {
|
|
119
|
-
const jpeg = Boolean(o.opaque) && !o.lossless;
|
|
120
|
-
const quality = o.quality ?? 92;
|
|
121
|
-
if (sharp) {
|
|
122
|
-
let img = sharp(data, { raw: { width: w, height: h, channels: 4 } });
|
|
123
|
-
if (o.opaque) img = img.removeAlpha();
|
|
124
|
-
const out = jpeg ? await img.jpeg({ quality, chromaSubsampling: '4:4:4' }).toBuffer() : await img.png({ compressionLevel: 6 }).toBuffer();
|
|
125
|
-
return { mime: jpeg ? 'image/jpeg' : 'image/png', bytes: new Uint8Array(out.buffer, out.byteOffset, out.byteLength) };
|
|
126
|
-
}
|
|
127
|
-
const g = /** @type {any} */ (globalThis);
|
|
128
|
-
if (typeof g.OffscreenCanvas === 'function' || typeof g.document !== 'undefined') {
|
|
129
|
-
const { canvas, ctx } = canvasOf(w, h);
|
|
130
|
-
ctx.putImageData(new g.ImageData(new Uint8ClampedArray(data.buffer, data.byteOffset, data.byteLength), w, h), 0, 0);
|
|
131
|
-
const type = jpeg ? 'image/jpeg' : 'image/png';
|
|
132
|
-
const blob = typeof canvas.convertToBlob === 'function'
|
|
133
|
-
? await canvas.convertToBlob({ type, quality: quality / 100 })
|
|
134
|
-
: await new Promise((resolve, reject) => canvas.toBlob((/** @type {Blob | null} */ b) => (b ? resolve(b) : reject(new Error('Canvas encoding failed'))), type, quality / 100));
|
|
135
|
-
return { mime: type, bytes: new Uint8Array(await blob.arrayBuffer()) };
|
|
136
|
-
}
|
|
137
|
-
const { encodePngAsync } = await import('../png.js');
|
|
138
|
-
return { mime: 'image/png', bytes: await encodePngAsync(new Uint8Array(data.buffer, data.byteOffset, data.byteLength), w, h, 4) };
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Pixels through a real JPEG encoder and back (compression artefacts):
|
|
143
|
-
* libjpeg through sharp in Node, the browser's own encoder in a page.
|
|
144
|
-
* Undefined where neither is available.
|
|
145
|
-
*
|
|
146
|
-
* @param {Uint8ClampedArray} data - RGBA (alpha ignored).
|
|
147
|
-
* @param {number} w
|
|
148
|
-
* @param {number} h
|
|
149
|
-
* @param {number} quality - 1–100.
|
|
150
|
-
* @param {boolean} subsample - 4:2:0 chroma (blocky colour).
|
|
151
|
-
* @param {any} [sharp]
|
|
152
|
-
* @returns {Promise<Uint8ClampedArray | undefined>} RGBA, opaque.
|
|
153
|
-
*/
|
|
154
|
-
export async function jpegRoundTrip(data, w, h, quality, subsample, sharp) {
|
|
155
|
-
if (sharp) {
|
|
156
|
-
const jpg = await sharp(data, { raw: { width: w, height: h, channels: 4 } }).removeAlpha().jpeg({ quality, chromaSubsampling: subsample ? '4:2:0' : '4:4:4' }).toBuffer();
|
|
157
|
-
const out = await sharp(jpg).ensureAlpha().raw().toBuffer();
|
|
158
|
-
return new Uint8ClampedArray(out.buffer, out.byteOffset, out.byteLength);
|
|
159
|
-
}
|
|
160
|
-
const g = /** @type {any} */ (globalThis);
|
|
161
|
-
if (typeof g.createImageBitmap !== 'function' || (typeof g.OffscreenCanvas !== 'function' && typeof g.document === 'undefined')) return undefined;
|
|
162
|
-
const { canvas, ctx } = canvasOf(w, h);
|
|
163
|
-
ctx.putImageData(new g.ImageData(new Uint8ClampedArray(data), w, h), 0, 0);
|
|
164
|
-
const blob = typeof canvas.convertToBlob === 'function'
|
|
165
|
-
? await canvas.convertToBlob({ type: 'image/jpeg', quality: quality / 100 })
|
|
166
|
-
: await new Promise((resolve) => canvas.toBlob(resolve, 'image/jpeg', quality / 100));
|
|
167
|
-
const bitmap = await g.createImageBitmap(blob);
|
|
168
|
-
ctx.clearRect(0, 0, w, h);
|
|
169
|
-
ctx.drawImage(bitmap, 0, 0);
|
|
170
|
-
return new Uint8ClampedArray(ctx.getImageData(0, 0, w, h).data.buffer);
|
|
171
|
-
}
|
package/src/render/content.d.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
// Where a layer paints, for content-sized filter regions.
|
|
2
|
-
import type { Markup, RenderContext } from './context.js';
|
|
3
|
-
|
|
4
|
-
/** The box a layer's markup paints, in its units (drawn small with sharp); undefined when it can't be probed. */
|
|
5
|
-
export function contentBox(m: Markup, ctx: RenderContext): Promise<{ x: number; y: number; width: number; height: number } | undefined>;
|