wasm-html-to-image 1.0.11 → 1.0.13

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.
@@ -132,7 +132,7 @@ export function createOrchestration(module, options) {
132
132
  limits,
133
133
  resolveResource,
134
134
  diag,
135
- usage: { resourceCount: 0, totalResourceBytes: 0 },
135
+ usage: { resourceCount: 0, totalResourceBytes: 0, fontsSkippedByCount: 0 },
136
136
  t0,
137
137
  emitLog,
138
138
  addTime,
@@ -37,6 +37,8 @@ export interface DiagState {
37
37
  export interface ResourceUsage {
38
38
  resourceCount: number;
39
39
  totalResourceBytes: number;
40
+ /** Font files dropped because `maxResourceCount` was reached (across all rounds). */
41
+ fontsSkippedByCount: number;
40
42
  }
41
43
  /** Resolution context threaded through HTML discovery (internal). */
42
44
  export interface ResolveContext {
@@ -108,7 +108,8 @@ async function fetchResourceBytesInner(url, baseUrl, userAgent, timeoutMs, confi
108
108
  const headers = userAgent && isNodeRuntime() ? { "User-Agent": userAgent } : undefined;
109
109
  // Abort hung connections so one stalled server cannot outlive `timeoutMs`.
110
110
  const signal = timeoutMs !== undefined
111
- ? AbortSignal.timeout(Math.max(1, timeoutMs))
111
+ ? // Node >= 24 requires an integer delay; the remaining budget is fractional.
112
+ AbortSignal.timeout(Math.max(1, Math.ceil(timeoutMs)))
112
113
  : undefined;
113
114
  const fetchBytes = async (target) => {
114
115
  // Servers may vary the body by User-Agent (e.g. woff2 vs ttf CSS).
@@ -440,7 +441,16 @@ export async function resolveHtmlResources(module, satoruInst, htmls, width, hei
440
441
  // later sheet must not land before (and so lose to) a large earlier one. They are
441
442
  // buffered here and injected in discovery order once the whole round has settled.
442
443
  const loadedCss = [];
443
- await mapWithLimit(pending, FETCH_CONCURRENCY, async (r) => {
444
+ // When maxResourceCount cuts a round short, what is dropped should be the least harmful: a missing
445
+ // image leaves a hole, a missing font turns the whole text into tofu. Stylesheets go first (they
446
+ // cascade by document order and may reveal more), then fonts, then images; discovery order is
447
+ // kept within each kind.
448
+ const rank = (t) => (t === "css" ? 0 : t === "font" ? 1 : 2);
449
+ const ordered = pending
450
+ .map((r, i) => ({ r, i }))
451
+ .sort((a, b) => rank(a.r.type) - rank(b.r.type) || a.i - b.i)
452
+ .map((e) => e.r);
453
+ await mapWithLimit(ordered, FETCH_CONCURRENCY, async (r) => {
444
454
  const diag = ctx.diag;
445
455
  const entry = diag
446
456
  ? {
@@ -482,6 +492,8 @@ export async function resolveHtmlResources(module, satoruInst, htmls, width, hei
482
492
  if (ctx.limits.maxResourceCount !== undefined &&
483
493
  ctx.usage.resourceCount >= ctx.limits.maxResourceCount) {
484
494
  const message = `Maximum resource count (${ctx.limits.maxResourceCount}) exceeded`;
495
+ if (r.type === "font")
496
+ ctx.usage.fontsSkippedByCount++;
485
497
  skipWithError(DIAGNOSTIC_CODES.LIMIT_RESOURCE_COUNT, message);
486
498
  return;
487
499
  }
@@ -546,6 +558,21 @@ export async function resolveHtmlResources(module, satoruInst, htmls, width, hei
546
558
  (await addRes(satoruInst, css.url, 3, bytes));
547
559
  }
548
560
  }
561
+ if (ctx.usage.fontsSkippedByCount > 0) {
562
+ // Text in those fonts renders as tofu: say what to change instead of leaving a silent bad image.
563
+ // One message for the whole render, however many discovery rounds hit the limit.
564
+ const message = `${ctx.usage.fontsSkippedByCount} font file(s) were not loaded because maxResourceCount ` +
565
+ `(${ctx.limits.maxResourceCount}) was reached; text using them may render as missing glyphs. ` +
566
+ "Raise limits.maxResourceCount (a Google Fonts stylesheet alone can reference over a hundred files).";
567
+ ctx.diag?.warnings.push({
568
+ code: DIAGNOSTIC_CODES.LIMIT_RESOURCE_COUNT,
569
+ message,
570
+ });
571
+ ctx.emitLog(LogLevel.Warning, message);
572
+ if (ctx.limits.strictFonts) {
573
+ throw new Error(`[${DIAGNOSTIC_CODES.LIMIT_RESOURCE_COUNT}] ${message}`);
574
+ }
575
+ }
549
576
  // Merge the C++ collect-phase breakdown into the JS timings.
550
577
  if (profileEnabled && ctx.diag) {
551
578
  const getProfile = module.satoru_get_collect_profile;
@@ -108,6 +108,11 @@ export interface RenderLimits {
108
108
  maxTotalResourceBytes?: number;
109
109
  /** Maximum number of resources to load. */
110
110
  maxResourceCount?: number;
111
+ /**
112
+ * Throw (code `LIMIT_RESOURCE_COUNT`) instead of rendering when font files had to be skipped
113
+ * because of `maxResourceCount`, so text is never silently drawn as missing glyphs. Default `false`.
114
+ */
115
+ strictFonts?: boolean;
111
116
  /** Allowed URL protocols (e.g. `["http:", "https:"]`). */
112
117
  allowedProtocols?: string[];
113
118
  /** Allowed hostnames. */