@arraypress/covers 1.1.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +18 -809
  3. package/package.json +2 -1
  4. package/recipes/flyers/afterglow-motion.json +540 -0
  5. package/recipes/flyers/afterglow.json +70 -0
  6. package/recipes/flyers/arcfield-mirror-motion.json +397 -0
  7. package/recipes/flyers/arcfield-mirror.json +364 -0
  8. package/recipes/flyers/bauhaus-oktav.json +319 -0
  9. package/recipes/flyers/bauhaus-ritmo.json +735 -0
  10. package/recipes/flyers/bauhaus-volta.json +394 -0
  11. package/recipes/flyers/bloomfall.json +99 -0
  12. package/recipes/flyers/drag-solenne-motion.json +444 -0
  13. package/recipes/flyers/drag-solenne.json +426 -0
  14. package/recipes/flyers/fennick-patches.json +874 -0
  15. package/recipes/flyers/ferro-pass.json +398 -0
  16. package/recipes/flyers/ferrova.json +812 -0
  17. package/recipes/flyers/grid-rest-rhythm.json +125 -0
  18. package/recipes/flyers/groovy-honeycomb.json +816 -0
  19. package/recipes/flyers/groovy-marigold.json +479 -0
  20. package/recipes/flyers/groovy-peachfuzz.json +974 -0
  21. package/recipes/flyers/halden-orbit-motion.json +381 -0
  22. package/recipes/flyers/halden-orbit.json +356 -0
  23. package/recipes/flyers/halvard.json +120 -0
  24. package/recipes/flyers/kairo-weekender.json +696 -0
  25. package/recipes/flyers/linger-motion.json +518 -0
  26. package/recipes/flyers/linger.json +67 -0
  27. package/recipes/flyers/little-orchard.json +105 -0
  28. package/recipes/flyers/lofi-hexmoth.json +912 -0
  29. package/recipes/flyers/lofi-lowbit-motion.json +888 -0
  30. package/recipes/flyers/lofi-lowbit.json +880 -0
  31. package/recipes/flyers/lofi-nullpoint.json +3013 -0
  32. package/recipes/flyers/lumio-stack-motion.json +648 -0
  33. package/recipes/flyers/lumio-stack.json +438 -0
  34. package/recipes/flyers/magazine-cover.json +168 -0
  35. package/recipes/flyers/magazine-extended.json +269 -0
  36. package/recipes/flyers/magazine-knockout.json +40 -0
  37. package/recipes/flyers/northfold-varsity.json +618 -0
  38. package/recipes/flyers/open-studio.json +69 -0
  39. package/recipes/flyers/orrin-merch.json +715 -0
  40. package/recipes/flyers/oxyde.json +113 -0
  41. package/recipes/flyers/quiet-fields.json +610 -0
  42. package/recipes/flyers/scrapbook-halcyon.json +948 -0
  43. package/recipes/flyers/solenne-ticket.json +730 -0
  44. package/recipes/flyers/solvane-motion.json +576 -0
  45. package/recipes/flyers/solvane.json +98 -0
  46. package/recipes/flyers/subduct.json +99 -0
  47. package/recipes/flyers/sunvale-lineup.json +584 -0
  48. package/recipes/flyers/tidewell.json +91 -0
  49. package/recipes/flyers/tour-kairo.json +535 -0
  50. package/recipes/flyers/velora-stickers.json +885 -0
  51. package/recipes/flyers/wheatpaste-black.json +313 -0
  52. package/recipes/flyers/wheatpaste-mark.json +198 -0
  53. package/recipes/flyers/wheatpaste-torn.json +114 -0
  54. package/recipes/flyers/wheatpaste-wall.json +984 -0
  55. package/recipes/flyers/wheatpaste-words.json +233 -0
  56. package/recipes/release/aura.json +137 -0
  57. package/recipes/release/chrome.json +433 -0
  58. package/recipes/release/collage.json +372 -0
  59. package/recipes/release/duotone.json +113 -0
  60. package/recipes/release/film-still.json +102 -0
  61. package/recipes/release/headline.json +70 -0
  62. package/recipes/release/instant.json +170 -0
  63. package/recipes/release/label.json +364 -0
  64. package/recipes/release/liquid-glass.json +301 -0
  65. package/recipes/release/odd-tile-dark.json +688 -0
  66. package/recipes/release/odd-tile-dense.json +1056 -0
  67. package/recipes/release/odd-tile-shift.json +541 -0
  68. package/recipes/release/odd-tile-split.json +571 -0
  69. package/recipes/release/odd-tile.json +633 -0
  70. package/recipes/release/off-edge.json +96 -0
  71. package/recipes/release/plinth.json +794 -0
  72. package/recipes/release/riso.json +108 -0
  73. package/recipes/release/series/fluid-label.json +561 -0
  74. package/recipes/release/series/fluid-pixel.json +334 -0
  75. package/recipes/release/series/hairline.json +305 -0
  76. package/recipes/release/series/halo.json +282 -0
  77. package/recipes/release/series/mark-crop.json +135 -0
  78. package/recipes/release/series/mark-window.json +150 -0
  79. package/recipes/release/series/photo-label.json +475 -0
  80. package/recipes/release/series/smear-field.json +345 -0
  81. package/recipes/release/series/smear.json +202 -0
  82. package/recipes/release/series/spin.json +172 -0
  83. package/recipes/release/series/split-ghost.json +230 -0
  84. package/recipes/release/series/zoom.json +168 -0
  85. package/recipes/release/small-print.json +114 -0
  86. package/recipes/release/stack.json +156 -0
  87. package/recipes/release/swiss.json +117 -0
  88. package/schema/recipe.schema.json +332 -65
  89. package/src/animate/index.d.ts +9 -19
  90. package/src/animate/index.js +6 -111
  91. package/src/animate/keyframes.d.ts +21 -0
  92. package/src/animate/keyframes.js +119 -0
  93. package/src/animate/marquee.d.ts +30 -0
  94. package/src/animate/marquee.js +93 -0
  95. package/src/animate/morph.d.ts +8 -0
  96. package/src/animate/morph.js +99 -0
  97. package/src/animate/motion.d.ts +59 -0
  98. package/src/animate/motion.js +63 -0
  99. package/src/animate/sheet.d.ts +19 -0
  100. package/src/animate/sheet.js +61 -0
  101. package/src/animate/timing.d.ts +28 -0
  102. package/src/animate/timing.js +146 -0
  103. package/src/animate/tracks.d.ts +18 -0
  104. package/src/animate/tracks.js +139 -0
  105. package/src/diagram.js +4 -3
  106. package/src/extract.js +2 -2
  107. package/src/fonts.js +1 -2
  108. package/src/geometry.js +4 -34
  109. package/src/grid.js +1 -1
  110. package/src/handmade.js +9 -7
  111. package/src/icons.js +1 -2
  112. package/src/index.d.ts +39 -48
  113. package/src/index.js +5 -5
  114. package/src/inter-data.js +1 -1
  115. package/src/json-recipe.js +23 -7
  116. package/src/layer-options.js +21 -1
  117. package/src/layers/barcode.js +3 -3
  118. package/src/layers/geometry.js +5 -5
  119. package/src/layers/perforation.js +3 -3
  120. package/src/layers/repeat.js +2 -2
  121. package/src/layers/shape.d.ts +2 -2
  122. package/src/layers/shape.js +5 -5
  123. package/src/layers/soft.js +2 -2
  124. package/src/layers/svg.js +2 -3
  125. package/src/layers/table.js +3 -3
  126. package/src/layers/tear.d.ts +1 -1
  127. package/src/layers/tear.js +4 -4
  128. package/src/layers/texture.js +3 -3
  129. package/src/layers/util.js +15 -2
  130. package/src/layers.js +10 -26
  131. package/src/{effects/paint.js → paint/index.js} +5 -5
  132. package/src/{effects/paint-presets.d.ts → paint/presets.d.ts} +1 -1
  133. package/src/{effects/paint-presets.js → paint/presets.js} +1 -1
  134. package/src/{effects → paint}/tile.d.ts +1 -1
  135. package/src/{effects → paint}/tile.js +4 -4
  136. package/src/palette.js +1 -1
  137. package/src/platform/files.d.ts +6 -0
  138. package/src/platform/files.js +41 -0
  139. package/src/platform/harfbuzz.js +1 -1
  140. package/src/platform/index.d.ts +3 -3
  141. package/src/platform/index.js +6 -4
  142. package/src/platform/load.js +1 -2
  143. package/src/print.js +9 -7
  144. package/src/recipes.js +1 -1
  145. package/src/render/backdrop.js +2 -2
  146. package/src/render/grain.js +1 -1
  147. package/src/render/group.js +3 -3
  148. package/src/render/key.js +1 -2
  149. package/src/{effects → render}/transform.d.ts +2 -1
  150. package/src/{effects → render}/transform.js +17 -5
  151. package/src/render/use.d.ts +1 -1
  152. package/src/render/use.js +2 -2
  153. package/src/render/wrap.js +5 -60
  154. package/src/render.js +55 -18
  155. package/src/text.js +1 -1
  156. package/src/type/auto-color.js +1 -1
  157. package/src/type/glyphs.js +2 -2
  158. package/src/type/line.js +1 -1
  159. package/src/type/live.js +1 -1
  160. package/src/type/path.js +2 -2
  161. package/src/type/pixel.d.ts +0 -2
  162. package/src/type/pixel.js +3 -33
  163. package/src/type/spans.js +2 -2
  164. package/src/type/typeset.js +19 -8
  165. package/src/version.js +1 -1
  166. package/src/color.js +0 -246
  167. package/src/effects/ascii.d.ts +0 -7
  168. package/src/effects/ascii.js +0 -93
  169. package/src/effects/buffer.d.ts +0 -60
  170. package/src/effects/buffer.js +0 -472
  171. package/src/effects/detail.d.ts +0 -12
  172. package/src/effects/detail.js +0 -124
  173. package/src/effects/digital.d.ts +0 -14
  174. package/src/effects/digital.js +0 -216
  175. package/src/effects/distort.d.ts +0 -20
  176. package/src/effects/distort.js +0 -314
  177. package/src/effects/dither.d.ts +0 -8
  178. package/src/effects/dither.js +0 -147
  179. package/src/effects/drop-shadow.d.ts +0 -4
  180. package/src/effects/drop-shadow.js +0 -45
  181. package/src/effects/extrude.d.ts +0 -5
  182. package/src/effects/extrude.js +0 -55
  183. package/src/effects/film.d.ts +0 -11
  184. package/src/effects/film.js +0 -204
  185. package/src/effects/glow.d.ts +0 -8
  186. package/src/effects/glow.js +0 -56
  187. package/src/effects/grade.d.ts +0 -30
  188. package/src/effects/grade.js +0 -274
  189. package/src/effects/halftone.d.ts +0 -8
  190. package/src/effects/halftone.js +0 -95
  191. package/src/effects/index.d.ts +0 -125
  192. package/src/effects/index.js +0 -277
  193. package/src/effects/ink.d.ts +0 -12
  194. package/src/effects/ink.js +0 -70
  195. package/src/effects/lens.d.ts +0 -14
  196. package/src/effects/lens.js +0 -277
  197. package/src/effects/light.d.ts +0 -10
  198. package/src/effects/light.js +0 -59
  199. package/src/effects/looks.d.ts +0 -9
  200. package/src/effects/looks.js +0 -74
  201. package/src/effects/mask.d.ts +0 -52
  202. package/src/effects/mask.js +0 -314
  203. package/src/effects/motion.d.ts +0 -6
  204. package/src/effects/motion.js +0 -60
  205. package/src/effects/newsprint.d.ts +0 -6
  206. package/src/effects/newsprint.js +0 -89
  207. package/src/effects/optics.d.ts +0 -10
  208. package/src/effects/optics.js +0 -213
  209. package/src/effects/options.d.ts +0 -6
  210. package/src/effects/options.js +0 -106
  211. package/src/effects/pixels.d.ts +0 -24
  212. package/src/effects/pixels.js +0 -138
  213. package/src/effects/polar.d.ts +0 -10
  214. package/src/effects/polar.js +0 -265
  215. package/src/effects/region.d.ts +0 -8
  216. package/src/effects/region.js +0 -136
  217. package/src/effects/round-border.d.ts +0 -5
  218. package/src/effects/round-border.js +0 -62
  219. package/src/effects/screen.d.ts +0 -14
  220. package/src/effects/screen.js +0 -212
  221. package/src/effects/sort.d.ts +0 -8
  222. package/src/effects/sort.js +0 -125
  223. package/src/effects/symmetry.d.ts +0 -6
  224. package/src/effects/symmetry.js +0 -74
  225. package/src/effects/tone.d.ts +0 -16
  226. package/src/effects/tone.js +0 -117
  227. package/src/effects/util.d.ts +0 -16
  228. package/src/effects/util.js +0 -85
  229. package/src/platform/bytes.d.ts +0 -22
  230. package/src/platform/bytes.js +0 -103
  231. package/src/platform/compress.d.ts +0 -12
  232. package/src/platform/compress.js +0 -81
  233. package/src/platform/node.d.ts +0 -26
  234. package/src/platform/node.js +0 -86
  235. package/src/png.js +0 -126
  236. package/src/raster/index.d.ts +0 -24
  237. package/src/raster/index.js +0 -171
  238. package/src/render/content.d.ts +0 -5
  239. package/src/render/content.js +0 -58
  240. package/src/render/tiles.d.ts +0 -6
  241. package/src/render/tiles.js +0 -77
  242. package/src/rng.js +0 -95
  243. package/src/svg.js +0 -69
  244. /package/src/{effects/paint.d.ts → paint/index.d.ts} +0 -0
@@ -1,58 +0,0 @@
1
- /**
2
- * Where a layer paints: its markup drawn small and the box round every
3
- * pixel it covers, so filters can work over that box instead of the whole
4
- * canvas (in `renderImage`, where librsvg's cost is per pixel of region).
5
- *
6
- * @module render/content
7
- */
8
-
9
- import { num } from '../svg.js';
10
-
11
- /** The probe's longer side, px. */
12
- const PROBE = 256;
13
-
14
- /** Markup carrying more embedded data than this isn't probed (decoding it would cost more than it saves). */
15
- const MAX_PROBE_BYTES = 400_000;
16
-
17
- /**
18
- * The box a layer's markup paints, in its units: drawn at most 256 px
19
- * across over the canvas and half again round it, one probe pixel of slack
20
- * on every side. Undefined when it can't be probed (no sharp, heavy
21
- * embedded images) or paints nothing.
22
- *
23
- * @param {{ defs: string, body: string }} m
24
- * @param {any} ctx
25
- * @returns {Promise<{ x: number, y: number, width: number, height: number } | undefined>}
26
- */
27
- export async function contentBox(m, ctx) {
28
- if (!ctx.raster || !ctx.sharp || m.body.length + m.defs.length > MAX_PROBE_BYTES) return undefined;
29
- const ox = -ctx.width / 2;
30
- const oy = -ctx.height / 2;
31
- const uw = ctx.width * 2;
32
- const uh = ctx.height * 2;
33
- const s = PROBE / Math.max(uw, uh);
34
- const w = Math.max(1, Math.round(uw * s));
35
- const h = Math.max(1, Math.round(uh * s));
36
- const doc = `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="${num(ox, 4)} ${num(oy, 4)} ${num(uw, 4)} ${num(uh, 4)}" preserveAspectRatio="none">${m.defs ? `<defs>${m.defs}</defs>` : ''}${m.body}</svg>`;
37
- let px;
38
- try {
39
- px = await ctx.sharp(new TextEncoder().encode(doc)).ensureAlpha().raw().toBuffer();
40
- } catch {
41
- return undefined;
42
- }
43
- let x0 = w;
44
- let y0 = h;
45
- let x1 = -1;
46
- let y1 = -1;
47
- for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) {
48
- if (!px[(y * w + x) * 4 + 3]) continue;
49
- if (x < x0) x0 = x;
50
- if (x > x1) x1 = x;
51
- if (y < y0) y0 = y;
52
- if (y > y1) y1 = y;
53
- }
54
- if (x1 < 0) return undefined;
55
- // Anything touching the probe's edge may run further: the whole region then.
56
- if (x0 === 0 || y0 === 0 || x1 === w - 1 || y1 === h - 1) return undefined;
57
- return { x: ox + (x0 - 1) / s, y: oy + (y0 - 1) / s, width: (x1 - x0 + 3) / s, height: (y1 - y0 + 3) / s };
58
- }
@@ -1,6 +0,0 @@
1
- // Large SVGs with filters, rasterised in tiles.
2
-
3
- /** An SVG document's RGBA pixels at its own size (in tiles when large and filtered). */
4
- export function svgPixels(sharp: unknown, svg: string, width: number, height: number, known?: number): Promise<Uint8Array>;
5
- /** How far any filter in a document reaches, output px, estimated from its markup. */
6
- export function reach(svg: string): number;
@@ -1,77 +0,0 @@
1
- /**
2
- * Rasterising a large SVG with filters in tiles.
3
- *
4
- * librsvg (through libvips) slows down sharply past a few megapixels when
5
- * a document has filters: a 3000² displacement map takes ~28 s in one
6
- * render and ~5 s as four 1500² tiles. So a large document with filters is
7
- * drawn as tiles, each with a margin round it (librsvg clips a filter's
8
- * input to the viewport, so a blur or displacement at a tile's edge needs
9
- * the pixels beyond it: the margin is the furthest any filter reaches),
10
- * cropped and assembled. Tiles render in parallel. A document whose
11
- * filters reach further than a tile renders in one piece.
12
- *
13
- * @module render/tiles
14
- */
15
-
16
- import { filterReach } from '../effects/region.js';
17
-
18
- /** Documents below this many pixels render in one piece. */
19
- const MIN_TILED = 2.4e6;
20
-
21
- /** Tile side, px (before margins). */
22
- const TILE = 1024;
23
-
24
- /**
25
- * An SVG document's RGBA pixels at its own size.
26
- *
27
- * @param {any} sharp
28
- * @param {string} svg - A document whose root is `width`×`height` with `viewBox="0 0 width height"`.
29
- * @param {number} width
30
- * @param {number} height
31
- * @param {number} [known=0] - How far the filters the layers drew reach, output px (they report it at their own resolution).
32
- * @returns {Promise<Uint8Array>}
33
- */
34
- export async function svgPixels(sharp, svg, width, height, known = 0) {
35
- const margin = width * height < MIN_TILED || !/<filter\b/.test(svg) ? Infinity : Math.max(known + 16, reach(svg));
36
- // Filters reaching further than a tile (a heavy blur) render in one piece.
37
- if (margin > TILE) return sharp(Buffer.from(svg)).ensureAlpha().raw().toBuffer();
38
- const open = /^<svg\b[^>]*>/.exec(svg);
39
- if (!open) return sharp(Buffer.from(svg)).ensureAlpha().raw().toBuffer();
40
- const rest = svg.slice(open[0].length);
41
- const root = open[0].replace(/\s(width|height|viewBox)="[^"]*"/g, '').replace(/>$/, '');
42
- const out = Buffer.alloc(width * height * 4);
43
- /** @type {(() => Promise<void>)[]} */
44
- const jobs = [];
45
- for (let y = 0; y < height; y += TILE) {
46
- for (let x = 0; x < width; x += TILE) {
47
- const tw = Math.min(TILE, width - x);
48
- const th = Math.min(TILE, height - y);
49
- jobs.push(async () => {
50
- const vw = tw + 2 * margin;
51
- const vh = th + 2 * margin;
52
- const doc = `${root} width="${vw}" height="${vh}" viewBox="${x - margin} ${y - margin} ${vw} ${vh}">${rest}`;
53
- const px = await sharp(Buffer.from(doc)).ensureAlpha().extract({ left: margin, top: margin, width: tw, height: th }).raw().toBuffer();
54
- for (let r = 0; r < th; r++) px.copy(out, ((y + r) * width + x) * 4, r * tw * 4, (r + 1) * tw * 4);
55
- });
56
- }
57
- }
58
- // A few at a time: each holds its filter surfaces.
59
- const lanes = 4;
60
- await Promise.all(Array.from({ length: lanes }, async (_, i) => { for (let j = i; j < jobs.length; j += lanes) await jobs[j](); }));
61
- return out;
62
- }
63
-
64
- /**
65
- * How far, in output px, any filter in a document reaches, estimated from
66
- * the markup: the primitives' reach times the largest scale it's drawn at.
67
- * (Effects report their own reach at their own resolution; this covers
68
- * filters written by other layers.)
69
- *
70
- * @param {string} svg
71
- * @returns {number}
72
- */
73
- export function reach(svg) {
74
- let scale = 1;
75
- for (const m of svg.matchAll(/scale\(([^)]*)\)/g)) for (const v of m[1].trim().split(/[\s,]+/)) scale = Math.max(scale, Math.abs(Number(v)) || 0);
76
- return Math.ceil(filterReach(svg) * scale) + 16;
77
- }
package/src/rng.js DELETED
@@ -1,95 +0,0 @@
1
- /**
2
- * Seeded randomness.
3
- *
4
- * Every random choice a cover makes comes from a generator seeded by the
5
- * cover's `seed` string, so the same input always draws the same image and
6
- * two posts with different slugs draw different ones. Each layer gets its
7
- * own stream (seed + layer index + layer type), so adding or removing one
8
- * layer never reshuffles the others.
9
- *
10
- * @module rng
11
- */
12
-
13
- /**
14
- * Hash a string to a 32-bit unsigned integer (cyrb53, folded to 32 bits).
15
- * Fast, well distributed, and stable across platforms and Node versions.
16
- *
17
- * @param {string} str - Any string.
18
- * @param {number} [salt=0] - Optional salt, to get several independent hashes of one string.
19
- * @returns {number} An integer in `[0, 2^32)`.
20
- *
21
- * @example
22
- * hashString('my-post'); // 1838215339 (always)
23
- */
24
- export function hashString(str, salt = 0) {
25
- let h1 = 0xdeadbeef ^ salt;
26
- let h2 = 0x41c6ce57 ^ salt;
27
- for (let i = 0; i < str.length; i++) {
28
- const ch = str.charCodeAt(i);
29
- h1 = Math.imul(h1 ^ ch, 2654435761);
30
- h2 = Math.imul(h2 ^ ch, 1597334677);
31
- }
32
- h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
33
- h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
34
- return (h1 ^ h2) >>> 0;
35
- }
36
-
37
- /**
38
- * A small seeded pseudo-random generator (sfc32) with helpers.
39
- *
40
- * @typedef {object} Rng
41
- * @property {() => number} next - A float in `[0, 1)`.
42
- * @property {(min: number, max: number) => number} range - A float in `[min, max)`.
43
- * @property {(min: number, max: number) => number} int - An integer in `[min, max]`.
44
- * @property {<T>(items: readonly T[]) => T} pick - One item.
45
- * @property {<T>(items: readonly T[]) => T[]} shuffle - A shuffled copy.
46
- * @property {(chance?: number) => boolean} chance - `true` with the given probability (default 0.5).
47
- */
48
-
49
- /**
50
- * Create a seeded generator. Strings are hashed; numbers are used as-is.
51
- *
52
- * @param {string | number} seed - The seed.
53
- * @returns {Rng} The generator.
54
- *
55
- * @example
56
- * const rng = createRng('my-post');
57
- * rng.range(0, 360); // same angle every run
58
- */
59
- export function createRng(seed) {
60
- const s = String(seed);
61
- let a = hashString(s, 1);
62
- let b = hashString(s, 2);
63
- let c = hashString(s, 3);
64
- let d = hashString(s, 4);
65
-
66
- const next = () => {
67
- a >>>= 0; b >>>= 0; c >>>= 0; d >>>= 0;
68
- let t = (a + b) | 0;
69
- a = b ^ (b >>> 9);
70
- b = (c + (c << 3)) | 0;
71
- c = (c << 21) | (c >>> 11);
72
- d = (d + 1) | 0;
73
- t = (t + d) | 0;
74
- c = (c + t) | 0;
75
- return (t >>> 0) / 4294967296;
76
- };
77
- // Warm up: the first few sfc32 outputs are correlated with the seed.
78
- for (let i = 0; i < 12; i++) next();
79
-
80
- return {
81
- next,
82
- range: (min, max) => min + next() * (max - min),
83
- int: (min, max) => Math.floor(min + next() * (max - min + 1)),
84
- pick: (items) => items[Math.floor(next() * items.length)],
85
- shuffle: (items) => {
86
- const out = items.slice();
87
- for (let i = out.length - 1; i > 0; i--) {
88
- const j = Math.floor(next() * (i + 1));
89
- [out[i], out[j]] = [out[j], out[i]];
90
- }
91
- return out;
92
- },
93
- chance: (p = 0.5) => next() < p,
94
- };
95
- }
package/src/svg.js DELETED
@@ -1,69 +0,0 @@
1
- /**
2
- * Small helpers for writing SVG markup deterministically.
3
- *
4
- * Numbers are always written through {@link num}, so the same input gives
5
- * byte-identical output on every machine (no `0.30000000000000004`, no
6
- * locale formatting, no `-0`).
7
- *
8
- * @module svg
9
- */
10
-
11
- /**
12
- * Format a number for SVG: at most `digits` decimals, no trailing zeros.
13
- *
14
- * @param {number} v - The number.
15
- * @param {number} [digits=2] - Maximum decimals.
16
- * @returns {string} The formatted number.
17
- *
18
- * @example
19
- * num(1 / 3); // '0.33'
20
- * num(12.5); // '12.5'
21
- * num(-0.001); // '0'
22
- */
23
- export function num(v, digits = 2) {
24
- if (!Number.isFinite(v)) return '0';
25
- const s = v.toFixed(digits).replace(/\.?0+$/, '');
26
- return s === '-0' || s === '' ? '0' : s;
27
- }
28
-
29
- /**
30
- * Escape text for an XML attribute or text node.
31
- *
32
- * @param {string} s - Raw text.
33
- * @returns {string} Escaped text.
34
- */
35
- export function esc(s) {
36
- return String(s).replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c]);
37
- }
38
-
39
- /**
40
- * Serialise an attribute object. `undefined`, `null` and `false` values are
41
- * skipped; numbers go through {@link num}. Keys keep their insertion order.
42
- *
43
- * @param {Record<string, string | number | boolean | null | undefined>} obj - Attributes.
44
- * @returns {string} ` key="value"` pairs (with a leading space), or `''`.
45
- *
46
- * @example
47
- * attrs({ x: 1.005, fill: '#fff', opacity: undefined }); // ' x="1" fill="#fff"'
48
- */
49
- export function attrs(obj) {
50
- let out = '';
51
- for (const [k, v] of Object.entries(obj)) {
52
- if (v === undefined || v === null || v === false) continue;
53
- out += ` ${k}="${typeof v === 'number' ? num(v) : esc(String(v))}"`;
54
- }
55
- return out;
56
- }
57
-
58
- /**
59
- * Opacity and blend-mode attributes shared by every layer.
60
- *
61
- * @param {{ opacity?: number, blend?: string }} layer - The layer.
62
- * @returns {Record<string, string | number | undefined>} Attributes for the layer's group.
63
- */
64
- export function layerAttrs(layer) {
65
- return {
66
- opacity: layer.opacity != null && layer.opacity < 1 ? Math.max(0, layer.opacity) : undefined,
67
- style: layer.blend && layer.blend !== 'normal' ? `mix-blend-mode:${layer.blend}` : undefined,
68
- };
69
- }
File without changes