book-cover-generator 1.9.1 → 1.9.2

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/README.md CHANGED
@@ -191,25 +191,25 @@ Returns the SVG markup.
191
191
 
192
192
  Same as `generateCover`, but also tells you which style and palette were chosen. This is useful when either one was random. `body` is the markup inside the outer `<svg>`, for nesting the cover in a larger SVG (its ids are unique to the cover, so several can share a document).
193
193
 
194
- | option | type | default | notes |
195
- | ----------- | ------------------ | ---------------------- | ------------------------------------------------- |
196
- | `title` | `string` | `'Untitled'` | a line break (`\n`) breaks the title's line there |
197
- | `author` | `string` | `''` | optional |
198
- | `subtitle` | `string` | `''` | optional subtitle or tagline |
199
- | `series` | `string` | `''` | a series line above the title |
200
- | `credit` | `string` | `''` | a credit under the subtitle ("Translated by…") |
201
- | `badge` | `string` | `''` | a seal in a corner clear of the text |
202
- | `texture` | `boolean` | `false` | adds a fine film grain |
203
- | `style` | `string` | `'random'` | a style id, see `styles` |
204
- | `palette` | `string \| object` | `'random'` | a palette id, or `{ bg, ink, accents: [...] }` |
205
- | `seed` | `string \| number` | hash of title + author | controls every random choice |
206
- | `width` | `number` | `600` | px, 50–10000; aspect ratio 1:3 to 3:1 |
207
- | `height` | `number` | `width × 1.5` | px, 50–10000 |
208
- | `lang` | `string` | inferred | language of the text, such as `'ja'` |
209
- | `variants` | `object` | `{}` | pin a style's choices: `{ scene: 'forest' }` |
210
- | `genre` | `string` | | with a random style, one that suits this genre |
211
- | `font` | `string` | the style's | the title's font, one of `FONTS` |
212
- | `titleCase` | `string` | the style's | `'upper'` (capitals) or `'normal'` (as typed) |
194
+ | option | type | default | notes |
195
+ | ----------- | ------------------ | -------------------- | ------------------------------------------------- |
196
+ | `title` | `string` | `'Untitled'` | a line break (`\n`) breaks the title's line there |
197
+ | `author` | `string` | `''` | optional |
198
+ | `subtitle` | `string` | `''` | optional subtitle or tagline |
199
+ | `series` | `string` | `''` | a series line above the title |
200
+ | `credit` | `string` | `''` | a credit under the subtitle ("Translated by…") |
201
+ | `badge` | `string` | `''` | a seal in a corner clear of the text |
202
+ | `texture` | `boolean` | `false` | adds a fine film grain (any truthy value) |
203
+ | `style` | `string` | `'random'` | a style id, see `styles` |
204
+ | `palette` | `string \| object` | `'random'` | a palette id, or `{ bg, ink, accents: [...] }` |
205
+ | `seed` | `string \| number` | the title and author | controls every random choice |
206
+ | `width` | `number` | `600` | px, 50–10000; aspect ratio 1:3 to 3:1 |
207
+ | `height` | `number` | `width × 1.5` | px, 50–10000 |
208
+ | `lang` | `string` | inferred | language of the text, such as `'ja'` |
209
+ | `variants` | `object` | `{}` | pin a style's choices: `{ scene: 'forest' }` |
210
+ | `genre` | `string` | | with a random style, one that suits this genre |
211
+ | `font` | `string` | the style's | the title's font, one of `FONTS` |
212
+ | `titleCase` | `string` | the style's | `'upper'` (capitals) or `'normal'` (as typed) |
213
213
 
214
214
  Styles, palettes, genres and fonts may be given by id or by name, in any case: `'artdeco'`, `'Art Deco'` and `'ART DECO'` are the same style, and a book list or a link can name them as the app's menus do.
215
215
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "book-cover-generator",
3
- "version": "1.9.1",
3
+ "version": "1.9.2",
4
4
  "description": "Generate SVG book covers from a title and an author. 39 styles, 28 palettes, seeded randomizer, zero dependencies.",
5
5
  "keywords": [
6
6
  "book",
package/src/browser.d.ts CHANGED
@@ -1,7 +1,27 @@
1
+ /**
2
+ * Browser helpers to export a cover: rasterise it to PNG, JPEG or WebP, download it, copy it to the
3
+ * clipboard, or zip several files. `setDpi` and `zipFiles` also work in Node.
4
+ *
5
+ * ```js
6
+ * import { createCover } from '@otsirh/book-cover-generator';
7
+ * import { downloadCover, rasterize } from '@otsirh/book-cover-generator/browser';
8
+ *
9
+ * const { svg } = createCover({ title: 'Dune', author: 'Frank Herbert' });
10
+ * await downloadCover(svg, 'png', 'dune', { scale: 3, dpi: 300 }); // dune.png, 1800 × 2700 px
11
+ * const blob = await rasterize(svg, { type: 'image/webp', width: 1600 }); // 1600 × 2400 px
12
+ * ```
13
+ *
14
+ * @module
15
+ */
16
+
17
+ /** SVG markup as a `data:` URL, for an `<img>`'s `src`. */
1
18
  export declare function svgToDataUrl(svg: string): string;
19
+ /** SVG markup as a Blob of type `image/svg+xml;charset=utf-8`. */
2
20
  export declare function svgToBlob(svg: string): Blob;
3
21
 
22
+ /** Options for `rasterize`. */
4
23
  export interface RasterizeOptions {
24
+ /** The image's type. Default `'image/png'`; a browser that can't encode it makes a PNG. */
5
25
  type?: 'image/png' | 'image/jpeg' | 'image/webp';
6
26
  /** Multiplier applied to the SVG's width and height. Default 1. Ignored when `width` or `height` is given. */
7
27
  scale?: number;
@@ -22,6 +42,7 @@ export declare function rasterize(svg: string, opts?: RasterizeOptions): Promise
22
42
  /** Trigger a file download for a Blob. */
23
43
  export declare function downloadBlob(blob: Blob, filename: string): void;
24
44
 
45
+ /** The formats `downloadCover` makes: the SVG itself, or a PNG, JPEG (`'jpeg'` or `'jpg'`) or WebP image. */
25
46
  export type ExportFormat = 'svg' | 'png' | 'jpeg' | 'jpg' | 'webp';
26
47
  /**
27
48
  * Download a cover; `basename` gets the right extension appended. Resolves to the file name,
package/src/core.d.ts CHANGED
@@ -1,3 +1,9 @@
1
+ /**
2
+ * The SVG, colour and noise helpers the built-in styles use, exported as `svgUtils`.
3
+ *
4
+ * @module
5
+ */
6
+
1
7
  /** Seeded pseudo-random number generator. */
2
8
  export interface Rng {
3
9
  /** Next float in `[0, 1)`. */
@@ -8,20 +14,32 @@ export interface Rng {
8
14
  int(min: number, max: number): number;
9
15
  /** `true` with probability `p`. */
10
16
  bool(p?: number): boolean;
17
+ /** One of `arr`'s items, at random (undefined for an empty array). */
11
18
  pick<T>(arr: readonly T[]): T;
12
19
  /** A shuffled copy. */
13
20
  shuffle<T>(arr: readonly T[]): T[];
14
21
  }
15
22
 
23
+ /** A 32-bit hash of `String(input)` (cyrb53, folded to 32 bits), as an unsigned integer. */
16
24
  export declare function hashString(input: unknown): number;
25
+ /**
26
+ * A seeded random number generator (mulberry32). The seed is hashed as a string, so the same seed
27
+ * (42 or '42') always gives the same numbers.
28
+ */
17
29
  export declare function createRng(seed: unknown): Rng;
18
30
 
31
+ /** `v` kept within `lo`–`hi`. */
19
32
  export declare function clamp(v: number, lo: number, hi: number): number;
33
+ /** Linear interpolation: `a` at `t = 0`, `b` at `t = 1` (`t` isn't clamped). */
20
34
  export declare function lerp(a: number, b: number, t: number): number;
35
+ /** 0 at `e0` and below, 1 at `e1` and above, and a smooth S-curve in between. */
21
36
  export declare function smoothstep(e0: number, e1: number, x: number): number;
22
37
 
38
+ /** Whether `v` is a hex colour string, `#rgb` or `#rrggbb` in any case (spaces at either end allowed). */
23
39
  export declare function isHex(v: unknown): boolean;
40
+ /** A hex colour (`#rgb` or `#rrggbb`) as `[r, g, b]`, each 0–255. It isn't checked: see `isHex`. */
24
41
  export declare function hexToRgb(hex: string): [number, number, number];
42
+ /** `[r, g, b]` as `#rrggbb`; each channel is rounded and clamped to 0–255. */
25
43
  export declare function rgbToHex(rgb: readonly number[]): string;
26
44
  /** Linear blend of two hex colours; `t = 0` is `a`, `t = 1` is `b`. */
27
45
  export declare function mix(a: string, b: string, t: number): string;
@@ -36,19 +54,42 @@ export declare function inkCandidates(p: { readonly ink: string; readonly bg: st
36
54
  /** Best text colour on `bg`: the palette's ink or background, or white / near-black. */
37
55
  export declare function inkOn(bg: string, p: { readonly ink: string; readonly bg: string }): string;
38
56
 
57
+ /** A 2D noise function: a value between -1 and 1 for each point. */
39
58
  export type Noise2D = (x: number, y: number) => number;
59
+ /** Smooth 2D value noise from `rng`: values between -1 and 1, changing over about one unit. */
40
60
  export declare function makeNoise2D(rng: Rng): Noise2D;
61
+ /**
62
+ * Fractal noise at (x, y): `octaves` layers of `noise` (default 4), each at twice the frequency and
63
+ * half the weight of the one before, averaged by weight so it stays in the range of `noise`.
64
+ */
41
65
  export declare function fbm(noise: Noise2D, x: number, y: number, octaves?: number): number;
42
66
  /** Midpoint-displacement ridge line with values in `[-1, 1]`. */
43
67
  export declare function ridge(rng: Rng, segments?: number, roughness?: number): number[];
44
68
 
45
69
  /** Round to one decimal place. */
46
70
  export declare function n(v: number): number;
71
+ /**
72
+ * `String(str)` without the characters XML 1.0 forbids: control characters other than tab, LF and
73
+ * CR, U+FFFE, U+FFFF and lone surrogates.
74
+ */
47
75
  export declare function stripInvalidXml(str: unknown): string;
76
+ /**
77
+ * `String(str)` made safe for SVG text and attribute values: the characters XML forbids dropped, and
78
+ * `& < > " '` escaped.
79
+ */
48
80
  export declare function escapeXml(str: unknown): string;
49
81
 
82
+ /** A `<rect>`. Numbers are rounded to one decimal; `fill` and `extra` (raw attributes) are written as given. */
50
83
  export declare function rect(x: number, y: number, w: number, h: number, fill: string, extra?: string): string;
84
+ /**
85
+ * A `<circle>` (a negative radius is drawn as 0). Numbers are rounded to one decimal; `fill` and
86
+ * `extra` are written as given.
87
+ */
51
88
  export declare function circle(cx: number, cy: number, r: number, fill: string, extra?: string): string;
89
+ /**
90
+ * A `<line>` with a stroke `width` px wide. Numbers are rounded to one decimal; `stroke` and
91
+ * `extra` are written as given.
92
+ */
52
93
  export declare function line(
53
94
  x1: number,
54
95
  y1: number,
@@ -60,39 +101,67 @@ export declare function line(
60
101
  ): string;
61
102
  /** `[[x, y], ...]` to a path `d` string. */
62
103
  export declare function pathD(points: ReadonlyArray<readonly number[]>, close?: boolean): string;
104
+ /** A `<path>`; `d`, `fill` and `extra` (raw attributes) are written as given. */
63
105
  export declare function path(d: string, fill: string, extra?: string): string;
64
106
  /** Path for a quarter disc filling the square (x, y, size), centred on its bottom-left corner. */
65
107
  export declare function quarterDisc(x: number, y: number, size: number): string;
66
108
  /** Path for a half disc standing on the bottom edge of the square (x, y, size). */
67
109
  export declare function halfDisc(x: number, y: number, size: number): string;
68
110
 
111
+ /** Options for `grain`. */
69
112
  export interface GrainOptions {
113
+ /** The layer's opacity, 0–1. Default 0.12. */
70
114
  opacity?: number;
115
+ /** White specks instead of black. Default `false`. */
71
116
  light?: boolean;
117
+ /**
118
+ * The noise's base frequency for a short side of 600 px (it is divided by `u`, so the grain
119
+ * scales with the cover). Default 0.75.
120
+ */
72
121
  freq?: number;
73
122
  /** Filter id suffix, when a cover needs more than one grain filter. Default `'grain'`. */
74
123
  id?: string;
75
124
  /** Noise seed, so two grain layers don't line up. Default 0. */
76
125
  seed?: number;
77
126
  }
127
+ /**
128
+ * A film-grain overlay: a `<filter>` for the cover's `<defs>` (`defs`), and a rectangle the size
129
+ * of the cover drawn with it (`layer`). `c` is the style's context.
130
+ */
78
131
  export declare function grain(
79
132
  c: { id(name: string): string; readonly u: number; readonly W: number; readonly H: number },
80
133
  opts?: GrainOptions,
81
134
  ): { defs: string; layer: string };
135
+ /**
136
+ * A soft drop-shadow `<filter>` with this id: the shape over its shadow, blurred by `blur` (a
137
+ * standard deviation) and moved down by `dy`, both times `u`, at `opacity` (defaults 4, 2 and 0.35).
138
+ */
82
139
  export declare function shadowFilter(
83
140
  id: string,
84
141
  u: number,
85
142
  opts?: { blur?: number; dy?: number; opacity?: number },
86
143
  ): string;
144
+ /**
145
+ * A Gaussian-blur `<filter>` with this id and standard deviation `std` (px), with room around the
146
+ * shape for the blur.
147
+ */
87
148
  export declare function blurFilter(id: string, std: number): string;
88
149
 
89
150
  /** `[offset 0–1, colour, opacity?]` */
90
151
  export type GradientStop = readonly [offset: number, color: string, opacity?: number];
152
+ /**
153
+ * A `<linearGradient>` with this id. The vector is in fractions of the shape's box: top to bottom
154
+ * by default (`x1` 0, `y1` 0, `x2` 0, `y2` 1).
155
+ */
91
156
  export declare function linearGradient(
92
157
  id: string,
93
158
  stops: readonly GradientStop[],
94
159
  vector?: { x1?: number; y1?: number; x2?: number; y2?: number },
95
160
  ): string;
161
+ /**
162
+ * A `<radialGradient>` with this id: centre (`cx`, `cy`) and radius `r` in fractions of the
163
+ * shape's box (0.5 each by default), and the focal point (`fx`, `fy`) if given.
164
+ */
96
165
  export declare function radialGradient(
97
166
  id: string,
98
167
  stops: readonly GradientStop[],
@@ -1,16 +1,38 @@
1
+ /**
2
+ * `<book-cover>`: a cover on any page, drawn from the element's attributes. Importing this module
3
+ * defines the element (outside a browser, such as in Node, it loads and defines nothing). See
4
+ * `BookCover` for its attributes and events.
5
+ *
6
+ * ```html
7
+ * <script type="module" src="https://covers.otsirh.dev/src/element.js"></script>
8
+ *
9
+ * <book-cover cover-title="Dune" author="Frank Herbert" cover-style="retro"></book-cover>
10
+ * <!-- drawn once it comes near the viewport, as a lazy image is -->
11
+ * <book-cover cover-title="Emma" author="Jane Austen" loading="lazy"></book-cover>
12
+ * ```
13
+ *
14
+ * ```js
15
+ * // Or from a module (Deno, or a bundler):
16
+ * import '@otsirh/book-cover-generator/element';
17
+ * ```
18
+ *
19
+ * @module
20
+ */
21
+
1
22
  import type { CoverOptions, createCover } from './index.js';
2
23
 
24
+ // (The class alone: Deno and JSR use these types, as a package there may not declare global ones.
25
+ // element.d.ts adds the tag to `document.createElement`'s types for TypeScript from npm.)
26
+
3
27
  /**
4
28
  * `<book-cover>`: a cover on any page, from its attributes (cover-title, author, subtitle, series,
5
29
  * credit, badge, cover-style, genre, palette, seed, format, font, case, texture, variant, lang). It
6
30
  * fires `cover` (detail: the cover) when it draws, and `cover-error` (detail: the error) when the
7
31
  * attributes can't make one. With `loading="lazy"` it draws once it comes near the viewport, as an
8
32
  * image does; until then it holds the cover's shape and is named for screen readers.
9
- *
10
- * (The class alone: Deno and JSR use these types, as a package there may not declare global ones.
11
- * element.d.ts adds the tag to `document.createElement`'s types for TypeScript from npm.)
12
33
  */
13
34
  export declare class BookCover extends HTMLElement {
35
+ /** The attributes it watches: those that make the cover, and `loading`. */
14
36
  static observedAttributes: string[];
15
37
  /** The cover drawn last, or null (before the first, or after an error). */
16
38
  cover: ReturnType<typeof createCover> | null;
package/src/index.d.ts CHANGED
@@ -1,3 +1,29 @@
1
+ /**
2
+ * Draw book covers as SVG from a title and an author. Every random choice (the style, the palette,
3
+ * the artwork) comes from a seed, by default the title and author, so the same options give the
4
+ * same cover.
5
+ *
6
+ * ```js
7
+ * import { createCover } from '@otsirh/book-cover-generator';
8
+ *
9
+ * const { svg, style, palette } = createCover({
10
+ * title: 'Dune',
11
+ * author: 'Frank Herbert',
12
+ * style: 'retro', // or 'random' (the default): picked from the seed
13
+ * seed: 42,
14
+ * });
15
+ * ```
16
+ *
17
+ * `generateCover` returns the SVG alone, and `createMockup` shows a cover as a book or on a social
18
+ * card. To make a PNG, JPEG or WebP in a browser, see the `./browser` entry point; for a cover from
19
+ * an HTML element, `./element`. `registerStyle` adds a style of your own, drawn with the same text and
20
+ * colour helpers the built-in styles use.
21
+ *
22
+ * (On npm the package is `book-cover-generator`: `import { createCover } from 'book-cover-generator'`.)
23
+ *
24
+ * @module
25
+ */
26
+
1
27
  import type { Rng } from './core.js';
2
28
  import type { Palette, PaletteId } from './palettes.js';
3
29
  import type { FontSpec, FONTS } from './text.js';
@@ -22,11 +48,14 @@ export {
22
48
  } from './text.js';
23
49
  export { titleBlock, authorFit, fitStack, drawStack, splitLead } from './type.js';
24
50
  export { strongAccents, textColor, tintText, fitTitleWith, textPanel, TEXT_CONTRAST } from './styles/util.js';
51
+ /** The SVG, colour and noise helpers the built-in styles use. */
25
52
  export * as svgUtils from './core.js';
26
53
 
54
+ /** A kind of mockup: a product shot (`flat`, `book`, `ereader`) or a social card (`card`, `square`, `story`). */
27
55
  export type MockupKind = 'flat' | 'book' | 'ereader' | 'card' | 'square' | 'story';
28
56
  /** The kinds of mockup `createMockup` makes. */
29
57
  export declare const mockupKinds: readonly MockupKind[];
58
+ /** Options for `createMockup`. */
30
59
  export interface MockupOptions {
31
60
  /** Default `'book'`. `card` is 1200 × 630, `square` 1080 × 1080, `story` 1080 × 1920; the others follow the cover's size. */
32
61
  kind?: MockupKind;
@@ -68,6 +97,7 @@ export declare const formats: Readonly<{
68
97
  square: readonly [number, number];
69
98
  }>;
70
99
 
100
+ /** The ids of the built-in styles. */
71
101
  export type StyleId =
72
102
  | 'minimal'
73
103
  | 'swiss'
@@ -109,18 +139,29 @@ export type StyleId =
109
139
  | 'storybook'
110
140
  | 'idea';
111
141
 
142
+ /** The families the built-in styles belong to. */
112
143
  export type StyleGroup = 'Typographic' | 'Geometric' | 'Pattern' | 'Scenic' | 'Vintage' | 'Genre';
113
144
 
114
145
  /** A palette you define yourself. All colours must be hex (`#rgb` or `#rrggbb`). */
115
146
  export interface CustomPalette {
147
+ /** The background. */
116
148
  bg: string;
149
+ /** The text colour. */
117
150
  ink: string;
151
+ /** One or more accents; a cover repeats them to make three if there are fewer. */
118
152
  accents: readonly string[];
153
+ /** Its id: what the cover reports as its `palette` (default `'custom'`). `registerPalette` needs one. */
119
154
  id?: string;
155
+ /** Its name as people read it. Default `'Custom'`, or for `registerPalette` its id. */
120
156
  name?: string;
157
+ /** Whether the background is dark. Default: whether `bg` is darker than `ink`. */
121
158
  dark?: boolean;
122
159
  }
123
160
 
161
+ /**
162
+ * Options for `createCover` and `generateCover`, all optional. An option that isn't valid throws
163
+ * an error that says why (`texture` excepted: any truthy value turns it on).
164
+ */
124
165
  export interface CoverOptions {
125
166
  /** Defaults to `'Untitled'`. Up to 300 characters are used. */
126
167
  title?: string;
@@ -134,7 +175,7 @@ export interface CoverOptions {
134
175
  credit?: string;
135
176
  /** A seal set in a corner clear of the text, such as `'Winner of the Hugo Award'`. Up to 60 characters. */
136
177
  badge?: string;
137
- /** Add a fine film-grain texture over the cover. Default `false`. */
178
+ /** Add a fine film-grain texture over the cover. Default `false`; any truthy value (even `'false'`) turns it on. */
138
179
  texture?: boolean;
139
180
  /** A style id, a registered custom style, or `'random'` (default; picked from the seed). */
140
181
  style?: StyleId | 'random' | (string & {});
@@ -142,7 +183,7 @@ export interface CoverOptions {
142
183
  genre?: GenreId | (string & {});
143
184
  /** A palette id, a custom palette, or `'random'` / `'auto'` (default; picked from the seed). */
144
185
  palette?: PaletteId | 'random' | 'auto' | CustomPalette | (string & {});
145
- /** Controls every random choice. Defaults to a hash of title + author. */
186
+ /** Controls every random choice. Defaults to the title and author (`Cover.seed` says which seed was used). */
146
187
  seed?: string | number;
147
188
  /** Width in px, 50–10000. Default 600. */
148
189
  width?: number;
@@ -169,7 +210,9 @@ export interface CoverOptions {
169
210
  titleCase?: 'upper' | 'normal';
170
211
  }
171
212
 
213
+ /** A cover made by `createCover`: its SVG, and the choices it was made with. */
172
214
  export interface Cover {
215
+ /** The SVG document, `width` × `height` px, named for screen readers by its title, author and other text. */
173
216
  svg: string;
174
217
  /** The markup inside the outer `<svg>`, to nest the cover in another SVG (its ids are unique to the cover). */
175
218
  body: string;
@@ -179,28 +222,42 @@ export interface Cover {
179
222
  palette: string;
180
223
  /** The palette's colours, as the cover used them. */
181
224
  colors: { bg: string; ink: string; accents: string[]; dark: boolean };
225
+ /**
226
+ * The seed the cover was drawn from: the `seed` option as a string, else one made from the title
227
+ * and author. The same options with this seed give the same cover.
228
+ */
182
229
  seed: string;
230
+ /** Width in px. */
183
231
  width: number;
232
+ /** Height in px. */
184
233
  height: number;
185
234
  /** The language set on the cover (given or inferred), or `''`. */
186
235
  lang: string;
187
236
  /** The value of each of the style's variants this cover has, pinned or picked by the seed. */
188
237
  variants: Record<string, string>;
189
- /** The title as set (line breaks as "\n"), and the author, after cleaning. */
238
+ /** The title as set, after cleaning (line breaks as "\n"; `'Untitled'` when there is none). */
190
239
  title: string;
240
+ /** The author after cleaning, or `''`. */
191
241
  author: string;
192
242
  }
193
243
 
194
244
  /** A choice a style lets you pin, such as Silhouette's scene. */
195
245
  export interface StyleVariant {
246
+ /** The name to pin it by, such as `'scene'`. */
196
247
  name: string;
248
+ /** The name as people read it, such as `'Scene'`. */
197
249
  label: string;
250
+ /** The values it can take, each with a label as people read it. */
198
251
  values: { value: string; label: string }[];
199
252
  }
200
253
 
254
+ /** A style as `listStyles()` describes it. */
201
255
  export interface StyleInfo {
256
+ /** Its id, as `createCover({ style })` takes it. */
202
257
  id: string;
258
+ /** Its name as people read it, such as `'Art Deco'`; for a registered style without one, its id. */
203
259
  name: string;
260
+ /** What it looks like, in a sentence; empty for a registered style without one. */
204
261
  description: string;
205
262
  /** The family the style belongs to; `'Custom'` for registered styles without one. */
206
263
  group: StyleGroup | (string & {});
@@ -210,13 +267,16 @@ export interface StyleInfo {
210
267
  genres: string[];
211
268
  }
212
269
 
270
+ /** The ids of the genres in `genres`. */
213
271
  export type GenreId = 'literary' | 'thriller' | 'horror' | 'romance' | 'fantasy' | 'children' | 'nonfiction';
214
272
  /** The genres styles can suit, as `createCover({ genre })` and `listStyles()` name them. */
215
273
  export declare const genres: readonly { readonly id: GenreId; readonly name: string }[];
216
274
 
217
275
  /** A palette as seen by styles, with derived light/dark colours. */
218
276
  export interface ResolvedPalette extends Palette {
277
+ /** At least three accents: a palette with fewer has them repeated. */
219
278
  readonly accents: readonly string[];
279
+ /** Whether the background is dark: the palette's `dark`, else whether `bg` is darker than `ink`. */
220
280
  readonly dark: boolean;
221
281
  /** The lighter of `bg` and `ink`. */
222
282
  readonly paper: string;
@@ -226,7 +286,9 @@ export interface ResolvedPalette extends Palette {
226
286
 
227
287
  /** Everything a style's `render` function receives. */
228
288
  export interface StyleContext {
289
+ /** The title after cleaning (`'Untitled'` if there is none), with any line breaks it was typed with as "\n". */
229
290
  readonly title: string;
291
+ /** The author after cleaning, or empty. */
230
292
  readonly author: string;
231
293
  /**
232
294
  * The subtitle, then the credit ("Translated by …"), if any, on a line of its own (after "\n"):
@@ -247,6 +309,7 @@ export interface StyleContext {
247
309
  readonly m: number;
248
310
  /** Seeded random numbers, unique to this cover and style. */
249
311
  readonly rng: Rng;
312
+ /** The palette, with its lighter and darker colours as `paper` and `night`. */
250
313
  readonly p: ResolvedPalette;
251
314
  /** Make an id that is unique to this cover, for gradients, filters, masks… */
252
315
  id(name: string): string;
@@ -261,10 +324,15 @@ export interface StyleContext {
261
324
  readonly titleCase: 'upper' | 'normal' | null;
262
325
  }
263
326
 
327
+ /** A style to add with `registerStyle`. */
264
328
  export interface CoverStyle {
329
+ /** Its id: a non-empty string without spaces at either end, and not `'random'` (in any case). */
265
330
  id: string;
331
+ /** Its name as people read it. Default: the id. */
266
332
  name?: string;
333
+ /** What it looks like, in a sentence. Default empty. */
267
334
  description?: string;
335
+ /** The family it belongs to, as `listStyles()` reports it. Default `'Custom'`. */
268
336
  group?: StyleGroup | (string & {});
269
337
  /** Background preference when the palette is picked automatically. */
270
338
  prefer?: 'light' | 'dark';
@@ -284,7 +352,9 @@ export interface CoverStyle {
284
352
 
285
353
  /** Ids of every available style (including registered custom styles). */
286
354
  export declare const styles: string[];
355
+ /** Every available style, built-in and registered, as copies: changing them changes no style. */
287
356
  export declare function listStyles(): StyleInfo[];
357
+ /** Options for `registerStyle` and `registerPalette`. */
288
358
  export interface RegisterOptions {
289
359
  /** Allow replacing the built-in style (or palette) with the same id. */
290
360
  replace?: boolean;
package/src/index.js CHANGED
@@ -32,7 +32,7 @@ export { createMockup, mockupKinds } from './mockups.js';
32
32
  export { parseBookList } from './books.js';
33
33
 
34
34
  /** This version of the library (artwork for a seed can change between minor versions). */
35
- export const version = '1.9.1';
35
+ export const version = '1.9.2';
36
36
 
37
37
  /** The sizes a cover can have: each side `min` to `max` px, and neither more than `ratio` times the other. */
38
38
  export const sizeLimits = Object.freeze({ min: 50, max: 10000, ratio: 3 });
@@ -200,7 +200,7 @@ export function styleIdFor(name) {
200
200
  return idFor(name, registry.values());
201
201
  }
202
202
 
203
- /** Metadata for every available style: [{ id, name, description, group }] */
203
+ /** Every available style: its id, name, description and group, the genres it suits, and its variants. */
204
204
  export function listStyles() {
205
205
  return [...registry.values()].map(({ id, name, description, group, variants, genres: suits }) => ({
206
206
  id,
package/src/palettes.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /** The ids of the built-in palettes. */
1
2
  export type PaletteId =
2
3
  | 'parchment'
3
4
  | 'bauhaus'
@@ -28,13 +29,17 @@ export type PaletteId =
28
29
  | 'thriller'
29
30
  | 'dusk';
30
31
 
32
+ /** A palette: a background, an ink colour for text, and accents. */
31
33
  export interface Palette {
34
+ /** Its id, as `createCover({ palette })` takes it. */
32
35
  readonly id: string;
36
+ /** Its name as people read it, such as `'Midnight'`. */
33
37
  readonly name: string;
34
38
  /** Background, as hex. */
35
39
  readonly bg: string;
36
40
  /** Text colour with strong contrast against `bg`. */
37
41
  readonly ink: string;
42
+ /** Accent colours, as hex (three in each built-in palette). */
38
43
  readonly accents: readonly string[];
39
44
  /** `true` for dark backgrounds. */
40
45
  readonly dark?: boolean;
package/src/text.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /** A font to set text in: a CSS font-family stack, and what fitting text in it needs to know. */
1
2
  export interface FontSpec {
2
3
  /** Its name as the app's menu shows it (the built-in fonts). */
3
4
  name?: string;
@@ -11,6 +12,10 @@ export interface FontSpec {
11
12
  mono?: boolean;
12
13
  }
13
14
 
15
+ /**
16
+ * The built-in fonts. Each is a stack of system fonts, as an SVG drawn into a canvas (for PNG or
17
+ * JPEG export) can't load web fonts.
18
+ */
14
19
  export declare const FONTS: Readonly<{
15
20
  serif: FontSpec;
16
21
  didone: FontSpec;
@@ -23,9 +28,13 @@ export declare const FONTS: Readonly<{
23
28
  mono: FontSpec;
24
29
  }>;
25
30
 
31
+ /** The type `measureText` measures with: a font, its weight and style, and letter-spacing. */
26
32
  export interface TextSpec {
33
+ /** The font. */
27
34
  font: FontSpec;
35
+ /** Font weight. Default 400. */
28
36
  weight?: number;
37
+ /** Italic. Default `false`. */
29
38
  italic?: boolean;
30
39
  /** In em. */
31
40
  letterSpacing?: number;
@@ -43,6 +52,12 @@ export type TextMeasurer = (
43
52
  * string and font at a reference size of 100 px, and other sizes are scaled from it. Pass `null` to restore the default.
44
53
  */
45
54
  export declare function setTextMeasurer(fn: TextMeasurer | null): void;
55
+ /**
56
+ * The width in px of `text` at `size` px, letter-spacing included: measured with the function set
57
+ * by `setTextMeasurer`, else with a canvas, else from the built-in fonts' character widths (other
58
+ * fonts are estimated). Throws a TypeError if that function returns anything but a finite width of
59
+ * 0 or more.
60
+ */
46
61
  export declare function measureText(text: string, size: number, spec: TextSpec): number;
47
62
  /** Throw a TypeError if `maxWidth` is missing or `maxHeight` is NaN. */
48
63
  export declare function checkBox(fn: string, o: { maxWidth: number; maxHeight?: number }): void;
@@ -60,9 +75,13 @@ export declare const LINE_BREAK: RegExp;
60
75
  export declare function typedLines(text: string): number;
61
76
  /** Where some text was drawn. */
62
77
  export interface TextBox {
78
+ /** Left edge, in px. */
63
79
  x0: number;
80
+ /** Top edge, in px. */
64
81
  y0: number;
82
+ /** Right edge, in px. */
65
83
  x1: number;
84
+ /** Bottom edge, in px. */
66
85
  y1: number;
67
86
  }
68
87
  /** Note that text was drawn in this box (drawText and drawStack do it), so a badge can be placed clear of it. */
@@ -74,23 +93,32 @@ export declare function clipGraphemes(str: string, max: number, options?: { unit
74
93
  /** Split a string into user-perceived characters. */
75
94
  export declare function graphemes(str: string): string[];
76
95
 
96
+ /** Options for `fitText`: the box to fit, and how to set the text. */
77
97
  export interface FitOptions {
78
98
  /** Box width in px. */
79
99
  maxWidth: number;
80
100
  /** Largest font size in px. */
81
101
  maxSize: number;
82
- /** Box height in px (cap height of the first line to the last baseline). */
102
+ /**
103
+ * Box height in px: from the top of the first line (its cap height, or its marks when it has
104
+ * accents or marks above, such as É, Thai or Devanagari) to the last baseline.
105
+ */
83
106
  maxHeight?: number;
84
107
  /** Smallest font size in px before words are broken and truncated. */
85
108
  minSize?: number;
109
+ /** The font. Default `FONTS.sans`. */
86
110
  font?: FontSpec;
111
+ /** Font weight. Default 400. */
87
112
  weight?: number;
113
+ /** Italic. Default `false`. */
88
114
  italic?: boolean;
89
115
  /** In em. Ignored for scripts whose letters join (Arabic…), which browsers don't letter-space. */
90
116
  letterSpacing?: number;
91
117
  /** Multiple of the font size. */
92
118
  lineHeight?: number;
119
+ /** The most lines to wrap to. Default 5. */
93
120
  maxLines?: number;
121
+ /** Set the text in capitals. Default `false`. */
94
122
  upper?: boolean;
95
123
  /** Even out line lengths (default `true`). */
96
124
  balance?: boolean;
@@ -100,18 +128,27 @@ export interface FitOptions {
100
128
  hyphenate?: number;
101
129
  }
102
130
 
131
+ /** Text fitted to a box by `fitText`, ready for `drawText`. */
103
132
  export interface TextFit {
133
+ /** The lines, as set (in capitals with `upper`); empty for empty text. */
104
134
  lines: string[];
135
+ /** Font size in px (0 for empty text). */
105
136
  size: number;
137
+ /** The widest line's width in px. */
106
138
  width: number;
107
139
  /** From the top of the first line (its cap height, or its accents and marks where they reach higher) to the baseline of the last. */
108
140
  height: number;
141
+ /** The font. */
109
142
  font: FontSpec;
143
+ /** Font weight. */
110
144
  weight: number;
145
+ /** Whether it is italic. */
111
146
  italic: boolean;
147
+ /** Letter-spacing in em (0 for scripts whose letters join). */
112
148
  letterSpacing: number;
113
149
  /** The pitch between baselines, as a multiple of the size: the requested line height, or more where a line's accents or marks would reach the line above. */
114
150
  lineHeight: number;
151
+ /** Cap height as a fraction of the size: the font's `cap`, else 0.72. */
115
152
  cap: number;
116
153
  /** Room above the first line's cap height for its accents and marks, as a fraction of the size. */
117
154
  above?: number;
@@ -121,12 +158,17 @@ export interface TextFit {
121
158
  truncated: boolean;
122
159
  }
123
160
 
161
+ /** Where and how `drawText` sets a fit. */
124
162
  export interface DrawOptions {
163
+ /** The x of the anchor point, in px. */
125
164
  x: number;
126
165
  /** Top of the text block: the cap height of the first line, or its accents and marks where they reach higher. */
127
166
  y: number;
167
+ /** How the lines align on `x`, as SVG's `text-anchor`. Default `'start'`. */
128
168
  anchor?: 'start' | 'middle' | 'end';
169
+ /** Text colour. Default `'#000'`. */
129
170
  fill?: string;
171
+ /** Opacity, 0–1. Default 1. */
130
172
  opacity?: number;
131
173
  /** Extra raw SVG attributes for the wrapping `<g>`. */
132
174
  attrs?: string;
@@ -142,7 +184,11 @@ export declare function baselineOf(fit: TextFit, y: number, i: number): number;
142
184
  export declare function markExtent(text: string): { above: number; below: number };
143
185
  /** Room below a line for descenders and marks, in em. */
144
186
  export declare function descentOf(line: string): number;
145
- /** Attributes shared by every line of a text block (escaped), for its wrapping `<g>`. */
187
+ /**
188
+ * Attributes shared by every line of a text block, for its wrapping `<g>`: the font family and fill
189
+ * are escaped, `attrs` is written as given (raw SVG). Unlike the fitting options, `size` and
190
+ * `letterSpacing` are in px here (`drawText` passes a fit's letter-spacing × its size).
191
+ */
146
192
  export declare function textGroupAttrs(o: {
147
193
  font: FontSpec;
148
194
  fill: string;
package/src/type.d.ts CHANGED
@@ -3,6 +3,7 @@ import type { StyleContext } from './index.js';
3
3
 
4
4
  /** A justified stack: every line spans the full width at its own size. */
5
5
  export interface StackFit {
6
+ /** Tells a stack from a `TextFit`. */
6
7
  kind: 'stack';
7
8
  /**
8
9
  * `descent` is the room below each line for descenders and marks, and `above` the room above
@@ -13,30 +14,50 @@ export interface StackFit {
13
14
  height: number;
14
15
  /** Room needed below the last baseline, in px. */
15
16
  descent: number;
17
+ /** The widest line's width in px. */
16
18
  width: number;
17
19
  /** The largest line size. */
18
20
  size: number;
21
+ /** The font. */
19
22
  font: FontSpec;
23
+ /** Font weight (`fitStack` defaults to 700). */
20
24
  weight: number;
25
+ /** Whether it is italic. */
21
26
  italic: boolean;
27
+ /** Letter-spacing in em (0 for scripts whose letters join). */
22
28
  letterSpacing: number;
23
29
  /** Space between lines, as a fraction of the lower line's size. */
24
30
  gap: number;
31
+ /** Cap height as a fraction of each line's size: the font's `cap`, else 0.72. */
25
32
  cap: number;
26
33
  }
27
34
 
35
+ /** Options for `titleBlock`. */
28
36
  export interface TitleBlockOptions {
37
+ /** Box width in px. */
29
38
  maxWidth: number;
39
+ /** Box height in px, for all of it: the series line, the lead, the title and the subtitle. */
30
40
  maxHeight: number;
31
- /** Largest title size in px. */
41
+ /** Largest title size in px (a short one-word title, such as "Dune", may grow to 1.6 times this). */
32
42
  maxSize: number;
43
+ /**
44
+ * Smallest title size in px (default as for `fitText`). Rather than set the title smaller or cut
45
+ * it, the leading article rejoins the title, then the subtitle and then the series line are left out.
46
+ */
33
47
  minSize?: number;
48
+ /** The title's font. Default `FONTS.serif`; the cover's `font` option wins. */
34
49
  font?: FontSpec;
50
+ /** The title's weight. Default 400 (at most 700 when the cover's `font` option swaps in a serif). */
35
51
  weight?: number;
52
+ /** Set the title in italics. Default `false`. */
36
53
  italic?: boolean;
54
+ /** Set the title in capitals. Default `false`; the cover's `titleCase` option wins. */
37
55
  upper?: boolean;
56
+ /** The title's letter-spacing in em. Default 0. */
38
57
  letterSpacing?: number;
58
+ /** The title's line height, as a multiple of its size. Default 1.05. */
39
59
  lineHeight?: number;
60
+ /** The most title lines. Default 5 (or as many as the title was typed on, if more). */
40
61
  maxLines?: number;
41
62
  /**
42
63
  * Set a leading article of three letters or more ("The", "Les", "Der") as a small line above the
@@ -47,19 +68,25 @@ export interface TitleBlockOptions {
47
68
  stack?: boolean;
48
69
  /** Include the subtitle under the title. Default `true`. */
49
70
  subtitle?: boolean;
71
+ /** The font of the subtitle and the series line. Default: the title's. */
50
72
  subtitleFont?: FontSpec;
51
73
  /** `false` to leave out the cover's series line (for a style that sets it itself). */
52
74
  series?: false;
75
+ /** Set the subtitle in italics. Default `true`, except in `FONTS.mono`. */
53
76
  subtitleItalic?: boolean;
77
+ /** Set the subtitle in spaced capitals. Default `false`. */
54
78
  subtitleUpper?: boolean;
79
+ /** Largest subtitle size in px. Default 3.6% of the cover's short side. */
55
80
  subtitleSize?: number;
56
81
  /** Use this text instead of the cover's title. */
57
82
  text?: string;
58
83
  }
59
84
 
85
+ /** A title laid out by `titleBlock`, ready to draw. */
60
86
  export interface TitleBlock {
61
87
  /** From the top of the first line to the bottom of the block. */
62
88
  height: number;
89
+ /** The widest line's width in px (the title's, the lead's, the subtitle's or the series line's). */
63
90
  width: number;
64
91
  /** The main title size (the largest line, for stacks). */
65
92
  size: number;
@@ -71,6 +98,11 @@ export interface TitleBlock {
71
98
  lines: number;
72
99
  /** The title was cut with an ellipsis because it didn't fit. */
73
100
  truncated: boolean;
101
+ /**
102
+ * The block as SVG, its top at `y`. `fill` colours the title, `leadFill` the lead and the series
103
+ * line, and `subtitleFill` the subtitle (both default to `fill`); `attrs` (raw SVG attributes)
104
+ * go on every part.
105
+ */
74
106
  draw(o: {
75
107
  x: number;
76
108
  y: number;
@@ -90,6 +122,10 @@ export declare function splitLead(title: string): { lead: string; main: string }
90
122
  export declare function stackLines(text: string): string[];
91
123
  /** Fit a justified stack, or `null` when the text doesn't suit one. */
92
124
  export declare function fitStack(text: string, opts: FitOptions & { gap?: number }): StackFit | null;
125
+ /**
126
+ * Render a stack as SVG `<text>` elements, its top at `y` (as for `drawText`); `lineFills` colours
127
+ * lines one by one.
128
+ */
93
129
  export declare function drawStack(fit: StackFit, opts: DrawOptions & { lineFills?: (string | undefined)[] }): string;
94
130
  /** Fit the title (with a demoted leading article and the subtitle) into a box. */
95
131
  export declare function titleBlock(c: StyleContext, opts: TitleBlockOptions): TitleBlock;