@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.
Files changed (245) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/LICENSE +29 -0
  3. package/README.md +846 -2
  4. package/fonts/OFL.txt +93 -0
  5. package/fonts/inter-latin-400.woff +0 -0
  6. package/fonts/inter-latin-500.woff +0 -0
  7. package/fonts/inter-latin-600.woff +0 -0
  8. package/fonts/inter-latin-700.woff +0 -0
  9. package/package.json +124 -4
  10. package/recipes/aurora.json +132 -0
  11. package/recipes/bauhaus.json +113 -0
  12. package/recipes/bigType.json +103 -0
  13. package/recipes/brand.json +1143 -0
  14. package/recipes/contour.json +109 -0
  15. package/recipes/cutout.json +191 -0
  16. package/recipes/diagram.json +92 -0
  17. package/recipes/dither.json +170 -0
  18. package/recipes/duotone.json +75 -0
  19. package/recipes/gridTitle.json +136 -0
  20. package/recipes/iconTile.json +214 -0
  21. package/recipes/issue.json +221 -0
  22. package/recipes/mark.json +170 -0
  23. package/recipes/photo.json +156 -0
  24. package/recipes/riso.json +387 -0
  25. package/recipes/stripes.json +206 -0
  26. package/recipes/waveform.json +154 -0
  27. package/schema/recipe.schema.json +8822 -0
  28. package/src/animate/index.d.ts +19 -0
  29. package/src/animate/index.js +116 -0
  30. package/src/astro.d.ts +31 -0
  31. package/src/astro.js +86 -0
  32. package/src/builtins.js +4 -0
  33. package/src/color.js +246 -0
  34. package/src/diagram.js +248 -0
  35. package/src/effects/ascii.d.ts +7 -0
  36. package/src/effects/ascii.js +93 -0
  37. package/src/effects/buffer.d.ts +60 -0
  38. package/src/effects/buffer.js +472 -0
  39. package/src/effects/detail.d.ts +12 -0
  40. package/src/effects/detail.js +124 -0
  41. package/src/effects/digital.d.ts +14 -0
  42. package/src/effects/digital.js +216 -0
  43. package/src/effects/distort.d.ts +20 -0
  44. package/src/effects/distort.js +314 -0
  45. package/src/effects/dither.d.ts +8 -0
  46. package/src/effects/dither.js +147 -0
  47. package/src/effects/drop-shadow.d.ts +4 -0
  48. package/src/effects/drop-shadow.js +45 -0
  49. package/src/effects/extrude.d.ts +5 -0
  50. package/src/effects/extrude.js +55 -0
  51. package/src/effects/film.d.ts +11 -0
  52. package/src/effects/film.js +204 -0
  53. package/src/effects/glow.d.ts +8 -0
  54. package/src/effects/glow.js +56 -0
  55. package/src/effects/grade.d.ts +30 -0
  56. package/src/effects/grade.js +274 -0
  57. package/src/effects/halftone.d.ts +8 -0
  58. package/src/effects/halftone.js +95 -0
  59. package/src/effects/index.d.ts +125 -0
  60. package/src/effects/index.js +277 -0
  61. package/src/effects/ink.d.ts +12 -0
  62. package/src/effects/ink.js +70 -0
  63. package/src/effects/lens.d.ts +14 -0
  64. package/src/effects/lens.js +277 -0
  65. package/src/effects/light.d.ts +10 -0
  66. package/src/effects/light.js +59 -0
  67. package/src/effects/looks.d.ts +9 -0
  68. package/src/effects/looks.js +74 -0
  69. package/src/effects/mask.d.ts +52 -0
  70. package/src/effects/mask.js +314 -0
  71. package/src/effects/motion.d.ts +6 -0
  72. package/src/effects/motion.js +60 -0
  73. package/src/effects/newsprint.d.ts +6 -0
  74. package/src/effects/newsprint.js +89 -0
  75. package/src/effects/optics.d.ts +10 -0
  76. package/src/effects/optics.js +213 -0
  77. package/src/effects/options.d.ts +6 -0
  78. package/src/effects/options.js +106 -0
  79. package/src/effects/paint-presets.d.ts +10 -0
  80. package/src/effects/paint-presets.js +47 -0
  81. package/src/effects/paint.d.ts +49 -0
  82. package/src/effects/paint.js +66 -0
  83. package/src/effects/pixels.d.ts +24 -0
  84. package/src/effects/pixels.js +138 -0
  85. package/src/effects/polar.d.ts +10 -0
  86. package/src/effects/polar.js +265 -0
  87. package/src/effects/region.d.ts +8 -0
  88. package/src/effects/region.js +136 -0
  89. package/src/effects/round-border.d.ts +5 -0
  90. package/src/effects/round-border.js +62 -0
  91. package/src/effects/screen.d.ts +14 -0
  92. package/src/effects/screen.js +212 -0
  93. package/src/effects/sort.d.ts +8 -0
  94. package/src/effects/sort.js +125 -0
  95. package/src/effects/symmetry.d.ts +6 -0
  96. package/src/effects/symmetry.js +74 -0
  97. package/src/effects/tile.d.ts +7 -0
  98. package/src/effects/tile.js +55 -0
  99. package/src/effects/tone.d.ts +16 -0
  100. package/src/effects/tone.js +117 -0
  101. package/src/effects/transform.d.ts +35 -0
  102. package/src/effects/transform.js +127 -0
  103. package/src/effects/util.d.ts +16 -0
  104. package/src/effects/util.js +85 -0
  105. package/src/extract.js +91 -0
  106. package/src/flatten.js +234 -0
  107. package/src/fonts.d.ts +55 -0
  108. package/src/fonts.js +393 -0
  109. package/src/formats/cut.d.ts +10 -0
  110. package/src/formats/cut.js +77 -0
  111. package/src/formats/index.d.ts +6 -0
  112. package/src/formats/index.js +9 -0
  113. package/src/formats/presets.d.ts +41 -0
  114. package/src/formats/presets.js +89 -0
  115. package/src/formats/print.d.ts +25 -0
  116. package/src/formats/print.js +117 -0
  117. package/src/formats/zones.d.ts +10 -0
  118. package/src/formats/zones.js +64 -0
  119. package/src/geometry.js +187 -0
  120. package/src/grid.js +41 -0
  121. package/src/handmade.js +253 -0
  122. package/src/icons.js +191 -0
  123. package/src/index.d.ts +1083 -0
  124. package/src/index.js +27 -0
  125. package/src/inter-data.js +27 -0
  126. package/src/json-recipe.js +616 -0
  127. package/src/kit/index.d.ts +20 -0
  128. package/src/kit/index.js +58 -0
  129. package/src/layer-options.js +56 -0
  130. package/src/layers/barcode.d.ts +19 -0
  131. package/src/layers/barcode.js +63 -0
  132. package/src/layers/code128.d.ts +4 -0
  133. package/src/layers/code128.js +28 -0
  134. package/src/layers/expr.d.ts +6 -0
  135. package/src/layers/expr.js +121 -0
  136. package/src/layers/geometry.d.ts +6 -0
  137. package/src/layers/geometry.js +238 -0
  138. package/src/layers/index.d.ts +18 -0
  139. package/src/layers/index.js +15 -0
  140. package/src/layers/perforation.d.ts +14 -0
  141. package/src/layers/perforation.js +47 -0
  142. package/src/layers/repeat.d.ts +29 -0
  143. package/src/layers/repeat.js +90 -0
  144. package/src/layers/shape.d.ts +46 -0
  145. package/src/layers/shape.js +115 -0
  146. package/src/layers/soft.d.ts +5 -0
  147. package/src/layers/soft.js +43 -0
  148. package/src/layers/svg.d.ts +23 -0
  149. package/src/layers/svg.js +109 -0
  150. package/src/layers/table.d.ts +25 -0
  151. package/src/layers/table.js +80 -0
  152. package/src/layers/tear.d.ts +21 -0
  153. package/src/layers/tear.js +74 -0
  154. package/src/layers/texture.d.ts +17 -0
  155. package/src/layers/texture.js +106 -0
  156. package/src/layers/util.d.ts +8 -0
  157. package/src/layers/util.js +48 -0
  158. package/src/layers.js +1056 -0
  159. package/src/lint/image-size.d.ts +4 -0
  160. package/src/lint/image-size.js +34 -0
  161. package/src/lint/index.d.ts +16 -0
  162. package/src/lint/index.js +127 -0
  163. package/src/lint/rules.d.ts +5 -0
  164. package/src/lint/rules.js +19 -0
  165. package/src/node.d.ts +2 -0
  166. package/src/node.js +20 -0
  167. package/src/palette.js +185 -0
  168. package/src/platform/bytes.d.ts +22 -0
  169. package/src/platform/bytes.js +103 -0
  170. package/src/platform/compress.d.ts +12 -0
  171. package/src/platform/compress.js +81 -0
  172. package/src/platform/harfbuzz.d.ts +9 -0
  173. package/src/platform/harfbuzz.js +80 -0
  174. package/src/platform/index.d.ts +8 -0
  175. package/src/platform/index.js +13 -0
  176. package/src/platform/load.d.ts +6 -0
  177. package/src/platform/load.js +72 -0
  178. package/src/platform/no-harfbuzz.d.ts +2 -0
  179. package/src/platform/no-harfbuzz.js +11 -0
  180. package/src/platform/node.d.ts +26 -0
  181. package/src/platform/node.js +86 -0
  182. package/src/platform/settings.d.ts +28 -0
  183. package/src/platform/settings.js +51 -0
  184. package/src/png.js +126 -0
  185. package/src/print.js +659 -0
  186. package/src/raster/index.d.ts +24 -0
  187. package/src/raster/index.js +171 -0
  188. package/src/recipes.js +79 -0
  189. package/src/render/backdrop.d.ts +6 -0
  190. package/src/render/backdrop.js +55 -0
  191. package/src/render/content.d.ts +5 -0
  192. package/src/render/content.js +58 -0
  193. package/src/render/context.d.ts +82 -0
  194. package/src/render/grain.d.ts +7 -0
  195. package/src/render/grain.js +113 -0
  196. package/src/render/group.d.ts +23 -0
  197. package/src/render/group.js +67 -0
  198. package/src/render/key.d.ts +5 -0
  199. package/src/render/key.js +91 -0
  200. package/src/render/report.d.ts +5 -0
  201. package/src/render/report.js +19 -0
  202. package/src/render/tiles.d.ts +6 -0
  203. package/src/render/tiles.js +77 -0
  204. package/src/render/use.d.ts +27 -0
  205. package/src/render/use.js +73 -0
  206. package/src/render/wrap.d.ts +14 -0
  207. package/src/render/wrap.js +199 -0
  208. package/src/render.js +553 -0
  209. package/src/rng.js +95 -0
  210. package/src/svg.js +69 -0
  211. package/src/text.d.ts +29 -0
  212. package/src/text.js +237 -0
  213. package/src/type/auto-color.d.ts +9 -0
  214. package/src/type/auto-color.js +44 -0
  215. package/src/type/fallback.d.ts +11 -0
  216. package/src/type/fallback.js +92 -0
  217. package/src/type/glyphs.d.ts +24 -0
  218. package/src/type/glyphs.js +87 -0
  219. package/src/type/index.d.ts +5 -0
  220. package/src/type/index.js +10 -0
  221. package/src/type/kern.d.ts +4 -0
  222. package/src/type/kern.js +144 -0
  223. package/src/type/layout-ref.d.ts +10 -0
  224. package/src/type/layout-ref.js +74 -0
  225. package/src/type/line.d.ts +11 -0
  226. package/src/type/line.js +105 -0
  227. package/src/type/live.d.ts +6 -0
  228. package/src/type/live.js +39 -0
  229. package/src/type/name.d.ts +4 -0
  230. package/src/type/name.js +32 -0
  231. package/src/type/path.d.ts +18 -0
  232. package/src/type/path.js +89 -0
  233. package/src/type/pixel.d.ts +9 -0
  234. package/src/type/pixel.js +115 -0
  235. package/src/type/sfnt.d.ts +20 -0
  236. package/src/type/sfnt.js +258 -0
  237. package/src/type/spans.d.ts +33 -0
  238. package/src/type/spans.js +138 -0
  239. package/src/type/symbols.d.ts +7 -0
  240. package/src/type/symbols.js +89 -0
  241. package/src/type/typeset.d.ts +10 -0
  242. package/src/type/typeset.js +352 -0
  243. package/src/type/woff2.d.ts +6 -0
  244. package/src/type/woff2.js +301 -0
  245. package/src/version.js +4 -0
@@ -0,0 +1,18 @@
1
+ // The poster layers (1.1).
2
+ export { shape } from './shape.js';
3
+ export type { ShapeLayer, ShapeSpec } from './shape.js';
4
+ export { svg, sanitize, shapesOnly } from './svg.js';
5
+ export type { SvgLayer } from './svg.js';
6
+ export { table } from './table.js';
7
+ export type { TableLayer, TableCell } from './table.js';
8
+ export { barcode } from './barcode.js';
9
+ export type { BarcodeLayer } from './barcode.js';
10
+ export { code128 } from './code128.js';
11
+ export { perforation } from './perforation.js';
12
+ export type { PerforationLayer } from './perforation.js';
13
+ export { tear } from './tear.js';
14
+ export type { TearLayer } from './tear.js';
15
+ export { texture } from './texture.js';
16
+ export type { TextureLayer } from './texture.js';
17
+ export { expandRepeat } from './repeat.js';
18
+ export type { RepeatLayer } from './repeat.js';
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The poster layers (1.1): one module each.
3
+ *
4
+ * @module layers
5
+ */
6
+
7
+ export { shape } from './shape.js';
8
+ export { svg, sanitize, shapesOnly } from './svg.js';
9
+ export { table } from './table.js';
10
+ export { barcode } from './barcode.js';
11
+ export { code128 } from './code128.js';
12
+ export { perforation } from './perforation.js';
13
+ export { tear } from './tear.js';
14
+ export { texture } from './texture.js';
15
+ export { expandRepeat } from './repeat.js';
@@ -0,0 +1,14 @@
1
+ // The `perforation` layer.
2
+ import type { Color, Length, BaseLayer, Anchor } from '../index.js';
3
+ import type { RenderContext, Markup } from '../render/context.js';
4
+
5
+ export interface PerforationLayer extends BaseLayer {
6
+ type: 'perforation';
7
+ kind?: 'holes' | 'dashes' | 'notches';
8
+ x?: Length | `${number}%`; x1?: Length | `${number}%`; y1?: Length | `${number}%`; x2?: Length | `${number}%`; y2?: Length | `${number}%`;
9
+ size?: Length; gap?: Length; inset?: Length; color?: Color; strokeWidth?: Length;
10
+ }
11
+
12
+
13
+ /** Draw a tear-off line. */
14
+ export function perforation(layer: PerforationLayer, ctx: RenderContext): Markup;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The `perforation` layer.
3
+ *
4
+ * @module layers/perforation
5
+ */
6
+
7
+ import { attrs, num } from '../svg.js';
8
+ import { len } from '../geometry.js';
9
+ import { toHex } from '../color.js';
10
+ import { pos, wrap } from './util.js';
11
+
12
+
13
+ /**
14
+ * `perforation` — a tear-off line: `holes` (punched dots), `dashes` or
15
+ * `notches` (half-circle bites at both ends), from `x1, y1` to `x2, y2`.
16
+ * In a layer's `knockout` it punches real holes through it.
17
+ *
18
+ * @param {any} layer
19
+ * @param {import('../layers.js').RenderContext} ctx
20
+ * @returns {import('../layers.js').Markup}
21
+ */
22
+ export function perforation(layer, ctx) {
23
+ const x1 = pos(layer.x1 ?? layer.x ?? '50%', 'x', ctx.canvas);
24
+ const y1 = pos(layer.y1 ?? 0, 'y', ctx.canvas);
25
+ const x2 = pos(layer.x2 ?? layer.x ?? '50%', 'x', ctx.canvas);
26
+ const y2 = pos(layer.y2 ?? '100%', 'y', ctx.canvas);
27
+ const color = layer.color ? toHex(layer.color) : ctx.palette.text;
28
+ const kind = layer.kind ?? 'holes';
29
+ const L = Math.hypot(x2 - x1, y2 - y1);
30
+ const size = len(layer.size, ctx.canvas, kind === 'notches' ? '1.6vmin' : '0.25vmin');
31
+ const gap = len(layer.gap, ctx.canvas, kind === 'dashes' ? '0.8vmin' : '1.2vmin');
32
+ let body = '';
33
+ if (kind === 'dashes') {
34
+ body = `<path${attrs({ d: `M${num(x1)} ${num(y1)}L${num(x2)} ${num(y2)}`, stroke: color, 'stroke-width': len(layer.strokeWidth, ctx.canvas, '0.2vmin'), 'stroke-dasharray': `${num(gap)} ${num(gap * 0.8)}`, fill: 'none' })}/>`;
35
+ } else if (kind === 'notches') {
36
+ body = `<circle${attrs({ cx: x1, cy: y1, r: size, fill: color })}/><circle${attrs({ cx: x2, cy: y2, r: size, fill: color })}/>`;
37
+ } else {
38
+ const inset = len(layer.inset, ctx.canvas, gap);
39
+ const n = Math.max(1, Math.floor((L - 2 * inset) / gap));
40
+ for (let i = 0; i <= n; i++) {
41
+ const t = (inset + i * ((L - 2 * inset) / n)) / L;
42
+ body += `<circle${attrs({ cx: x1 + (x2 - x1) * t, cy: y1 + (y2 - y1) * t, r: size })}/>`;
43
+ }
44
+ body = `<g fill="${color}">${body}</g>`;
45
+ }
46
+ return { defs: '', body: wrap(layer, body) };
47
+ }
@@ -0,0 +1,29 @@
1
+ // The `repeat` layer.
2
+ import type { Length, Layer, BaseLayer } from '../index.js';
3
+
4
+ /** `repeat`: a layer drawn many times (a grid, a ring, a line), with per-instance `{#…}` values. */
5
+ export interface RepeatLayer extends BaseLayer {
6
+ type: 'repeat';
7
+ /** The template (or `layers`, drawn as a group). */
8
+ layer?: Layer;
9
+ layers?: Layer[];
10
+ rows?: number; cols?: number;
11
+ count?: number;
12
+ /** `[dx, dy]` between instances (a grid's default divides the canvas). */
13
+ step?: [Length, Length];
14
+ x?: Length | `${number}%`; y?: Length | `${number}%`;
15
+ cx?: Length | `${number}%`; cy?: Length | `${number}%`; radius?: Length;
16
+ /** Degrees, 0 up, clockwise. */
17
+ start?: number; sweep?: number;
18
+ /** Turn each ring instance to its angle (default true). */
19
+ turn?: boolean;
20
+ /** Overrides for odd instances (a checkerboard on a grid). */
21
+ odd?: Record<string, unknown>;
22
+ /** Overrides taken in turn. */
23
+ cycle?: Record<string, unknown>[];
24
+ /** An expression (`{#…}`) that leaves an instance out when true. */
25
+ skip?: string;
26
+ }
27
+
28
+ /** The instances of a repeat, as a group of placed copies. */
29
+ export function expandRepeat(layer: RepeatLayer, ctx: { width: number; height: number; canvas: { width: number; height: number } }): Layer;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * `repeat`: one layer (or a stack) drawn many times, so tile grids, rings
3
+ * of marks and poster walls are data, not generator scripts.
4
+ *
5
+ * - A grid: `rows` × `cols`, `step: [dx, dy]` apart (default the canvas
6
+ * divided by the count), starting at `x`, `y`.
7
+ * - A ring: `count` round `cx`, `cy` at `radius`, from `start` degrees (0
8
+ * up, clockwise) over `sweep` (default 360), each turned to its angle
9
+ * (`turn: false` keeps them upright).
10
+ * - A line: `count` instances `step: [dx, dy]` apart.
11
+ *
12
+ * Each instance is the template (`layer`, or `layers` as a group) placed at
13
+ * its point, with `{#…}` expressions filled in from its variables: `i`,
14
+ * `n` (the count), `row`, `col`, `rows`, `cols`, `x`, `y`, `angle` and
15
+ * `odd` (a checkerboard on a grid, every other one otherwise). `odd` holds
16
+ * overrides for odd instances, `cycle` a list of overrides taken in turn,
17
+ * and `skip` an expression that leaves an instance out.
18
+ *
19
+ * @module layers/repeat
20
+ */
21
+
22
+ import { num } from '../svg.js';
23
+ import { len } from '../geometry.js';
24
+ import { pos } from './util.js';
25
+ import { fill, evaluate } from './expr.js';
26
+
27
+ /**
28
+ * The instances of a repeat, as a group of placed copies.
29
+ *
30
+ * @param {any} layer
31
+ * @param {{ width: number, height: number, canvas: { width: number, height: number } }} ctx
32
+ * @returns {any} A `group` layer.
33
+ */
34
+ export function expandRepeat(layer, ctx) {
35
+ const template = layer.layer ?? { type: 'group', layers: layer.layers ?? [] };
36
+ const places = points(layer, ctx);
37
+ const grid = layer.rows != null || layer.cols != null;
38
+ const key = layer.seed ?? layer.id ?? 'repeat';
39
+ const layers = [];
40
+ places.forEach((p, i) => {
41
+ const odd = grid ? (p.row + p.col) % 2 === 1 : i % 2 === 1;
42
+ const vars = { i, n: places.length, row: p.row, col: p.col, rows: p.rows, cols: p.cols, x: p.x, y: p.y, angle: p.angle, odd };
43
+ if (layer.skip != null && evaluate(String(layer.skip).replace(/^\{#|\}$/g, ''), vars)) return;
44
+ let one = { ...template };
45
+ if (odd && layer.odd) one = { ...one, ...layer.odd };
46
+ if (layer.cycle?.length) one = { ...one, ...layer.cycle[i % layer.cycle.length] };
47
+ const turn = p.angle && layer.turn !== false ? ` rotate(${num(p.angle, 3)})` : '';
48
+ layers.push({ type: 'group', seed: `${key}#${i}`, transform: `translate(${num(p.x, 3)} ${num(p.y, 3)})${turn}`, layers: [fill(one, vars)] });
49
+ });
50
+ return { type: 'group', layers, opacity: layer.opacity, blend: layer.blend };
51
+ }
52
+
53
+ /**
54
+ * Where the instances go.
55
+ *
56
+ * @param {any} l
57
+ * @param {{ width: number, height: number, canvas: { width: number, height: number } }} ctx
58
+ * @returns {{ x: number, y: number, row: number, col: number, rows: number, cols: number, angle: number }[]}
59
+ */
60
+ function points(l, ctx) {
61
+ const c = ctx.canvas;
62
+ const x0 = pos(l.x ?? 0, 'x', c);
63
+ const y0 = pos(l.y ?? 0, 'y', c);
64
+ if (l.rows != null || l.cols != null) {
65
+ const rows = Math.max(1, Math.round(l.rows ?? 1));
66
+ const cols = Math.max(1, Math.round(l.cols ?? 1));
67
+ const dx = l.step ? len(l.step[0], c) : (c.width - x0) / cols;
68
+ const dy = l.step ? len(l.step[1], c) : (c.height - y0) / rows;
69
+ const out = [];
70
+ for (let row = 0; row < rows; row++) for (let col = 0; col < cols; col++) out.push({ x: x0 + col * dx, y: y0 + row * dy, row, col, rows, cols, angle: 0 });
71
+ return out;
72
+ }
73
+ const count = Math.max(0, Math.round(l.count ?? 0));
74
+ if (l.radius != null) {
75
+ const cx = pos(l.cx ?? '50%', 'x', c);
76
+ const cy = pos(l.cy ?? '50%', 'y', c);
77
+ const r = len(l.radius, c);
78
+ const sweep = l.sweep ?? 360;
79
+ const start = l.start ?? 0;
80
+ // A full circle doesn't put the last on the first.
81
+ const stepA = count > 1 ? sweep / (Math.abs(sweep) >= 360 ? count : count - 1) : 0;
82
+ return Array.from({ length: count }, (_, i) => {
83
+ const angle = start + i * stepA;
84
+ const a = (angle * Math.PI) / 180;
85
+ return { x: cx + r * Math.sin(a), y: cy - r * Math.cos(a), row: 0, col: i, rows: 1, cols: count, angle };
86
+ });
87
+ }
88
+ const [dx, dy] = l.step ? [len(l.step[0], c), len(l.step[1], c)] : [0, 0];
89
+ return Array.from({ length: count }, (_, i) => ({ x: x0 + i * dx, y: y0 + i * dy, row: 0, col: i, rows: 1, cols: count, angle: 0 }));
90
+ }
@@ -0,0 +1,46 @@
1
+ // The `shape` layer.
2
+ import type { Color, Length, BaseLayer, Anchor } from '../index.js';
3
+ import type { RenderContext, Markup } from '../render/context.js';
4
+ import type { Paint } from '../effects/paint.js';
5
+
6
+ /** One exact shape (see the README's `shape` table). */
7
+ export interface ShapeSpec {
8
+ kind?: 'rect' | 'circle' | 'ellipse' | 'line' | 'polygon' | 'polyline' | 'star' | 'burst' | 'scallop' | 'rays' | 'ring' | 'sector' | 'arch' | 'wave' | 'path' | 'cut';
9
+ x?: Length | `${number}%`; y?: Length | `${number}%`; width?: Length | `${number}%`; height?: Length | `${number}%`;
10
+ cx?: Length | `${number}%`; cy?: Length | `${number}%`; r?: Length; rx?: Length; ry?: Length;
11
+ x1?: Length | `${number}%`; y1?: Length | `${number}%`; x2?: Length | `${number}%`; y2?: Length | `${number}%`;
12
+ points?: number | [Length, Length][];
13
+ inner?: Length; count?: number; start?: number; end?: number; depth?: Length;
14
+ amplitude?: Length; wavelength?: Length; phase?: number; band?: Length;
15
+ d?: string; viewBox?: [number, number, number, number];
16
+ radius?: Length;
17
+ edge?: 'deckle' | 'torn' | 'zigzag' | 'wave' | 'scallop' | { kind: 'deckle' | 'torn' | 'zigzag' | 'wave' | 'scallop'; size?: Length; step?: Length; sides?: ('top' | 'right' | 'bottom' | 'left')[] };
18
+ rotate?: number;
19
+ /** Rotate about this point (px or `'%'`) instead of the centre. */
20
+ origin?: [Length | `${number}%`, Length | `${number}%`] | { x?: Length | `${number}%`; y?: Length | `${number}%` };
21
+ /** `arch`: the arc and its sides only (stroked). */
22
+ open?: boolean;
23
+ /** A blur radius, drawn padded: soft orbs with no edge fade or square edge. */
24
+ soft?: Length;
25
+ /** `cut`: grow (or shrink) the format's die-cut outline. */
26
+ outset?: Length;
27
+ /** Where the dash pattern starts. */
28
+ dashOffset?: Length;
29
+ fill?: Paint; stroke?: Paint; strokeWidth?: Length;
30
+ strokes?: { color: Color; width: Length; dash?: Length[] }[];
31
+ dash?: 'dashed' | 'dotted' | Length[];
32
+ linecap?: 'butt' | 'round' | 'square'; linejoin?: 'miter' | 'round' | 'bevel'; fillRule?: 'nonzero' | 'evenodd';
33
+ opacity?: number;
34
+ }
35
+
36
+ export interface ShapeLayer extends BaseLayer, Omit<ShapeSpec, 'opacity'> {
37
+ type: 'shape';
38
+ /** Several shapes in one layer (layer-level fill, stroke, … are their defaults). */
39
+ items?: ShapeSpec[];
40
+ }
41
+
42
+
43
+ /** Draw one shape, or `items` of them. */
44
+ export function shape(layer: ShapeLayer, ctx: RenderContext): Promise<Markup>;
45
+ /** A dash pattern as an SVG `stroke-dasharray`. */
46
+ export function dashArray(dash: 'dashed' | 'dotted' | Length[], width: number, ctx: RenderContext): string;
@@ -0,0 +1,115 @@
1
+ /**
2
+ * The `shape` layer.
3
+ *
4
+ * @module layers/shape
5
+ */
6
+
7
+ import { attrs, num } from '../svg.js';
8
+ import { len } from '../geometry.js';
9
+ import { toHex } from '../color.js';
10
+ import { paint } from '../effects/paint.js';
11
+ import { presetStrokes } from '../effects/paint-presets.js';
12
+ import { wrap, pos } from './util.js';
13
+ import { geometry } from './geometry.js';
14
+ import { soften } from './soft.js';
15
+
16
+ /** Kinds drawn around a centre, whose `rotate` turns their points (about the centre). */
17
+ const POLAR = ['star', 'burst', 'scallop', 'rays', 'sector'];
18
+
19
+ /** Kinds that are lines unless closed: no fill by default. */
20
+ const isOpen = (/** @type {any} */ s) => s.kind === 'line' || s.kind === 'polyline' || (s.kind === 'wave' && !s.band) || (s.kind === 'arch' && s.open);
21
+
22
+ /**
23
+ * `shape` — one exact shape, or `items` of them: rectangles (with deckled,
24
+ * torn, zigzag or wavy `edge`s), circles, ellipses, lines, polygons, stars,
25
+ * bursts, scallops, rays, rings, sectors, arches (open: an arc), waves, raw
26
+ * paths and the print format's `cut`. Each takes `fill` and `stroke` (a
27
+ * colour, a gradient, a tile or a preset), `strokeWidth`, `dash`,
28
+ * `dashOffset`, `linecap`, `linejoin`, `fillRule`, `opacity`, `rotate`
29
+ * (about its centre, or `origin`), `soft` (a blur, drawn padded so it never
30
+ * shows a square edge) and `strokes` (stacked keylines, widest first).
31
+ *
32
+ * @param {any} layer
33
+ * @param {import('../layers.js').RenderContext} ctx
34
+ * @returns {Promise<import('../layers.js').Markup>}
35
+ */
36
+ export async function shape(layer, ctx) {
37
+ const items = layer.items ?? [layer];
38
+ let defs = '';
39
+ let body = '';
40
+ for (let i = 0; i < items.length; i++) {
41
+ const raw = items[i];
42
+ const s = layer.items ? { fill: layer.fill, stroke: layer.stroke, strokeWidth: layer.strokeWidth, dash: layer.dash, dashOffset: layer.dashOffset, linecap: layer.linecap, linejoin: layer.linejoin, strokes: layer.strokes, soft: layer.soft, ...raw } : raw;
43
+ const polar = POLAR.includes(s.kind) && s.origin == null;
44
+ const g = /** @type {any} */ (geometry(polar ? s : { ...s, rotate: undefined }, ctx));
45
+ const open = g.el ?? `<path d="${g.d}"`;
46
+ const fp = paint(s.fill, isOpen(s) ? 'none' : ctx.palette.text, { id: `${ctx.id}s${i}f`, box: g.box, canvas: ctx.canvas });
47
+ const sp = s.stroke ? paint(s.stroke, 'none', { id: `${ctx.id}s${i}k`, box: g.box, canvas: ctx.canvas }) : null;
48
+ let own = fp.defs + (sp?.defs ?? '');
49
+ const sw = s.strokeWidth != null ? len(s.strokeWidth, ctx.canvas) : sp ? len('0.2vmin', ctx.canvas) : undefined;
50
+ const tr = [rotation(s, g, ctx, polar), g.transform].filter(Boolean).join(' ') || undefined;
51
+ const common = { 'fill-rule': s.fillRule, opacity: s.opacity != null && s.opacity < 1 ? s.opacity : undefined, transform: tr };
52
+ let markup = '';
53
+ const strokes = s.strokes ?? presetStrokes(s.fill);
54
+ if (strokes) {
55
+ const sorted = [...strokes].sort((a, b) => len(b.width, ctx.canvas) - len(a.width, ctx.canvas));
56
+ for (const k of sorted) markup += `${open}${attrs({ fill: 'none', stroke: toHex(k.color), 'stroke-width': len(k.width, ctx.canvas), 'stroke-linejoin': 'round', 'stroke-dasharray': k.dash ? dashArray(k.dash, len(k.width, ctx.canvas), ctx) : undefined, ...common })}/>`;
57
+ }
58
+ markup += `${open}${attrs({ fill: fp.value, stroke: sp?.value, 'stroke-width': sp ? sw : undefined, 'stroke-dasharray': s.dash ? dashArray(s.dash, sw ?? 1, ctx) : undefined, 'stroke-dashoffset': s.dash && s.dashOffset != null ? len(s.dashOffset, ctx.canvas) : undefined, 'stroke-linecap': s.linecap ?? (s.dash === 'dotted' ? 'round' : undefined), 'stroke-linejoin': s.linejoin, ...common })}/>`;
59
+ if (s.soft) {
60
+ // The blur's reach, round the shape's bounds (turned, when it's turned).
61
+ const soft = await soften({ defs: own, body: markup }, reach(g.box, s, tr ? s.rotate ?? 0 : 0, sw), len(s.soft, ctx.canvas), ctx, `${ctx.id}s${i}b`);
62
+ own = soft.defs;
63
+ markup = soft.body;
64
+ }
65
+ defs += own;
66
+ body += markup;
67
+ }
68
+ return { defs, body: wrap(layer, body) };
69
+ }
70
+
71
+ /**
72
+ * A dash pattern: `'dashed'`, `'dotted'`, or lengths `[on, off, …]`.
73
+ *
74
+ * @param {any} dash @param {number} width - The stroke width (dashed and dotted scale with it).
75
+ * @param {import('../layers.js').RenderContext} ctx
76
+ * @returns {string}
77
+ */
78
+ export function dashArray(dash, width, ctx) {
79
+ if (Array.isArray(dash)) return dash.map((/** @type {any} */ d) => num(len(d, ctx.canvas))).join(' ');
80
+ return dash === 'dotted' ? `0 ${num(width * 2.4)}` : `${num(width * 3)} ${num(width * 2.4)}`;
81
+ }
82
+
83
+ /**
84
+ * The rotation of a shape: about its centre, or `origin` (`[x, y]`, px or
85
+ * `'%'`). Polar kinds turn their points about the centre instead.
86
+ *
87
+ * @param {any} s @param {{ box: { x: number, y: number, width: number, height: number } }} g
88
+ * @param {import('../layers.js').RenderContext} ctx @param {boolean} polar
89
+ * @returns {string | undefined}
90
+ */
91
+ function rotation(s, g, ctx, polar) {
92
+ if (!s.rotate || polar) return undefined;
93
+ const [ox, oy] = s.origin != null
94
+ ? (Array.isArray(s.origin) ? [pos(s.origin[0], 'x', ctx.canvas), pos(s.origin[1], 'y', ctx.canvas)] : [pos(s.origin.x, 'x', ctx.canvas), pos(s.origin.y, 'y', ctx.canvas)])
95
+ : [g.box.x + g.box.width / 2, g.box.y + g.box.height / 2];
96
+ return `rotate(${num(s.rotate, 3)} ${num(ox)} ${num(oy)})`;
97
+ }
98
+
99
+ /**
100
+ * The box a shape can paint in: its bounds (turned, when turned about its
101
+ * centre) plus its stroke.
102
+ *
103
+ * @param {{ x: number, y: number, width: number, height: number }} box
104
+ * @param {any} s @param {number} rotate @param {number | undefined} sw
105
+ * @returns {{ x: number, y: number, width: number, height: number }}
106
+ */
107
+ function reach(box, s, rotate, sw) {
108
+ const a = (rotate * Math.PI) / 180;
109
+ const w = s.origin == null ? Math.abs(box.width * Math.cos(a)) + Math.abs(box.height * Math.sin(a)) : Math.hypot(box.width, box.height) * 2;
110
+ const h = s.origin == null ? Math.abs(box.width * Math.sin(a)) + Math.abs(box.height * Math.cos(a)) : w;
111
+ const stroke = (sw ?? 0) + Math.max(0, ...(s.strokes ?? []).map((/** @type {any} */ k) => (typeof k.width === 'number' ? k.width : 0)));
112
+ const cx = box.x + box.width / 2;
113
+ const cy = box.y + box.height / 2;
114
+ return { x: cx - w / 2 - stroke, y: cy - h / 2 - stroke, width: w + 2 * stroke, height: h + 2 * stroke };
115
+ }
@@ -0,0 +1,5 @@
1
+ // Soft (blurred) shapes, drawn padded.
2
+ import type { RenderContext, Markup } from '../render/context.js';
3
+
4
+ /** Blur markup by `sigma`, padded 3σ round `box` (raster with sharp, else an SVG blur). */
5
+ export function soften(m: Markup, box: { x: number; y: number; width: number; height: number }, sigma: number, ctx: RenderContext, id: string): Promise<Markup>;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Soft shapes: a blur drawn padded, so a blurred orb never fades where it
3
+ * meets the canvas edge (librsvg clips a filter's input to the canvas) or
4
+ * shows the square edge of a filter region.
5
+ *
6
+ * @module layers/soft
7
+ */
8
+
9
+ import { attrs, num } from '../svg.js';
10
+ import { toBase64 } from '../platform/bytes.js';
11
+
12
+ /**
13
+ * Blur markup by `sigma` (px in the layer's units). In `renderImage`, the markup
14
+ * is drawn on its own padded canvas (3σ round `box`) at the resolution the
15
+ * blur needs (about four pixels per σ, never more than the output's), blurred
16
+ * there, and embedded as an image; elsewhere it's an SVG blur over a filter
17
+ * region padded the same way.
18
+ *
19
+ * @param {{ defs: string, body: string }} m
20
+ * @param {{ x: number, y: number, width: number, height: number }} box - What the markup paints.
21
+ * @param {number} sigma
22
+ * @param {import('../layers.js').RenderContext} ctx
23
+ * @param {string} id
24
+ * @returns {Promise<{ defs: string, body: string }>}
25
+ */
26
+ export async function soften(m, box, sigma, ctx, id) {
27
+ const pad = 3 * sigma;
28
+ const r = { x: box.x - pad, y: box.y - pad, width: box.width + 2 * pad, height: box.height + 2 * pad };
29
+ // Pixels only for renderImage (an SVG keeps its blur as a filter).
30
+ const sharp = /** @type {any} */ (ctx).raster ? /** @type {any} */ (ctx.sharp) : undefined;
31
+ if (!sharp || sigma <= 0) {
32
+ if (sigma <= 0) return m;
33
+ const filter = `<filter${attrs({ id, filterUnits: 'userSpaceOnUse', ...r, 'color-interpolation-filters': 'sRGB' })}><feGaussianBlur stdDeviation="${num(sigma, 3)}"/></filter>`;
34
+ return { defs: m.defs + filter, body: `<g filter="url(#${id})">${m.body}</g>` };
35
+ }
36
+ const res = /** @type {any} */ (ctx).res ?? 1;
37
+ const ppu = Math.min(res, Math.max(4 / sigma, 64 / Math.max(r.width, r.height)));
38
+ const w = Math.max(1, Math.ceil(r.width * ppu));
39
+ const h = Math.max(1, Math.ceil(r.height * ppu));
40
+ const doc = `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="${num(r.x, 4)} ${num(r.y, 4)} ${num(w / ppu, 4)} ${num(h / ppu, 4)}" preserveAspectRatio="none">${m.defs ? `<defs>${m.defs}</defs>` : ''}${m.body}</svg>`;
41
+ const png = await sharp(new TextEncoder().encode(doc)).blur(Math.max(0.3, sigma * ppu)).png().toBuffer();
42
+ return { defs: '', body: `<image${attrs({ x: r.x, y: r.y, width: num(w / ppu, 4), height: num(h / ppu, 4), preserveAspectRatio: 'none', href: `data:image/png;base64,${toBase64(png)}` })}/>` };
43
+ }
@@ -0,0 +1,23 @@
1
+ // The `svg` layer.
2
+ import type { Color, Length, BaseLayer, Anchor } from '../index.js';
3
+ import type { RenderContext, Markup } from '../render/context.js';
4
+
5
+ /** `svg`: raw markup, the escape hatch. */
6
+ export interface SvgLayer extends BaseLayer {
7
+ type: 'svg';
8
+ /** Tokens: `{{@accent}}`, `{{@colors.1}}`, `{{@width}}`, `{{id}}`, `{{seed}}`; in JSON recipes `{{title}}` or any option. */
9
+ markup: string;
10
+ defs?: string;
11
+ viewBox?: [number, number, number, number];
12
+ x?: Length | `${number}%`; y?: Length | `${number}%`; width?: Length | `${number}%`; height?: Length | `${number}%`;
13
+ fit?: 'contain' | 'cover' | 'stretch';
14
+ align?: string;
15
+ }
16
+
17
+
18
+ /** Strip scripts, foreignObject, event handlers and links out of SVG markup. */
19
+ export function sanitize(s: string): string;
20
+ /** Shapes from a slot's markup (paths and basic shapes with geometry and paint attributes); anything else is escaped. */
21
+ export function shapesOnly(markup: string): string;
22
+ /** Draw raw markup with its tokens filled. */
23
+ export function svg(layer: SvgLayer, ctx: RenderContext): Markup;
@@ -0,0 +1,109 @@
1
+ /**
2
+ * The `svg` layer: raw markup, sanitised, with render-time tokens.
3
+ *
4
+ * @module layers/svg
5
+ */
6
+
7
+ import { attrs, esc, num } from '../svg.js';
8
+ import { unit } from '../geometry.js';
9
+ import { pos, wrap } from './util.js';
10
+ import { expandDropShadows } from '../effects/drop-shadow.js';
11
+ import { filterReach } from '../effects/region.js';
12
+
13
+
14
+ /**
15
+ * Remove what an SVG fragment must never carry: scripts, foreign objects,
16
+ * event handlers and links out (`href`s other than `#…` and `data:`).
17
+ *
18
+ * @param {string} s
19
+ * @returns {string}
20
+ */
21
+ export function sanitize(s) {
22
+ return String(s)
23
+ .replace(/<script\b[\s\S]*?<\/script\s*>/gi, '')
24
+ .replace(/<script\b[^>]*\/>/gi, '')
25
+ .replace(/<foreignObject\b[\s\S]*?<\/foreignObject\s*>/gi, '')
26
+ .replace(/\son[a-z]+\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi, '')
27
+ .replace(/\s(xlink:)?href\s*=\s*("|')(?!#|data:)[^"']*\2/gi, '');
28
+ }
29
+
30
+ /** Elements a markup slot may carry (shapes and groups). */
31
+ const SHAPES = new Set(['g', 'path', 'circle', 'ellipse', 'rect', 'polygon', 'polyline', 'line']);
32
+
33
+ /** Attributes a markup slot may carry. */
34
+ const SHAPE_ATTRS = new Set(['d', 'cx', 'cy', 'r', 'rx', 'ry', 'x', 'y', 'width', 'height', 'points', 'x1', 'y1', 'x2', 'y2', 'transform', 'fill', 'stroke', 'stroke-width', 'opacity', 'fill-opacity', 'stroke-opacity', 'fill-rule', 'clip-rule', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-dasharray', 'stroke-dashoffset']);
35
+
36
+ /**
37
+ * Shapes from a slot's markup (a mark's paths): `g`, `path`, `circle`,
38
+ * `ellipse`, `rect`, `polygon`, `polyline` and `line` with their geometry
39
+ * and paint attributes are kept; any other element is escaped (inert
40
+ * text), and other attributes dropped. Paint can't reference anything
41
+ * outside (`url(…)` is dropped).
42
+ *
43
+ * @param {string} markup
44
+ * @returns {string}
45
+ */
46
+ export function shapesOnly(markup) {
47
+ return String(markup).replace(/<\/?([a-zA-Z][\w:-]*)([^>]*)>/g, (tag, name, rest) => {
48
+ if (!SHAPES.has(name)) return tag.replace(/[&<>"']/g, (ch) => /** @type {Record<string, string>} */ ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[ch]);
49
+ if (tag.startsWith('</')) return `</${name}>`;
50
+ const self = /\/\s*$/.test(rest);
51
+ let kept = '';
52
+ for (const m of rest.matchAll(/([\w:-]+)\s*=\s*("([^"]*)"|'([^']*)')/g)) {
53
+ const value = m[3] ?? m[4];
54
+ if (SHAPE_ATTRS.has(m[1]) && !/url\s*\(|javascript:/i.test(value)) kept += ` ${m[1]}="${esc(value)}"`;
55
+ }
56
+ return `<${name}${kept}${self ? '/' : ''}>`;
57
+ });
58
+ }
59
+
60
+ /**
61
+ * Replace render-time tokens in markup: `{{@accent}}` and the other palette
62
+ * and canvas tokens, `{{id}}` (a prefix unique to this layer, for ids in
63
+ * `defs`), and `{{seed}}` (a seeded number).
64
+ *
65
+ * @param {string} s @param {import('../layers.js').RenderContext} ctx
66
+ * @returns {string}
67
+ */
68
+ function tokens(s, ctx) {
69
+ const p = /** @type {any} */ (ctx.palette);
70
+ return s.replace(/\{\{\s*([@\w.]+)\s*\}\}/g, (m, k) => {
71
+ if (k === 'id') return ctx.id;
72
+ if (k === 'seed') return String(ctx.rng.int(1, 9999));
73
+ if (k.startsWith('@')) {
74
+ const [head, idx] = k.slice(1).split('.');
75
+ if (head === 'width' || head === 'height') return num(ctx[head]);
76
+ if (head === 'u') return num(unit(ctx.canvas));
77
+ if (head === 'colors') return esc(p.colors[Number(idx ?? 0) % p.colors.length]);
78
+ if (Object.hasOwn(p, head)) return esc(String(p[head]));
79
+ }
80
+ return m;
81
+ });
82
+ }
83
+
84
+ /**
85
+ * `svg` — raw SVG markup, the escape hatch for anything too bespoke for a
86
+ * layer. `markup` is drawn in the layer's canvas, or from a `viewBox` into
87
+ * a box (`x`, `y`, `width`, `height`, `fit`); `defs` go in the document's
88
+ * defs. Palette and canvas tokens are written `{{@accent}}`, `{{@colors.1}}`,
89
+ * `{{@width}}`; `{{id}}` prefixes ids; in a JSON recipe `{{title}}` and any
90
+ * other option is filled in (escaped) too. Scripts, event handlers and
91
+ * external links are stripped.
92
+ *
93
+ * @param {any} layer
94
+ * @param {import('../layers.js').RenderContext} ctx
95
+ * @returns {import('../layers.js').Markup}
96
+ */
97
+ export function svg(layer, ctx) {
98
+ const markup = expandDropShadows(sanitize(tokens(String(layer.markup ?? ''), ctx)));
99
+ const defs = layer.defs ? expandDropShadows(sanitize(tokens(String(layer.defs), ctx))) : '';
100
+ /** @type {any} */ (ctx).noteReach?.(filterReach(defs + markup) * (/** @type {any} */ (ctx).res ?? 1));
101
+ let body = markup;
102
+ if (layer.viewBox) {
103
+ const vb = Array.isArray(layer.viewBox) ? layer.viewBox : String(layer.viewBox).split(/[\s,]+/).map(Number);
104
+ const fit = layer.fit ?? 'contain';
105
+ const par = fit === 'stretch' ? 'none' : `${layer.align ?? 'xMidYMid'} ${fit === 'cover' ? 'slice' : 'meet'}`;
106
+ body = `<svg${attrs({ x: pos(layer.x ?? 0, 'x', ctx.canvas), y: pos(layer.y ?? 0, 'y', ctx.canvas), width: layer.width != null ? pos(layer.width, 'x', ctx.canvas) : ctx.width, height: layer.height != null ? pos(layer.height, 'y', ctx.canvas) : ctx.height, viewBox: vb.map((/** @type {number} */ v) => num(v, 3)).join(' '), preserveAspectRatio: par, overflow: 'visible' })}>${markup}</svg>`;
107
+ }
108
+ return { defs, body: wrap(layer, body) };
109
+ }
@@ -0,0 +1,25 @@
1
+ // The `table` layer.
2
+ import type { Color, Length, BaseLayer, Anchor } from '../index.js';
3
+ import type { RenderContext, Markup } from '../render/context.js';
4
+
5
+ /** A table cell: text, or text with its own style. */
6
+ export type TableCell = string | number | { content: string | number; weight?: number; size?: Length; color?: Color; align?: 'left' | 'center' | 'right'; font?: string; opacity?: number; width?: Length };
7
+
8
+ export interface TableLayer extends BaseLayer {
9
+ type: 'table';
10
+ rows: (TableCell[] | string | Record<string, TableCell>)[];
11
+ columns?: { x?: Length | `${number}%`; align?: 'left' | 'center' | 'right'; width?: Length; key?: string; weight?: number; size?: Length; color?: Color; font?: string; letterSpacing?: number; transform?: 'uppercase'; opacity?: number; lengthAdjust?: 'spacingAndGlyphs' | 'spacing' }[];
12
+ x?: Length | `${number}%`;
13
+ /** First baseline. */
14
+ y?: Length | `${number}%`;
15
+ size?: Length; lineHeight?: Length; rowHeight?: Length; rowGap?: Length;
16
+ font?: string; weight?: number; style?: 'normal' | 'italic'; axes?: Record<string, number>;
17
+ color?: Color; letterSpacing?: number;
18
+ header?: Record<string, unknown>;
19
+ rules?: boolean | { at?: 'between' | 'each' | 'top'; color?: Color; width?: Length; opacity?: number; x1?: Length; x2?: Length; above?: number };
20
+ columnWidth?: Length;
21
+ }
22
+
23
+
24
+ /** Draw text on a grid. */
25
+ export function table(layer: TableLayer, ctx: RenderContext): Promise<Markup>;