wasm-html-to-image 1.0.9 → 1.0.11
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 +1 -1
- package/dist/cli-options.d.ts +4 -0
- package/dist/cli-options.js +2 -0
- package/dist/cli.js +4 -0
- package/dist/core-orchestrate.d.ts +7 -0
- package/dist/core-orchestrate.js +25 -3
- package/dist/core.d.ts +35 -0
- package/dist/core.js +1 -0
- package/dist/encode-args.d.ts +1 -0
- package/dist/encode-args.js +1 -0
- package/dist/html-to-image-wasm.js +3 -3
- package/dist/html-to-image.wasm +0 -0
- package/dist/loader.d.ts +4 -0
- package/dist/single.d.ts +1 -1
- package/dist/web-workers.js +20 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -123,7 +123,7 @@ npx wasm-html-to-image loader.html -o loader.gif -f gif --animation 2000
|
|
|
123
123
|
### Outputs (`format`)
|
|
124
124
|
`png`, `jpeg`, `webp`, `avif`, `jxl`, `apng`, `gif`, `tiff`, `svg`, `pdf`, `thumbhash`, `raw` (uncompressed RGBA).
|
|
125
125
|
|
|
126
|
-
**Print colors:** `format: "tiff"` keeps RGBA; add `colorSpace: "cmyk"` (CLI: `--color-space cmyk`) to get a CMYK TIFF, or a PDF whose colors and images are DeviceCMYK. The conversion is the plain device formula (no ICC profile), so convert with your print shop's profile if you need exact press colors.
|
|
126
|
+
**Print colors:** `format: "tiff"` keeps RGBA; add `colorSpace: "cmyk"` (CLI: `--color-space cmyk`) to get a CMYK TIFF, or a PDF whose colors and images are DeviceCMYK. The conversion is the plain device formula (no ICC profile), so convert with your print shop's profile if you need exact press colors. For print resolution, add `scale` (CLI: `--scale`): an 8.33in-wide page at 300 dpi is `width: 800, scale: 3.125`, and the TIFF records `96 × scale` ppi (override with `dpi`).
|
|
127
127
|
|
|
128
128
|
Every call returns a `RenderResult` object:
|
|
129
129
|
```typescript
|
package/dist/cli-options.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ export interface CliFlags {
|
|
|
10
10
|
baseUrl?: string;
|
|
11
11
|
/** `--color-space <srgb|cmyk>`; absent means the default (srgb). */
|
|
12
12
|
colorSpace?: "srgb" | "cmyk";
|
|
13
|
+
/** `--scale <n>`; absent means 1. */
|
|
14
|
+
scale?: number;
|
|
15
|
+
/** `--dpi <n>`; absent means 96 x scale. */
|
|
16
|
+
dpi?: number;
|
|
13
17
|
/** `--animation <duration-ms>`; absent means "no keyframe animation". */
|
|
14
18
|
animation?: number;
|
|
15
19
|
/** `--animation-fps <n>`; defaults to 15 when `--animation` is set. */
|
package/dist/cli-options.js
CHANGED
|
@@ -86,6 +86,8 @@ export async function buildRenderOptions(input, flags) {
|
|
|
86
86
|
...source,
|
|
87
87
|
...(flags.baseUrl !== undefined ? { baseUrl: flags.baseUrl } : {}),
|
|
88
88
|
...(flags.colorSpace !== undefined ? { colorSpace: flags.colorSpace } : {}),
|
|
89
|
+
...(flags.scale !== undefined ? { scale: flags.scale } : {}),
|
|
90
|
+
...(flags.dpi !== undefined ? { dpi: flags.dpi } : {}),
|
|
89
91
|
...(animation ? { animation } : {}),
|
|
90
92
|
};
|
|
91
93
|
}
|
package/dist/cli.js
CHANGED
|
@@ -34,6 +34,8 @@ program
|
|
|
34
34
|
.option("-q, --quality <number>", "encode quality 0-100 (encode stage only)", (v) => parseFloat(v), 85)
|
|
35
35
|
.option("--base-url <url>", "base URL for relative resources (URL input: defaults to the post-redirect document URL; file input: defaults to the file's directory)")
|
|
36
36
|
.option("--color-space <srgb|cmyk>", "output color space of tiff and pdf (default srgb; cmyk rewrites colors for print, with no ICC profile)", (v) => v.toLowerCase())
|
|
37
|
+
.option("--scale <n>", "pixel density of raster output from HTML (default 1; 3.125 draws an 800px layout as 2500px)", (v) => parseFloat(v))
|
|
38
|
+
.option("--dpi <n>", "resolution written to tiff output, in pixels per inch (default 96 x scale)", (v) => parseInt(v, 10))
|
|
37
39
|
.option("--animation <duration-ms>", "sample the document's @keyframes opacity / transform / color animation into an animated WebP / APNG / GIF; one loop duration in ms (webp, apng and gif output only; the CSS animation-duration wins and this value is an upper bound)", (v) => parseInt(v, 10))
|
|
38
40
|
.option("--animation-fps <n>", "animation frame rate (default 15, capped at ~75 frames total; requires --animation)", (v) => parseInt(v, 10))
|
|
39
41
|
.option("--animation-loop <n>", "how many times the animated WebP / APNG / GIF repeats (0 = infinite, the default; 1 = play once; requires --animation)", (v) => parseInt(v, 10))
|
|
@@ -70,6 +72,8 @@ program
|
|
|
70
72
|
quality: options.quality,
|
|
71
73
|
baseUrl: options.baseUrl,
|
|
72
74
|
colorSpace: options.colorSpace,
|
|
75
|
+
scale: options.scale,
|
|
76
|
+
dpi: options.dpi,
|
|
73
77
|
animation: options.animation,
|
|
74
78
|
animationFps: options.animationFps,
|
|
75
79
|
animationLoop: options.animationLoop,
|
|
@@ -42,6 +42,7 @@ export interface Orchestration {
|
|
|
42
42
|
onLog: HtmlToImageOptions["onLog"];
|
|
43
43
|
diagnostics: boolean;
|
|
44
44
|
onDiagnostics: HtmlToImageOptions["onDiagnostics"];
|
|
45
|
+
onLayout: HtmlToImageOptions["onLayout"];
|
|
45
46
|
resolveResource: ResolveResourceHook | undefined;
|
|
46
47
|
mediaType: "screen" | "print";
|
|
47
48
|
textToPaths: boolean | undefined;
|
|
@@ -63,6 +64,12 @@ export interface Orchestration {
|
|
|
63
64
|
fmtInt: number;
|
|
64
65
|
/** Output color space of `tiff` / `pdf`. */
|
|
65
66
|
colorSpace: "srgb" | "cmyk";
|
|
67
|
+
/** Raster pixel density of HTML input (1 = CSS px). */
|
|
68
|
+
scale: number;
|
|
69
|
+
/** TIFF resolution tag in pixels per inch (image input: 96 unless set). */
|
|
70
|
+
dpi: number;
|
|
71
|
+
/** The caller's explicit `dpi` (image input has no `scale`, so it falls back to 96). */
|
|
72
|
+
dpiOption: number | undefined;
|
|
66
73
|
mediaTypeInt: number;
|
|
67
74
|
t0: number;
|
|
68
75
|
diag: DiagState | null;
|
package/dist/core-orchestrate.js
CHANGED
|
@@ -34,7 +34,7 @@ export function defaultSpeed(format) {
|
|
|
34
34
|
}
|
|
35
35
|
/** Build per-call orchestration state (log gate, timings, diag, resolve ctx). */
|
|
36
36
|
export function createOrchestration(module, options) {
|
|
37
|
-
const { format = "png", quality = 85, speed: speedOption, width, height, crop, fit, value, url, baseUrl, fontMap, userAgent, fallbackFonts, logLevel = LogLevel.None, onLog, diagnostics = false, onDiagnostics, resolveResource, mediaType = "screen", textToPaths, css, fonts: preloadFonts, limits = {}, pdfTitle, pdfAuthor, pdfSubject, pdfKeywords, pdfCreator, pdfProducer, pdfMargin, pdfHeader, pdfFooter, } = options;
|
|
37
|
+
const { format = "png", quality = 85, speed: speedOption, width, height, crop, fit, value, url, baseUrl, fontMap, userAgent, fallbackFonts, logLevel = LogLevel.None, onLog, diagnostics = false, onDiagnostics, onLayout, resolveResource, mediaType = "screen", textToPaths, css, fonts: preloadFonts, limits = {}, pdfTitle, pdfAuthor, pdfSubject, pdfKeywords, pdfCreator, pdfProducer, pdfMargin, pdfHeader, pdfFooter, } = options;
|
|
38
38
|
// `animation` accepts a boolean (image input: keep frames) or, for HTML
|
|
39
39
|
// input, an object requesting keyframe sampling. The object form turns on
|
|
40
40
|
// the boolean flag for the encode stage and enables the frame path.
|
|
@@ -47,6 +47,16 @@ export function createOrchestration(module, options) {
|
|
|
47
47
|
const animationLoop = Math.max(0, Math.trunc(animationFrames?.loop ?? 0));
|
|
48
48
|
const speed = speedOption ?? defaultSpeed(format);
|
|
49
49
|
const colorSpace = options.colorSpace === "cmyk" ? "cmyk" : "srgb";
|
|
50
|
+
const scale = options.scale ?? 1;
|
|
51
|
+
if (!Number.isFinite(scale) || scale <= 0 || scale > 16) {
|
|
52
|
+
throw new Error(`wasm-html-to-image: scale must be a number in (0, 16], got ${String(options.scale)}`);
|
|
53
|
+
}
|
|
54
|
+
if (options.dpi !== undefined &&
|
|
55
|
+
(!Number.isFinite(options.dpi) || options.dpi < 1 || options.dpi > 65535)) {
|
|
56
|
+
throw new Error(`wasm-html-to-image: dpi must be a number in [1, 65535], got ${String(options.dpi)}`);
|
|
57
|
+
}
|
|
58
|
+
// One CSS px is 1/96 in, so a scaled HTML render carries 96 * scale ppi by default.
|
|
59
|
+
const dpi = Math.round(options.dpi ?? 96 * scale);
|
|
50
60
|
const fmtInt = format === "tiff" && colorSpace === "cmyk"
|
|
51
61
|
? TIFF_CMYK_INT
|
|
52
62
|
: FORMAT_INT[format];
|
|
@@ -157,6 +167,7 @@ export function createOrchestration(module, options) {
|
|
|
157
167
|
onLog,
|
|
158
168
|
diagnostics,
|
|
159
169
|
onDiagnostics,
|
|
170
|
+
onLayout,
|
|
160
171
|
resolveResource,
|
|
161
172
|
mediaType,
|
|
162
173
|
textToPaths,
|
|
@@ -174,6 +185,9 @@ export function createOrchestration(module, options) {
|
|
|
174
185
|
pdfFooter,
|
|
175
186
|
fmtInt,
|
|
176
187
|
colorSpace,
|
|
188
|
+
scale,
|
|
189
|
+
dpi,
|
|
190
|
+
dpiOption: options.dpi,
|
|
177
191
|
mediaTypeInt,
|
|
178
192
|
t0,
|
|
179
193
|
diag,
|
|
@@ -248,7 +262,7 @@ export async function resolveHtmlSource(orch) {
|
|
|
248
262
|
* `imageBytesFromUnknown` (`unknown` in, no `as` casts).
|
|
249
263
|
*/
|
|
250
264
|
export async function runImageInput(module, value, orch) {
|
|
251
|
-
const { format, quality, speed, animation, animationLoop, width, height, crop, fit, fmtInt, colorSpace, t0, emitLog, logged, loggedBinding, addTime, deliverReport, } = orch;
|
|
265
|
+
const { format, quality, speed, animation, animationLoop, width, height, crop, fit, fmtInt, colorSpace, dpiOption, t0, emitLog, logged, loggedBinding, addTime, deliverReport, } = orch;
|
|
252
266
|
// Narrowed here: caller selected this branch via `isImageInput(value)`.
|
|
253
267
|
const loadImage = module.converter_load_image;
|
|
254
268
|
if (typeof loadImage !== "function")
|
|
@@ -343,6 +357,8 @@ export async function runImageInput(module, value, orch) {
|
|
|
343
357
|
const encodeBinding = module.converter_encode;
|
|
344
358
|
if (typeof encodeBinding !== "function")
|
|
345
359
|
throw loggedBinding("converter_encode");
|
|
360
|
+
if (format === "tiff")
|
|
361
|
+
module.converter_set_dpi?.(inst, Math.round(dpiOption ?? 96));
|
|
346
362
|
const encodeStart = now();
|
|
347
363
|
const out = (await encodeBinding(inst, fmtInt, quality, speed, animation, animationLoop));
|
|
348
364
|
addTime("encode", now() - encodeStart);
|
|
@@ -376,7 +392,7 @@ export async function runImageInput(module, value, orch) {
|
|
|
376
392
|
* pre-resolved resources, so it stays a fallback only).
|
|
377
393
|
*/
|
|
378
394
|
export async function tryRunHtmlInstance(module, htmls, satoruOpts, defaultFontMap, orch) {
|
|
379
|
-
const { format, quality, speed, animation, animationFrames, animationLoop, width, height, baseUrl, fontMap, userAgent, fallbackFonts, fmtInt, t0, emitLog, logged, loggedBinding, addTime, deliverReport, resolveCtx, } = orch;
|
|
395
|
+
const { format, quality, speed, animation, animationFrames, animationLoop, width, height, baseUrl, fontMap, userAgent, fallbackFonts, fmtInt, dpi, t0, emitLog, logged, loggedBinding, addTime, deliverReport, resolveCtx, } = orch;
|
|
380
396
|
const renderBinding = module.satoru_render;
|
|
381
397
|
const encodeBinding = module.converter_encode;
|
|
382
398
|
if (typeof renderBinding !== "function" ||
|
|
@@ -392,6 +408,10 @@ export async function tryRunHtmlInstance(module, htmls, satoruOpts, defaultFontM
|
|
|
392
408
|
await resolveHtmlResources(module, sInst, htmls, width, height, baseUrl, fontMap ?? defaultFontMap, userAgent ?? DEFAULT_USER_AGENT, fallbackFonts, resolveCtx);
|
|
393
409
|
addTime("resolveResources", now() - resolveStart);
|
|
394
410
|
emitLog(LogLevel.Info, "resources resolved");
|
|
411
|
+
if (orch.onLayout && typeof module.satoru_layout_json === "function") {
|
|
412
|
+
const page = Array.isArray(htmls) ? htmls[0] : htmls;
|
|
413
|
+
orch.onLayout(JSON.parse(module.satoru_layout_json(sInst, page, width, height ?? 0, orch.mediaTypeInt)));
|
|
414
|
+
}
|
|
395
415
|
if (format === "svg" || format === "pdf") {
|
|
396
416
|
const renderStart = now();
|
|
397
417
|
const out = (await renderBinding(sInst, htmls, width, height ?? 0, fmtInt, satoruOpts));
|
|
@@ -458,6 +478,8 @@ export async function tryRunHtmlInstance(module, htmls, satoruOpts, defaultFontM
|
|
|
458
478
|
}
|
|
459
479
|
const outWidth = module.converter_get_width?.(cInst) ?? width;
|
|
460
480
|
const outHeight = module.converter_get_height?.(cInst) ?? height ?? 0;
|
|
481
|
+
if (format === "tiff")
|
|
482
|
+
module.converter_set_dpi?.(cInst, dpi);
|
|
461
483
|
const encodeStart = now();
|
|
462
484
|
const out = (await encodeBinding(cInst, fmtInt, quality, speed, animation, animationLoop));
|
|
463
485
|
addTime("encode", now() - encodeStart);
|
package/dist/core.d.ts
CHANGED
|
@@ -60,6 +60,21 @@ export interface RenderOptions {
|
|
|
60
60
|
* - Image input: render stage is skipped; `width`/`height`/`crop`/`fit`
|
|
61
61
|
* are applied as output resize on the encode stage.
|
|
62
62
|
*/
|
|
63
|
+
/** One element's border box from `onLayout` (page coordinates, CSS px). */
|
|
64
|
+
export interface LayoutBox {
|
|
65
|
+
/** Path of element-child indexes from the root, e.g. "0.1.3". */
|
|
66
|
+
p: string;
|
|
67
|
+
/** Lower-case tag name. */
|
|
68
|
+
t: string;
|
|
69
|
+
/** `id` attribute ("" when absent). */
|
|
70
|
+
i: string;
|
|
71
|
+
/** `class` attribute, truncated ("" when absent). */
|
|
72
|
+
c: string;
|
|
73
|
+
x: number;
|
|
74
|
+
y: number;
|
|
75
|
+
w: number;
|
|
76
|
+
h: number;
|
|
77
|
+
}
|
|
63
78
|
export interface HtmlToImageOptions extends RenderOptions {
|
|
64
79
|
/** Encode quality 0-100 (encode stage only, default 85; ignored for svg/pdf). */
|
|
65
80
|
quality?: number;
|
|
@@ -127,6 +142,13 @@ export interface HtmlToImageOptions extends RenderOptions {
|
|
|
127
142
|
diagnostics?: boolean;
|
|
128
143
|
/** Receives the diagnostics report when `diagnostics` is enabled. */
|
|
129
144
|
onDiagnostics?: (report: RenderDiagnostics) => void;
|
|
145
|
+
/**
|
|
146
|
+
* Debug aid for comparing against a reference browser: receives every laid-out element's border
|
|
147
|
+
* box in page coordinates (HTML input only; `html-to-image` instance pipeline). `p` is the
|
|
148
|
+
* element's path among element children of the shadow-flattened tree ("0.1.3"), so a browser
|
|
149
|
+
* walk produces the same keys. Inline boxes are omitted except replaced ones (img, svg, ...).
|
|
150
|
+
*/
|
|
151
|
+
onLayout?: (boxes: LayoutBox[]) => void;
|
|
130
152
|
/**
|
|
131
153
|
* Override hook for resource resolution, applied to every fetch path
|
|
132
154
|
* (discovery fetches, fallback-font URLs, HTML `url` fetch). Return
|
|
@@ -146,6 +168,19 @@ export interface HtmlToImageOptions extends RenderOptions {
|
|
|
146
168
|
}[];
|
|
147
169
|
/** Safety/performance limits, enforced in JS around resource resolution. */
|
|
148
170
|
limits?: RenderLimits;
|
|
171
|
+
/**
|
|
172
|
+
* Pixel density of raster output from HTML input (default `1`). The page is laid out at
|
|
173
|
+
* `width` CSS px and drawn `scale` times larger, so `scale: 3.125` turns an 800px layout
|
|
174
|
+
* into a 2500px bitmap (300 dpi for an 8.33in page) with text and shapes re-rasterized
|
|
175
|
+
* sharply. `width` / `height` / `crop` stay in CSS px; the result's `width` / `height` are
|
|
176
|
+
* the bitmap size. Range 0-16; ignored for `svg` / `pdf` and for image input.
|
|
177
|
+
*/
|
|
178
|
+
scale?: number;
|
|
179
|
+
/**
|
|
180
|
+
* Resolution written to the `tiff` file, in pixels per inch (default `96 * scale`, rounded:
|
|
181
|
+
* one CSS px is 1/96 in). A print shop reads the physical size from it.
|
|
182
|
+
*/
|
|
183
|
+
dpi?: number;
|
|
149
184
|
/**
|
|
150
185
|
* Output color space of `tiff` and `pdf` (default `"srgb"`).
|
|
151
186
|
*
|
package/dist/core.js
CHANGED
|
@@ -47,6 +47,7 @@ export async function htmlToImage(module, options) {
|
|
|
47
47
|
textToPaths: orch.textToPaths,
|
|
48
48
|
mediaTypeInt: orch.mediaTypeInt,
|
|
49
49
|
colorSpace: orch.colorSpace,
|
|
50
|
+
scale: orch.scale,
|
|
50
51
|
pdfTitle: orch.pdfTitle,
|
|
51
52
|
pdfAuthor: orch.pdfAuthor,
|
|
52
53
|
pdfSubject: orch.pdfSubject,
|
package/dist/encode-args.d.ts
CHANGED
package/dist/encode-args.js
CHANGED
|
@@ -45,6 +45,7 @@ export function buildFullSatoruOptions(input) {
|
|
|
45
45
|
svgTextToPaths: textToPaths ?? true,
|
|
46
46
|
mediaType: mediaTypeInt,
|
|
47
47
|
...(input.colorSpace === "cmyk" ? { colorSpace: 1 } : {}),
|
|
48
|
+
...(input.scale !== undefined && input.scale !== 1 ? { scale: input.scale } : {}),
|
|
48
49
|
pdfTitle: pdfTitle ?? "",
|
|
49
50
|
pdfAuthor: pdfAuthor ?? "",
|
|
50
51
|
pdfSubject: pdfSubject ?? "",
|