@pixldocs/canvas-renderer 0.5.77 → 0.5.79

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/dist/index.d.ts CHANGED
@@ -256,7 +256,7 @@ export declare function normalizeFontFamily(fontStack: string): string;
256
256
  * Package version banner. Bump alongside package.json so we can confirm
257
257
  * (via browser:log) that the deployed bundle matches the expected build.
258
258
  */
259
- export declare const PACKAGE_VERSION = "0.5.77";
259
+ export declare const PACKAGE_VERSION = "0.5.79";
260
260
 
261
261
  export declare interface PageSettings {
262
262
  backgroundColor?: string;
package/dist/index.js CHANGED
@@ -11645,6 +11645,232 @@ function applyContentBoundsPagination(config) {
11645
11645
  if (!mutated) return config;
11646
11646
  return { ...config, pages: resultPages };
11647
11647
  }
11648
+ function normalizeFontFamily(fontStack) {
11649
+ const first = fontStack.split(",")[0].trim();
11650
+ return first.replace(/^['"]|['"]$/g, "");
11651
+ }
11652
+ const loadedFonts = /* @__PURE__ */ new Set();
11653
+ const loadingPromises = /* @__PURE__ */ new Map();
11654
+ function withTimeout(promise, timeoutMs = 4e3) {
11655
+ let timeoutId;
11656
+ return Promise.race([
11657
+ promise,
11658
+ new Promise((resolve) => {
11659
+ timeoutId = setTimeout(resolve, timeoutMs);
11660
+ })
11661
+ ]).finally(() => {
11662
+ if (timeoutId) clearTimeout(timeoutId);
11663
+ });
11664
+ }
11665
+ async function loadGoogleFontCSS(rawFontFamily) {
11666
+ if (!rawFontFamily || typeof document === "undefined") return;
11667
+ const fontFamily = normalizeFontFamily(rawFontFamily);
11668
+ if (!fontFamily) return;
11669
+ if (loadedFonts.has(fontFamily)) return;
11670
+ const existing = loadingPromises.get(fontFamily);
11671
+ if (existing) return existing;
11672
+ const promise = (async () => {
11673
+ try {
11674
+ const encoded = encodeURIComponent(fontFamily);
11675
+ const url = `https://fonts.googleapis.com/css?family=${encoded}:300,400,500,600,700&display=swap`;
11676
+ const link = document.createElement("link");
11677
+ link.rel = "stylesheet";
11678
+ link.href = url;
11679
+ link.crossOrigin = "anonymous";
11680
+ await new Promise((resolve, reject) => {
11681
+ link.onload = () => resolve();
11682
+ link.onerror = () => reject(new Error(`Failed to load font: ${fontFamily}`));
11683
+ document.head.appendChild(link);
11684
+ });
11685
+ loadedFonts.add(fontFamily);
11686
+ } catch (e) {
11687
+ console.warn(`[@pixldocs/canvas-renderer] Font load failed: ${fontFamily}`, e);
11688
+ }
11689
+ })();
11690
+ loadingPromises.set(fontFamily, promise);
11691
+ await promise;
11692
+ loadingPromises.delete(fontFamily);
11693
+ }
11694
+ function collectFontsFromConfig(config) {
11695
+ var _a;
11696
+ const fonts = /* @__PURE__ */ new Set();
11697
+ fonts.add("Open Sans");
11698
+ fonts.add("Hind");
11699
+ function walk(nodes) {
11700
+ var _a2;
11701
+ if (!nodes) return;
11702
+ for (const node of nodes) {
11703
+ if (node.fontFamily) fonts.add(normalizeFontFamily(node.fontFamily));
11704
+ if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) fonts.add(normalizeFontFamily(node.smartProps.fontFamily));
11705
+ if (node.styles && Array.isArray(node.styles)) {
11706
+ for (const lineStyle of node.styles) {
11707
+ if (lineStyle && typeof lineStyle === "object") {
11708
+ for (const charStyle of Object.values(lineStyle)) {
11709
+ if (charStyle == null ? void 0 : charStyle.fontFamily) fonts.add(normalizeFontFamily(charStyle.fontFamily));
11710
+ }
11711
+ }
11712
+ }
11713
+ }
11714
+ if (node.children) walk(node.children);
11715
+ }
11716
+ }
11717
+ for (const page of config.pages || []) {
11718
+ walk(page.children || []);
11719
+ }
11720
+ if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
11721
+ for (const def of Object.values(config.themeConfig.variables)) {
11722
+ if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
11723
+ if (def.label && /font/i.test(def.label)) {
11724
+ fonts.add(normalizeFontFamily(def.value));
11725
+ }
11726
+ }
11727
+ }
11728
+ }
11729
+ return fonts;
11730
+ }
11731
+ function collectFontDescriptorsFromConfig(config) {
11732
+ var _a;
11733
+ const seen = /* @__PURE__ */ new Set();
11734
+ const descriptors = [];
11735
+ function add(family, weight, style) {
11736
+ const f = normalizeFontFamily(family);
11737
+ if (!f) return;
11738
+ const w = weight ?? 400;
11739
+ const s = style ?? "normal";
11740
+ const key = `${f}|${w}|${s}`;
11741
+ if (seen.has(key)) return;
11742
+ seen.add(key);
11743
+ descriptors.push({ family: f, weight: w, style: s });
11744
+ }
11745
+ function walk(nodes) {
11746
+ var _a2;
11747
+ if (!nodes) return;
11748
+ for (const node of nodes) {
11749
+ if (node.fontFamily) {
11750
+ add(node.fontFamily, node.fontWeight, node.fontStyle);
11751
+ if (node.type === "text") {
11752
+ for (const w of [300, 400, 500, 600, 700]) {
11753
+ add(node.fontFamily, w, node.fontStyle);
11754
+ }
11755
+ }
11756
+ }
11757
+ if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) {
11758
+ add(node.smartProps.fontFamily, node.smartProps.fontWeight, node.smartProps.fontStyle);
11759
+ }
11760
+ if (node.styles) {
11761
+ const styleEntries = Array.isArray(node.styles) ? node.styles : Object.values(node.styles);
11762
+ for (const lineStyle of styleEntries) {
11763
+ if (lineStyle && typeof lineStyle === "object") {
11764
+ for (const charStyle of Object.values(lineStyle)) {
11765
+ if (charStyle == null ? void 0 : charStyle.fontFamily) {
11766
+ add(charStyle.fontFamily, charStyle.fontWeight, charStyle.fontStyle);
11767
+ }
11768
+ }
11769
+ }
11770
+ }
11771
+ }
11772
+ if (node.children) walk(node.children);
11773
+ }
11774
+ }
11775
+ add("Open Sans", 400, "normal");
11776
+ add("Hind", 400, "normal");
11777
+ add("Hind", 700, "normal");
11778
+ for (const page of config.pages || []) {
11779
+ walk(page.children || []);
11780
+ }
11781
+ if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
11782
+ for (const def of Object.values(config.themeConfig.variables)) {
11783
+ if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
11784
+ if (def.label && /font/i.test(def.label)) {
11785
+ add(def.value);
11786
+ }
11787
+ }
11788
+ }
11789
+ }
11790
+ return descriptors;
11791
+ }
11792
+ async function ensureFontsForResolvedConfig(config) {
11793
+ if (typeof document === "undefined") return;
11794
+ const descriptors = collectFontDescriptorsFromConfig(config);
11795
+ const families = new Set(descriptors.map((d) => d.family));
11796
+ await withTimeout(Promise.all([...families].map((f) => loadGoogleFontCSS(f))), 3500);
11797
+ if (document.fonts) {
11798
+ descriptors.forEach((d) => {
11799
+ const stylePrefix = d.style === "italic" ? "italic " : "";
11800
+ const weightStr = String(d.weight);
11801
+ const spec = `${stylePrefix}${weightStr} 16px "${d.family}"`;
11802
+ document.fonts.load(spec).catch(() => {
11803
+ });
11804
+ });
11805
+ }
11806
+ }
11807
+ function configHasAutoShrinkText$1(config) {
11808
+ var _a;
11809
+ if (!((_a = config == null ? void 0 : config.pages) == null ? void 0 : _a.length)) return false;
11810
+ const walk = (nodes) => {
11811
+ for (const node of nodes || []) {
11812
+ if (!node) continue;
11813
+ if (node.type === "text" && node.overflowPolicy === "auto-shrink") return true;
11814
+ if (Array.isArray(node.children) && node.children.length && walk(node.children)) return true;
11815
+ }
11816
+ return false;
11817
+ };
11818
+ for (const page of config.pages) {
11819
+ if (walk(page.children || [])) return true;
11820
+ }
11821
+ return false;
11822
+ }
11823
+ async function awaitFontsForConfig(config, maxWaitMs) {
11824
+ if (typeof document === "undefined" || !document.fonts) return;
11825
+ await ensureFontsForResolvedConfig(config);
11826
+ const descriptors = collectFontDescriptorsFromConfig(config);
11827
+ if (descriptors.length === 0) return;
11828
+ const loads = Promise.all(
11829
+ descriptors.map((d) => {
11830
+ const stylePrefix = d.style === "italic" ? "italic " : "";
11831
+ const spec = `${stylePrefix}${d.weight} 16px "${d.family}"`;
11832
+ return document.fonts.load(spec).catch(() => []);
11833
+ })
11834
+ ).then(() => void 0);
11835
+ await Promise.race([
11836
+ loads,
11837
+ new Promise((resolve) => setTimeout(resolve, maxWaitMs))
11838
+ ]);
11839
+ await Promise.race([
11840
+ document.fonts.ready.catch(() => void 0).then(() => void 0),
11841
+ new Promise((r) => setTimeout(r, Math.min(500, maxWaitMs)))
11842
+ ]);
11843
+ const checkSpecs = [];
11844
+ for (const d of descriptors) {
11845
+ const stylePrefix = d.style === "italic" ? "italic " : "";
11846
+ checkSpecs.push(`${stylePrefix}${d.weight} 16px "${d.family}"`);
11847
+ }
11848
+ const startedAt = Date.now();
11849
+ const pollBudget = Math.min(maxWaitMs, 2500);
11850
+ const allReady = () => {
11851
+ for (const spec of checkSpecs) {
11852
+ try {
11853
+ if (!document.fonts.check(spec)) return false;
11854
+ } catch {
11855
+ }
11856
+ }
11857
+ return true;
11858
+ };
11859
+ while (!allReady() && Date.now() - startedAt < pollBudget) {
11860
+ await new Promise((resolve) => setTimeout(resolve, 50));
11861
+ }
11862
+ await new Promise(
11863
+ (resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))
11864
+ );
11865
+ }
11866
+ async function awaitFontsBeforeTextReflow(config) {
11867
+ if (typeof document === "undefined" || !configHasAutoShrinkText$1(config)) return;
11868
+ try {
11869
+ await awaitFontsForConfig(config, 4e3);
11870
+ } catch (error) {
11871
+ console.warn("[@pixldocs/canvas-renderer] Font wait before text reflow failed:", error);
11872
+ }
11873
+ }
11648
11874
  function repeatablePageToSection(page) {
11649
11875
  return {
11650
11876
  id: page.id,
@@ -11838,6 +12064,7 @@ async function resolveTemplateData(options) {
11838
12064
  }
11839
12065
  }
11840
12066
  const repeatablePagesInput = deriveRepeatablePagesFromTemplate(config, inlineFormSchema, mergedFormData);
12067
+ await awaitFontsBeforeTextReflow(config);
11841
12068
  const resolvedConfig = applyFormDataToConfig(
11842
12069
  config,
11843
12070
  mappings,
@@ -11982,6 +12209,7 @@ async function resolveFromForm(options) {
11982
12209
  };
11983
12210
  collectFormats(formConfig.sections);
11984
12211
  }
12212
+ await awaitFontsBeforeTextReflow(templateConfig);
11985
12213
  let resolvedConfig = applyFormDataToConfig(
11986
12214
  templateConfig,
11987
12215
  mappings,
@@ -12160,224 +12388,6 @@ function paintRepeatableSections(config, repeatableSections) {
12160
12388
  }
12161
12389
  }
12162
12390
  }
12163
- function normalizeFontFamily(fontStack) {
12164
- const first = fontStack.split(",")[0].trim();
12165
- return first.replace(/^['"]|['"]$/g, "");
12166
- }
12167
- const loadedFonts = /* @__PURE__ */ new Set();
12168
- const loadingPromises = /* @__PURE__ */ new Map();
12169
- function withTimeout(promise, timeoutMs = 4e3) {
12170
- let timeoutId;
12171
- return Promise.race([
12172
- promise,
12173
- new Promise((resolve) => {
12174
- timeoutId = setTimeout(resolve, timeoutMs);
12175
- })
12176
- ]).finally(() => {
12177
- if (timeoutId) clearTimeout(timeoutId);
12178
- });
12179
- }
12180
- async function loadGoogleFontCSS(rawFontFamily) {
12181
- if (!rawFontFamily || typeof document === "undefined") return;
12182
- const fontFamily = normalizeFontFamily(rawFontFamily);
12183
- if (!fontFamily) return;
12184
- if (loadedFonts.has(fontFamily)) return;
12185
- const existing = loadingPromises.get(fontFamily);
12186
- if (existing) return existing;
12187
- const promise = (async () => {
12188
- try {
12189
- const encoded = encodeURIComponent(fontFamily);
12190
- const url = `https://fonts.googleapis.com/css?family=${encoded}:300,400,500,600,700&display=swap`;
12191
- const link = document.createElement("link");
12192
- link.rel = "stylesheet";
12193
- link.href = url;
12194
- link.crossOrigin = "anonymous";
12195
- await new Promise((resolve, reject) => {
12196
- link.onload = () => resolve();
12197
- link.onerror = () => reject(new Error(`Failed to load font: ${fontFamily}`));
12198
- document.head.appendChild(link);
12199
- });
12200
- loadedFonts.add(fontFamily);
12201
- } catch (e) {
12202
- console.warn(`[@pixldocs/canvas-renderer] Font load failed: ${fontFamily}`, e);
12203
- }
12204
- })();
12205
- loadingPromises.set(fontFamily, promise);
12206
- await promise;
12207
- loadingPromises.delete(fontFamily);
12208
- }
12209
- function collectFontsFromConfig(config) {
12210
- var _a;
12211
- const fonts = /* @__PURE__ */ new Set();
12212
- fonts.add("Open Sans");
12213
- fonts.add("Hind");
12214
- function walk(nodes) {
12215
- var _a2;
12216
- if (!nodes) return;
12217
- for (const node of nodes) {
12218
- if (node.fontFamily) fonts.add(normalizeFontFamily(node.fontFamily));
12219
- if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) fonts.add(normalizeFontFamily(node.smartProps.fontFamily));
12220
- if (node.styles && Array.isArray(node.styles)) {
12221
- for (const lineStyle of node.styles) {
12222
- if (lineStyle && typeof lineStyle === "object") {
12223
- for (const charStyle of Object.values(lineStyle)) {
12224
- if (charStyle == null ? void 0 : charStyle.fontFamily) fonts.add(normalizeFontFamily(charStyle.fontFamily));
12225
- }
12226
- }
12227
- }
12228
- }
12229
- if (node.children) walk(node.children);
12230
- }
12231
- }
12232
- for (const page of config.pages || []) {
12233
- walk(page.children || []);
12234
- }
12235
- if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
12236
- for (const def of Object.values(config.themeConfig.variables)) {
12237
- if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
12238
- if (def.label && /font/i.test(def.label)) {
12239
- fonts.add(normalizeFontFamily(def.value));
12240
- }
12241
- }
12242
- }
12243
- }
12244
- return fonts;
12245
- }
12246
- function collectFontDescriptorsFromConfig(config) {
12247
- var _a;
12248
- const seen = /* @__PURE__ */ new Set();
12249
- const descriptors = [];
12250
- function add(family, weight, style) {
12251
- const f = normalizeFontFamily(family);
12252
- if (!f) return;
12253
- const w = weight ?? 400;
12254
- const s = style ?? "normal";
12255
- const key = `${f}|${w}|${s}`;
12256
- if (seen.has(key)) return;
12257
- seen.add(key);
12258
- descriptors.push({ family: f, weight: w, style: s });
12259
- }
12260
- function walk(nodes) {
12261
- var _a2;
12262
- if (!nodes) return;
12263
- for (const node of nodes) {
12264
- if (node.fontFamily) {
12265
- add(node.fontFamily, node.fontWeight, node.fontStyle);
12266
- if (node.type === "text") {
12267
- for (const w of [300, 400, 500, 600, 700]) {
12268
- add(node.fontFamily, w, node.fontStyle);
12269
- }
12270
- }
12271
- }
12272
- if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) {
12273
- add(node.smartProps.fontFamily, node.smartProps.fontWeight, node.smartProps.fontStyle);
12274
- }
12275
- if (node.styles) {
12276
- const styleEntries = Array.isArray(node.styles) ? node.styles : Object.values(node.styles);
12277
- for (const lineStyle of styleEntries) {
12278
- if (lineStyle && typeof lineStyle === "object") {
12279
- for (const charStyle of Object.values(lineStyle)) {
12280
- if (charStyle == null ? void 0 : charStyle.fontFamily) {
12281
- add(charStyle.fontFamily, charStyle.fontWeight, charStyle.fontStyle);
12282
- }
12283
- }
12284
- }
12285
- }
12286
- }
12287
- if (node.children) walk(node.children);
12288
- }
12289
- }
12290
- add("Open Sans", 400, "normal");
12291
- add("Hind", 400, "normal");
12292
- add("Hind", 700, "normal");
12293
- for (const page of config.pages || []) {
12294
- walk(page.children || []);
12295
- }
12296
- if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
12297
- for (const def of Object.values(config.themeConfig.variables)) {
12298
- if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
12299
- if (def.label && /font/i.test(def.label)) {
12300
- add(def.value);
12301
- }
12302
- }
12303
- }
12304
- }
12305
- return descriptors;
12306
- }
12307
- async function ensureFontsForResolvedConfig(config) {
12308
- if (typeof document === "undefined") return;
12309
- const descriptors = collectFontDescriptorsFromConfig(config);
12310
- const families = new Set(descriptors.map((d) => d.family));
12311
- await withTimeout(Promise.all([...families].map((f) => loadGoogleFontCSS(f))), 3500);
12312
- if (document.fonts) {
12313
- descriptors.forEach((d) => {
12314
- const stylePrefix = d.style === "italic" ? "italic " : "";
12315
- const weightStr = String(d.weight);
12316
- const spec = `${stylePrefix}${weightStr} 16px "${d.family}"`;
12317
- document.fonts.load(spec).catch(() => {
12318
- });
12319
- });
12320
- }
12321
- }
12322
- function configHasAutoShrinkText$1(config) {
12323
- var _a;
12324
- if (!((_a = config == null ? void 0 : config.pages) == null ? void 0 : _a.length)) return false;
12325
- const walk = (nodes) => {
12326
- for (const node of nodes || []) {
12327
- if (!node) continue;
12328
- if (node.type === "text" && node.overflowPolicy === "auto-shrink") return true;
12329
- if (Array.isArray(node.children) && node.children.length && walk(node.children)) return true;
12330
- }
12331
- return false;
12332
- };
12333
- for (const page of config.pages) {
12334
- if (walk(page.children || [])) return true;
12335
- }
12336
- return false;
12337
- }
12338
- async function awaitFontsForConfig(config, maxWaitMs) {
12339
- if (typeof document === "undefined" || !document.fonts) return;
12340
- await ensureFontsForResolvedConfig(config);
12341
- const descriptors = collectFontDescriptorsFromConfig(config);
12342
- if (descriptors.length === 0) return;
12343
- const loads = Promise.all(
12344
- descriptors.map((d) => {
12345
- const stylePrefix = d.style === "italic" ? "italic " : "";
12346
- const spec = `${stylePrefix}${d.weight} 16px "${d.family}"`;
12347
- return document.fonts.load(spec).catch(() => []);
12348
- })
12349
- ).then(() => void 0);
12350
- await Promise.race([
12351
- loads,
12352
- new Promise((resolve) => setTimeout(resolve, maxWaitMs))
12353
- ]);
12354
- await Promise.race([
12355
- document.fonts.ready.catch(() => void 0).then(() => void 0),
12356
- new Promise((r) => setTimeout(r, Math.min(500, maxWaitMs)))
12357
- ]);
12358
- const checkSpecs = [];
12359
- for (const d of descriptors) {
12360
- const stylePrefix = d.style === "italic" ? "italic " : "";
12361
- checkSpecs.push(`${stylePrefix}${d.weight} 16px "${d.family}"`);
12362
- }
12363
- const startedAt = Date.now();
12364
- const pollBudget = Math.min(maxWaitMs, 2500);
12365
- const allReady = () => {
12366
- for (const spec of checkSpecs) {
12367
- try {
12368
- if (!document.fonts.check(spec)) return false;
12369
- } catch {
12370
- }
12371
- }
12372
- return true;
12373
- };
12374
- while (!allReady() && Date.now() - startedAt < pollBudget) {
12375
- await new Promise((resolve) => setTimeout(resolve, 50));
12376
- }
12377
- await new Promise(
12378
- (resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))
12379
- );
12380
- }
12381
12391
  const PREVIEW_DEBUG_PREFIX = "[canvas-renderer][preview-debug]";
12382
12392
  function countUnderlinedNodes(config) {
12383
12393
  var _a;
@@ -12568,7 +12578,7 @@ function PixldocsPreview(props) {
12568
12578
  !canvasSettled && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) })
12569
12579
  ] });
12570
12580
  }
12571
- const PACKAGE_VERSION = "0.5.77";
12581
+ const PACKAGE_VERSION = "0.5.79";
12572
12582
  const roundParityValue = (value) => {
12573
12583
  if (typeof value !== "number") return value;
12574
12584
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -15478,6 +15488,25 @@ async function convertTextDecorationsToLines(svg) {
15478
15488
  }
15479
15489
  }
15480
15490
  }
15491
+ async function convertSvgTextDecorationsToLinesString(svgStr) {
15492
+ if (typeof DOMParser === "undefined" || typeof XMLSerializer === "undefined") {
15493
+ return svgStr;
15494
+ }
15495
+ if (!/text-decoration/i.test(svgStr) && !/underline/i.test(svgStr)) {
15496
+ return svgStr;
15497
+ }
15498
+ try {
15499
+ const parser = new DOMParser();
15500
+ const docEl = parser.parseFromString(svgStr, "image/svg+xml");
15501
+ const rootSvg = docEl.documentElement;
15502
+ if (!rootSvg) return svgStr;
15503
+ await convertTextDecorationsToLines(rootSvg);
15504
+ const serializer = new XMLSerializer();
15505
+ return serializer.serializeToString(rootSvg);
15506
+ } catch {
15507
+ return svgStr;
15508
+ }
15509
+ }
15481
15510
  async function rasterizeShadowMarkers(svg) {
15482
15511
  var _a, _b, _c, _d, _e;
15483
15512
  if (typeof window === "undefined" || typeof document === "undefined") return;
@@ -15746,6 +15775,14 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
15746
15775
  const shouldStripBg = stripPageBackground ?? hasGradient;
15747
15776
  const shouldOutlineText = options.outlineText !== false;
15748
15777
  let pageSvg = page.svg;
15778
+ try {
15779
+ pageSvg = await convertSvgTextDecorationsToLinesString(pageSvg);
15780
+ } catch (underlineErr) {
15781
+ console.warn(
15782
+ "[canvas-renderer][pdf] underline-to-line conversion failed (raw stage):",
15783
+ underlineErr
15784
+ );
15785
+ }
15749
15786
  if (shouldOutlineText) {
15750
15787
  try {
15751
15788
  const { convertAllTextToPath } = await import("./svgTextToPath-BP0Kppla.js");