wasm-html-to-image 1.0.8 → 1.0.10
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 +3 -1
- package/dist/cli-options.d.ts +6 -0
- package/dist/cli-options.js +3 -0
- package/dist/cli.js +13 -0
- package/dist/core-orchestrate.d.ts +9 -1
- package/dist/core-orchestrate.js +28 -6
- package/dist/core.d.ts +25 -1
- package/dist/core.js +2 -0
- package/dist/encode-args.d.ts +4 -0
- package/dist/encode-args.js +5 -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/web-workers.js +23 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -121,7 +121,9 @@ npx wasm-html-to-image loader.html -o loader.gif -f gif --animation 2000
|
|
|
121
121
|
- **Image Buffer / Data URL**: `PNG`, `JPEG`, `WebP`, `GIF`, `AVIF`, `JXL`, `BMP`.
|
|
122
122
|
|
|
123
123
|
### Outputs (`format`)
|
|
124
|
-
`png`, `jpeg`, `webp`, `avif`, `jxl`, `apng`, `gif`, `svg`, `pdf`, `thumbhash`, `raw` (uncompressed RGBA).
|
|
124
|
+
`png`, `jpeg`, `webp`, `avif`, `jxl`, `apng`, `gif`, `tiff`, `svg`, `pdf`, `thumbhash`, `raw` (uncompressed RGBA).
|
|
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. 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`).
|
|
125
127
|
|
|
126
128
|
Every call returns a `RenderResult` object:
|
|
127
129
|
```typescript
|
package/dist/cli-options.d.ts
CHANGED
|
@@ -8,6 +8,12 @@ export interface CliFlags {
|
|
|
8
8
|
quality: number;
|
|
9
9
|
/** Explicit `--base-url`; absent means "let the source decide". */
|
|
10
10
|
baseUrl?: string;
|
|
11
|
+
/** `--color-space <srgb|cmyk>`; absent means the default (srgb). */
|
|
12
|
+
colorSpace?: "srgb" | "cmyk";
|
|
13
|
+
/** `--scale <n>`; absent means 1. */
|
|
14
|
+
scale?: number;
|
|
15
|
+
/** `--dpi <n>`; absent means 96 x scale. */
|
|
16
|
+
dpi?: number;
|
|
11
17
|
/** `--animation <duration-ms>`; absent means "no keyframe animation". */
|
|
12
18
|
animation?: number;
|
|
13
19
|
/** `--animation-fps <n>`; defaults to 15 when `--animation` is set. */
|
package/dist/cli-options.js
CHANGED
|
@@ -85,6 +85,9 @@ export async function buildRenderOptions(input, flags) {
|
|
|
85
85
|
quality: flags.quality,
|
|
86
86
|
...source,
|
|
87
87
|
...(flags.baseUrl !== undefined ? { baseUrl: flags.baseUrl } : {}),
|
|
88
|
+
...(flags.colorSpace !== undefined ? { colorSpace: flags.colorSpace } : {}),
|
|
89
|
+
...(flags.scale !== undefined ? { scale: flags.scale } : {}),
|
|
90
|
+
...(flags.dpi !== undefined ? { dpi: flags.dpi } : {}),
|
|
88
91
|
...(animation ? { animation } : {}),
|
|
89
92
|
};
|
|
90
93
|
}
|
package/dist/cli.js
CHANGED
|
@@ -12,6 +12,7 @@ const OUTPUT_FORMATS = [
|
|
|
12
12
|
"webp",
|
|
13
13
|
"apng",
|
|
14
14
|
"gif",
|
|
15
|
+
"tiff",
|
|
15
16
|
"avif",
|
|
16
17
|
"jxl",
|
|
17
18
|
"raw",
|
|
@@ -32,6 +33,9 @@ program
|
|
|
32
33
|
.option("-f, --format <format>", `output format (${OUTPUT_FORMATS.join(", ")})`, "png")
|
|
33
34
|
.option("-q, --quality <number>", "encode quality 0-100 (encode stage only)", (v) => parseFloat(v), 85)
|
|
34
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
|
+
.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))
|
|
35
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))
|
|
36
40
|
.option("--animation-fps <n>", "animation frame rate (default 15, capped at ~75 frames total; requires --animation)", (v) => parseInt(v, 10))
|
|
37
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))
|
|
@@ -42,6 +46,12 @@ program
|
|
|
42
46
|
console.error(`Error: unsupported format "${options.format}". Choose from: ${OUTPUT_FORMATS.join(", ")}`);
|
|
43
47
|
process.exit(1);
|
|
44
48
|
}
|
|
49
|
+
if (options.colorSpace !== undefined &&
|
|
50
|
+
options.colorSpace !== "srgb" &&
|
|
51
|
+
options.colorSpace !== "cmyk") {
|
|
52
|
+
console.error(`Error: unsupported color space "${options.colorSpace}". Choose from: srgb, cmyk`);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
45
55
|
const isUrl = isUrlInput(input);
|
|
46
56
|
let outputPath = options.output;
|
|
47
57
|
if (!outputPath) {
|
|
@@ -61,6 +71,9 @@ program
|
|
|
61
71
|
format,
|
|
62
72
|
quality: options.quality,
|
|
63
73
|
baseUrl: options.baseUrl,
|
|
74
|
+
colorSpace: options.colorSpace,
|
|
75
|
+
scale: options.scale,
|
|
76
|
+
dpi: options.dpi,
|
|
64
77
|
animation: options.animation,
|
|
65
78
|
animationFps: options.animationFps,
|
|
66
79
|
animationLoop: options.animationLoop,
|
|
@@ -5,7 +5,7 @@ import type { HtmlToImageOptions, OutputFormat, RenderResult } from "./core.js";
|
|
|
5
5
|
declare const DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
|
|
6
6
|
export { DEFAULT_USER_AGENT };
|
|
7
7
|
/** Binding names reported by {@link requireBindingError}. */
|
|
8
|
-
export type WasmBindingName = "satoru_render" | "converter_encode" | "converter_encode_svg" | "converter_encode_pdf" | "converter_load_image" | "html_to_image";
|
|
8
|
+
export type WasmBindingName = "satoru_render" | "converter_encode" | "converter_encode_svg" | "converter_encode_pdf" | "converter_encode_pdf_cmyk" | "converter_load_image" | "html_to_image";
|
|
9
9
|
export declare function requireBindingError(name: WasmBindingName): Error;
|
|
10
10
|
/** Mutable orchestration state for one `htmlToImage` call. */
|
|
11
11
|
export interface Orchestration {
|
|
@@ -61,6 +61,14 @@ export interface Orchestration {
|
|
|
61
61
|
pdfHeader: string | undefined;
|
|
62
62
|
pdfFooter: string | undefined;
|
|
63
63
|
fmtInt: number;
|
|
64
|
+
/** Output color space of `tiff` / `pdf`. */
|
|
65
|
+
colorSpace: "srgb" | "cmyk";
|
|
66
|
+
/** Raster pixel density of HTML input (1 = CSS px). */
|
|
67
|
+
scale: number;
|
|
68
|
+
/** TIFF resolution tag in pixels per inch (image input: 96 unless set). */
|
|
69
|
+
dpi: number;
|
|
70
|
+
/** The caller's explicit `dpi` (image input has no `scale`, so it falls back to 96). */
|
|
71
|
+
dpiOption: number | undefined;
|
|
64
72
|
mediaTypeInt: number;
|
|
65
73
|
t0: number;
|
|
66
74
|
diag: DiagState | null;
|
package/dist/core-orchestrate.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* satoru option objects come from `./encode-args.ts` (canonical builder).
|
|
12
12
|
*/
|
|
13
13
|
import { pdfFirstPageSize, svgRootHeight } from "./output-size.js";
|
|
14
|
-
import { FIT_INT, FORMAT_INT } from "./encode-args.js";
|
|
14
|
+
import { FIT_INT, FORMAT_INT, TIFF_CMYK_INT } from "./encode-args.js";
|
|
15
15
|
import { imageBytesFromUnknown, toBytes, } from "./input.js";
|
|
16
16
|
import { DIAGNOSTIC_CODES, LogLevel, } from "./diagnostics.js";
|
|
17
17
|
import { isNodeRuntime, now, resolveHtmlResources, } from "./core-resources.js";
|
|
@@ -46,7 +46,20 @@ export function createOrchestration(module, options) {
|
|
|
46
46
|
// Loop count of the produced animated WebP (0 = infinite = legacy default).
|
|
47
47
|
const animationLoop = Math.max(0, Math.trunc(animationFrames?.loop ?? 0));
|
|
48
48
|
const speed = speedOption ?? defaultSpeed(format);
|
|
49
|
-
const
|
|
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);
|
|
60
|
+
const fmtInt = format === "tiff" && colorSpace === "cmyk"
|
|
61
|
+
? TIFF_CMYK_INT
|
|
62
|
+
: FORMAT_INT[format];
|
|
50
63
|
const mediaTypeInt = mediaType === "print" ? 1 : 0;
|
|
51
64
|
/** JS-side log hook, gated by `logLevel` (satoru parity). */
|
|
52
65
|
const emitLog = (level, message) => {
|
|
@@ -170,6 +183,10 @@ export function createOrchestration(module, options) {
|
|
|
170
183
|
pdfHeader,
|
|
171
184
|
pdfFooter,
|
|
172
185
|
fmtInt,
|
|
186
|
+
colorSpace,
|
|
187
|
+
scale,
|
|
188
|
+
dpi,
|
|
189
|
+
dpiOption: options.dpi,
|
|
173
190
|
mediaTypeInt,
|
|
174
191
|
t0,
|
|
175
192
|
diag,
|
|
@@ -244,7 +261,7 @@ export async function resolveHtmlSource(orch) {
|
|
|
244
261
|
* `imageBytesFromUnknown` (`unknown` in, no `as` casts).
|
|
245
262
|
*/
|
|
246
263
|
export async function runImageInput(module, value, orch) {
|
|
247
|
-
const { format, quality, speed, animation, animationLoop, width, height, crop, fit, fmtInt, t0, emitLog, logged, loggedBinding, addTime, deliverReport, } = orch;
|
|
264
|
+
const { format, quality, speed, animation, animationLoop, width, height, crop, fit, fmtInt, colorSpace, dpiOption, t0, emitLog, logged, loggedBinding, addTime, deliverReport, } = orch;
|
|
248
265
|
// Narrowed here: caller selected this branch via `isImageInput(value)`.
|
|
249
266
|
const loadImage = module.converter_load_image;
|
|
250
267
|
if (typeof loadImage !== "function")
|
|
@@ -311,9 +328,10 @@ export async function runImageInput(module, value, orch) {
|
|
|
311
328
|
};
|
|
312
329
|
}
|
|
313
330
|
if (format === "pdf") {
|
|
314
|
-
const
|
|
331
|
+
const pdfBindingName = colorSpace === "cmyk" ? "converter_encode_pdf_cmyk" : "converter_encode_pdf";
|
|
332
|
+
const pdfBinding = module[pdfBindingName];
|
|
315
333
|
if (typeof pdfBinding !== "function")
|
|
316
|
-
throw loggedBinding(
|
|
334
|
+
throw loggedBinding(pdfBindingName);
|
|
317
335
|
const encodeStart = now();
|
|
318
336
|
const out = (await pdfBinding(inst));
|
|
319
337
|
addTime("encode", now() - encodeStart);
|
|
@@ -338,6 +356,8 @@ export async function runImageInput(module, value, orch) {
|
|
|
338
356
|
const encodeBinding = module.converter_encode;
|
|
339
357
|
if (typeof encodeBinding !== "function")
|
|
340
358
|
throw loggedBinding("converter_encode");
|
|
359
|
+
if (format === "tiff")
|
|
360
|
+
module.converter_set_dpi?.(inst, Math.round(dpiOption ?? 96));
|
|
341
361
|
const encodeStart = now();
|
|
342
362
|
const out = (await encodeBinding(inst, fmtInt, quality, speed, animation, animationLoop));
|
|
343
363
|
addTime("encode", now() - encodeStart);
|
|
@@ -371,7 +391,7 @@ export async function runImageInput(module, value, orch) {
|
|
|
371
391
|
* pre-resolved resources, so it stays a fallback only).
|
|
372
392
|
*/
|
|
373
393
|
export async function tryRunHtmlInstance(module, htmls, satoruOpts, defaultFontMap, orch) {
|
|
374
|
-
const { format, quality, speed, animation, animationFrames, animationLoop, width, height, baseUrl, fontMap, userAgent, fallbackFonts, fmtInt, t0, emitLog, logged, loggedBinding, addTime, deliverReport, resolveCtx, } = orch;
|
|
394
|
+
const { format, quality, speed, animation, animationFrames, animationLoop, width, height, baseUrl, fontMap, userAgent, fallbackFonts, fmtInt, dpi, t0, emitLog, logged, loggedBinding, addTime, deliverReport, resolveCtx, } = orch;
|
|
375
395
|
const renderBinding = module.satoru_render;
|
|
376
396
|
const encodeBinding = module.converter_encode;
|
|
377
397
|
if (typeof renderBinding !== "function" ||
|
|
@@ -453,6 +473,8 @@ export async function tryRunHtmlInstance(module, htmls, satoruOpts, defaultFontM
|
|
|
453
473
|
}
|
|
454
474
|
const outWidth = module.converter_get_width?.(cInst) ?? width;
|
|
455
475
|
const outHeight = module.converter_get_height?.(cInst) ?? height ?? 0;
|
|
476
|
+
if (format === "tiff")
|
|
477
|
+
module.converter_set_dpi?.(cInst, dpi);
|
|
456
478
|
const encodeStart = now();
|
|
457
479
|
const out = (await encodeBinding(cInst, fmtInt, quality, speed, animation, animationLoop));
|
|
458
480
|
addTime("encode", now() - encodeStart);
|
package/dist/core.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export type { WasmBindingName } from "./core-orchestrate.js";
|
|
|
16
16
|
* (`converter_load_image` -> optional crop/resize -> `converter_encode`,
|
|
17
17
|
* or `converter_encode_svg` / `converter_encode_pdf`).
|
|
18
18
|
*/
|
|
19
|
-
export type OutputFormat = "svg" | "png" | "pdf" | "jpeg" | "webp" | "apng" | "gif" | "avif" | "jxl" | "raw" | "thumbhash" | "none";
|
|
19
|
+
export type OutputFormat = "svg" | "png" | "pdf" | "jpeg" | "webp" | "apng" | "gif" | "tiff" | "avif" | "jxl" | "raw" | "thumbhash" | "none";
|
|
20
20
|
/** Result of rendering an HTML document or converting an image. */
|
|
21
21
|
export interface RenderResult<T = Uint8Array | string> {
|
|
22
22
|
/** Output binary or text (string for svg, Uint8Array for all others). */
|
|
@@ -146,6 +146,30 @@ export interface HtmlToImageOptions extends RenderOptions {
|
|
|
146
146
|
}[];
|
|
147
147
|
/** Safety/performance limits, enforced in JS around resource resolution. */
|
|
148
148
|
limits?: RenderLimits;
|
|
149
|
+
/**
|
|
150
|
+
* Pixel density of raster output from HTML input (default `1`). The page is laid out at
|
|
151
|
+
* `width` CSS px and drawn `scale` times larger, so `scale: 3.125` turns an 800px layout
|
|
152
|
+
* into a 2500px bitmap (300 dpi for an 8.33in page) with text and shapes re-rasterized
|
|
153
|
+
* sharply. `width` / `height` / `crop` stay in CSS px; the result's `width` / `height` are
|
|
154
|
+
* the bitmap size. Range 0-16; ignored for `svg` / `pdf` and for image input.
|
|
155
|
+
*/
|
|
156
|
+
scale?: number;
|
|
157
|
+
/**
|
|
158
|
+
* Resolution written to the `tiff` file, in pixels per inch (default `96 * scale`, rounded:
|
|
159
|
+
* one CSS px is 1/96 in). A print shop reads the physical size from it.
|
|
160
|
+
*/
|
|
161
|
+
dpi?: number;
|
|
162
|
+
/**
|
|
163
|
+
* Output color space of `tiff` and `pdf` (default `"srgb"`).
|
|
164
|
+
*
|
|
165
|
+
* - `"srgb"`: RGB (TIFF keeps the alpha channel).
|
|
166
|
+
* - `"cmyk"`: CMYK for print. TIFF is written as a separated image with transparency
|
|
167
|
+
* flattened onto white; PDF has every fill / stroke color and image rewritten to
|
|
168
|
+
* DeviceCMYK. The conversion is the plain device formula (K = 1 - max(R, G, B), pure
|
|
169
|
+
* black stays K only); it applies no ICC profile, so saturated screen colors are not
|
|
170
|
+
* gamut-mapped. Other formats ignore it.
|
|
171
|
+
*/
|
|
172
|
+
colorSpace?: "srgb" | "cmyk";
|
|
149
173
|
/** PDF Title metadata. */
|
|
150
174
|
pdfTitle?: string;
|
|
151
175
|
/** PDF Author metadata. */
|
package/dist/core.js
CHANGED
|
@@ -46,6 +46,8 @@ export async function htmlToImage(module, options) {
|
|
|
46
46
|
fit: orch.fit,
|
|
47
47
|
textToPaths: orch.textToPaths,
|
|
48
48
|
mediaTypeInt: orch.mediaTypeInt,
|
|
49
|
+
colorSpace: orch.colorSpace,
|
|
50
|
+
scale: orch.scale,
|
|
49
51
|
pdfTitle: orch.pdfTitle,
|
|
50
52
|
pdfAuthor: orch.pdfAuthor,
|
|
51
53
|
pdfSubject: orch.pdfSubject,
|
package/dist/encode-args.d.ts
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
import type { HtmlToImageOptions, OutputFormat } from "./core.js";
|
|
6
6
|
/** `RenderFormat` enum values (mirrors `bridge_types.h`). */
|
|
7
7
|
export declare const FORMAT_INT: Record<OutputFormat, number>;
|
|
8
|
+
/** `RenderFormat::TIFFCmyk`: the CMYK variant of `tiff` (the color space rides in the format int). */
|
|
9
|
+
export declare const TIFF_CMYK_INT = 12;
|
|
8
10
|
export declare const FIT_INT: Record<NonNullable<HtmlToImageOptions["fit"]>, number>;
|
|
9
11
|
/** Map `crop`/`fit` to satoru render-option fields (`parse_satoru_options`). */
|
|
10
12
|
export declare function buildSatoruOptions(crop: HtmlToImageOptions["crop"], fit: HtmlToImageOptions["fit"]): Record<string, unknown>;
|
|
@@ -14,6 +16,8 @@ export interface FullSatoruOptionsInput {
|
|
|
14
16
|
fit: HtmlToImageOptions["fit"];
|
|
15
17
|
textToPaths?: boolean;
|
|
16
18
|
mediaTypeInt: number;
|
|
19
|
+
colorSpace?: "srgb" | "cmyk";
|
|
20
|
+
scale?: number;
|
|
17
21
|
pdfTitle?: string;
|
|
18
22
|
pdfAuthor?: string;
|
|
19
23
|
pdfSubject?: string;
|
package/dist/encode-args.js
CHANGED
|
@@ -9,10 +9,13 @@ export const FORMAT_INT = {
|
|
|
9
9
|
jxl: 8,
|
|
10
10
|
apng: 9,
|
|
11
11
|
gif: 10,
|
|
12
|
+
tiff: 11,
|
|
12
13
|
raw: 6,
|
|
13
14
|
thumbhash: 7,
|
|
14
15
|
none: -1,
|
|
15
16
|
};
|
|
17
|
+
/** `RenderFormat::TIFFCmyk`: the CMYK variant of `tiff` (the color space rides in the format int). */
|
|
18
|
+
export const TIFF_CMYK_INT = 12;
|
|
16
19
|
export const FIT_INT = {
|
|
17
20
|
contain: 0,
|
|
18
21
|
cover: 1,
|
|
@@ -41,6 +44,8 @@ export function buildFullSatoruOptions(input) {
|
|
|
41
44
|
...buildSatoruOptions(input.crop, input.fit),
|
|
42
45
|
svgTextToPaths: textToPaths ?? true,
|
|
43
46
|
mediaType: mediaTypeInt,
|
|
47
|
+
...(input.colorSpace === "cmyk" ? { colorSpace: 1 } : {}),
|
|
48
|
+
...(input.scale !== undefined && input.scale !== 1 ? { scale: input.scale } : {}),
|
|
44
49
|
pdfTitle: pdfTitle ?? "",
|
|
45
50
|
pdfAuthor: pdfAuthor ?? "",
|
|
46
51
|
pdfSubject: pdfSubject ?? "",
|