bookcover-web 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -1
- package/dist/embed_types.d.ts +13 -11
- package/dist/embed_types.js +7 -1
- package/dist/index.d.ts +10 -9
- package/dist/index.js +94 -24
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -64,7 +64,7 @@ To self-host instead, serve the same layout under any prefix on your origin:
|
|
|
64
64
|
for the renderer). Without a bundler, copy them from `node_modules` into your `public/` dir.
|
|
65
65
|
- **`<assets_prefix>/frames/`** — `painted.png` and `torn.png`, copied from the repo's
|
|
66
66
|
`assets/frames/`. (`backgrounds/` is only needed if your UI offers the suggested
|
|
67
|
-
background images.)
|
|
67
|
+
background images — see below.)
|
|
68
68
|
- **`<fonts_prefix>/`** — the fonts tree (`manifest.json`, one dir per family, Noto fallbacks
|
|
69
69
|
under `_noto/`). Generate it with the repo's font config:
|
|
70
70
|
|
|
@@ -74,12 +74,34 @@ To self-host instead, serve the same layout under any prefix on your origin:
|
|
|
74
74
|
```
|
|
75
75
|
|
|
76
76
|
|
|
77
|
+
### Built-in backgrounds
|
|
78
|
+
|
|
79
|
+
A built-in background's filename (e.g. `beach.jpg`) is its ID — the embed protocol sends a built-in
|
|
80
|
+
as `bg_image_builtin` with no bytes. Join it onto any of these to fetch a copy:
|
|
81
|
+
|
|
82
|
+
- `https://assets.paper.bible/backgrounds/<name>` — the original (2.8MB on average), for final
|
|
83
|
+
output
|
|
84
|
+
- `https://assets.paper.bible/backgrounds/previews_2700/<name>` (`BG_PREVIEW_DIR`) — ~380KB,
|
|
85
|
+
enough for on-screen previews of a 6x9" cover
|
|
86
|
+
- `https://assets.paper.bible/backgrounds/previews_800/<name>` — large thumbnails
|
|
87
|
+
- `https://assets.paper.bible/backgrounds/thumbnails/<name>` — 160x120 picker tiles
|
|
88
|
+
|
|
89
|
+
Pass the filename to `generate()` as `image_builtin` whichever copy you render, so the automatic
|
|
90
|
+
text colors come from its baked color data and a preview matches the final output exactly.
|
|
91
|
+
`get_builtin_bg_regions(name)` returns the same data, and `get_builtin_bg(name)` adds the
|
|
92
|
+
original's pixel size (for resolution checks without downloading it).
|
|
93
|
+
|
|
94
|
+
|
|
77
95
|
## `CoverGenerator.generate(options): Promise<GenerateResult>`
|
|
78
96
|
|
|
79
97
|
```ts
|
|
80
98
|
interface GenerateOptions {
|
|
81
99
|
schema: unknown // raw schema (see above)
|
|
82
100
|
image?: Blob // background image
|
|
101
|
+
// Filename of the built-in background `image` is a copy of — colors come from its baked data
|
|
102
|
+
image_builtin?: string
|
|
103
|
+
// Shrink `image` to at most this many pixels per inch of cover before compiling (previews)
|
|
104
|
+
image_max_dpi?: number
|
|
83
105
|
format?: 'pdf'|'svg'|'png' // default: 'pdf'
|
|
84
106
|
ppi?: number // PNG resolution, default: 144
|
|
85
107
|
split?: boolean // include split panels in result
|
package/dist/embed_types.d.ts
CHANGED
|
@@ -85,7 +85,8 @@ export type HostMessage = InitMessage | {
|
|
|
85
85
|
id: string;
|
|
86
86
|
bg_image: File | null;
|
|
87
87
|
};
|
|
88
|
-
/** Widget -> host. `bg_image`
|
|
88
|
+
/** Widget -> host. `bg_image` and `bg_image_builtin` ride on every data/finished message
|
|
89
|
+
* (cloning a File is cheap), at most one of them non-null;
|
|
89
90
|
* `custom_fonts` byte arrays are expensive to clone, so on 'data' messages the field is only
|
|
90
91
|
* present when the font set changed since the last message (absent = unchanged), while
|
|
91
92
|
* 'finished' always carries the complete array.
|
|
@@ -98,17 +99,18 @@ export type HostMessage = InitMessage | {
|
|
|
98
99
|
* InitMessage.custom_fonts for the rule, and font_families_in_form in bookcover-core for the
|
|
99
100
|
* applied set).
|
|
100
101
|
*
|
|
101
|
-
* `bg_image_builtin`
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* its extension off for the MIME type, so it must stay a filename (see the contract on
|
|
106
|
-
* BACKGROUNDS in the widget's services/backgrounds.ts).
|
|
107
|
-
*
|
|
108
|
-
* to a built-in only after checking it against your own allowlist
|
|
109
|
-
*
|
|
102
|
+
* `bg_image_builtin` IS the background when the user picked one of the widget's built-ins: it
|
|
103
|
+
* carries that image's published filename and `bg_image` is null. It is null for an upload or
|
|
104
|
+
* no image at all. The filename IS the id — hosts join it onto their own assets path to get the
|
|
105
|
+
* bytes (`backgrounds/<name>` for the original, BG_PREVIEW_DIR for a preview-sized copy) and
|
|
106
|
+
* slice its extension off for the MIME type, so it must stay a filename (see the contract on
|
|
107
|
+
* BACKGROUNDS in the widget's services/backgrounds.ts). Pass it to generate() as image_builtin
|
|
108
|
+
* so colors come from the baked table whichever copy is rendered. Treat it as untrusted input
|
|
109
|
+
* — map it to a built-in only after checking it against your own allowlist. A name that fails
|
|
110
|
+
* that check comes with no bytes to fall back on, so keep the cover's existing background
|
|
111
|
+
* rather than treating the message as clearing it.
|
|
110
112
|
*
|
|
111
|
-
*
|
|
113
|
+
* A non-null `bg_image` File is the one the host supplied, byte-for-byte — see the round-trip
|
|
112
114
|
* guarantee at the top of this file.
|
|
113
115
|
*
|
|
114
116
|
* 'bg_suggestion_selected' asks the host for the bytes behind one of the BgSuggestions it sent
|
package/dist/embed_types.js
CHANGED
|
@@ -8,7 +8,13 @@
|
|
|
8
8
|
// Messages carry no derived schema: hosts call build_schema(form) from bookcover-core, so there
|
|
9
9
|
// is exactly one place that form → schema conversion happens.
|
|
10
10
|
//
|
|
11
|
-
// BACKGROUND IMAGE
|
|
11
|
+
// A BACKGROUND IMAGE IS AN ID OR BYTES, NEVER BOTH. A built-in background travels as its
|
|
12
|
+
// filename in `bg_image_builtin` with `bg_image: null` — no bytes, in either direction, so
|
|
13
|
+
// neither side has to download a multi-megabyte original just to open or report the editor. An
|
|
14
|
+
// uploaded image (a user upload, or a host-supplied one) travels as `bg_image` bytes with
|
|
15
|
+
// `bg_image_builtin: null`.
|
|
16
|
+
//
|
|
17
|
+
// UPLOADED IMAGE BYTES ROUND-TRIP UNMODIFIED. Whatever File the host hands in comes back out
|
|
12
18
|
// byte-for-byte — never re-encoded, resized or stripped of metadata — so a host can hash the
|
|
13
19
|
// bytes to recognise an image it already stores. See FormState.bg_image in bookcover-core.
|
|
14
20
|
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -6,9 +6,9 @@ export { font_file_url } from 'typst-fonts/web';
|
|
|
6
6
|
export { get_fonts, get_bundled_font } from 'typst-fonts';
|
|
7
7
|
export { list_patterns, collect_fonts, collect_all_fonts, default_spine_title, list_vector_backgrounds, find_vector_background, list_builtin_icons, find_builtin_icon, icon_categories, suggested_icons, generate_palette, cover_schema } from 'bookcover-core';
|
|
8
8
|
export type { IconCategory } from 'bookcover-core';
|
|
9
|
-
export { tinted_contrast_text, pick_vivid_tint, synthesize_fill, blend_regions, region_hex, all_image_regions, analyze_pixel_regions, get_builtin_bg_regions, VECTOR_BG_AUTO_COLOR, resolve_colors, darken_hsl } from 'bookcover-core';
|
|
10
|
-
export type { RegionStats, ImageRegions, ResolvedColors } from 'bookcover-core';
|
|
11
|
-
export { make_blank_form_values, build_schema, curly_quotes, parse_font_family, find_pattern, derive_colors, hex_override_to_hsl, hex_to_hsl, is_dark_color } from 'bookcover-core';
|
|
9
|
+
export { tinted_contrast_text, pick_vivid_tint, synthesize_fill, blend_regions, region_hex, all_image_regions, analyze_pixel_regions, get_builtin_bg, get_builtin_bg_regions, BG_PREVIEW_DIR, VECTOR_BG_AUTO_COLOR, resolve_colors, darken_hsl } from 'bookcover-core';
|
|
10
|
+
export type { RegionStats, ImageRegions, ResolvedColors, BuiltinBg } from 'bookcover-core';
|
|
11
|
+
export { make_blank_form_values, build_schema, curly_quotes, parse_font_family, find_pattern, derive_colors, hex_override_to_hsl, hex_to_hsl, is_dark_color, warn_unknown } from 'bookcover-core';
|
|
12
12
|
export type { FormState, EmbedFormState, CustomFontStyle, DerivedColors } from 'bookcover-core';
|
|
13
13
|
export { resolve_dimensions } from 'bookcover-core';
|
|
14
14
|
export type { GetDimensionsResult, DimensionInputs } from 'bookcover-core';
|
|
@@ -22,6 +22,8 @@ export interface InitOptions {
|
|
|
22
22
|
export interface GenerateOptions {
|
|
23
23
|
schema: unknown;
|
|
24
24
|
image?: Blob;
|
|
25
|
+
image_builtin?: string;
|
|
26
|
+
image_max_dpi?: number;
|
|
25
27
|
format?: OutputFormat;
|
|
26
28
|
ppi?: number;
|
|
27
29
|
split?: boolean;
|
|
@@ -34,12 +36,11 @@ export interface GenerateResult {
|
|
|
34
36
|
}
|
|
35
37
|
/**
|
|
36
38
|
* Sample a background image's dominant colors, under both interpretations at once — see
|
|
37
|
-
* analyze_pixel_regions for what `dims` does.
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* the result back in via GenerateOptions.image_regions).
|
|
39
|
+
* analyze_pixel_regions for what `dims` does. Always decodes via the Canvas API — a builtin
|
|
40
|
+
* background's baked regions come from get_builtin_bg_regions() by its ID instead. Exported so
|
|
41
|
+
* hosts can sample ahead of generate() (e.g. the widget caches this on a debounced watcher,
|
|
42
|
+
* independent of when generate() itself runs, then passes the result back in via
|
|
43
|
+
* GenerateOptions.image_regions).
|
|
43
44
|
*/
|
|
44
45
|
export declare function analyze_image_regions(image: Blob, dims: GetDimensionsResult | null): Promise<ImageRegions>;
|
|
45
46
|
/** Stateful cover generator — each instance owns its own compiler and renderer */
|
package/dist/index.js
CHANGED
|
@@ -8,9 +8,9 @@ import { load_fonts_prefix, font_urls_for as build_font_urls, fetch_font_bytes,
|
|
|
8
8
|
export { font_file_url } from 'typst-fonts/web';
|
|
9
9
|
export { get_fonts, get_bundled_font } from 'typst-fonts';
|
|
10
10
|
export { list_patterns, collect_fonts, collect_all_fonts, default_spine_title, list_vector_backgrounds, find_vector_background, list_builtin_icons, find_builtin_icon, icon_categories, suggested_icons, generate_palette, cover_schema } from 'bookcover-core';
|
|
11
|
-
export { tinted_contrast_text, pick_vivid_tint, synthesize_fill, blend_regions, region_hex, all_image_regions, analyze_pixel_regions, get_builtin_bg_regions, VECTOR_BG_AUTO_COLOR, resolve_colors, darken_hsl } from 'bookcover-core';
|
|
11
|
+
export { tinted_contrast_text, pick_vivid_tint, synthesize_fill, blend_regions, region_hex, all_image_regions, analyze_pixel_regions, get_builtin_bg, get_builtin_bg_regions, BG_PREVIEW_DIR, VECTOR_BG_AUTO_COLOR, resolve_colors, darken_hsl } from 'bookcover-core';
|
|
12
12
|
// Form state + form->schema conversion, so hosts can derive the renderable schema themselves
|
|
13
|
-
export { make_blank_form_values, build_schema, curly_quotes, parse_font_family, find_pattern, derive_colors, hex_override_to_hsl, hex_to_hsl, is_dark_color } from 'bookcover-core';
|
|
13
|
+
export { make_blank_form_values, build_schema, curly_quotes, parse_font_family, find_pattern, derive_colors, hex_override_to_hsl, hex_to_hsl, is_dark_color, warn_unknown } from 'bookcover-core';
|
|
14
14
|
// Dimension resolution, so hosts can locate cover panels (e.g. for background-image analysis)
|
|
15
15
|
export { resolve_dimensions } from 'bookcover-core';
|
|
16
16
|
const decoder = new TextDecoder();
|
|
@@ -31,13 +31,17 @@ function make_canvas(width, height) {
|
|
|
31
31
|
canvas.height = height;
|
|
32
32
|
return canvas;
|
|
33
33
|
}
|
|
34
|
+
/** Encode a canvas' contents as an image Blob of the given type (handles both canvas types) */
|
|
35
|
+
async function canvas_blob(canvas, type, quality) {
|
|
36
|
+
if ('convertToBlob' in canvas)
|
|
37
|
+
return canvas.convertToBlob({ type, quality });
|
|
38
|
+
return new Promise((resolve) => {
|
|
39
|
+
canvas.toBlob((b) => resolve(b), type, quality);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
34
42
|
/** Encode a canvas' contents as PNG bytes (handles both canvas types) */
|
|
35
43
|
async function canvas_png_bytes(canvas) {
|
|
36
|
-
const blob = '
|
|
37
|
-
? await canvas.convertToBlob({ type: 'image/png' })
|
|
38
|
-
: await new Promise((resolve) => {
|
|
39
|
-
canvas.toBlob((b) => resolve(b), 'image/png');
|
|
40
|
-
});
|
|
44
|
+
const blob = await canvas_blob(canvas, 'image/png');
|
|
41
45
|
return new Uint8Array(await blob.arrayBuffer());
|
|
42
46
|
}
|
|
43
47
|
/** Crop a region from a PNG using the Canvas API — used as PngCropFn */
|
|
@@ -53,23 +57,78 @@ async function canvas_crop(data, x, y, w, h) {
|
|
|
53
57
|
// Downscale target (longest edge, px) for a live pixel decode — plenty of resolution for a
|
|
54
58
|
// dominant-color read while keeping the pixel scan cheap
|
|
55
59
|
const IMAGE_REGIONS_MAX_DIM = 400;
|
|
60
|
+
// Resizing is skipped when it would shrink the image by less than this fraction of its size —
|
|
61
|
+
// a near-target image (e.g. a built-in's preview copy, sized for a 6x9 cover) gains nothing from
|
|
62
|
+
// another decode and lossy re-encode
|
|
63
|
+
const MIN_DOWNSCALE = 0.85;
|
|
64
|
+
// The most recent downscaled copy (see downscale_image), so repeat previews of one image at one
|
|
65
|
+
// size skip the decode + re-encode. Just one entry: the target size shifts with every page count
|
|
66
|
+
// or trim edit, and older copies would otherwise pile up
|
|
67
|
+
let downscaled = null;
|
|
68
|
+
/** Identify an image across structured clones. A File posted to a worker arrives as a new object
|
|
69
|
+
* on every message, so identity alone would never match; its name, size, type and modified time
|
|
70
|
+
* together do. A plain Blob carries none of that beyond size/type, so it's matched by identity */
|
|
71
|
+
function image_key(image) {
|
|
72
|
+
if (!(image instanceof File))
|
|
73
|
+
return null;
|
|
74
|
+
return `${image.name}:${image.size}:${image.type}:${image.lastModified}`;
|
|
75
|
+
}
|
|
76
|
+
/** Shrink an image to the smallest size that still covers target_w x target_h — the templates
|
|
77
|
+
* place background images with fit "cover", so the LARGER of the two scale factors is the one
|
|
78
|
+
* that keeps every covered pixel. Never enlarges, and returns the image itself when it's already
|
|
79
|
+
* small enough or near enough (see MIN_DOWNSCALE). PNGs stay PNG so transparency survives;
|
|
80
|
+
* anything else is re-encoded as JPEG */
|
|
81
|
+
async function resize_to_cover(image, target_w, target_h) {
|
|
82
|
+
const bitmap = await createImageBitmap(image);
|
|
83
|
+
const scale = Math.max(target_w / bitmap.width, target_h / bitmap.height);
|
|
84
|
+
if (scale >= MIN_DOWNSCALE) {
|
|
85
|
+
bitmap.close();
|
|
86
|
+
return image;
|
|
87
|
+
}
|
|
88
|
+
const w = Math.max(1, Math.round(bitmap.width * scale));
|
|
89
|
+
const h = Math.max(1, Math.round(bitmap.height * scale));
|
|
90
|
+
const canvas = make_canvas(w, h);
|
|
91
|
+
const ctx = canvas.getContext('2d');
|
|
92
|
+
if (!ctx)
|
|
93
|
+
throw new Error('[generator-web] 2D canvas context unavailable');
|
|
94
|
+
ctx.drawImage(bitmap, 0, 0, w, h);
|
|
95
|
+
bitmap.close();
|
|
96
|
+
const type = image.type === 'image/png' ? 'image/png' : 'image/jpeg';
|
|
97
|
+
return canvas_blob(canvas, type, 0.85);
|
|
98
|
+
}
|
|
99
|
+
/** A copy of a background image with at most max_dpi pixels per inch across the full cover
|
|
100
|
+
* spread (see GenerateOptions.image_max_dpi), reusing the last one when nothing changed */
|
|
101
|
+
function downscale_image(image, dims, max_dpi) {
|
|
102
|
+
const target_w = Math.round(dims.cover_total_width.toNumber() / 25.4 * max_dpi);
|
|
103
|
+
const target_h = Math.round(dims.cover_total_height.toNumber() / 25.4 * max_dpi);
|
|
104
|
+
const size_key = `${target_w}x${target_h}`;
|
|
105
|
+
const key = image_key(image) ?? '';
|
|
106
|
+
const same_image = downscaled !== null
|
|
107
|
+
&& (downscaled.source === image || (key !== '' && downscaled.image_key === key));
|
|
108
|
+
if (downscaled && same_image && downscaled.size_key === size_key)
|
|
109
|
+
return downscaled.blob;
|
|
110
|
+
// Cache the promise so concurrent calls share one resize, but drop it if the resize fails
|
|
111
|
+
const blob = resize_to_cover(image, target_w, target_h);
|
|
112
|
+
const entry = { image_key: key, source: image, size_key, blob };
|
|
113
|
+
downscaled = entry;
|
|
114
|
+
blob.catch(() => {
|
|
115
|
+
if (downscaled === entry)
|
|
116
|
+
downscaled = null;
|
|
117
|
+
});
|
|
118
|
+
return blob;
|
|
119
|
+
}
|
|
56
120
|
// How many generate() calls to allow between forced memory resets, as a fallback for whatever
|
|
57
121
|
// slow growth an image-identity change alone wouldn't catch — see reset_memory_if_needed
|
|
58
122
|
const MEMORY_RESET_INTERVAL = 25;
|
|
59
123
|
/**
|
|
60
124
|
* Sample a background image's dominant colors, under both interpretations at once — see
|
|
61
|
-
* analyze_pixel_regions for what `dims` does.
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* the result back in via GenerateOptions.image_regions).
|
|
125
|
+
* analyze_pixel_regions for what `dims` does. Always decodes via the Canvas API — a builtin
|
|
126
|
+
* background's baked regions come from get_builtin_bg_regions() by its ID instead. Exported so
|
|
127
|
+
* hosts can sample ahead of generate() (e.g. the widget caches this on a debounced watcher,
|
|
128
|
+
* independent of when generate() itself runs, then passes the result back in via
|
|
129
|
+
* GenerateOptions.image_regions).
|
|
67
130
|
*/
|
|
68
131
|
export async function analyze_image_regions(image, dims) {
|
|
69
|
-
const filename = 'name' in image ? image.name : undefined;
|
|
70
|
-
const builtin = filename ? get_builtin_bg_regions(filename, image.size) : null;
|
|
71
|
-
if (builtin)
|
|
72
|
-
return builtin;
|
|
73
132
|
const bitmap = await createImageBitmap(image);
|
|
74
133
|
const scale = Math.min(1, IMAGE_REGIONS_MAX_DIM / Math.max(bitmap.width, bitmap.height));
|
|
75
134
|
const w = Math.max(1, Math.round(bitmap.width * scale));
|
|
@@ -314,10 +373,16 @@ export class CoverGenerator {
|
|
|
314
373
|
}
|
|
315
374
|
// Bound the compiler/renderer's WASM memory growth — see reset_memory_if_needed
|
|
316
375
|
await this.reset_memory_if_needed(options.image);
|
|
376
|
+
// The image actually compiled — a downscaled copy for previews (see image_max_dpi), which
|
|
377
|
+
// only ever feeds build(); colors and the memory-reset check above use the original
|
|
378
|
+
const cover_dims = resolve_dimensions(parsed);
|
|
379
|
+
const render_image = options.image && options.image_max_dpi
|
|
380
|
+
? await downscale_image(options.image, cover_dims, options.image_max_dpi)
|
|
381
|
+
: options.image;
|
|
317
382
|
// Convert Blob to ImageInput (Uint8Array + extension) for the core builder
|
|
318
383
|
let image_input;
|
|
319
|
-
if (
|
|
320
|
-
const blob =
|
|
384
|
+
if (render_image) {
|
|
385
|
+
const blob = render_image;
|
|
321
386
|
const ext = '.' + (blob.type.split('/')[1] ?? 'jpg').replace('jpeg', 'jpg');
|
|
322
387
|
image_input = { data: new Uint8Array(await blob.arrayBuffer()), ext };
|
|
323
388
|
}
|
|
@@ -329,16 +394,21 @@ export class CoverGenerator {
|
|
|
329
394
|
frame_blob = await resp.blob();
|
|
330
395
|
}
|
|
331
396
|
// Colors sampled from the background image, for auto text/blurb/spine coloring. A
|
|
332
|
-
// caller-supplied value (e.g. the widget's own debounced cache) is used as-is
|
|
333
|
-
// this samples `image` itself (see
|
|
334
|
-
// failure here would otherwise take
|
|
397
|
+
// caller-supplied value (e.g. the widget's own debounced cache) is used as-is, then a
|
|
398
|
+
// named builtin's baked regions; otherwise this samples the original `image` itself (see
|
|
399
|
+
// analyze_image_regions) — best-effort, since a decode failure here would otherwise take
|
|
400
|
+
// down an unrelated generate() call.
|
|
401
|
+
const builtin_regions = options.image_builtin
|
|
402
|
+
? get_builtin_bg_regions(options.image_builtin) : null;
|
|
335
403
|
let image_regions;
|
|
336
404
|
if (options.image_regions !== undefined) {
|
|
337
405
|
image_regions = options.image_regions;
|
|
338
406
|
}
|
|
407
|
+
else if (options.image && builtin_regions) {
|
|
408
|
+
image_regions = builtin_regions;
|
|
409
|
+
}
|
|
339
410
|
else if (options.image) {
|
|
340
|
-
image_regions = await analyze_image_regions(options.image,
|
|
341
|
-
.catch(() => null);
|
|
411
|
+
image_regions = await analyze_image_regions(options.image, cover_dims).catch(() => null);
|
|
342
412
|
}
|
|
343
413
|
else {
|
|
344
414
|
image_regions = null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bookcover-web",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"license": "MIT-0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@myriaddreamin/typst-ts-renderer": "0.7.0",
|
|
40
40
|
"@myriaddreamin/typst-ts-web-compiler": "0.7.0",
|
|
41
41
|
"@myriaddreamin/typst.ts": "0.7.0",
|
|
42
|
-
"bookcover-core": "^0.
|
|
42
|
+
"bookcover-core": "^0.20.0",
|
|
43
43
|
"typst-fonts": "^0.1.0"
|
|
44
44
|
},
|
|
45
45
|
"sideEffects": false
|