wasm-html-to-image 0.1.3 → 0.1.5
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 +2 -1
- package/dist/browser-workers.d.ts +1 -12
- package/dist/browser-workers.js +2 -1
- package/dist/child-workers.d.ts +1 -11
- package/dist/child-workers.js +2 -1
- package/dist/cli.js +2 -1
- package/dist/core-orchestrate.d.ts +98 -0
- package/dist/core-orchestrate.js +435 -0
- package/dist/core-resources.d.ts +66 -0
- package/dist/core-resources.js +414 -0
- package/dist/core.d.ts +7 -3
- package/dist/core.js +29 -786
- package/dist/diagnostics.d.ts +31 -1
- package/dist/diagnostics.js +39 -0
- package/dist/edge-light.js +3 -32
- package/dist/encode-args.d.ts +26 -0
- package/dist/encode-args.js +25 -0
- package/dist/html-to-image-single.js +0 -0
- package/dist/html-to-image-wasm.js +1 -1
- package/dist/html-to-image.js +2 -2
- package/dist/html-to-image.wasm +0 -0
- package/dist/input.d.ts +8 -0
- package/dist/input.js +13 -0
- package/dist/loader.d.ts +15 -0
- package/dist/loader.js +42 -0
- package/dist/web-workers.js +634 -448
- package/dist/workerd.js +3 -33
- package/dist/workers-dummy.d.ts +1 -1
- package/dist/workers-parent.js +17 -10
- package/dist/workers.d.ts +7 -1
- package/dist/workers.js +15 -1
- package/llms.txt +2 -2
- package/package.json +9 -8
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource layer split from `./core.ts` (behavior unchanged).
|
|
3
|
+
*
|
|
4
|
+
* Owns: fetched-bytes cache, `fetchResourceBytes`, pending-resources
|
|
5
|
+
* parsing, allow-list checks, and the `resolveHtmlResources` discovery loop.
|
|
6
|
+
* Orchestration context types (`DiagState`, `ResolveContext`) live here so
|
|
7
|
+
* both the loop and the orchestration/pipeline layer share them.
|
|
8
|
+
*/
|
|
9
|
+
import { importNode } from "./loader.js";
|
|
10
|
+
import { dataUrlToBytes, toBytes } from "./input.js";
|
|
11
|
+
import { DIAGNOSTIC_CODES, LogLevel, errorToDiagnostic, } from "./diagnostics.js";
|
|
12
|
+
/** Process-wide fetched-bytes cache (upstream `resourceCache` parity). */
|
|
13
|
+
const resourceCache = new Map();
|
|
14
|
+
function isNodeRuntime() {
|
|
15
|
+
const proc = globalThis
|
|
16
|
+
.process;
|
|
17
|
+
return typeof proc?.versions?.node === "string";
|
|
18
|
+
}
|
|
19
|
+
function fileUrlToFsPath(p) {
|
|
20
|
+
if (!p.startsWith("file://"))
|
|
21
|
+
return p;
|
|
22
|
+
let out = decodeURIComponent(p.slice("file://".length));
|
|
23
|
+
if (/^\/[A-Za-z]:\//.test(out))
|
|
24
|
+
out = out.slice(1);
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Fetch external resource bytes. `data:` URLs resolve inline in C++
|
|
29
|
+
* (`request()`), so they are skipped here. Relative URLs resolve against
|
|
30
|
+
* `baseUrl` (http base via fetch, fs path via node:fs on Node).
|
|
31
|
+
*/
|
|
32
|
+
export async function fetchResourceBytes(url, baseUrl, userAgent, resolveResource) {
|
|
33
|
+
if (url.startsWith("data:"))
|
|
34
|
+
return null;
|
|
35
|
+
const fallback = () => fetchResourceBytesInner(url, baseUrl, userAgent);
|
|
36
|
+
if (resolveResource) {
|
|
37
|
+
try {
|
|
38
|
+
return await resolveResource(url, fallback);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return fallback();
|
|
45
|
+
}
|
|
46
|
+
/** Built-in resolution behind the `resolveResource` hook (see above). */
|
|
47
|
+
async function fetchResourceBytesInner(url, baseUrl, userAgent) {
|
|
48
|
+
const headers = userAgent ? { "User-Agent": userAgent } : undefined;
|
|
49
|
+
const fetchBytes = async (target) => {
|
|
50
|
+
const cached = resourceCache.get(target);
|
|
51
|
+
if (cached)
|
|
52
|
+
return cached;
|
|
53
|
+
try {
|
|
54
|
+
const res = await fetch(target, headers ? { headers } : undefined);
|
|
55
|
+
if (!res.ok)
|
|
56
|
+
return null;
|
|
57
|
+
const bytes = toBytes(await res.arrayBuffer());
|
|
58
|
+
resourceCache.set(target, bytes);
|
|
59
|
+
return bytes;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(url)) {
|
|
66
|
+
return fetchBytes(url);
|
|
67
|
+
}
|
|
68
|
+
if (baseUrl !== undefined) {
|
|
69
|
+
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(baseUrl)) {
|
|
70
|
+
try {
|
|
71
|
+
return await fetchBytes(new URL(url, baseUrl).href);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (isNodeRuntime()) {
|
|
78
|
+
try {
|
|
79
|
+
const fs = await importNode("fs/promises");
|
|
80
|
+
const path = await importNode("path");
|
|
81
|
+
const joined = path.join(fileUrlToFsPath(baseUrl), url);
|
|
82
|
+
const cached = resourceCache.get(joined);
|
|
83
|
+
if (cached)
|
|
84
|
+
return cached;
|
|
85
|
+
// Keep an owning copy: fs Buffers may ride a shared pool.
|
|
86
|
+
const bytes = new Uint8Array(await fs.readFile(joined));
|
|
87
|
+
resourceCache.set(joined, bytes);
|
|
88
|
+
return bytes;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
if (isNodeRuntime()) {
|
|
97
|
+
try {
|
|
98
|
+
const cached = resourceCache.get(url);
|
|
99
|
+
if (cached)
|
|
100
|
+
return cached;
|
|
101
|
+
const fs = await importNode("fs/promises");
|
|
102
|
+
// Keep an owning copy: fs Buffers may ride a shared pool.
|
|
103
|
+
const bytes = new Uint8Array(await fs.readFile(url));
|
|
104
|
+
resourceCache.set(url, bytes);
|
|
105
|
+
return bytes;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
/** Parse the `get_pending_resources` binary form (see loader.ts). */
|
|
114
|
+
export function parsePendingResources(bin) {
|
|
115
|
+
if (!bin || bin.length < 4)
|
|
116
|
+
return [];
|
|
117
|
+
const view = new DataView(bin.buffer, bin.byteOffset, bin.byteLength);
|
|
118
|
+
const dec = new TextDecoder();
|
|
119
|
+
let off = 0;
|
|
120
|
+
const readStr = () => {
|
|
121
|
+
if (off + 4 > bin.byteLength)
|
|
122
|
+
return null;
|
|
123
|
+
const len = view.getUint32(off, true);
|
|
124
|
+
off += 4;
|
|
125
|
+
if (off + len > bin.byteLength)
|
|
126
|
+
return null;
|
|
127
|
+
const s = dec.decode(new Uint8Array(bin.buffer, bin.byteOffset + off, len));
|
|
128
|
+
off += len;
|
|
129
|
+
return s;
|
|
130
|
+
};
|
|
131
|
+
const count = view.getUint32(off, true);
|
|
132
|
+
off += 4;
|
|
133
|
+
const out = [];
|
|
134
|
+
for (let i = 0; i < count; i++) {
|
|
135
|
+
if (off + 2 > bin.byteLength)
|
|
136
|
+
break;
|
|
137
|
+
const typeInt = view.getUint8(off);
|
|
138
|
+
off += 2; // type + redraw_on_ready
|
|
139
|
+
const url = readStr();
|
|
140
|
+
const name = readStr();
|
|
141
|
+
if (readStr() === null || url === null || name === null)
|
|
142
|
+
break;
|
|
143
|
+
out.push({
|
|
144
|
+
type: typeInt === 2 ? "image" : typeInt === 3 ? "css" : "font",
|
|
145
|
+
url,
|
|
146
|
+
name,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
/** Monotonic-ish clock for timings (satoru parity). */
|
|
152
|
+
export function now() {
|
|
153
|
+
return typeof performance !== "undefined" &&
|
|
154
|
+
typeof performance.now === "function"
|
|
155
|
+
? performance.now()
|
|
156
|
+
: Date.now();
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Protocol/host allow-list check. Returns a block reason, or `null` when
|
|
160
|
+
* allowed. Relative URLs skip the check (satoru parity).
|
|
161
|
+
*/
|
|
162
|
+
export function checkResourceAllowed(url, limits) {
|
|
163
|
+
if (!limits.allowedProtocols &&
|
|
164
|
+
!limits.allowedHosts &&
|
|
165
|
+
!limits.blockedHosts) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
let parsed = null;
|
|
169
|
+
try {
|
|
170
|
+
parsed = new URL(url);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
if (limits.allowedProtocols &&
|
|
176
|
+
!limits.allowedProtocols.includes(parsed.protocol)) {
|
|
177
|
+
return `Protocol ${parsed.protocol} is blocked`;
|
|
178
|
+
}
|
|
179
|
+
if (limits.allowedHosts && !limits.allowedHosts.includes(parsed.hostname)) {
|
|
180
|
+
return `Host ${parsed.hostname} is not in allowed list`;
|
|
181
|
+
}
|
|
182
|
+
if (limits.blockedHosts && limits.blockedHosts.includes(parsed.hostname)) {
|
|
183
|
+
return `Host ${parsed.hostname} is blocked`;
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
/** Limit-violation code for a block reason (satoru parity). */
|
|
188
|
+
export function limitCodeForReason(reason) {
|
|
189
|
+
if (reason.startsWith("Protocol "))
|
|
190
|
+
return DIAGNOSTIC_CODES.LIMIT_PROTOCOL_BLOCKED;
|
|
191
|
+
return DIAGNOSTIC_CODES.LIMIT_HOST_BLOCKED;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Upstream-style discovery loop: collect pending URLs on the satoru
|
|
195
|
+
* instance, fetch them (file/http), and inject the bytes back, so the
|
|
196
|
+
* subsequent render on the SAME instance sees cached fonts/images.
|
|
197
|
+
* Missing bindings (old builds) skip the loop silently.
|
|
198
|
+
*/
|
|
199
|
+
export async function resolveHtmlResources(module, satoruInst, htmls, width, height, baseUrl, fontMap, userAgent, fallbackFonts, ctx) {
|
|
200
|
+
const collect = module.satoru_collect_resources;
|
|
201
|
+
const getPending = module.satoru_get_pending_resources;
|
|
202
|
+
const addRes = module.satoru_add_resource;
|
|
203
|
+
if (typeof collect !== "function" ||
|
|
204
|
+
typeof getPending !== "function" ||
|
|
205
|
+
typeof addRes !== "function")
|
|
206
|
+
return;
|
|
207
|
+
// Upstream default: generic families resolve via fontMap before discovery.
|
|
208
|
+
if (fontMap) {
|
|
209
|
+
const setFontMap = module.satoru_set_font_map;
|
|
210
|
+
if (typeof setFontMap === "function") {
|
|
211
|
+
(await setFontMap(satoruInst, { ...fontMap }));
|
|
212
|
+
ctx.emitLog(LogLevel.Debug, "fontMap applied");
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// Upstream idiom: user-supplied fallback fonts go in before discovery.
|
|
216
|
+
if (fallbackFonts && fallbackFonts.length > 0) {
|
|
217
|
+
const loadFallback = module.satoru_load_fallback_font;
|
|
218
|
+
if (typeof loadFallback === "function") {
|
|
219
|
+
for (const entry of fallbackFonts) {
|
|
220
|
+
try {
|
|
221
|
+
const bytes = typeof entry === "string"
|
|
222
|
+
? entry.startsWith("data:")
|
|
223
|
+
? dataUrlToBytes(entry)
|
|
224
|
+
: await fetchResourceBytes(entry, baseUrl, userAgent, ctx.resolveResource)
|
|
225
|
+
: entry instanceof Uint8Array
|
|
226
|
+
? entry
|
|
227
|
+
: new Uint8Array(entry);
|
|
228
|
+
if (!bytes || bytes.length === 0)
|
|
229
|
+
continue;
|
|
230
|
+
(await loadFallback(satoruInst, bytes));
|
|
231
|
+
ctx.diag?.fonts.push({
|
|
232
|
+
family: "(fallback)",
|
|
233
|
+
status: "loaded",
|
|
234
|
+
source: typeof entry === "string" ? entry : "(bytes)",
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
// Per-font failure is non-fatal; discovery proceeds regardless.
|
|
239
|
+
ctx.diag?.warnings.push({
|
|
240
|
+
code: DIAGNOSTIC_CODES.RESOURCE_FETCH_FAILED,
|
|
241
|
+
message: "Failed to load fallback font entry",
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// Named font preloads (`fonts` option), before discovery (satoru parity).
|
|
248
|
+
if (ctx.fonts && ctx.fonts.length > 0) {
|
|
249
|
+
const loadFont = module.satoru_load_font;
|
|
250
|
+
if (typeof loadFont === "function") {
|
|
251
|
+
for (const f of ctx.fonts) {
|
|
252
|
+
try {
|
|
253
|
+
(await loadFont(satoruInst, f.name, f.data));
|
|
254
|
+
ctx.diag?.fonts.push({
|
|
255
|
+
family: f.name,
|
|
256
|
+
status: "loaded",
|
|
257
|
+
source: "fonts option",
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
catch (e) {
|
|
261
|
+
ctx.diag?.warnings.push(errorToDiagnostic(e, DIAGNOSTIC_CODES.RESOURCE_FETCH_FAILED, f.name));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Extra CSS pre-scan (`css` option), before discovery (satoru parity).
|
|
267
|
+
if (ctx.css) {
|
|
268
|
+
const scanCss = module.satoru_scan_css;
|
|
269
|
+
if (typeof scanCss === "function") {
|
|
270
|
+
try {
|
|
271
|
+
(await scanCss(satoruInst, ctx.css));
|
|
272
|
+
ctx.emitLog(LogLevel.Debug, "css pre-scanned");
|
|
273
|
+
}
|
|
274
|
+
catch (e) {
|
|
275
|
+
ctx.diag?.warnings.push(errorToDiagnostic(e, DIAGNOSTIC_CODES.RESOURCE_FETCH_FAILED, "(css option)"));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Collect-phase profiling (merged into timings when diagnostics on).
|
|
280
|
+
const setProfile = module.satoru_set_collect_profile_enabled;
|
|
281
|
+
const profileEnabled = ctx.diag !== null && typeof setProfile === "function";
|
|
282
|
+
if (profileEnabled) {
|
|
283
|
+
try {
|
|
284
|
+
(await setProfile(satoruInst, true));
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
// Non-fatal; timings simply miss the C++ breakdown.
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const list = Array.isArray(htmls) ? htmls : [htmls];
|
|
291
|
+
for (let round = 0; round < 10; round++) {
|
|
292
|
+
ctx.checkTimeout();
|
|
293
|
+
let progressed = false;
|
|
294
|
+
for (const html of list) {
|
|
295
|
+
(await collect(satoruInst, html, width, height ?? 0, ctx.mediaTypeInt));
|
|
296
|
+
const bin = (await getPending(satoruInst));
|
|
297
|
+
const pending = parsePendingResources(bin instanceof Uint8Array ? bin : undefined);
|
|
298
|
+
if (pending.length === 0)
|
|
299
|
+
continue;
|
|
300
|
+
progressed = true;
|
|
301
|
+
await Promise.all(pending.map(async (r) => {
|
|
302
|
+
const diag = ctx.diag;
|
|
303
|
+
const entry = diag
|
|
304
|
+
? {
|
|
305
|
+
type: r.type,
|
|
306
|
+
url: r.url,
|
|
307
|
+
name: r.name || undefined,
|
|
308
|
+
status: "pending",
|
|
309
|
+
}
|
|
310
|
+
: undefined;
|
|
311
|
+
if (diag && entry)
|
|
312
|
+
diag.resources.push(entry);
|
|
313
|
+
const settle = (status, bytes, reason) => {
|
|
314
|
+
if (!entry)
|
|
315
|
+
return;
|
|
316
|
+
entry.status = status;
|
|
317
|
+
if (bytes !== undefined)
|
|
318
|
+
entry.bytes = bytes;
|
|
319
|
+
if (reason !== undefined)
|
|
320
|
+
entry.reason = reason;
|
|
321
|
+
};
|
|
322
|
+
const skipWithError = (code, message) => {
|
|
323
|
+
settle("skipped", undefined, message);
|
|
324
|
+
diag?.errors.push({ code, message, source: r.url });
|
|
325
|
+
ctx.emitLog(LogLevel.Warning, `${message} (${r.url})`);
|
|
326
|
+
};
|
|
327
|
+
try {
|
|
328
|
+
if (r.url.startsWith("data:")) {
|
|
329
|
+
// Inline data: URLs resolve inside C++; nothing to fetch.
|
|
330
|
+
settle("skipped", undefined, "inline data URL (resolved in C++)");
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (ctx.limits.maxResourceCount !== undefined &&
|
|
334
|
+
diag &&
|
|
335
|
+
diag.resourceCount >= ctx.limits.maxResourceCount) {
|
|
336
|
+
const message = `Maximum resource count (${ctx.limits.maxResourceCount}) exceeded`;
|
|
337
|
+
skipWithError(DIAGNOSTIC_CODES.LIMIT_RESOURCE_COUNT, message);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const blocked = checkResourceAllowed(r.url, ctx.limits);
|
|
341
|
+
if (blocked) {
|
|
342
|
+
skipWithError(limitCodeForReason(blocked), blocked);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
const bytes = await fetchResourceBytes(r.url, baseUrl, userAgent, ctx.resolveResource);
|
|
346
|
+
if (!bytes) {
|
|
347
|
+
settle("failed");
|
|
348
|
+
const message = `Failed to fetch resource: ${r.url}`;
|
|
349
|
+
diag?.warnings.push({
|
|
350
|
+
code: DIAGNOSTIC_CODES.RESOURCE_FETCH_FAILED,
|
|
351
|
+
message,
|
|
352
|
+
source: r.url,
|
|
353
|
+
});
|
|
354
|
+
ctx.emitLog(LogLevel.Warning, message);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (ctx.limits.maxResourceBytes !== undefined &&
|
|
358
|
+
bytes.length > ctx.limits.maxResourceBytes) {
|
|
359
|
+
const message = `Resource size (${bytes.length} bytes) exceeds limit ` +
|
|
360
|
+
`(${ctx.limits.maxResourceBytes})`;
|
|
361
|
+
skipWithError(DIAGNOSTIC_CODES.LIMIT_RESOURCE_SIZE, message);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (ctx.limits.maxTotalResourceBytes !== undefined &&
|
|
365
|
+
diag &&
|
|
366
|
+
diag.totalResourceBytes + bytes.length >
|
|
367
|
+
ctx.limits.maxTotalResourceBytes) {
|
|
368
|
+
const message = `Total resource size exceeds limit (${ctx.limits.maxTotalResourceBytes})`;
|
|
369
|
+
skipWithError(DIAGNOSTIC_CODES.LIMIT_TOTAL_SIZE, message);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (diag) {
|
|
373
|
+
diag.resourceCount++;
|
|
374
|
+
diag.totalResourceBytes += bytes.length;
|
|
375
|
+
}
|
|
376
|
+
settle("loaded", bytes.length);
|
|
377
|
+
(await addRes(satoruInst, r.url, r.type === "image" ? 2 : r.type === "css" ? 3 : 1, bytes));
|
|
378
|
+
if (r.type === "font") {
|
|
379
|
+
diag?.fonts.push({
|
|
380
|
+
family: r.name || r.url,
|
|
381
|
+
status: "loaded",
|
|
382
|
+
source: r.url,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
catch (e) {
|
|
387
|
+
// Per-resource failure is non-fatal; render proceeds regardless.
|
|
388
|
+
const d = errorToDiagnostic(e, DIAGNOSTIC_CODES.RESOURCE_FETCH_FAILED, r.url);
|
|
389
|
+
settle("failed", undefined, d.message);
|
|
390
|
+
diag?.warnings.push(d);
|
|
391
|
+
}
|
|
392
|
+
}));
|
|
393
|
+
}
|
|
394
|
+
if (!progressed)
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
// Merge the C++ collect-phase breakdown into the JS timings.
|
|
398
|
+
if (profileEnabled && ctx.diag) {
|
|
399
|
+
const getProfile = module.satoru_get_collect_profile;
|
|
400
|
+
if (typeof getProfile === "function") {
|
|
401
|
+
try {
|
|
402
|
+
const parsed = JSON.parse((await getProfile(satoruInst)));
|
|
403
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
404
|
+
if (typeof value === "number") {
|
|
405
|
+
ctx.addTime(key, value);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
catch {
|
|
410
|
+
// Non-fatal; timings simply miss the C++ breakdown.
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
package/dist/core.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { DIAGNOSTIC_CODES, LogLevel, type RenderDiagnostics, type RenderLimits,
|
|
|
4
4
|
export { isImageInput };
|
|
5
5
|
export { DIAGNOSTIC_CODES, LogLevel };
|
|
6
6
|
export type { DiagnosticMessage, FontDiagnostic, RenderDiagnostics, RenderLimits, ResolveResourceHook, ResourceDiagnostic, } from "./diagnostics.js";
|
|
7
|
+
export type { WasmBindingName } from "./core-orchestrate.js";
|
|
7
8
|
/**
|
|
8
9
|
* Final output formats. Single-WASM internal routing:
|
|
9
10
|
* - HTML input: resources are resolved on the instance first
|
|
@@ -15,7 +16,7 @@ export type { DiagnosticMessage, FontDiagnostic, RenderDiagnostics, RenderLimits
|
|
|
15
16
|
* (`converter_load_image` -> optional crop/resize -> `converter_encode`,
|
|
16
17
|
* or `converter_encode_svg` / `converter_encode_pdf`).
|
|
17
18
|
*/
|
|
18
|
-
export type OutputFormat = "svg" | "png" | "pdf" | "jpeg" | "webp" | "avif" | "raw" | "thumbhash" | "none";
|
|
19
|
+
export type OutputFormat = "svg" | "png" | "pdf" | "jpeg" | "webp" | "avif" | "jxl" | "raw" | "thumbhash" | "none";
|
|
19
20
|
/** Result of rendering an HTML document or converting an image. */
|
|
20
21
|
export interface RenderResult<T = Uint8Array | string> {
|
|
21
22
|
/** Output binary or text (string for svg, Uint8Array for all others). */
|
|
@@ -154,13 +155,16 @@ export interface HtmlToImageOptions extends RenderOptions {
|
|
|
154
155
|
* UA so woff2 is served); fetched at render time, nothing bundled.
|
|
155
156
|
*/
|
|
156
157
|
export declare const DEFAULT_FONT_MAP: Record<string, string>;
|
|
157
|
-
/** Binding names reported by {@link requireBindingError}. */
|
|
158
|
-
export type WasmBindingName = "satoru_render" | "converter_encode" | "converter_encode_svg" | "converter_encode_pdf" | "converter_load_image" | "html_to_image";
|
|
159
158
|
/**
|
|
160
159
|
* Render HTML — or convert an image — through the single unified WASM
|
|
161
160
|
* module. Image input skips the render stage and goes straight to
|
|
162
161
|
* `converter_encode`. Returns bytes (`Uint8Array`); only `svg` resolves
|
|
163
162
|
* to `string`.
|
|
163
|
+
*
|
|
164
|
+
* Thin orchestration: context comes from `./core-orchestrate.ts`, resource
|
|
165
|
+
* discovery from `./core-resources.ts`, satoru options from
|
|
166
|
+
* `./encode-args.ts` (canonical builder). Branch selection uses
|
|
167
|
+
* `isImageInput` (`./input.ts`).
|
|
164
168
|
*/
|
|
165
169
|
export declare function htmlToImage(module: HtmlToImageModule, options: HtmlToImageOptions & {
|
|
166
170
|
format: "svg";
|