@pixldocs/canvas-renderer 0.5.66 → 0.5.68

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.js CHANGED
@@ -12123,1384 +12123,19 @@ function paintRepeatableSections(config, repeatableSections) {
12123
12123
  }
12124
12124
  }
12125
12125
  }
12126
- function normalizeFontFamily(fontStack) {
12127
- const first = fontStack.split(",")[0].trim();
12128
- return first.replace(/^['"]|['"]$/g, "");
12129
- }
12130
- const loadedFonts = /* @__PURE__ */ new Set();
12131
- const loadingPromises = /* @__PURE__ */ new Map();
12132
- function withTimeout(promise, timeoutMs = 4e3) {
12133
- let timeoutId;
12134
- return Promise.race([
12135
- promise,
12136
- new Promise((resolve) => {
12137
- timeoutId = setTimeout(resolve, timeoutMs);
12138
- })
12139
- ]).finally(() => {
12140
- if (timeoutId) clearTimeout(timeoutId);
12141
- });
12142
- }
12143
- async function loadGoogleFontCSS(rawFontFamily) {
12144
- if (!rawFontFamily || typeof document === "undefined") return;
12145
- const fontFamily = normalizeFontFamily(rawFontFamily);
12146
- if (!fontFamily) return;
12147
- if (loadedFonts.has(fontFamily)) return;
12148
- const existing = loadingPromises.get(fontFamily);
12149
- if (existing) return existing;
12150
- const promise = (async () => {
12151
- try {
12152
- const encoded = encodeURIComponent(fontFamily);
12153
- const url = `https://fonts.googleapis.com/css?family=${encoded}:300,400,500,600,700&display=swap`;
12154
- const link = document.createElement("link");
12155
- link.rel = "stylesheet";
12156
- link.href = url;
12157
- link.crossOrigin = "anonymous";
12158
- await new Promise((resolve, reject) => {
12159
- link.onload = () => resolve();
12160
- link.onerror = () => reject(new Error(`Failed to load font: ${fontFamily}`));
12161
- document.head.appendChild(link);
12162
- });
12163
- loadedFonts.add(fontFamily);
12164
- } catch (e) {
12165
- console.warn(`[@pixldocs/canvas-renderer] Font load failed: ${fontFamily}`, e);
12166
- }
12167
- })();
12168
- loadingPromises.set(fontFamily, promise);
12169
- await promise;
12170
- loadingPromises.delete(fontFamily);
12171
- }
12172
- function collectFontsFromConfig(config) {
12173
- var _a;
12174
- const fonts = /* @__PURE__ */ new Set();
12175
- fonts.add("Open Sans");
12176
- fonts.add("Hind");
12177
- function walk(nodes) {
12178
- var _a2;
12179
- if (!nodes) return;
12180
- for (const node of nodes) {
12181
- if (node.fontFamily) fonts.add(normalizeFontFamily(node.fontFamily));
12182
- if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) fonts.add(normalizeFontFamily(node.smartProps.fontFamily));
12183
- if (node.styles && Array.isArray(node.styles)) {
12184
- for (const lineStyle of node.styles) {
12185
- if (lineStyle && typeof lineStyle === "object") {
12186
- for (const charStyle of Object.values(lineStyle)) {
12187
- if (charStyle == null ? void 0 : charStyle.fontFamily) fonts.add(normalizeFontFamily(charStyle.fontFamily));
12188
- }
12189
- }
12190
- }
12191
- }
12192
- if (node.children) walk(node.children);
12193
- }
12194
- }
12195
- for (const page of config.pages || []) {
12196
- walk(page.children || []);
12197
- }
12198
- if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
12199
- for (const def of Object.values(config.themeConfig.variables)) {
12200
- if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
12201
- if (def.label && /font/i.test(def.label)) {
12202
- fonts.add(normalizeFontFamily(def.value));
12203
- }
12204
- }
12205
- }
12206
- }
12207
- return fonts;
12208
- }
12209
- function collectFontDescriptorsFromConfig(config) {
12210
- var _a;
12211
- const seen = /* @__PURE__ */ new Set();
12212
- const descriptors = [];
12213
- function add(family, weight, style) {
12214
- const f = normalizeFontFamily(family);
12215
- if (!f) return;
12216
- const w = weight ?? 400;
12217
- const s = style ?? "normal";
12218
- const key = `${f}|${w}|${s}`;
12219
- if (seen.has(key)) return;
12220
- seen.add(key);
12221
- descriptors.push({ family: f, weight: w, style: s });
12222
- }
12223
- function walk(nodes) {
12224
- var _a2;
12225
- if (!nodes) return;
12226
- for (const node of nodes) {
12227
- if (node.fontFamily) {
12228
- add(node.fontFamily, node.fontWeight, node.fontStyle);
12229
- if (node.type === "text") {
12230
- for (const w of [300, 400, 500, 600, 700]) {
12231
- add(node.fontFamily, w, node.fontStyle);
12232
- }
12233
- }
12234
- }
12235
- if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) {
12236
- add(node.smartProps.fontFamily, node.smartProps.fontWeight, node.smartProps.fontStyle);
12237
- }
12238
- if (node.styles) {
12239
- const styleEntries = Array.isArray(node.styles) ? node.styles : Object.values(node.styles);
12240
- for (const lineStyle of styleEntries) {
12241
- if (lineStyle && typeof lineStyle === "object") {
12242
- for (const charStyle of Object.values(lineStyle)) {
12243
- if (charStyle == null ? void 0 : charStyle.fontFamily) {
12244
- add(charStyle.fontFamily, charStyle.fontWeight, charStyle.fontStyle);
12245
- }
12246
- }
12247
- }
12248
- }
12249
- }
12250
- if (node.children) walk(node.children);
12251
- }
12252
- }
12253
- add("Open Sans", 400, "normal");
12254
- add("Hind", 400, "normal");
12255
- add("Hind", 700, "normal");
12256
- for (const page of config.pages || []) {
12257
- walk(page.children || []);
12258
- }
12259
- if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
12260
- for (const def of Object.values(config.themeConfig.variables)) {
12261
- if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
12262
- if (def.label && /font/i.test(def.label)) {
12263
- add(def.value);
12264
- }
12265
- }
12266
- }
12267
- }
12268
- return descriptors;
12269
- }
12270
- async function ensureFontsForResolvedConfig(config) {
12271
- if (typeof document === "undefined") return;
12272
- const descriptors = collectFontDescriptorsFromConfig(config);
12273
- const families = new Set(descriptors.map((d) => d.family));
12274
- void withTimeout(Promise.all([...families].map((f) => loadGoogleFontCSS(f))), 2500);
12275
- if (document.fonts) {
12276
- descriptors.forEach((d) => {
12277
- const stylePrefix = d.style === "italic" ? "italic " : "";
12278
- const weightStr = String(d.weight);
12279
- const spec = `${stylePrefix}${weightStr} 16px "${d.family}"`;
12280
- document.fonts.load(spec).catch(() => {
12281
- });
12282
- });
12283
- }
12284
- }
12285
- function configHasAutoShrinkText$1(config) {
12286
- var _a;
12287
- if (!((_a = config == null ? void 0 : config.pages) == null ? void 0 : _a.length)) return false;
12288
- const walk = (nodes) => {
12289
- for (const node of nodes || []) {
12290
- if (!node) continue;
12291
- if (node.type === "text" && node.overflowPolicy === "auto-shrink") return true;
12292
- if (Array.isArray(node.children) && node.children.length && walk(node.children)) return true;
12293
- }
12294
- return false;
12295
- };
12296
- for (const page of config.pages) {
12297
- if (walk(page.children || [])) return true;
12298
- }
12299
- return false;
12300
- }
12301
- async function awaitFontsForConfig(config, maxWaitMs) {
12302
- if (typeof document === "undefined" || !document.fonts) return;
12303
- void ensureFontsForResolvedConfig(config);
12304
- const descriptors = collectFontDescriptorsFromConfig(config);
12305
- if (descriptors.length === 0) return;
12306
- const loads = Promise.all(
12307
- descriptors.map((d) => {
12308
- const stylePrefix = d.style === "italic" ? "italic " : "";
12309
- const spec = `${stylePrefix}${d.weight} 16px "${d.family}"`;
12310
- return document.fonts.load(spec).catch(() => []);
12311
- })
12312
- ).then(() => void 0);
12313
- await Promise.race([
12314
- loads,
12315
- new Promise((resolve) => setTimeout(resolve, maxWaitMs))
12316
- ]);
12317
- await Promise.race([
12318
- document.fonts.ready.catch(() => void 0).then(() => void 0),
12319
- new Promise((r) => setTimeout(r, Math.min(500, maxWaitMs)))
12320
- ]);
12321
- await new Promise(
12322
- (resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))
12323
- );
12324
- }
12325
- const PREVIEW_DEBUG_PREFIX = "[canvas-renderer][preview-debug]";
12326
- function countUnderlinedNodes(config) {
12327
- var _a;
12328
- if (!((_a = config == null ? void 0 : config.pages) == null ? void 0 : _a.length)) return 0;
12329
- let count = 0;
12330
- const walk = (nodes) => {
12331
- var _a2;
12332
- for (const node of nodes || []) {
12333
- if (node == null ? void 0 : node.underline) count += 1;
12334
- if ((_a2 = node == null ? void 0 : node.children) == null ? void 0 : _a2.length) walk(node.children);
12335
- }
12336
- };
12337
- for (const page of config.pages) walk(page.children || []);
12338
- return count;
12339
- }
12340
- function PixldocsPreview(props) {
12341
- const {
12342
- pageIndex = 0,
12343
- zoom = 1,
12344
- absoluteZoom = false,
12345
- imageProxyUrl,
12346
- className,
12347
- style,
12348
- onDynamicFieldClick,
12349
- onReady,
12350
- onError,
12351
- // Default `false` so PageCanvas blocks textbox creation until the host
12352
- // browser actually has the @font-face rules registered. This matters for
12353
- // `overflowPolicy: 'auto-shrink'` text — `createText` runs the shrink
12354
- // loop synchronously at mount time using whatever font metrics Fabric
12355
- // can measure right then. If the real font hasn't loaded yet, Fabric
12356
- // falls back to the system font (typically narrower), the shrink loop
12357
- // decides "fits, no shrink needed", and when the real font finally
12358
- // loads the text overflows the box.
12359
- //
12360
- // The renderer's imperative PNG/PDF paths (`renderPageViaPreviewCanvas`,
12361
- // `captureSvgViaPreviewCanvas`) already pass `skipFontReadyWait: false`
12362
- // for this exact reason — that's why the downloaded PDF was correct
12363
- // while the on-screen preview wasn't.
12364
- skipFontReadyWait = false
12365
- } = props;
12366
- useEffect(() => {
12367
- setPackageApiUrl(imageProxyUrl);
12368
- }, [imageProxyUrl]);
12369
- const [resolvedConfig, setResolvedConfig] = useState(null);
12370
- const [isLoading, setIsLoading] = useState(false);
12371
- const [fontsReady, setFontsReady] = useState(false);
12372
- const [fontsReadyVersion, setFontsReadyVersion] = useState(0);
12373
- const [canvasSettled, setCanvasSettled] = useState(false);
12374
- const [stabilizationPass, setStabilizationPass] = useState(0);
12375
- const isResolveMode = !("config" in props && props.config);
12376
- useEffect(() => {
12377
- if (!isResolveMode) {
12378
- setResolvedConfig(null);
12379
- setCanvasSettled(false);
12380
- console.log(PREVIEW_DEBUG_PREFIX, "config-mode active");
12381
- return;
12382
- }
12383
- const p = props;
12384
- if (!p.templateId || !p.formSchemaId || !p.supabaseUrl || !p.supabaseAnonKey) return;
12385
- let cancelled = false;
12386
- setIsLoading(true);
12387
- setFontsReady(false);
12388
- setCanvasSettled(false);
12389
- console.log(PREVIEW_DEBUG_PREFIX, "resolve-start", {
12390
- templateId: p.templateId,
12391
- formSchemaId: p.formSchemaId,
12392
- themeId: p.themeId ?? null,
12393
- pageIndex
12394
- });
12395
- resolveFromForm({
12396
- templateId: p.templateId,
12397
- formSchemaId: p.formSchemaId,
12398
- sectionState: p.sectionState,
12399
- themeId: p.themeId,
12400
- supabaseUrl: p.supabaseUrl,
12401
- supabaseAnonKey: p.supabaseAnonKey
12402
- }).then((resolved) => {
12403
- var _a, _b;
12404
- if (!cancelled) {
12405
- console.log(PREVIEW_DEBUG_PREFIX, "resolve-done", {
12406
- pages: ((_b = (_a = resolved.config) == null ? void 0 : _a.pages) == null ? void 0 : _b.length) ?? 0,
12407
- underlinedNodes: countUnderlinedNodes(resolved.config)
12408
- });
12409
- setResolvedConfig(resolved.config);
12410
- const hasAutoShrink = configHasAutoShrinkText$1(resolved.config);
12411
- const waitMs = hasAutoShrink ? 4e3 : 1800;
12412
- awaitFontsForConfig(resolved.config, waitMs).then(() => {
12413
- if (!cancelled) {
12414
- console.log(PREVIEW_DEBUG_PREFIX, "resolve-mode fonts settled", { hasAutoShrink, waitMs });
12415
- setFontsReady(true);
12416
- setIsLoading(false);
12417
- }
12418
- }).catch((err) => {
12419
- if (!cancelled) {
12420
- console.warn(PREVIEW_DEBUG_PREFIX, "resolve-mode font wait failed", err);
12421
- setFontsReady(true);
12422
- setIsLoading(false);
12423
- }
12424
- });
12425
- }
12426
- }).catch((err) => {
12427
- if (!cancelled) {
12428
- setIsLoading(false);
12429
- console.warn(PREVIEW_DEBUG_PREFIX, "resolve-error", err);
12430
- onError == null ? void 0 : onError(err instanceof Error ? err : new Error(String(err)));
12431
- }
12432
- });
12433
- return () => {
12434
- cancelled = true;
12435
- };
12436
- }, [
12437
- isResolveMode,
12438
- // For resolve mode, re-resolve when these change
12439
- isResolveMode ? props.templateId : void 0,
12440
- isResolveMode ? props.formSchemaId : void 0,
12441
- isResolveMode ? JSON.stringify(props.sectionState) : void 0,
12442
- isResolveMode ? props.themeId : void 0
12443
- ]);
12444
- const config = isResolveMode ? resolvedConfig : props.config;
12445
- useEffect(() => {
12446
- var _a, _b, _c;
12447
- if (!config) return;
12448
- let cancelled = false;
12449
- setCanvasSettled(false);
12450
- setStabilizationPass(0);
12451
- console.log(PREVIEW_DEBUG_PREFIX, "config-changed", {
12452
- pageIndex,
12453
- pages: ((_a = config.pages) == null ? void 0 : _a.length) ?? 0,
12454
- underlinedNodes: countUnderlinedNodes(config),
12455
- isResolveMode
12456
- });
12457
- const bump = () => {
12458
- if (cancelled) return;
12459
- clearMeasurementCache();
12460
- clearFabricCharCache();
12461
- setFontsReadyVersion((v) => {
12462
- const next = v + 1;
12463
- console.log(PREVIEW_DEBUG_PREFIX, "font-bump", { pageIndex, next, stabilizationPass });
12464
- return next;
12465
- });
12466
- };
12467
- (_c = (_b = document.fonts) == null ? void 0 : _b.ready) == null ? void 0 : _c.then(bump);
12468
- const timeoutId = window.setTimeout(bump, 350);
12469
- return () => {
12470
- cancelled = true;
12471
- window.clearTimeout(timeoutId);
12472
- };
12473
- }, [config]);
12474
- const previewKey = useMemo(
12475
- () => `${pageIndex}-${fontsReadyVersion}-${stabilizationPass}`,
12476
- [pageIndex, fontsReadyVersion, stabilizationPass]
12477
- );
12478
- useEffect(() => {
12479
- if (isResolveMode) return;
12480
- if (!config) {
12481
- setFontsReady(false);
12482
- setCanvasSettled(false);
12483
- setStabilizationPass(0);
12484
- return;
12485
- }
12486
- setFontsReady(false);
12487
- setCanvasSettled(false);
12488
- setStabilizationPass(0);
12489
- let cancelled = false;
12490
- const hasAutoShrink = configHasAutoShrinkText$1(config);
12491
- const waitMs = hasAutoShrink ? 4e3 : 1800;
12492
- awaitFontsForConfig(config, waitMs).then(() => {
12493
- if (cancelled) return;
12494
- console.log(PREVIEW_DEBUG_PREFIX, "config-mode fonts settled", {
12495
- pageIndex,
12496
- hasAutoShrink,
12497
- waitMs,
12498
- underlinedNodes: countUnderlinedNodes(config)
12499
- });
12500
- setFontsReady(true);
12501
- }).catch((err) => {
12502
- if (cancelled) return;
12503
- console.warn(PREVIEW_DEBUG_PREFIX, "config-mode font wait failed", err);
12504
- setFontsReady(true);
12505
- });
12506
- return () => {
12507
- cancelled = true;
12508
- };
12509
- }, [isResolveMode, config]);
12510
- const handleCanvasReady = useCallback(() => {
12511
- if (stabilizationPass === 0) {
12512
- console.log(PREVIEW_DEBUG_PREFIX, "canvas-ready-pass", { pageIndex, stabilizationPass, action: "stabilize-again" });
12513
- setCanvasSettled(false);
12514
- setStabilizationPass(1);
12515
- return;
12516
- }
12517
- console.log(PREVIEW_DEBUG_PREFIX, "canvas-ready-pass", { pageIndex, stabilizationPass, action: "settled" });
12518
- setCanvasSettled(true);
12519
- onReady == null ? void 0 : onReady();
12520
- }, [onReady, pageIndex, stabilizationPass]);
12521
- if (isLoading) {
12522
- return /* @__PURE__ */ jsx("div", { className, style: { ...style, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) });
12523
- }
12524
- if (!config) return null;
12525
- if (!fontsReady) {
12526
- return /* @__PURE__ */ jsx("div", { className, style: { ...style, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) });
12527
- }
12528
- return /* @__PURE__ */ jsxs("div", { className, style: { ...style, position: "relative" }, children: [
12529
- /* @__PURE__ */ jsx("div", { style: { visibility: canvasSettled ? "visible" : "hidden" }, children: /* @__PURE__ */ jsx(
12530
- PreviewCanvas,
12531
- {
12532
- config,
12533
- pageIndex,
12534
- zoom,
12535
- absoluteZoom,
12536
- skipFontReadyWait,
12537
- onDynamicFieldClick,
12538
- onReady: handleCanvasReady
12539
- },
12540
- previewKey
12541
- ) }),
12542
- !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..." }) })
12543
- ] });
12544
- }
12545
- const PACKAGE_VERSION = "0.5.66";
12546
- let __underlineFixInstalled = false;
12547
- function installUnderlineFix(fab) {
12548
- var _a;
12549
- if (__underlineFixInstalled) return;
12550
- const TextProto = (_a = fab.Text) == null ? void 0 : _a.prototype;
12551
- if (!TextProto || typeof TextProto._renderTextDecoration !== "function") return;
12552
- const original = TextProto._renderTextDecoration;
12553
- const measureLineTextWidth = (obj, ctx, lineIndex) => {
12554
- var _a2, _b, _c, _d, _e, _f;
12555
- const rawLine = (_a2 = obj._textLines) == null ? void 0 : _a2[lineIndex];
12556
- const lineText = Array.isArray(rawLine) ? rawLine.join("") : String(rawLine ?? "");
12557
- if (!lineText) return 0;
12558
- const fontSize = Number(((_b = obj.getValueOfPropertyAt) == null ? void 0 : _b.call(obj, lineIndex, 0, "fontSize")) ?? obj.fontSize ?? 0);
12559
- const fontStyle = String(((_c = obj.getValueOfPropertyAt) == null ? void 0 : _c.call(obj, lineIndex, 0, "fontStyle")) ?? obj.fontStyle ?? "normal");
12560
- const fontWeight = String(((_d = obj.getValueOfPropertyAt) == null ? void 0 : _d.call(obj, lineIndex, 0, "fontWeight")) ?? obj.fontWeight ?? "400");
12561
- const fontFamily = String(((_e = obj.getValueOfPropertyAt) == null ? void 0 : _e.call(obj, lineIndex, 0, "fontFamily")) ?? obj.fontFamily ?? "sans-serif");
12562
- const charSpacing = Number(((_f = obj.getValueOfPropertyAt) == null ? void 0 : _f.call(obj, lineIndex, 0, "charSpacing")) ?? obj.charSpacing ?? 0);
12563
- ctx.save();
12564
- ctx.font = `${fontStyle} normal ${fontWeight} ${fontSize}px ${fontFamily}`;
12565
- const measured = ctx.measureText(lineText).width;
12566
- ctx.restore();
12567
- const graphemeCount = Array.from(lineText).length;
12568
- const spacingWidth = graphemeCount > 1 ? charSpacing / 1e3 * fontSize * (graphemeCount - 1) : 0;
12569
- return Math.max(0, measured + spacingWidth);
12570
- };
12571
- TextProto._renderTextDecoration = function patchedRenderTextDecoration(ctx, type) {
12572
- try {
12573
- const hasOwn = !!this[type];
12574
- const hasStyled = typeof this.styleHas === "function" && this.styleHas(type);
12575
- if (!hasOwn && !hasStyled) return;
12576
- const lines = this._textLines;
12577
- const offsets = this.offsets;
12578
- if (!Array.isArray(lines) || !offsets) {
12579
- return original.call(this, ctx, type);
12580
- }
12581
- const offsetY = offsets[type];
12582
- const offsetAligner = type === "linethrough" ? 0.5 : type === "overline" ? 1 : 0;
12583
- const leftOffset = this._getLeftOffset();
12584
- let topOffset = this._getTopOffset();
12585
- for (let i = 0, len = lines.length; i < len; i++) {
12586
- const heightOfLine = this.getHeightOfLine(i);
12587
- const lineHas = !!this[type] || typeof this.styleHas === "function" && this.styleHas(type, i);
12588
- if (!lineHas) {
12589
- topOffset += heightOfLine;
12590
- continue;
12591
- }
12592
- const fillStyle = this.getValueOfPropertyAt(i, 0, "fill");
12593
- const thickness = this.getValueOfPropertyAt(i, 0, "textDecorationThickness");
12594
- const charSize = this.getHeightOfChar(i, 0);
12595
- const dy = this.getValueOfPropertyAt(i, 0, "deltaY") || 0;
12596
- const finalThickness = this.fontSize * (thickness || 0) / 1e3;
12597
- if (!fillStyle || !finalThickness) {
12598
- topOffset += heightOfLine;
12599
- continue;
12600
- }
12601
- const lineWidth = measureLineTextWidth(this, ctx, i);
12602
- if (!lineWidth) {
12603
- topOffset += heightOfLine;
12604
- continue;
12605
- }
12606
- const availableWidth = Number(this.width ?? lineWidth);
12607
- let lineLeftOffset = 0;
12608
- const align = String(this.textAlign ?? "left");
12609
- if (align === "center") lineLeftOffset = (availableWidth - lineWidth) / 2;
12610
- else if (align === "right" || align === "end") lineLeftOffset = availableWidth - lineWidth;
12611
- let drawStart = leftOffset + lineLeftOffset;
12612
- if (this.direction === "rtl") {
12613
- drawStart = this.width - drawStart - lineWidth;
12614
- }
12615
- const maxHeight = heightOfLine / this.lineHeight;
12616
- const top = topOffset + maxHeight * (1 - this._fontSizeFraction);
12617
- ctx.fillStyle = fillStyle;
12618
- ctx.fillRect(
12619
- drawStart,
12620
- top + offsetY * charSize + dy - offsetAligner * finalThickness,
12621
- lineWidth,
12622
- finalThickness
12623
- );
12624
- topOffset += heightOfLine;
12625
- }
12626
- if (typeof this._removeShadow === "function") {
12627
- try {
12628
- this._removeShadow(ctx);
12629
- } catch {
12630
- }
12631
- }
12632
- } catch {
12633
- try {
12634
- return original.call(this, ctx, type);
12635
- } catch {
12636
- }
12637
- }
12638
- };
12639
- __underlineFixInstalled = true;
12640
- console.log(`[canvas-renderer] underline-fix monkey patch installed (v${PACKAGE_VERSION})`);
12641
- }
12642
- function configHasAutoShrinkText(config) {
12643
- var _a;
12644
- if (!((_a = config == null ? void 0 : config.pages) == null ? void 0 : _a.length)) return false;
12645
- const walk = (nodes) => {
12646
- for (const node of nodes || []) {
12647
- if (!node) continue;
12648
- if (node.type === "text" && node.overflowPolicy === "auto-shrink") return true;
12649
- if (Array.isArray(node.children) && node.children.length && walk(node.children)) return true;
12650
- }
12651
- return false;
12652
- };
12653
- for (const page of config.pages) {
12654
- if (walk(page.children || [])) return true;
12655
- }
12656
- return false;
12657
- }
12658
- class PixldocsRenderer {
12659
- constructor(config) {
12660
- __publicField(this, "config");
12661
- this.config = config;
12662
- installUnderlineFix(fabric);
12663
- try {
12664
- console.log(`[canvas-renderer] PixldocsRenderer v${PACKAGE_VERSION} initialized`);
12665
- } catch {
12666
- }
12667
- }
12668
- /**
12669
- * Render a pre-resolved template config to an image using the full PageCanvas engine.
12670
- * Mounts a hidden PreviewCanvas component and captures the Fabric canvas output.
12671
- */
12672
- async render(templateConfig, options = {}) {
12673
- const pageIndex = options.pageIndex ?? 0;
12674
- const format = options.format ?? "png";
12675
- const quality = options.quality ?? 0.92;
12676
- const pixelRatio = options.pixelRatio ?? this.config.pixelRatio ?? 2;
12677
- const canvasWidth = templateConfig.canvas.width;
12678
- const canvasHeight = templateConfig.canvas.height;
12679
- const page = templateConfig.pages[pageIndex];
12680
- if (!page) {
12681
- throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
12682
- }
12683
- await ensureFontsForResolvedConfig(templateConfig);
12684
- if (!options.skipFontReadyWait) {
12685
- const hasAutoShrink = configHasAutoShrinkText(templateConfig);
12686
- const defaultWait = hasAutoShrink ? 4e3 : 1800;
12687
- await this.awaitFontsForConfig(templateConfig, options.waitForFontsMs ?? defaultWait);
12688
- }
12689
- const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
12690
- setPackageApiUrl2(this.config.imageProxyUrl);
12691
- const dataUrl = await this.renderPageViaPreviewCanvas(
12692
- templateConfig,
12693
- pageIndex,
12694
- pixelRatio,
12695
- format,
12696
- quality,
12697
- { skipFontReadyWait: options.skipFontReadyWait, waitForFontsMs: options.waitForFontsMs }
12698
- );
12699
- return {
12700
- dataUrl,
12701
- width: canvasWidth,
12702
- height: canvasHeight,
12703
- pixelWidth: canvasWidth * pixelRatio,
12704
- pixelHeight: canvasHeight * pixelRatio
12705
- };
12706
- }
12707
- /**
12708
- * Render all pages and return array of results.
12709
- */
12710
- async renderAllPages(templateConfig, options = {}) {
12711
- if (!options.skipFontReadyWait) {
12712
- const hasAutoShrink = configHasAutoShrinkText(templateConfig);
12713
- const defaultWait = hasAutoShrink ? 4e3 : 1800;
12714
- await this.awaitFontsForConfig(templateConfig, options.waitForFontsMs ?? defaultWait);
12715
- }
12716
- const results = [];
12717
- for (let i = 0; i < templateConfig.pages.length; i++) {
12718
- results.push(await this.render(templateConfig, { ...options, pageIndex: i, skipFontReadyWait: true }));
12719
- }
12720
- return results;
12721
- }
12722
- /**
12723
- * Resolve from V2 sectionState (like the server API) and render all pages.
12724
- * This is the primary external API for the package.
12725
- */
12726
- async renderFromForm(options) {
12727
- const { templateId, formSchemaId, sectionState, themeId, watermark, watermarkOptions, prefetched, ...renderOpts } = options;
12728
- const resolved = await resolveFromForm({
12729
- templateId,
12730
- formSchemaId,
12731
- sectionState,
12732
- themeId,
12733
- supabaseUrl: this.config.supabaseUrl,
12734
- supabaseAnonKey: this.config.supabaseAnonKey,
12735
- prefetched
12736
- });
12737
- const shouldWatermark = watermark ?? resolved.price > 0;
12738
- let configToRender = resolved.config;
12739
- if (shouldWatermark) {
12740
- const { injectWatermark } = await import("./canvasWatermark-pkhacGge.js");
12741
- configToRender = injectWatermark(configToRender, watermarkOptions);
12742
- }
12743
- return this.renderAllPages(configToRender, renderOpts);
12744
- }
12745
- /**
12746
- * Render a page and capture the Fabric canvas SVG output (vector, not raster).
12747
- * This is the key building block for client-side vector PDF export.
12748
- */
12749
- async renderPageSvg(templateConfig, pageIndex = 0) {
12750
- const page = templateConfig.pages[pageIndex];
12751
- if (!page) {
12752
- throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
12753
- }
12754
- await ensureFontsForResolvedConfig(templateConfig);
12755
- const hasAutoShrinkSvg = configHasAutoShrinkText(templateConfig);
12756
- await this.awaitFontsForConfig(templateConfig, hasAutoShrinkSvg ? 4e3 : 1800);
12757
- const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
12758
- setPackageApiUrl2(this.config.imageProxyUrl);
12759
- const canvasWidth = templateConfig.canvas.width;
12760
- const canvasHeight = templateConfig.canvas.height;
12761
- return this.captureSvgViaPreviewCanvas(templateConfig, pageIndex, canvasWidth, canvasHeight);
12762
- }
12763
- /**
12764
- * Render all pages and return SVG strings for each.
12765
- */
12766
- async renderAllPageSvgs(templateConfig) {
12767
- await ensureFontsForResolvedConfig(templateConfig);
12768
- const hasAutoShrinkSvg = configHasAutoShrinkText(templateConfig);
12769
- await this.awaitFontsForConfig(templateConfig, hasAutoShrinkSvg ? 4e3 : 1800);
12770
- const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
12771
- setPackageApiUrl2(this.config.imageProxyUrl);
12772
- const results = [];
12773
- for (let i = 0; i < templateConfig.pages.length; i++) {
12774
- const canvasWidth = templateConfig.canvas.width;
12775
- const canvasHeight = templateConfig.canvas.height;
12776
- results.push(await this.captureSvgViaPreviewCanvas(templateConfig, i, canvasWidth, canvasHeight));
12777
- }
12778
- return results;
12779
- }
12780
- /**
12781
- * Resolve from V2 sectionState and return SVGs for all pages (for server vector PDF).
12782
- */
12783
- async renderSvgsFromForm(options) {
12784
- const { templateId, formSchemaId, sectionState, themeId, watermark, watermarkOptions, prefetched } = options;
12785
- const resolved = await resolveFromForm({
12786
- templateId,
12787
- formSchemaId,
12788
- sectionState,
12789
- themeId,
12790
- supabaseUrl: this.config.supabaseUrl,
12791
- supabaseAnonKey: this.config.supabaseAnonKey,
12792
- prefetched
12793
- });
12794
- const shouldWatermark = watermark ?? resolved.price > 0;
12795
- let configToRender = resolved.config;
12796
- if (shouldWatermark) {
12797
- const { injectWatermark } = await import("./canvasWatermark-pkhacGge.js");
12798
- configToRender = injectWatermark(configToRender, watermarkOptions);
12799
- }
12800
- return this.renderAllPageSvgs(configToRender);
12801
- }
12802
- /**
12803
- * Render a pre-resolved template config to a vector PDF.
12804
- * Returns a Blob and ArrayBuffer.
12805
- */
12806
- async renderPdf(templateConfig, options) {
12807
- const svgs = await this.renderAllPageSvgs(templateConfig);
12808
- const { assemblePdfFromSvgs: assemblePdfFromSvgs2 } = await Promise.resolve().then(() => pdfExport);
12809
- return assemblePdfFromSvgs2(svgs, { title: options == null ? void 0 : options.title, fontBaseUrl: options == null ? void 0 : options.fontBaseUrl });
12810
- }
12811
- /**
12812
- * Resolve from V2 sectionState and render a vector PDF.
12813
- * This is the primary PDF export API — mirrors renderFromForm() but returns a PDF.
12814
- */
12815
- async renderPdfFromForm(options) {
12816
- const { templateId, formSchemaId, sectionState, themeId, watermark, watermarkOptions, prefetched, title, fontBaseUrl } = options;
12817
- const resolved = await resolveFromForm({
12818
- templateId,
12819
- formSchemaId,
12820
- sectionState,
12821
- themeId,
12822
- supabaseUrl: this.config.supabaseUrl,
12823
- supabaseAnonKey: this.config.supabaseAnonKey,
12824
- prefetched
12825
- });
12826
- const shouldWatermark = watermark ?? resolved.price > 0;
12827
- let configToRender = resolved.config;
12828
- if (shouldWatermark) {
12829
- const { injectWatermark } = await import("./canvasWatermark-pkhacGge.js");
12830
- configToRender = injectWatermark(configToRender, watermarkOptions);
12831
- }
12832
- const svgs = await this.renderAllPageSvgs(configToRender);
12833
- const { assemblePdfFromSvgs: assemblePdfFromSvgs2 } = await Promise.resolve().then(() => pdfExport);
12834
- return assemblePdfFromSvgs2(svgs, { title: title ?? resolved.config.name, fontBaseUrl });
12835
- }
12836
- async renderById(templateId, formData, options) {
12837
- const resolved = await resolveTemplateData({
12838
- templateId,
12839
- formData,
12840
- supabaseUrl: this.config.supabaseUrl,
12841
- supabaseAnonKey: this.config.supabaseAnonKey
12842
- });
12843
- return this.render(resolved.config, options);
12844
- }
12845
- /**
12846
- * Convenience: fetch by ID with flat data and render ALL pages.
12847
- */
12848
- async renderAllById(templateId, formData, options) {
12849
- const resolved = await resolveTemplateData({
12850
- templateId,
12851
- formData,
12852
- supabaseUrl: this.config.supabaseUrl,
12853
- supabaseAnonKey: this.config.supabaseAnonKey
12854
- });
12855
- return this.renderAllPages(resolved.config, options);
12856
- }
12857
- // ─── Internal: render a page using the full PreviewCanvas engine ───
12858
- getExpectedImageCount(config, pageIndex) {
12859
- const page = config.pages[pageIndex];
12860
- if (!(page == null ? void 0 : page.children)) return 0;
12861
- let count = 0;
12862
- const walk = (nodes) => {
12863
- for (const node of nodes) {
12864
- if (!node || node.visible === false) continue;
12865
- const src = typeof node.src === "string" ? node.src.trim() : "";
12866
- const imageUrl = typeof node.imageUrl === "string" ? node.imageUrl.trim() : "";
12867
- if (node.type === "image" && (src || imageUrl)) count += 1;
12868
- if (Array.isArray(node.children) && node.children.length > 0) {
12869
- walk(node.children);
12870
- }
12871
- }
12872
- };
12873
- walk(page.children);
12874
- return count;
12875
- }
12876
- waitForCanvasImages(container, expectedImageCount, maxWaitMs = 15e3, pollMs = 120) {
12877
- return new Promise((resolve) => {
12878
- const start = Date.now();
12879
- let stableFrames = 0;
12880
- let lastSummary = "";
12881
- const isRenderableImage = (value) => value instanceof HTMLImageElement && value.complete && value.naturalWidth > 0 && value.naturalHeight > 0;
12882
- const collectRenderableImages = (obj, seen) => {
12883
- if (!obj || typeof obj !== "object") return;
12884
- const candidates = [obj._element, obj._originalElement, obj._filteredEl, obj._cacheCanvasEl];
12885
- for (const candidate of candidates) {
12886
- if (isRenderableImage(candidate)) {
12887
- seen.add(candidate);
12888
- } else if (candidate instanceof HTMLImageElement) {
12889
- return false;
12890
- }
12891
- }
12892
- const nested = Array.isArray(obj._objects) ? obj._objects : Array.isArray(obj.objects) ? obj.objects : [];
12893
- for (const child of nested) {
12894
- if (collectRenderableImages(child, seen) === false) return false;
12895
- }
12896
- return true;
12897
- };
12898
- const getFabricCanvas = () => {
12899
- const registry2 = window.__fabricCanvasRegistry;
12900
- if (registry2 instanceof Map) {
12901
- for (const entry of registry2.values()) {
12902
- const canvas = entry == null ? void 0 : entry.canvas;
12903
- const el = (canvas == null ? void 0 : canvas.lowerCanvasEl) || (canvas == null ? void 0 : canvas.upperCanvasEl);
12904
- if (el && container.contains(el)) return canvas;
12905
- }
12906
- }
12907
- return null;
12908
- };
12909
- const settle = () => requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
12910
- const getImageDebugInfo = (obj, bucket) => {
12911
- if (!obj || typeof obj !== "object") return;
12912
- const candidates = [obj._element, obj._originalElement, obj._filteredEl, obj._cacheCanvasEl];
12913
- for (const candidate of candidates) {
12914
- if (candidate instanceof HTMLImageElement) {
12915
- bucket.push({
12916
- id: obj.__docuforgeId || obj.id || "unknown",
12917
- src: (candidate.currentSrc || candidate.src || "").slice(0, 240),
12918
- complete: candidate.complete,
12919
- naturalWidth: candidate.naturalWidth,
12920
- naturalHeight: candidate.naturalHeight
12921
- });
12922
- }
12923
- }
12924
- const nested = Array.isArray(obj._objects) ? obj._objects : Array.isArray(obj.objects) ? obj.objects : [];
12925
- for (const child of nested) {
12926
- getImageDebugInfo(child, bucket);
12927
- }
12928
- };
12929
- const check = () => {
12930
- const elapsed = Date.now() - start;
12931
- const domImages = Array.from(container.querySelectorAll("img"));
12932
- const allDomLoaded = domImages.every((img) => img.complete && img.naturalWidth > 0 && img.naturalHeight > 0);
12933
- const fabricCanvas = getFabricCanvas();
12934
- const fabricObjects = fabricCanvas && typeof fabricCanvas.getObjects === "function" ? fabricCanvas.getObjects() : [];
12935
- const renderableImages = /* @__PURE__ */ new Set();
12936
- const fabricReady = fabricObjects.every((obj) => collectRenderableImages(obj, renderableImages) !== false);
12937
- const actualImageCount = Math.max(domImages.length, renderableImages.size);
12938
- const canvasReady = !!fabricCanvas && !!(fabricCanvas.lowerCanvasEl || fabricCanvas.upperCanvasEl);
12939
- const hasExpectedAssets = expectedImageCount === 0 ? true : actualImageCount >= expectedImageCount;
12940
- const ready = allDomLoaded && fabricReady && hasExpectedAssets;
12941
- const summary = `expected=${expectedImageCount} actual=${actualImageCount} dom=${domImages.length} fabricReady=${fabricReady} domReady=${allDomLoaded} canvasReady=${canvasReady}`;
12942
- if (summary !== lastSummary) {
12943
- lastSummary = summary;
12944
- console.log(`[canvas-renderer][asset-wait] ${summary}`);
12945
- }
12946
- if (ready) {
12947
- stableFrames += 1;
12948
- if (stableFrames >= 2) {
12949
- console.log(`[canvas-renderer][asset-wait] ready after ${elapsed}ms (${summary})`);
12950
- settle();
12951
- return;
12952
- }
12953
- } else {
12954
- stableFrames = 0;
12955
- }
12956
- if (elapsed >= maxWaitMs) {
12957
- const fabricImageDebug = [];
12958
- for (const obj of fabricObjects) {
12959
- getImageDebugInfo(obj, fabricImageDebug);
12960
- }
12961
- const domImageDebug = domImages.map((img, index) => ({
12962
- index,
12963
- src: (img.currentSrc || img.src || "").slice(0, 240),
12964
- complete: img.complete,
12965
- naturalWidth: img.naturalWidth,
12966
- naturalHeight: img.naturalHeight
12967
- }));
12968
- console.warn(`[canvas-renderer][asset-wait-timeout] elapsed=${elapsed}ms ${summary}`);
12969
- console.warn("[canvas-renderer][asset-wait-timeout][dom-images]", domImageDebug);
12970
- console.warn("[canvas-renderer][asset-wait-timeout][fabric-images]", fabricImageDebug);
12971
- settle();
12972
- return;
12973
- }
12974
- setTimeout(check, pollMs);
12975
- };
12976
- setTimeout(check, 0);
12977
- });
12978
- }
12979
- waitForCanvasScene(container, config, pageIndex, maxWaitMs = 8e3, pollMs = 50) {
12980
- return new Promise((resolve) => {
12981
- var _a, _b;
12982
- const start = Date.now();
12983
- const pageHasContent = (((_b = (_a = config.pages[pageIndex]) == null ? void 0 : _a.children) == null ? void 0 : _b.length) ?? 0) > 0;
12984
- const settle = () => requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
12985
- const check = () => {
12986
- const fabricCanvas = this.getFabricCanvasFromContainer(container);
12987
- const lowerCanvas = (fabricCanvas == null ? void 0 : fabricCanvas.lowerCanvasEl) || container.querySelector("canvas.lower-canvas, canvas");
12988
- const objectCount = typeof (fabricCanvas == null ? void 0 : fabricCanvas.getObjects) === "function" ? fabricCanvas.getObjects().length : 0;
12989
- const ready = !!lowerCanvas && (!pageHasContent || objectCount > 0);
12990
- if (ready) {
12991
- console.log(`[canvas-renderer][scene-wait] ready after ${Date.now() - start}ms (objects=${objectCount})`);
12992
- settle();
12993
- return;
12994
- }
12995
- if (Date.now() - start >= maxWaitMs) {
12996
- console.warn(`[canvas-renderer][scene-wait-timeout] elapsed=${Date.now() - start}ms objects=${objectCount} pageHasContent=${pageHasContent}`);
12997
- settle();
12998
- return;
12999
- }
13000
- setTimeout(check, pollMs);
13001
- };
13002
- setTimeout(check, 0);
13003
- });
13004
- }
13005
- async waitForRelevantFonts(config, maxWaitMs = 1800) {
13006
- if (typeof document === "undefined" || !document.fonts) return;
13007
- const descriptors = collectFontDescriptorsFromConfig(config);
13008
- if (descriptors.length === 0) return;
13009
- const loads = Promise.all(
13010
- descriptors.map((descriptor) => {
13011
- const stylePrefix = descriptor.style === "italic" ? "italic " : "";
13012
- const spec = `${stylePrefix}${descriptor.weight} 16px "${descriptor.family}"`;
13013
- return document.fonts.load(spec).catch(() => []);
13014
- })
13015
- ).then(() => void 0);
13016
- await Promise.race([
13017
- loads,
13018
- new Promise((resolve) => setTimeout(resolve, maxWaitMs))
13019
- ]);
13020
- await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
13021
- }
13022
- /**
13023
- * Block until the webfonts referenced by `config` have actually loaded
13024
- * (or `maxWaitMs` elapses). Used by the headless capture path BEFORE
13025
- * mounting `PreviewCanvas`, so the synchronous `createText` auto-shrink
13026
- * loop measures against final font metrics instead of fallback ones.
13027
- *
13028
- * Stronger than `ensureFontsForResolvedConfig` (which is fire-and-forget)
13029
- * — this awaits each `document.fonts.load(spec)` AND `document.fonts.ready`,
13030
- * racing the whole thing against `maxWaitMs` so a slow CDN can't hang the
13031
- * renderer.
13032
- */
13033
- async awaitFontsForConfig(config, maxWaitMs) {
13034
- if (typeof document === "undefined" || !document.fonts) return;
13035
- void ensureFontsForResolvedConfig(config);
13036
- await this.waitForRelevantFonts(config, maxWaitMs);
13037
- await Promise.race([
13038
- document.fonts.ready.catch(() => void 0).then(() => void 0),
13039
- new Promise((r) => setTimeout(r, Math.min(500, maxWaitMs)))
13040
- ]);
13041
- }
13042
- getNormalizedGradientStops(gradient) {
13043
- const stops = Array.isArray(gradient == null ? void 0 : gradient.stops) ? gradient.stops.map((stop) => ({
13044
- offset: Math.max(0, Math.min(1, Number((stop == null ? void 0 : stop.offset) ?? 0))),
13045
- color: String((stop == null ? void 0 : stop.color) ?? "#ffffff")
13046
- })).filter((stop) => Number.isFinite(stop.offset)).sort((a, b) => a.offset - b.offset) : [];
13047
- if (stops.length === 0) return [];
13048
- const normalized = [...stops];
13049
- if (normalized[0].offset > 0) {
13050
- normalized.unshift({ offset: 0, color: normalized[0].color });
13051
- }
13052
- if (normalized[normalized.length - 1].offset < 1) {
13053
- normalized.push({ offset: 1, color: normalized[normalized.length - 1].color });
13054
- }
13055
- return normalized;
13056
- }
13057
- paintPageBackground(ctx, page, width, height) {
13058
- var _a, _b;
13059
- const backgroundColor = ((_a = page == null ? void 0 : page.settings) == null ? void 0 : _a.backgroundColor) || "#ffffff";
13060
- const gradient = (_b = page == null ? void 0 : page.settings) == null ? void 0 : _b.backgroundGradient;
13061
- ctx.clearRect(0, 0, width, height);
13062
- ctx.fillStyle = backgroundColor;
13063
- ctx.fillRect(0, 0, width, height);
13064
- const stops = this.getNormalizedGradientStops(gradient);
13065
- if (stops.length < 2) return;
13066
- try {
13067
- let canvasGradient = null;
13068
- if ((gradient == null ? void 0 : gradient.type) === "radial") {
13069
- const cx = Number.isFinite(gradient == null ? void 0 : gradient.cx) ? gradient.cx : 0.5;
13070
- const cy = Number.isFinite(gradient == null ? void 0 : gradient.cy) ? gradient.cy : 0.5;
13071
- const centerX = width * cx;
13072
- const centerY = height * cy;
13073
- const radius = Math.max(
13074
- Math.hypot(centerX, centerY),
13075
- Math.hypot(width - centerX, centerY),
13076
- Math.hypot(centerX, height - centerY),
13077
- Math.hypot(width - centerX, height - centerY)
13078
- );
13079
- canvasGradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, radius);
13080
- } else if ((gradient == null ? void 0 : gradient.type) === "conic" && typeof ctx.createConicGradient === "function") {
13081
- const cx = Number.isFinite(gradient == null ? void 0 : gradient.cx) ? gradient.cx : 0.5;
13082
- const cy = Number.isFinite(gradient == null ? void 0 : gradient.cy) ? gradient.cy : 0.5;
13083
- const startAngle = (((gradient == null ? void 0 : gradient.angle) ?? 0) - 90) * Math.PI / 180;
13084
- canvasGradient = ctx.createConicGradient(startAngle, width * cx, height * cy);
13085
- } else {
13086
- const angleDeg = (gradient == null ? void 0 : gradient.angle) ?? 90;
13087
- const angleRad = angleDeg * Math.PI / 180;
13088
- const sinA = Math.sin(angleRad);
13089
- const cosA = Math.cos(angleRad);
13090
- const midX = width / 2;
13091
- const midY = height / 2;
13092
- const corners = [
13093
- [0, 0],
13094
- [width, 0],
13095
- [width, height],
13096
- [0, height]
13097
- ];
13098
- const projections = corners.map(([x, y]) => x * sinA - y * cosA);
13099
- const minProjection = Math.min(...projections);
13100
- const maxProjection = Math.max(...projections);
13101
- canvasGradient = ctx.createLinearGradient(
13102
- midX + minProjection * sinA,
13103
- midY - minProjection * cosA,
13104
- midX + maxProjection * sinA,
13105
- midY - maxProjection * cosA
13106
- );
13107
- }
13108
- if (!canvasGradient) return;
13109
- stops.forEach((stop) => canvasGradient.addColorStop(stop.offset, stop.color));
13110
- ctx.fillStyle = canvasGradient;
13111
- ctx.fillRect(0, 0, width, height);
13112
- } catch {
13113
- }
13114
- }
13115
- async renderPageViaPreviewCanvas(config, pageIndex, pixelRatio, format, quality, options = {}) {
13116
- const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
13117
- const canvasWidth = config.canvas.width;
13118
- const canvasHeight = config.canvas.height;
13119
- const hasAutoShrink = configHasAutoShrinkText(config);
13120
- let firstMountSettled = false;
13121
- let lateFontSettleDetected = false;
13122
- if (typeof document !== "undefined" && document.fonts && hasAutoShrink) {
13123
- document.fonts.ready.then(() => {
13124
- if (firstMountSettled) lateFontSettleDetected = true;
13125
- }).catch(() => {
13126
- });
13127
- }
13128
- return new Promise((resolve, reject) => {
13129
- const container = document.createElement("div");
13130
- container.style.cssText = `
13131
- position: fixed; left: -99999px; top: -99999px;
13132
- width: ${canvasWidth}px; height: ${canvasHeight}px;
13133
- overflow: hidden; pointer-events: none; opacity: 0;
13134
- `;
13135
- document.body.appendChild(container);
13136
- const timeout = setTimeout(() => {
13137
- cleanup();
13138
- reject(new Error("Render timeout (30s)"));
13139
- }, 3e4);
13140
- let root;
13141
- let mountKey = 0;
13142
- const cleanup = () => {
13143
- clearTimeout(timeout);
13144
- try {
13145
- root.unmount();
13146
- } catch {
13147
- }
13148
- container.remove();
13149
- };
13150
- const remountWithFreshKey = async () => {
13151
- mountKey += 1;
13152
- try {
13153
- clearMeasurementCache();
13154
- } catch {
13155
- }
13156
- try {
13157
- clearFabricCharCache();
13158
- } catch {
13159
- }
13160
- try {
13161
- root.unmount();
13162
- } catch {
13163
- }
13164
- root = createRoot(container);
13165
- await new Promise((settle) => {
13166
- const onReadyOnce = () => {
13167
- this.waitForCanvasScene(container, config, pageIndex).then(async () => {
13168
- const fabricInstance = this.getFabricCanvasFromContainer(container);
13169
- const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
13170
- await this.waitForCanvasImages(container, expectedImageCount);
13171
- await this.waitForStableTextMetrics(container, config);
13172
- await this.waitForCanvasScene(container, config, pageIndex);
13173
- if (!fabricInstance) return settle();
13174
- settle();
13175
- }).catch(() => settle());
13176
- };
13177
- root.render(
13178
- createElement(PreviewCanvas2, {
13179
- key: `remount-${mountKey}`,
13180
- config,
13181
- pageIndex,
13182
- zoom: pixelRatio,
13183
- absoluteZoom: true,
13184
- skipFontReadyWait: false,
13185
- onReady: onReadyOnce
13186
- })
13187
- );
13188
- });
13189
- };
13190
- const onReady = () => {
13191
- this.waitForCanvasScene(container, config, pageIndex).then(async () => {
13192
- try {
13193
- const fabricInstance = this.getFabricCanvasFromContainer(container);
13194
- const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
13195
- await this.waitForCanvasImages(container, expectedImageCount);
13196
- await this.waitForStableTextMetrics(container, config);
13197
- await this.waitForCanvasScene(container, config, pageIndex);
13198
- firstMountSettled = true;
13199
- if (hasAutoShrink && lateFontSettleDetected) {
13200
- console.log("[canvas-renderer][parity] late font-settle detected — remounting for auto-shrink reflow");
13201
- await remountWithFreshKey();
13202
- }
13203
- const fabricCanvas = container.querySelector("canvas.upper-canvas, canvas");
13204
- const sourceCanvas = (fabricInstance == null ? void 0 : fabricInstance.lowerCanvasEl) || container.querySelector("canvas.lower-canvas") || fabricCanvas;
13205
- const fabricInstanceAfter = this.getFabricCanvasFromContainer(container) || fabricInstance;
13206
- const sourceCanvasAfter = (fabricInstanceAfter == null ? void 0 : fabricInstanceAfter.lowerCanvasEl) || container.querySelector("canvas.lower-canvas") || sourceCanvas;
13207
- if (!sourceCanvas) {
13208
- cleanup();
13209
- reject(new Error("No canvas element found after render"));
13210
- return;
13211
- }
13212
- const exportCanvas = document.createElement("canvas");
13213
- exportCanvas.width = sourceCanvasAfter.width;
13214
- exportCanvas.height = sourceCanvasAfter.height;
13215
- const exportCtx = exportCanvas.getContext("2d");
13216
- if (!exportCtx) {
13217
- cleanup();
13218
- reject(new Error("Failed to create export canvas"));
13219
- return;
13220
- }
13221
- exportCtx.save();
13222
- exportCtx.scale(sourceCanvasAfter.width / canvasWidth, sourceCanvasAfter.height / canvasHeight);
13223
- this.paintPageBackground(exportCtx, config.pages[pageIndex], canvasWidth, canvasHeight);
13224
- exportCtx.restore();
13225
- exportCtx.drawImage(sourceCanvasAfter, 0, 0);
13226
- const mimeType = format === "jpeg" ? "image/jpeg" : format === "webp" ? "image/webp" : "image/png";
13227
- const dataUrl = exportCanvas.toDataURL(mimeType, quality);
13228
- cleanup();
13229
- resolve(dataUrl);
13230
- } catch (err) {
13231
- cleanup();
13232
- reject(err);
13233
- }
13234
- });
13235
- };
13236
- root = createRoot(container);
13237
- root.render(
13238
- createElement(PreviewCanvas2, {
13239
- config,
13240
- pageIndex,
13241
- zoom: pixelRatio,
13242
- absoluteZoom: true,
13243
- skipFontReadyWait: false,
13244
- onReady
13245
- })
13246
- );
13247
- });
13248
- }
13249
- // ─── Internal: capture SVG from a rendered Fabric canvas ───
13250
- //
13251
- // APPROACH: Use the SAME PreviewCanvas that renders perfect PNGs, then call
13252
- // Fabric's toSVG() on that canvas. This guarantees 100% layout parity —
13253
- // the SVG is a vector snapshot of exactly what's on screen.
13254
- //
13255
- // The trick: before calling toSVG(), we temporarily neutralize the viewport
13256
- // transform and retina scaling so Fabric emits coordinates in logical
13257
- // document space (e.g. 612x792) instead of inflated pixel space.
13258
- captureSvgViaPreviewCanvas(config, pageIndex, canvasWidth, canvasHeight) {
13259
- return new Promise(async (resolve, reject) => {
13260
- const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
13261
- const hasAutoShrink = configHasAutoShrinkText(config);
13262
- const container = document.createElement("div");
13263
- container.style.cssText = `
13264
- position: fixed; left: -99999px; top: -99999px;
13265
- width: ${canvasWidth}px; height: ${canvasHeight}px;
13266
- overflow: hidden; pointer-events: none; opacity: 0;
13267
- `;
13268
- document.body.appendChild(container);
13269
- const timeout = setTimeout(() => {
13270
- cleanup();
13271
- reject(new Error("SVG render timeout (30s)"));
13272
- }, 3e4);
13273
- let root = null;
13274
- let mountKey = 0;
13275
- let didPreviewParityRemount = false;
13276
- const cleanup = () => {
13277
- clearTimeout(timeout);
13278
- try {
13279
- root == null ? void 0 : root.unmount();
13280
- } catch {
13281
- }
13282
- container.remove();
13283
- };
13284
- const mountPreview = (readyHandler) => {
13285
- root = createRoot(container);
13286
- root.render(
13287
- createElement(PreviewCanvas2, {
13288
- key: `svg-capture-${mountKey}`,
13289
- config,
13290
- pageIndex,
13291
- zoom: 1,
13292
- absoluteZoom: true,
13293
- skipFontReadyWait: false,
13294
- onReady: readyHandler
13295
- })
13296
- );
13297
- };
13298
- const remountForPreviewParity = async () => {
13299
- if (didPreviewParityRemount) return;
13300
- didPreviewParityRemount = true;
13301
- mountKey += 1;
13302
- try {
13303
- clearMeasurementCache();
13304
- } catch {
13305
- }
13306
- try {
13307
- clearFabricCharCache();
13308
- } catch {
13309
- }
13310
- try {
13311
- root == null ? void 0 : root.unmount();
13312
- } catch {
13313
- }
13314
- await new Promise((settle) => {
13315
- mountPreview(() => {
13316
- this.waitForCanvasScene(container, config, pageIndex).then(async () => {
13317
- const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
13318
- await this.waitForCanvasImages(container, expectedImageCount);
13319
- await this.waitForStableTextMetrics(container, config);
13320
- await this.waitForCanvasScene(container, config, pageIndex);
13321
- settle();
13322
- }).catch(() => settle());
13323
- });
13324
- });
13325
- };
13326
- const onReady = () => {
13327
- this.waitForCanvasScene(container, config, pageIndex).then(async () => {
13328
- var _a, _b;
13329
- try {
13330
- const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
13331
- await this.waitForCanvasImages(container, expectedImageCount);
13332
- await this.waitForStableTextMetrics(container, config);
13333
- await this.waitForCanvasScene(container, config, pageIndex);
13334
- if (hasAutoShrink && !didPreviewParityRemount) {
13335
- console.log("[canvas-renderer][svg-parity] remounting once to match PixldocsPreview auto-shrink stabilization");
13336
- await remountForPreviewParity();
13337
- }
13338
- const fabricInstance = this.getFabricCanvasFromContainer(container);
13339
- if (!fabricInstance) {
13340
- cleanup();
13341
- reject(new Error("No Fabric canvas instance found for SVG capture"));
13342
- return;
13343
- }
13344
- const prevVPT = fabricInstance.viewportTransform ? [...fabricInstance.viewportTransform] : void 0;
13345
- const prevSvgVPT = fabricInstance.svgViewportTransformation;
13346
- const prevRetina = fabricInstance.enableRetinaScaling;
13347
- const prevWidth = fabricInstance.width;
13348
- const prevHeight = fabricInstance.height;
13349
- fabricInstance.viewportTransform = [1, 0, 0, 1, 0, 0];
13350
- fabricInstance.svgViewportTransformation = false;
13351
- fabricInstance.enableRetinaScaling = false;
13352
- fabricInstance.setDimensions(
13353
- { width: canvasWidth, height: canvasHeight },
13354
- { cssOnly: false, backstoreOnly: false }
13355
- );
13356
- const rawSvgString = fabricInstance.toSVG();
13357
- const svgString = this.normalizeSvgDimensions(
13358
- rawSvgString,
13359
- canvasWidth,
13360
- canvasHeight
13361
- );
13362
- fabricInstance.enableRetinaScaling = prevRetina;
13363
- fabricInstance.setDimensions(
13364
- { width: prevWidth, height: prevHeight },
13365
- { cssOnly: false, backstoreOnly: false }
13366
- );
13367
- if (prevVPT) fabricInstance.viewportTransform = prevVPT;
13368
- fabricInstance.svgViewportTransformation = prevSvgVPT;
13369
- const page = config.pages[pageIndex];
13370
- const backgroundColor = ((_a = page == null ? void 0 : page.settings) == null ? void 0 : _a.backgroundColor) || "#ffffff";
13371
- const backgroundGradient = (_b = page == null ? void 0 : page.settings) == null ? void 0 : _b.backgroundGradient;
13372
- cleanup();
13373
- resolve({
13374
- svg: svgString,
13375
- width: canvasWidth,
13376
- height: canvasHeight,
13377
- backgroundColor,
13378
- backgroundGradient
13379
- });
13380
- } catch (err) {
13381
- cleanup();
13382
- reject(err);
13383
- }
13384
- });
13385
- };
13386
- mountPreview(onReady);
13387
- });
13388
- }
13389
- /**
13390
- * Normalize the SVG's width/height/viewBox to match the logical page dimensions.
13391
- * Fabric's toSVG() may output dimensions scaled by the canvas element's actual
13392
- * pixel size (e.g., 2x due to devicePixelRatio), causing svg2pdf to render
13393
- * content at the wrong scale. This rewrites the root <svg> attributes to ensure
13394
- * the SVG coordinate system matches the intended page size exactly.
13395
- */
13396
- normalizeSvgDimensions(svg, targetWidth, targetHeight) {
13397
- const widthMatch = svg.match(/<svg[^>]*\bwidth="([^"]+)"/i);
13398
- const heightMatch = svg.match(/<svg[^>]*\bheight="([^"]+)"/i);
13399
- const svgWidth = widthMatch ? parseFloat(widthMatch[1]) : targetWidth;
13400
- const svgHeight = heightMatch ? parseFloat(heightMatch[1]) : targetHeight;
13401
- console.log(
13402
- `[canvas-renderer][svg-normalize] root ${svgWidth}x${svgHeight} → page ${targetWidth}x${targetHeight}`
13403
- );
13404
- let normalized = svg;
13405
- if (/\bwidth="[^"]*"/i.test(normalized)) {
13406
- normalized = normalized.replace(/(<svg[^>]*\b)width="[^"]*"/i, `$1width="${targetWidth}"`);
13407
- } else {
13408
- normalized = normalized.replace(/<svg\b/i, `<svg width="${targetWidth}"`);
13409
- }
13410
- if (/\bheight="[^"]*"/i.test(normalized)) {
13411
- normalized = normalized.replace(/(<svg[^>]*\b)height="[^"]*"/i, `$1height="${targetHeight}"`);
13412
- } else {
13413
- normalized = normalized.replace(/<svg\b/i, `<svg height="${targetHeight}"`);
13414
- }
13415
- const viewBox = `0 0 ${targetWidth} ${targetHeight}`;
13416
- if (/\bviewBox="[^"]*"/i.test(normalized)) {
13417
- normalized = normalized.replace(/viewBox="[^"]*"/i, `viewBox="${viewBox}"`);
13418
- } else {
13419
- normalized = normalized.replace(/<svg\b/i, `<svg viewBox="${viewBox}"`);
13420
- }
13421
- normalized = normalized.replace(/="undefined"/g, '="0"');
13422
- normalized = normalized.replace(/="NaN"/g, '="0"');
13423
- if (/\bx="[^"]*"/i.test(normalized)) {
13424
- normalized = normalized.replace(/(<svg[^>]*\b)x="[^"]*"/i, '$1x="0"');
13425
- } else {
13426
- normalized = normalized.replace(/<svg\b/i, '<svg x="0"');
13427
- }
13428
- if (/\by="[^"]*"/i.test(normalized)) {
13429
- normalized = normalized.replace(/(<svg[^>]*\b)y="[^"]*"/i, '$1y="0"');
13430
- } else {
13431
- normalized = normalized.replace(/<svg\b/i, '<svg y="0"');
13432
- }
13433
- normalized = normalized.replace(/\bpreserveAspectRatio="[^"]*"/i, 'preserveAspectRatio="none"');
13434
- if (!/\bpreserveAspectRatio="[^"]*"/i.test(normalized)) {
13435
- normalized = normalized.replace(/<svg\b/i, '<svg preserveAspectRatio="none"');
13436
- }
13437
- return normalized;
13438
- }
13439
- /**
13440
- * Find the Fabric.Canvas instance that belongs to a given container element,
13441
- * using the global __fabricCanvasRegistry (set by PageCanvas).
13442
- */
13443
- getFabricCanvasFromContainer(container) {
13444
- const registry2 = window.__fabricCanvasRegistry;
13445
- if (registry2 instanceof Map) {
13446
- for (const entry of registry2.values()) {
13447
- const canvas = (entry == null ? void 0 : entry.canvas) || entry;
13448
- if (!canvas || typeof canvas.toSVG !== "function") continue;
13449
- const el = canvas.lowerCanvasEl || canvas.upperCanvasEl;
13450
- if (el && container.contains(el)) return canvas;
13451
- }
13452
- }
13453
- return null;
13454
- }
13455
- async waitForStableTextMetrics(container, config) {
13456
- var _a, _b, _c;
13457
- if (typeof document !== "undefined") {
13458
- void ensureFontsForResolvedConfig(config);
13459
- await this.waitForRelevantFonts(config);
13460
- }
13461
- const fabricInstance = this.getFabricCanvasFromContainer(container);
13462
- if (!(fabricInstance == null ? void 0 : fabricInstance.getObjects)) return;
13463
- const waitForPaint = () => new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(() => r())));
13464
- const primeCharBounds = (obj) => {
13465
- if (obj instanceof fabric.Textbox) {
13466
- const lines = obj._textLines;
13467
- if (Array.isArray(lines)) {
13468
- for (let i = 0; i < lines.length; i++) {
13469
- try {
13470
- obj.getLineWidth(i);
13471
- } catch {
13472
- }
13473
- }
13474
- }
13475
- obj.dirty = true;
13476
- return;
13477
- }
13478
- if (obj instanceof fabric.Group) {
13479
- obj.getObjects().forEach(primeCharBounds);
13480
- obj.dirty = true;
13481
- }
13482
- };
13483
- fabricInstance.getObjects().forEach(primeCharBounds);
13484
- (_a = fabricInstance.calcOffset) == null ? void 0 : _a.call(fabricInstance);
13485
- (_b = fabricInstance.renderAll) == null ? void 0 : _b.call(fabricInstance);
13486
- await waitForPaint();
13487
- (_c = fabricInstance.renderAll) == null ? void 0 : _c.call(fabricInstance);
13488
- await waitForPaint();
13489
- }
13490
- }
13491
- const FONT_WEIGHT_LABELS = {
13492
- 300: "Light",
13493
- 400: "Regular",
13494
- 500: "Medium",
13495
- 600: "SemiBold",
13496
- 700: "Bold"
13497
- };
13498
- function resolveFontWeight(weight) {
13499
- if (weight <= 350) return 300;
13500
- if (weight <= 450) return 400;
13501
- if (weight <= 550) return 500;
13502
- if (weight <= 650) return 600;
13503
- return 700;
12126
+ const FONT_WEIGHT_LABELS = {
12127
+ 300: "Light",
12128
+ 400: "Regular",
12129
+ 500: "Medium",
12130
+ 600: "SemiBold",
12131
+ 700: "Bold"
12132
+ };
12133
+ function resolveFontWeight(weight) {
12134
+ if (weight <= 350) return 300;
12135
+ if (weight <= 450) return 400;
12136
+ if (weight <= 550) return 500;
12137
+ if (weight <= 650) return 600;
12138
+ return 700;
13504
12139
  }
13505
12140
  const FONT_FALLBACK_SYMBOLS = "Noto Sans";
13506
12141
  const FONT_FALLBACK_DEVANAGARI = "Hind";
@@ -13985,327 +12620,1731 @@ const FONT_FILES = {
13985
12620
  italic: "CormorantGaramond-Italic.ttf",
13986
12621
  boldItalic: "CormorantGaramond-BoldItalic.ttf"
13987
12622
  }
13988
- };
13989
- const WEIGHT_TO_KEYS = {
13990
- 300: ["light", "regular"],
13991
- 400: ["regular"],
13992
- 500: ["medium", "regular"],
13993
- 600: ["semibold", "bold", "regular"],
13994
- 700: ["bold", "regular"]
13995
- };
13996
- const WEIGHT_TO_ITALIC_KEYS = {
13997
- 300: ["lightItalic", "italic", "light", "regular"],
13998
- 400: ["italic", "regular"],
13999
- 500: ["mediumItalic", "italic", "medium", "regular"],
14000
- 600: ["semiboldItalic", "boldItalic", "italic", "semibold", "bold", "regular"],
14001
- 700: ["boldItalic", "italic", "bold", "regular"]
14002
- };
14003
- function getFontPathForWeight(files, weight, isItalic = false) {
14004
- const keys = isItalic ? WEIGHT_TO_ITALIC_KEYS[weight] : WEIGHT_TO_KEYS[weight];
14005
- if (!keys) return files.regular;
14006
- for (const k of keys) {
14007
- const path = files[k];
14008
- if (path) return path;
12623
+ };
12624
+ const WEIGHT_TO_KEYS = {
12625
+ 300: ["light", "regular"],
12626
+ 400: ["regular"],
12627
+ 500: ["medium", "regular"],
12628
+ 600: ["semibold", "bold", "regular"],
12629
+ 700: ["bold", "regular"]
12630
+ };
12631
+ const WEIGHT_TO_ITALIC_KEYS = {
12632
+ 300: ["lightItalic", "italic", "light", "regular"],
12633
+ 400: ["italic", "regular"],
12634
+ 500: ["mediumItalic", "italic", "medium", "regular"],
12635
+ 600: ["semiboldItalic", "boldItalic", "italic", "semibold", "bold", "regular"],
12636
+ 700: ["boldItalic", "italic", "bold", "regular"]
12637
+ };
12638
+ function getFontPathForWeight(files, weight, isItalic = false) {
12639
+ const keys = isItalic ? WEIGHT_TO_ITALIC_KEYS[weight] : WEIGHT_TO_KEYS[weight];
12640
+ if (!keys) return files.regular;
12641
+ for (const k of keys) {
12642
+ const path = files[k];
12643
+ if (path) return path;
12644
+ }
12645
+ return files.regular;
12646
+ }
12647
+ function isItalicPath(files, path) {
12648
+ return path === files.italic || path === files.boldItalic || path === files.lightItalic || path === files.mediumItalic || path === files.semiboldItalic;
12649
+ }
12650
+ function getJsPDFFontName(fontName) {
12651
+ return fontName.replace(/\s+/g, "");
12652
+ }
12653
+ function getEmbeddedJsPDFFontName(fontName, weight, isItalic = false) {
12654
+ const resolved = resolveFontWeight(weight);
12655
+ const label = FONT_WEIGHT_LABELS[resolved];
12656
+ const italicSuffix = isItalic ? "Italic" : "";
12657
+ return `${getJsPDFFontName(fontName)}-${label}${italicSuffix}`;
12658
+ }
12659
+ function isFontAvailable(fontName) {
12660
+ return fontName in FONT_FILES;
12661
+ }
12662
+ const ttfCache = /* @__PURE__ */ new Map();
12663
+ async function fetchTTFAsBase64(url) {
12664
+ const cached = ttfCache.get(url);
12665
+ if (cached) return cached;
12666
+ try {
12667
+ const res = await fetch(url);
12668
+ if (!res.ok) return null;
12669
+ const buf = await res.arrayBuffer();
12670
+ const bytes = new Uint8Array(buf);
12671
+ let binary = "";
12672
+ for (let i = 0; i < bytes.length; i++) {
12673
+ binary += String.fromCharCode(bytes[i]);
12674
+ }
12675
+ const b64 = btoa(binary);
12676
+ ttfCache.set(url, b64);
12677
+ return b64;
12678
+ } catch {
12679
+ return null;
12680
+ }
12681
+ }
12682
+ async function embedFont(pdf, fontName, weight, fontBaseUrl, isItalic = false) {
12683
+ const fontFiles = FONT_FILES[fontName];
12684
+ if (!fontFiles) return false;
12685
+ const baseUrl = fontBaseUrl.endsWith("/") ? fontBaseUrl : fontBaseUrl + "/";
12686
+ const resolvedWeight = resolveFontWeight(weight);
12687
+ const fontPath = getFontPathForWeight(fontFiles, resolvedWeight, isItalic);
12688
+ if (!fontPath) return false;
12689
+ const hasItalicFile = isItalic && isItalicPath(fontFiles, fontPath);
12690
+ const jsPdfFontName = getEmbeddedJsPDFFontName(fontName, weight, hasItalicFile);
12691
+ const label = FONT_WEIGHT_LABELS[resolvedWeight];
12692
+ const italicSuffix = hasItalicFile ? "Italic" : "";
12693
+ const fileName = `${getJsPDFFontName(fontName)}-${label}${italicSuffix}.ttf`;
12694
+ const url = baseUrl + fontPath;
12695
+ try {
12696
+ const b64 = await fetchTTFAsBase64(url);
12697
+ if (!b64) return false;
12698
+ pdf.addFileToVFS(fileName, b64);
12699
+ pdf.addFont(fileName, jsPdfFontName, "normal");
12700
+ if (fontName !== jsPdfFontName) {
12701
+ try {
12702
+ pdf.addFont(fileName, fontName, "normal");
12703
+ } catch {
12704
+ }
12705
+ }
12706
+ return true;
12707
+ } catch (e) {
12708
+ console.warn(`[pdf-fonts] Failed to embed ${fontName} w${weight}:`, e);
12709
+ return false;
12710
+ }
12711
+ }
12712
+ async function embedFontsForConfig(pdf, config, fontBaseUrl) {
12713
+ const fontKeys = /* @__PURE__ */ new Set();
12714
+ const SEP = "";
12715
+ const walkElements = (elements) => {
12716
+ for (const el of elements) {
12717
+ if (el.fontFamily) {
12718
+ const w = resolveFontWeight(el.fontWeight ?? 400);
12719
+ fontKeys.add(`${el.fontFamily}${SEP}${w}`);
12720
+ }
12721
+ if (el.styles && typeof el.styles === "object") {
12722
+ for (const lineKey of Object.keys(el.styles)) {
12723
+ const lineStyles = el.styles[lineKey];
12724
+ if (lineStyles && typeof lineStyles === "object") {
12725
+ for (const charKey of Object.keys(lineStyles)) {
12726
+ const s = lineStyles[charKey];
12727
+ if (s == null ? void 0 : s.fontFamily) {
12728
+ const w = resolveFontWeight(s.fontWeight ?? 400);
12729
+ fontKeys.add(`${s.fontFamily}${SEP}${w}`);
12730
+ }
12731
+ }
12732
+ }
12733
+ }
12734
+ }
12735
+ if (el.children) walkElements(el.children);
12736
+ if (el.objects) walkElements(el.objects);
12737
+ }
12738
+ };
12739
+ for (const page of (config == null ? void 0 : config.pages) || []) {
12740
+ if (page.children) walkElements(page.children);
12741
+ if (page.elements) walkElements(page.elements);
12742
+ }
12743
+ fontKeys.add(`${FONT_FALLBACK_SYMBOLS}${SEP}400`);
12744
+ for (const w of [300, 400, 500, 600, 700]) {
12745
+ fontKeys.add(`${FONT_FALLBACK_DEVANAGARI}${SEP}${w}`);
12746
+ }
12747
+ const embedded = /* @__PURE__ */ new Set();
12748
+ const tasks = [];
12749
+ for (const key of fontKeys) {
12750
+ const sep = key.indexOf(SEP);
12751
+ const fontName = key.slice(0, sep);
12752
+ const weight = parseInt(key.slice(sep + 1), 10);
12753
+ if (!isFontAvailable(fontName)) continue;
12754
+ tasks.push(
12755
+ embedFont(pdf, fontName, weight, fontBaseUrl).then((ok) => {
12756
+ if (ok) embedded.add(key);
12757
+ })
12758
+ );
12759
+ }
12760
+ await Promise.all(tasks);
12761
+ console.log(`[pdf-fonts] Embedded ${embedded.size} font variants from config`);
12762
+ return embedded;
12763
+ }
12764
+ function isDevanagari(char) {
12765
+ const c = char.codePointAt(0) ?? 0;
12766
+ return c >= 2304 && c <= 2431 || c >= 43232 && c <= 43263 || c >= 7376 && c <= 7423;
12767
+ }
12768
+ function containsDevanagari(text) {
12769
+ if (!text) return false;
12770
+ for (const char of text) {
12771
+ if (isDevanagari(char)) return true;
12772
+ }
12773
+ return false;
12774
+ }
12775
+ function isBasicLatinOrLatin1(char) {
12776
+ const c = char.codePointAt(0) ?? 0;
12777
+ return c <= 591;
12778
+ }
12779
+ function classifyChar(char) {
12780
+ if (isBasicLatinOrLatin1(char)) return "main";
12781
+ if (isDevanagari(char)) return "devanagari";
12782
+ return "symbol";
12783
+ }
12784
+ function splitIntoRuns(text) {
12785
+ if (!text) return [];
12786
+ const runs = [];
12787
+ let currentType = null;
12788
+ let currentText = "";
12789
+ for (const char of text) {
12790
+ const type = classifyChar(char);
12791
+ if (type !== currentType && currentText) {
12792
+ runs.push({ text: currentText, runType: currentType });
12793
+ currentText = "";
12794
+ }
12795
+ currentType = type;
12796
+ currentText += char;
12797
+ }
12798
+ if (currentText && currentType) {
12799
+ runs.push({ text: currentText, runType: currentType });
12800
+ }
12801
+ return runs;
12802
+ }
12803
+ function rewriteSvgFontsForJsPDF(svgStr) {
12804
+ var _a, _b;
12805
+ const parser = new DOMParser();
12806
+ const doc = parser.parseFromString(svgStr, "image/svg+xml");
12807
+ const textEls = doc.querySelectorAll("text, tspan, textPath");
12808
+ const readStyleToken = (style, prop) => {
12809
+ var _a2;
12810
+ const match = style.match(new RegExp(`${prop}\\s*:\\s*([^;]+)`, "i"));
12811
+ return ((_a2 = match == null ? void 0 : match[1]) == null ? void 0 : _a2.trim()) || null;
12812
+ };
12813
+ const resolveInheritedValue = (el, attr, styleProp = attr) => {
12814
+ var _a2;
12815
+ let current = el;
12816
+ while (current) {
12817
+ const attrVal = (_a2 = current.getAttribute(attr)) == null ? void 0 : _a2.trim();
12818
+ if (attrVal) return attrVal;
12819
+ const styleVal = readStyleToken(current.getAttribute("style") || "", styleProp);
12820
+ if (styleVal) return styleVal;
12821
+ current = current.parentElement;
12822
+ }
12823
+ return null;
12824
+ };
12825
+ const resolveWeightNum = (weightRaw) => {
12826
+ const parsedWeight = Number.parseInt(weightRaw, 10);
12827
+ return Number.isFinite(parsedWeight) ? parsedWeight : /bold/i.test(weightRaw) ? 700 : /medium/i.test(weightRaw) ? 500 : /semi/i.test(weightRaw) ? 600 : /light/i.test(weightRaw) ? 300 : 400;
12828
+ };
12829
+ const buildStyleString = (existingStyle, fontName) => {
12830
+ const stylePairs = existingStyle.split(";").map((part) => part.trim()).filter(Boolean).filter((part) => !/^font-family\s*:/i.test(part) && !/^font-weight\s*:/i.test(part) && !/^font-style\s*:/i.test(part));
12831
+ stylePairs.push(`font-family: ${fontName}`);
12832
+ stylePairs.push(`font-weight: normal`);
12833
+ stylePairs.push(`font-style: normal`);
12834
+ return stylePairs.join("; ");
12835
+ };
12836
+ for (const el of textEls) {
12837
+ const inlineStyle = el.getAttribute("style") || "";
12838
+ const rawFf = resolveInheritedValue(el, "font-family");
12839
+ if (!rawFf) continue;
12840
+ const clean = (_a = rawFf.split(",")[0]) == null ? void 0 : _a.replace(/['"]/g, "").trim();
12841
+ if (!isFontAvailable(clean)) continue;
12842
+ const weightRaw = resolveInheritedValue(el, "font-weight") || "400";
12843
+ const styleRaw = resolveInheritedValue(el, "font-style") || "normal";
12844
+ const weight = resolveWeightNum(weightRaw);
12845
+ const resolved = resolveFontWeight(weight);
12846
+ const isItalic = /italic|oblique/i.test(styleRaw);
12847
+ const jsPdfName = getEmbeddedJsPDFFontName(clean, resolved, isItalic);
12848
+ el.setAttribute("data-source-font-family", clean);
12849
+ el.setAttribute("data-source-font-weight", String(resolved));
12850
+ el.setAttribute("data-source-font-style", isItalic ? "italic" : "normal");
12851
+ const directText = Array.from(el.childNodes).filter((n) => n.nodeType === 3).map((n) => n.textContent || "").join("");
12852
+ const hasDevanagari = containsDevanagari(directText);
12853
+ if (hasDevanagari && directText.length > 0) {
12854
+ const devanagariWeight = resolveFontWeight(weight);
12855
+ const devanagariJsPdfName = getEmbeddedJsPDFFontName(FONT_FALLBACK_DEVANAGARI, devanagariWeight);
12856
+ const symbolJsPdfName = isFontAvailable(FONT_FALLBACK_SYMBOLS) ? getEmbeddedJsPDFFontName(FONT_FALLBACK_SYMBOLS, 400) : jsPdfName;
12857
+ const childNodes = Array.from(el.childNodes);
12858
+ for (const node of childNodes) {
12859
+ if (node.nodeType !== 3 || !node.textContent) continue;
12860
+ const runs = splitIntoRuns(node.textContent);
12861
+ if (runs.length <= 1 && ((_b = runs[0]) == null ? void 0 : _b.runType) !== "devanagari") continue;
12862
+ const fragment = doc.createDocumentFragment();
12863
+ for (const run of runs) {
12864
+ const tspan = doc.createElementNS("http://www.w3.org/2000/svg", "tspan");
12865
+ let runFont;
12866
+ if (run.runType === "devanagari") {
12867
+ runFont = devanagariJsPdfName;
12868
+ } else if (run.runType === "symbol") {
12869
+ runFont = symbolJsPdfName;
12870
+ } else {
12871
+ runFont = jsPdfName;
12872
+ }
12873
+ tspan.setAttribute("font-family", runFont);
12874
+ tspan.setAttribute("font-weight", "normal");
12875
+ tspan.setAttribute("font-style", "normal");
12876
+ tspan.textContent = run.text;
12877
+ fragment.appendChild(tspan);
12878
+ }
12879
+ el.replaceChild(fragment, node);
12880
+ }
12881
+ el.setAttribute("font-family", jsPdfName);
12882
+ el.setAttribute("font-weight", "normal");
12883
+ el.setAttribute("font-style", "normal");
12884
+ el.setAttribute("style", buildStyleString(inlineStyle, jsPdfName));
12885
+ } else {
12886
+ el.setAttribute("font-family", jsPdfName);
12887
+ el.setAttribute("font-weight", "normal");
12888
+ el.setAttribute("font-style", "normal");
12889
+ el.setAttribute("style", buildStyleString(inlineStyle, jsPdfName));
12890
+ }
12891
+ }
12892
+ return new XMLSerializer().serializeToString(doc.documentElement);
12893
+ }
12894
+ function extractFontFamiliesFromSvgs(svgs) {
12895
+ const families = /* @__PURE__ */ new Set();
12896
+ const regex = /font-family[=:]\s*["']?([^"';},]+)/gi;
12897
+ for (const svg of svgs) {
12898
+ let m;
12899
+ while ((m = regex.exec(svg)) !== null) {
12900
+ const raw = m[1].trim().split(",")[0].trim().replace(/^['"]|['"]$/g, "");
12901
+ if (raw && raw !== "serif" && raw !== "sans-serif" && raw !== "monospace") {
12902
+ families.add(raw);
12903
+ }
12904
+ }
12905
+ }
12906
+ return families;
12907
+ }
12908
+ async function embedFontsInPdf(pdf, fontFamilies, fontBaseUrl) {
12909
+ const embedded = /* @__PURE__ */ new Set();
12910
+ const weights = [300, 400, 500, 600, 700];
12911
+ const tasks = [];
12912
+ for (const family of fontFamilies) {
12913
+ if (!isFontAvailable(family)) {
12914
+ console.warn(`[pdf-fonts] No TTF mapping for "${family}" — will use Helvetica fallback`);
12915
+ continue;
12916
+ }
12917
+ for (const w of weights) {
12918
+ tasks.push(
12919
+ embedFont(pdf, family, w, fontBaseUrl).then((ok) => {
12920
+ if (ok) embedded.add(`${family}${w}`);
12921
+ })
12922
+ );
12923
+ }
12924
+ }
12925
+ await Promise.all(tasks);
12926
+ console.log(`[pdf-fonts] Embedded ${embedded.size} font variants for ${fontFamilies.size} families`);
12927
+ return embedded;
12928
+ }
12929
+ const pdfFonts = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
12930
+ __proto__: null,
12931
+ FONT_FALLBACK_DEVANAGARI,
12932
+ FONT_FALLBACK_SYMBOLS,
12933
+ FONT_FILES,
12934
+ FONT_WEIGHT_LABELS,
12935
+ embedFont,
12936
+ embedFontsForConfig,
12937
+ embedFontsInPdf,
12938
+ extractFontFamiliesFromSvgs,
12939
+ getEmbeddedJsPDFFontName,
12940
+ getFontPathForWeight,
12941
+ isFontAvailable,
12942
+ resolveFontWeight,
12943
+ rewriteSvgFontsForJsPDF
12944
+ }, Symbol.toStringTag, { value: "Module" }));
12945
+ function normalizeFontFamily(fontStack) {
12946
+ const first = fontStack.split(",")[0].trim();
12947
+ return first.replace(/^['"]|['"]$/g, "");
12948
+ }
12949
+ const loadedFonts = /* @__PURE__ */ new Set();
12950
+ const loadingPromises = /* @__PURE__ */ new Map();
12951
+ function getFontBaseUrl() {
12952
+ if (typeof window === "undefined") return "/fonts/";
12953
+ return `${window.location.origin}/fonts/`;
12954
+ }
12955
+ function injectLocalFontFaces(rawFontFamily) {
12956
+ if (typeof document === "undefined") return false;
12957
+ const fontFamily = normalizeFontFamily(rawFontFamily);
12958
+ const files = FONT_FILES[fontFamily];
12959
+ if (!files) return false;
12960
+ const id = `pixldocs-local-font-${fontFamily.replace(/[^a-z0-9_-]+/gi, "-")}`;
12961
+ if (document.getElementById(id)) return true;
12962
+ const baseUrl = getFontBaseUrl();
12963
+ const rules = [];
12964
+ const seen = /* @__PURE__ */ new Set();
12965
+ for (const weight of [300, 400, 500, 600, 700]) {
12966
+ for (const italic of [false, true]) {
12967
+ const resolvedWeight = resolveFontWeight(weight);
12968
+ const file = getFontPathForWeight(files, resolvedWeight, italic);
12969
+ if (!file) continue;
12970
+ const key = `${file}|${weight}|${italic ? "italic" : "normal"}`;
12971
+ if (seen.has(key)) continue;
12972
+ seen.add(key);
12973
+ rules.push(
12974
+ `@font-face{font-family:${JSON.stringify(fontFamily)};src:url(${JSON.stringify(baseUrl + file)}) format("truetype");font-weight:${weight};font-style:${italic ? "italic" : "normal"};font-display:block;}`
12975
+ );
12976
+ }
12977
+ }
12978
+ if (rules.length === 0) return false;
12979
+ const style = document.createElement("style");
12980
+ style.id = id;
12981
+ style.textContent = rules.join("\n");
12982
+ document.head.appendChild(style);
12983
+ return true;
12984
+ }
12985
+ function withTimeout(promise, timeoutMs = 4e3) {
12986
+ let timeoutId;
12987
+ return Promise.race([
12988
+ promise,
12989
+ new Promise((resolve) => {
12990
+ timeoutId = setTimeout(resolve, timeoutMs);
12991
+ })
12992
+ ]).finally(() => {
12993
+ if (timeoutId) clearTimeout(timeoutId);
12994
+ });
12995
+ }
12996
+ async function loadGoogleFontCSS(rawFontFamily) {
12997
+ if (!rawFontFamily || typeof document === "undefined") return;
12998
+ const fontFamily = normalizeFontFamily(rawFontFamily);
12999
+ if (!fontFamily) return;
13000
+ if (loadedFonts.has(fontFamily)) return;
13001
+ const existing = loadingPromises.get(fontFamily);
13002
+ if (existing) return existing;
13003
+ const promise = (async () => {
13004
+ try {
13005
+ if (injectLocalFontFaces(fontFamily)) {
13006
+ loadedFonts.add(fontFamily);
13007
+ return;
13008
+ }
13009
+ const encoded = encodeURIComponent(fontFamily);
13010
+ const url = `https://fonts.googleapis.com/css?family=${encoded}:300,400,500,600,700&display=swap`;
13011
+ const link = document.createElement("link");
13012
+ link.rel = "stylesheet";
13013
+ link.href = url;
13014
+ link.crossOrigin = "anonymous";
13015
+ await new Promise((resolve, reject) => {
13016
+ link.onload = () => resolve();
13017
+ link.onerror = () => reject(new Error(`Failed to load font: ${fontFamily}`));
13018
+ document.head.appendChild(link);
13019
+ });
13020
+ loadedFonts.add(fontFamily);
13021
+ } catch (e) {
13022
+ console.warn(`[@pixldocs/canvas-renderer] Font load failed: ${fontFamily}`, e);
13023
+ }
13024
+ })();
13025
+ loadingPromises.set(fontFamily, promise);
13026
+ await promise;
13027
+ loadingPromises.delete(fontFamily);
13028
+ }
13029
+ function collectFontsFromConfig(config) {
13030
+ var _a;
13031
+ const fonts = /* @__PURE__ */ new Set();
13032
+ fonts.add("Open Sans");
13033
+ fonts.add("Hind");
13034
+ function walk(nodes) {
13035
+ var _a2;
13036
+ if (!nodes) return;
13037
+ for (const node of nodes) {
13038
+ if (node.fontFamily) fonts.add(normalizeFontFamily(node.fontFamily));
13039
+ if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) fonts.add(normalizeFontFamily(node.smartProps.fontFamily));
13040
+ if (node.styles && Array.isArray(node.styles)) {
13041
+ for (const lineStyle of node.styles) {
13042
+ if (lineStyle && typeof lineStyle === "object") {
13043
+ for (const charStyle of Object.values(lineStyle)) {
13044
+ if (charStyle == null ? void 0 : charStyle.fontFamily) fonts.add(normalizeFontFamily(charStyle.fontFamily));
13045
+ }
13046
+ }
13047
+ }
13048
+ }
13049
+ if (node.children) walk(node.children);
13050
+ }
13051
+ }
13052
+ for (const page of config.pages || []) {
13053
+ walk(page.children || []);
13054
+ }
13055
+ if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
13056
+ for (const def of Object.values(config.themeConfig.variables)) {
13057
+ if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
13058
+ if (def.label && /font/i.test(def.label)) {
13059
+ fonts.add(normalizeFontFamily(def.value));
13060
+ }
13061
+ }
13062
+ }
13063
+ }
13064
+ return fonts;
13065
+ }
13066
+ function collectFontDescriptorsFromConfig(config) {
13067
+ var _a;
13068
+ const seen = /* @__PURE__ */ new Set();
13069
+ const descriptors = [];
13070
+ function add(family, weight, style) {
13071
+ const f = normalizeFontFamily(family);
13072
+ if (!f) return;
13073
+ const w = weight ?? 400;
13074
+ const s = style ?? "normal";
13075
+ const key = `${f}|${w}|${s}`;
13076
+ if (seen.has(key)) return;
13077
+ seen.add(key);
13078
+ descriptors.push({ family: f, weight: w, style: s });
13079
+ }
13080
+ function walk(nodes) {
13081
+ var _a2;
13082
+ if (!nodes) return;
13083
+ for (const node of nodes) {
13084
+ if (node.fontFamily) {
13085
+ add(node.fontFamily, node.fontWeight, node.fontStyle);
13086
+ if (node.type === "text") {
13087
+ for (const w of [300, 400, 500, 600, 700]) {
13088
+ add(node.fontFamily, w, node.fontStyle);
13089
+ }
13090
+ }
13091
+ }
13092
+ if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) {
13093
+ add(node.smartProps.fontFamily, node.smartProps.fontWeight, node.smartProps.fontStyle);
13094
+ }
13095
+ if (node.styles) {
13096
+ const styleEntries = Array.isArray(node.styles) ? node.styles : Object.values(node.styles);
13097
+ for (const lineStyle of styleEntries) {
13098
+ if (lineStyle && typeof lineStyle === "object") {
13099
+ for (const charStyle of Object.values(lineStyle)) {
13100
+ if (charStyle == null ? void 0 : charStyle.fontFamily) {
13101
+ add(charStyle.fontFamily, charStyle.fontWeight, charStyle.fontStyle);
13102
+ }
13103
+ }
13104
+ }
13105
+ }
13106
+ }
13107
+ if (node.children) walk(node.children);
13108
+ }
13109
+ }
13110
+ add("Open Sans", 400, "normal");
13111
+ add("Hind", 400, "normal");
13112
+ add("Hind", 700, "normal");
13113
+ for (const page of config.pages || []) {
13114
+ walk(page.children || []);
13115
+ }
13116
+ if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
13117
+ for (const def of Object.values(config.themeConfig.variables)) {
13118
+ if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
13119
+ if (def.label && /font/i.test(def.label)) {
13120
+ add(def.value);
13121
+ }
13122
+ }
13123
+ }
13124
+ }
13125
+ return descriptors;
13126
+ }
13127
+ async function ensureFontsForResolvedConfig(config) {
13128
+ if (typeof document === "undefined") return;
13129
+ const descriptors = collectFontDescriptorsFromConfig(config);
13130
+ const families = new Set(descriptors.map((d) => d.family));
13131
+ void withTimeout(Promise.all([...families].map((f) => loadGoogleFontCSS(f))), 2500);
13132
+ if (document.fonts) {
13133
+ descriptors.forEach((d) => {
13134
+ const stylePrefix = d.style === "italic" ? "italic " : "";
13135
+ const weightStr = String(d.weight);
13136
+ const spec = `${stylePrefix}${weightStr} 16px "${d.family}"`;
13137
+ document.fonts.load(spec).catch(() => {
13138
+ });
13139
+ });
13140
+ }
13141
+ }
13142
+ function configHasAutoShrinkText$1(config) {
13143
+ var _a;
13144
+ if (!((_a = config == null ? void 0 : config.pages) == null ? void 0 : _a.length)) return false;
13145
+ const walk = (nodes) => {
13146
+ for (const node of nodes || []) {
13147
+ if (!node) continue;
13148
+ if (node.type === "text" && node.overflowPolicy === "auto-shrink") return true;
13149
+ if (Array.isArray(node.children) && node.children.length && walk(node.children)) return true;
13150
+ }
13151
+ return false;
13152
+ };
13153
+ for (const page of config.pages) {
13154
+ if (walk(page.children || [])) return true;
13155
+ }
13156
+ return false;
13157
+ }
13158
+ async function awaitFontsForConfig(config, maxWaitMs) {
13159
+ if (typeof document === "undefined" || !document.fonts) return;
13160
+ void ensureFontsForResolvedConfig(config);
13161
+ const descriptors = collectFontDescriptorsFromConfig(config);
13162
+ if (descriptors.length === 0) return;
13163
+ const loads = Promise.all(
13164
+ descriptors.map((d) => {
13165
+ const stylePrefix = d.style === "italic" ? "italic " : "";
13166
+ const spec = `${stylePrefix}${d.weight} 16px "${d.family}"`;
13167
+ return document.fonts.load(spec).catch(() => []);
13168
+ })
13169
+ ).then(() => void 0);
13170
+ await Promise.race([
13171
+ loads,
13172
+ new Promise((resolve) => setTimeout(resolve, maxWaitMs))
13173
+ ]);
13174
+ await Promise.race([
13175
+ document.fonts.ready.catch(() => void 0).then(() => void 0),
13176
+ new Promise((r) => setTimeout(r, Math.min(500, maxWaitMs)))
13177
+ ]);
13178
+ await new Promise(
13179
+ (resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))
13180
+ );
13181
+ }
13182
+ const PREVIEW_DEBUG_PREFIX = "[canvas-renderer][preview-debug]";
13183
+ function countUnderlinedNodes(config) {
13184
+ var _a;
13185
+ if (!((_a = config == null ? void 0 : config.pages) == null ? void 0 : _a.length)) return 0;
13186
+ let count = 0;
13187
+ const walk = (nodes) => {
13188
+ var _a2;
13189
+ for (const node of nodes || []) {
13190
+ if (node == null ? void 0 : node.underline) count += 1;
13191
+ if ((_a2 = node == null ? void 0 : node.children) == null ? void 0 : _a2.length) walk(node.children);
13192
+ }
13193
+ };
13194
+ for (const page of config.pages) walk(page.children || []);
13195
+ return count;
13196
+ }
13197
+ function PixldocsPreview(props) {
13198
+ const {
13199
+ pageIndex = 0,
13200
+ zoom = 1,
13201
+ absoluteZoom = false,
13202
+ imageProxyUrl,
13203
+ className,
13204
+ style,
13205
+ onDynamicFieldClick,
13206
+ onReady,
13207
+ onError,
13208
+ // Default `false` so PageCanvas blocks textbox creation until the host
13209
+ // browser actually has the @font-face rules registered. This matters for
13210
+ // `overflowPolicy: 'auto-shrink'` text — `createText` runs the shrink
13211
+ // loop synchronously at mount time using whatever font metrics Fabric
13212
+ // can measure right then. If the real font hasn't loaded yet, Fabric
13213
+ // falls back to the system font (typically narrower), the shrink loop
13214
+ // decides "fits, no shrink needed", and when the real font finally
13215
+ // loads the text overflows the box.
13216
+ //
13217
+ // The renderer's imperative PNG/PDF paths (`renderPageViaPreviewCanvas`,
13218
+ // `captureSvgViaPreviewCanvas`) already pass `skipFontReadyWait: false`
13219
+ // for this exact reason — that's why the downloaded PDF was correct
13220
+ // while the on-screen preview wasn't.
13221
+ skipFontReadyWait = false
13222
+ } = props;
13223
+ useEffect(() => {
13224
+ setPackageApiUrl(imageProxyUrl);
13225
+ }, [imageProxyUrl]);
13226
+ const [resolvedConfig, setResolvedConfig] = useState(null);
13227
+ const [isLoading, setIsLoading] = useState(false);
13228
+ const [fontsReady, setFontsReady] = useState(false);
13229
+ const [fontsReadyVersion, setFontsReadyVersion] = useState(0);
13230
+ const [canvasSettled, setCanvasSettled] = useState(false);
13231
+ const [stabilizationPass, setStabilizationPass] = useState(0);
13232
+ const isResolveMode = !("config" in props && props.config);
13233
+ useEffect(() => {
13234
+ if (!isResolveMode) {
13235
+ setResolvedConfig(null);
13236
+ setCanvasSettled(false);
13237
+ console.log(PREVIEW_DEBUG_PREFIX, "config-mode active");
13238
+ return;
13239
+ }
13240
+ const p = props;
13241
+ if (!p.templateId || !p.formSchemaId || !p.supabaseUrl || !p.supabaseAnonKey) return;
13242
+ let cancelled = false;
13243
+ setIsLoading(true);
13244
+ setFontsReady(false);
13245
+ setCanvasSettled(false);
13246
+ console.log(PREVIEW_DEBUG_PREFIX, "resolve-start", {
13247
+ templateId: p.templateId,
13248
+ formSchemaId: p.formSchemaId,
13249
+ themeId: p.themeId ?? null,
13250
+ pageIndex
13251
+ });
13252
+ resolveFromForm({
13253
+ templateId: p.templateId,
13254
+ formSchemaId: p.formSchemaId,
13255
+ sectionState: p.sectionState,
13256
+ themeId: p.themeId,
13257
+ supabaseUrl: p.supabaseUrl,
13258
+ supabaseAnonKey: p.supabaseAnonKey
13259
+ }).then((resolved) => {
13260
+ var _a, _b;
13261
+ if (!cancelled) {
13262
+ console.log(PREVIEW_DEBUG_PREFIX, "resolve-done", {
13263
+ pages: ((_b = (_a = resolved.config) == null ? void 0 : _a.pages) == null ? void 0 : _b.length) ?? 0,
13264
+ underlinedNodes: countUnderlinedNodes(resolved.config)
13265
+ });
13266
+ setResolvedConfig(resolved.config);
13267
+ const hasAutoShrink = configHasAutoShrinkText$1(resolved.config);
13268
+ const waitMs = hasAutoShrink ? 4e3 : 1800;
13269
+ awaitFontsForConfig(resolved.config, waitMs).then(() => {
13270
+ if (!cancelled) {
13271
+ console.log(PREVIEW_DEBUG_PREFIX, "resolve-mode fonts settled", { hasAutoShrink, waitMs });
13272
+ setFontsReady(true);
13273
+ setIsLoading(false);
13274
+ }
13275
+ }).catch((err) => {
13276
+ if (!cancelled) {
13277
+ console.warn(PREVIEW_DEBUG_PREFIX, "resolve-mode font wait failed", err);
13278
+ setFontsReady(true);
13279
+ setIsLoading(false);
13280
+ }
13281
+ });
13282
+ }
13283
+ }).catch((err) => {
13284
+ if (!cancelled) {
13285
+ setIsLoading(false);
13286
+ console.warn(PREVIEW_DEBUG_PREFIX, "resolve-error", err);
13287
+ onError == null ? void 0 : onError(err instanceof Error ? err : new Error(String(err)));
13288
+ }
13289
+ });
13290
+ return () => {
13291
+ cancelled = true;
13292
+ };
13293
+ }, [
13294
+ isResolveMode,
13295
+ // For resolve mode, re-resolve when these change
13296
+ isResolveMode ? props.templateId : void 0,
13297
+ isResolveMode ? props.formSchemaId : void 0,
13298
+ isResolveMode ? JSON.stringify(props.sectionState) : void 0,
13299
+ isResolveMode ? props.themeId : void 0
13300
+ ]);
13301
+ const config = isResolveMode ? resolvedConfig : props.config;
13302
+ useEffect(() => {
13303
+ var _a, _b, _c;
13304
+ if (!config) return;
13305
+ let cancelled = false;
13306
+ setCanvasSettled(false);
13307
+ setStabilizationPass(0);
13308
+ console.log(PREVIEW_DEBUG_PREFIX, "config-changed", {
13309
+ pageIndex,
13310
+ pages: ((_a = config.pages) == null ? void 0 : _a.length) ?? 0,
13311
+ underlinedNodes: countUnderlinedNodes(config),
13312
+ isResolveMode
13313
+ });
13314
+ const bump = () => {
13315
+ if (cancelled) return;
13316
+ clearMeasurementCache();
13317
+ clearFabricCharCache();
13318
+ setFontsReadyVersion((v) => {
13319
+ const next = v + 1;
13320
+ console.log(PREVIEW_DEBUG_PREFIX, "font-bump", { pageIndex, next, stabilizationPass });
13321
+ return next;
13322
+ });
13323
+ };
13324
+ (_c = (_b = document.fonts) == null ? void 0 : _b.ready) == null ? void 0 : _c.then(bump);
13325
+ const timeoutId = window.setTimeout(bump, 350);
13326
+ return () => {
13327
+ cancelled = true;
13328
+ window.clearTimeout(timeoutId);
13329
+ };
13330
+ }, [config]);
13331
+ const previewKey = useMemo(
13332
+ () => `${pageIndex}-${fontsReadyVersion}-${stabilizationPass}`,
13333
+ [pageIndex, fontsReadyVersion, stabilizationPass]
13334
+ );
13335
+ useEffect(() => {
13336
+ if (isResolveMode) return;
13337
+ if (!config) {
13338
+ setFontsReady(false);
13339
+ setCanvasSettled(false);
13340
+ setStabilizationPass(0);
13341
+ return;
13342
+ }
13343
+ setFontsReady(false);
13344
+ setCanvasSettled(false);
13345
+ setStabilizationPass(0);
13346
+ let cancelled = false;
13347
+ const hasAutoShrink = configHasAutoShrinkText$1(config);
13348
+ const waitMs = hasAutoShrink ? 4e3 : 1800;
13349
+ awaitFontsForConfig(config, waitMs).then(() => {
13350
+ if (cancelled) return;
13351
+ console.log(PREVIEW_DEBUG_PREFIX, "config-mode fonts settled", {
13352
+ pageIndex,
13353
+ hasAutoShrink,
13354
+ waitMs,
13355
+ underlinedNodes: countUnderlinedNodes(config)
13356
+ });
13357
+ setFontsReady(true);
13358
+ }).catch((err) => {
13359
+ if (cancelled) return;
13360
+ console.warn(PREVIEW_DEBUG_PREFIX, "config-mode font wait failed", err);
13361
+ setFontsReady(true);
13362
+ });
13363
+ return () => {
13364
+ cancelled = true;
13365
+ };
13366
+ }, [isResolveMode, config]);
13367
+ const handleCanvasReady = useCallback(() => {
13368
+ if (stabilizationPass === 0) {
13369
+ console.log(PREVIEW_DEBUG_PREFIX, "canvas-ready-pass", { pageIndex, stabilizationPass, action: "stabilize-again" });
13370
+ setCanvasSettled(false);
13371
+ setStabilizationPass(1);
13372
+ return;
13373
+ }
13374
+ console.log(PREVIEW_DEBUG_PREFIX, "canvas-ready-pass", { pageIndex, stabilizationPass, action: "settled" });
13375
+ setCanvasSettled(true);
13376
+ onReady == null ? void 0 : onReady();
13377
+ }, [onReady, pageIndex, stabilizationPass]);
13378
+ if (isLoading) {
13379
+ return /* @__PURE__ */ jsx("div", { className, style: { ...style, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) });
13380
+ }
13381
+ if (!config) return null;
13382
+ if (!fontsReady) {
13383
+ return /* @__PURE__ */ jsx("div", { className, style: { ...style, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) });
13384
+ }
13385
+ return /* @__PURE__ */ jsxs("div", { className, style: { ...style, position: "relative" }, children: [
13386
+ /* @__PURE__ */ jsx("div", { style: { visibility: canvasSettled ? "visible" : "hidden" }, children: /* @__PURE__ */ jsx(
13387
+ PreviewCanvas,
13388
+ {
13389
+ config,
13390
+ pageIndex,
13391
+ zoom,
13392
+ absoluteZoom,
13393
+ skipFontReadyWait,
13394
+ onDynamicFieldClick,
13395
+ onReady: handleCanvasReady
13396
+ },
13397
+ previewKey
13398
+ ) }),
13399
+ !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..." }) })
13400
+ ] });
13401
+ }
13402
+ const PACKAGE_VERSION = "0.5.68";
13403
+ let __underlineFixInstalled = false;
13404
+ function installUnderlineFix(fab) {
13405
+ var _a;
13406
+ if (__underlineFixInstalled) return;
13407
+ const TextProto = (_a = fab.Text) == null ? void 0 : _a.prototype;
13408
+ if (!TextProto || typeof TextProto._renderTextDecoration !== "function") return;
13409
+ const original = TextProto._renderTextDecoration;
13410
+ const measureLineTextWidth = (obj, ctx, lineIndex) => {
13411
+ var _a2, _b, _c, _d, _e, _f;
13412
+ const rawLine = (_a2 = obj._textLines) == null ? void 0 : _a2[lineIndex];
13413
+ const lineText = Array.isArray(rawLine) ? rawLine.join("") : String(rawLine ?? "");
13414
+ if (!lineText) return 0;
13415
+ const fontSize = Number(((_b = obj.getValueOfPropertyAt) == null ? void 0 : _b.call(obj, lineIndex, 0, "fontSize")) ?? obj.fontSize ?? 0);
13416
+ const fontStyle = String(((_c = obj.getValueOfPropertyAt) == null ? void 0 : _c.call(obj, lineIndex, 0, "fontStyle")) ?? obj.fontStyle ?? "normal");
13417
+ const fontWeight = String(((_d = obj.getValueOfPropertyAt) == null ? void 0 : _d.call(obj, lineIndex, 0, "fontWeight")) ?? obj.fontWeight ?? "400");
13418
+ const fontFamily = String(((_e = obj.getValueOfPropertyAt) == null ? void 0 : _e.call(obj, lineIndex, 0, "fontFamily")) ?? obj.fontFamily ?? "sans-serif");
13419
+ const charSpacing = Number(((_f = obj.getValueOfPropertyAt) == null ? void 0 : _f.call(obj, lineIndex, 0, "charSpacing")) ?? obj.charSpacing ?? 0);
13420
+ ctx.save();
13421
+ ctx.font = `${fontStyle} normal ${fontWeight} ${fontSize}px ${fontFamily}`;
13422
+ const measured = ctx.measureText(lineText).width;
13423
+ ctx.restore();
13424
+ const graphemeCount = Array.from(lineText).length;
13425
+ const spacingWidth = graphemeCount > 1 ? charSpacing / 1e3 * fontSize * (graphemeCount - 1) : 0;
13426
+ return Math.max(0, measured + spacingWidth);
13427
+ };
13428
+ TextProto._renderTextDecoration = function patchedRenderTextDecoration(ctx, type) {
13429
+ try {
13430
+ const hasOwn = !!this[type];
13431
+ const hasStyled = typeof this.styleHas === "function" && this.styleHas(type);
13432
+ if (!hasOwn && !hasStyled) return;
13433
+ const lines = this._textLines;
13434
+ const offsets = this.offsets;
13435
+ if (!Array.isArray(lines) || !offsets) {
13436
+ return original.call(this, ctx, type);
13437
+ }
13438
+ const offsetY = offsets[type];
13439
+ const offsetAligner = type === "linethrough" ? 0.5 : type === "overline" ? 1 : 0;
13440
+ const leftOffset = this._getLeftOffset();
13441
+ let topOffset = this._getTopOffset();
13442
+ for (let i = 0, len = lines.length; i < len; i++) {
13443
+ const heightOfLine = this.getHeightOfLine(i);
13444
+ const lineHas = !!this[type] || typeof this.styleHas === "function" && this.styleHas(type, i);
13445
+ if (!lineHas) {
13446
+ topOffset += heightOfLine;
13447
+ continue;
13448
+ }
13449
+ const fillStyle = this.getValueOfPropertyAt(i, 0, "fill");
13450
+ const thickness = this.getValueOfPropertyAt(i, 0, "textDecorationThickness");
13451
+ const charSize = this.getHeightOfChar(i, 0);
13452
+ const dy = this.getValueOfPropertyAt(i, 0, "deltaY") || 0;
13453
+ const finalThickness = this.fontSize * (thickness || 0) / 1e3;
13454
+ if (!fillStyle || !finalThickness) {
13455
+ topOffset += heightOfLine;
13456
+ continue;
13457
+ }
13458
+ const lineWidth = measureLineTextWidth(this, ctx, i);
13459
+ if (!lineWidth) {
13460
+ topOffset += heightOfLine;
13461
+ continue;
13462
+ }
13463
+ const availableWidth = Number(this.width ?? lineWidth);
13464
+ let lineLeftOffset = 0;
13465
+ const align = String(this.textAlign ?? "left");
13466
+ if (align === "center") lineLeftOffset = (availableWidth - lineWidth) / 2;
13467
+ else if (align === "right" || align === "end") lineLeftOffset = availableWidth - lineWidth;
13468
+ let drawStart = leftOffset + lineLeftOffset;
13469
+ if (this.direction === "rtl") {
13470
+ drawStart = this.width - drawStart - lineWidth;
13471
+ }
13472
+ const maxHeight = heightOfLine / this.lineHeight;
13473
+ const top = topOffset + maxHeight * (1 - this._fontSizeFraction);
13474
+ ctx.fillStyle = fillStyle;
13475
+ ctx.fillRect(
13476
+ drawStart,
13477
+ top + offsetY * charSize + dy - offsetAligner * finalThickness,
13478
+ lineWidth,
13479
+ finalThickness
13480
+ );
13481
+ topOffset += heightOfLine;
13482
+ }
13483
+ if (typeof this._removeShadow === "function") {
13484
+ try {
13485
+ this._removeShadow(ctx);
13486
+ } catch {
13487
+ }
13488
+ }
13489
+ } catch {
13490
+ try {
13491
+ return original.call(this, ctx, type);
13492
+ } catch {
13493
+ }
13494
+ }
13495
+ };
13496
+ __underlineFixInstalled = true;
13497
+ console.log(`[canvas-renderer] underline-fix monkey patch installed (v${PACKAGE_VERSION})`);
13498
+ }
13499
+ function configHasAutoShrinkText(config) {
13500
+ var _a;
13501
+ if (!((_a = config == null ? void 0 : config.pages) == null ? void 0 : _a.length)) return false;
13502
+ const walk = (nodes) => {
13503
+ for (const node of nodes || []) {
13504
+ if (!node) continue;
13505
+ if (node.type === "text" && node.overflowPolicy === "auto-shrink") return true;
13506
+ if (Array.isArray(node.children) && node.children.length && walk(node.children)) return true;
13507
+ }
13508
+ return false;
13509
+ };
13510
+ for (const page of config.pages) {
13511
+ if (walk(page.children || [])) return true;
13512
+ }
13513
+ return false;
13514
+ }
13515
+ class PixldocsRenderer {
13516
+ constructor(config) {
13517
+ __publicField(this, "config");
13518
+ this.config = config;
13519
+ installUnderlineFix(fabric);
13520
+ try {
13521
+ console.log(`[canvas-renderer] PixldocsRenderer v${PACKAGE_VERSION} initialized`);
13522
+ } catch {
13523
+ }
13524
+ }
13525
+ /**
13526
+ * Render a pre-resolved template config to an image using the full PageCanvas engine.
13527
+ * Mounts a hidden PreviewCanvas component and captures the Fabric canvas output.
13528
+ */
13529
+ async render(templateConfig, options = {}) {
13530
+ const pageIndex = options.pageIndex ?? 0;
13531
+ const format = options.format ?? "png";
13532
+ const quality = options.quality ?? 0.92;
13533
+ const pixelRatio = options.pixelRatio ?? this.config.pixelRatio ?? 2;
13534
+ const canvasWidth = templateConfig.canvas.width;
13535
+ const canvasHeight = templateConfig.canvas.height;
13536
+ const page = templateConfig.pages[pageIndex];
13537
+ if (!page) {
13538
+ throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
13539
+ }
13540
+ await ensureFontsForResolvedConfig(templateConfig);
13541
+ if (!options.skipFontReadyWait) {
13542
+ const hasAutoShrink = configHasAutoShrinkText(templateConfig);
13543
+ const defaultWait = hasAutoShrink ? 4e3 : 1800;
13544
+ await this.awaitFontsForConfig(templateConfig, options.waitForFontsMs ?? defaultWait);
13545
+ }
13546
+ const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
13547
+ setPackageApiUrl2(this.config.imageProxyUrl);
13548
+ const dataUrl = await this.renderPageViaPreviewCanvas(
13549
+ templateConfig,
13550
+ pageIndex,
13551
+ pixelRatio,
13552
+ format,
13553
+ quality,
13554
+ { skipFontReadyWait: options.skipFontReadyWait, waitForFontsMs: options.waitForFontsMs }
13555
+ );
13556
+ return {
13557
+ dataUrl,
13558
+ width: canvasWidth,
13559
+ height: canvasHeight,
13560
+ pixelWidth: canvasWidth * pixelRatio,
13561
+ pixelHeight: canvasHeight * pixelRatio
13562
+ };
13563
+ }
13564
+ /**
13565
+ * Render all pages and return array of results.
13566
+ */
13567
+ async renderAllPages(templateConfig, options = {}) {
13568
+ if (!options.skipFontReadyWait) {
13569
+ const hasAutoShrink = configHasAutoShrinkText(templateConfig);
13570
+ const defaultWait = hasAutoShrink ? 4e3 : 1800;
13571
+ await this.awaitFontsForConfig(templateConfig, options.waitForFontsMs ?? defaultWait);
13572
+ }
13573
+ const results = [];
13574
+ for (let i = 0; i < templateConfig.pages.length; i++) {
13575
+ results.push(await this.render(templateConfig, { ...options, pageIndex: i, skipFontReadyWait: true }));
13576
+ }
13577
+ return results;
13578
+ }
13579
+ /**
13580
+ * Resolve from V2 sectionState (like the server API) and render all pages.
13581
+ * This is the primary external API for the package.
13582
+ */
13583
+ async renderFromForm(options) {
13584
+ const { templateId, formSchemaId, sectionState, themeId, watermark, watermarkOptions, prefetched, ...renderOpts } = options;
13585
+ const resolved = await resolveFromForm({
13586
+ templateId,
13587
+ formSchemaId,
13588
+ sectionState,
13589
+ themeId,
13590
+ supabaseUrl: this.config.supabaseUrl,
13591
+ supabaseAnonKey: this.config.supabaseAnonKey,
13592
+ prefetched
13593
+ });
13594
+ const shouldWatermark = watermark ?? resolved.price > 0;
13595
+ let configToRender = resolved.config;
13596
+ if (shouldWatermark) {
13597
+ const { injectWatermark } = await import("./canvasWatermark-pkhacGge.js");
13598
+ configToRender = injectWatermark(configToRender, watermarkOptions);
13599
+ }
13600
+ return this.renderAllPages(configToRender, renderOpts);
13601
+ }
13602
+ /**
13603
+ * Render a page and capture the Fabric canvas SVG output (vector, not raster).
13604
+ * This is the key building block for client-side vector PDF export.
13605
+ */
13606
+ async renderPageSvg(templateConfig, pageIndex = 0) {
13607
+ const page = templateConfig.pages[pageIndex];
13608
+ if (!page) {
13609
+ throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
13610
+ }
13611
+ await ensureFontsForResolvedConfig(templateConfig);
13612
+ const hasAutoShrinkSvg = configHasAutoShrinkText(templateConfig);
13613
+ await this.awaitFontsForConfig(templateConfig, hasAutoShrinkSvg ? 4e3 : 1800);
13614
+ const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
13615
+ setPackageApiUrl2(this.config.imageProxyUrl);
13616
+ const canvasWidth = templateConfig.canvas.width;
13617
+ const canvasHeight = templateConfig.canvas.height;
13618
+ return this.captureSvgViaPreviewCanvas(templateConfig, pageIndex, canvasWidth, canvasHeight);
13619
+ }
13620
+ /**
13621
+ * Render all pages and return SVG strings for each.
13622
+ */
13623
+ async renderAllPageSvgs(templateConfig) {
13624
+ await ensureFontsForResolvedConfig(templateConfig);
13625
+ const hasAutoShrinkSvg = configHasAutoShrinkText(templateConfig);
13626
+ await this.awaitFontsForConfig(templateConfig, hasAutoShrinkSvg ? 4e3 : 1800);
13627
+ const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
13628
+ setPackageApiUrl2(this.config.imageProxyUrl);
13629
+ const results = [];
13630
+ for (let i = 0; i < templateConfig.pages.length; i++) {
13631
+ const canvasWidth = templateConfig.canvas.width;
13632
+ const canvasHeight = templateConfig.canvas.height;
13633
+ results.push(await this.captureSvgViaPreviewCanvas(templateConfig, i, canvasWidth, canvasHeight));
13634
+ }
13635
+ return results;
13636
+ }
13637
+ /**
13638
+ * Resolve from V2 sectionState and return SVGs for all pages (for server vector PDF).
13639
+ */
13640
+ async renderSvgsFromForm(options) {
13641
+ const { templateId, formSchemaId, sectionState, themeId, watermark, watermarkOptions, prefetched } = options;
13642
+ const resolved = await resolveFromForm({
13643
+ templateId,
13644
+ formSchemaId,
13645
+ sectionState,
13646
+ themeId,
13647
+ supabaseUrl: this.config.supabaseUrl,
13648
+ supabaseAnonKey: this.config.supabaseAnonKey,
13649
+ prefetched
13650
+ });
13651
+ const shouldWatermark = watermark ?? resolved.price > 0;
13652
+ let configToRender = resolved.config;
13653
+ if (shouldWatermark) {
13654
+ const { injectWatermark } = await import("./canvasWatermark-pkhacGge.js");
13655
+ configToRender = injectWatermark(configToRender, watermarkOptions);
13656
+ }
13657
+ return this.renderAllPageSvgs(configToRender);
13658
+ }
13659
+ /**
13660
+ * Render a pre-resolved template config to a vector PDF.
13661
+ * Returns a Blob and ArrayBuffer.
13662
+ */
13663
+ async renderPdf(templateConfig, options) {
13664
+ const svgs = await this.renderAllPageSvgs(templateConfig);
13665
+ const { assemblePdfFromSvgs: assemblePdfFromSvgs2 } = await Promise.resolve().then(() => pdfExport);
13666
+ return assemblePdfFromSvgs2(svgs, { title: options == null ? void 0 : options.title, fontBaseUrl: options == null ? void 0 : options.fontBaseUrl });
13667
+ }
13668
+ /**
13669
+ * Resolve from V2 sectionState and render a vector PDF.
13670
+ * This is the primary PDF export API — mirrors renderFromForm() but returns a PDF.
13671
+ */
13672
+ async renderPdfFromForm(options) {
13673
+ const { templateId, formSchemaId, sectionState, themeId, watermark, watermarkOptions, prefetched, title, fontBaseUrl } = options;
13674
+ const resolved = await resolveFromForm({
13675
+ templateId,
13676
+ formSchemaId,
13677
+ sectionState,
13678
+ themeId,
13679
+ supabaseUrl: this.config.supabaseUrl,
13680
+ supabaseAnonKey: this.config.supabaseAnonKey,
13681
+ prefetched
13682
+ });
13683
+ const shouldWatermark = watermark ?? resolved.price > 0;
13684
+ let configToRender = resolved.config;
13685
+ if (shouldWatermark) {
13686
+ const { injectWatermark } = await import("./canvasWatermark-pkhacGge.js");
13687
+ configToRender = injectWatermark(configToRender, watermarkOptions);
13688
+ }
13689
+ const svgs = await this.renderAllPageSvgs(configToRender);
13690
+ const { assemblePdfFromSvgs: assemblePdfFromSvgs2 } = await Promise.resolve().then(() => pdfExport);
13691
+ return assemblePdfFromSvgs2(svgs, { title: title ?? resolved.config.name, fontBaseUrl });
13692
+ }
13693
+ async renderById(templateId, formData, options) {
13694
+ const resolved = await resolveTemplateData({
13695
+ templateId,
13696
+ formData,
13697
+ supabaseUrl: this.config.supabaseUrl,
13698
+ supabaseAnonKey: this.config.supabaseAnonKey
13699
+ });
13700
+ return this.render(resolved.config, options);
13701
+ }
13702
+ /**
13703
+ * Convenience: fetch by ID with flat data and render ALL pages.
13704
+ */
13705
+ async renderAllById(templateId, formData, options) {
13706
+ const resolved = await resolveTemplateData({
13707
+ templateId,
13708
+ formData,
13709
+ supabaseUrl: this.config.supabaseUrl,
13710
+ supabaseAnonKey: this.config.supabaseAnonKey
13711
+ });
13712
+ return this.renderAllPages(resolved.config, options);
13713
+ }
13714
+ // ─── Internal: render a page using the full PreviewCanvas engine ───
13715
+ getExpectedImageCount(config, pageIndex) {
13716
+ const page = config.pages[pageIndex];
13717
+ if (!(page == null ? void 0 : page.children)) return 0;
13718
+ let count = 0;
13719
+ const walk = (nodes) => {
13720
+ for (const node of nodes) {
13721
+ if (!node || node.visible === false) continue;
13722
+ const src = typeof node.src === "string" ? node.src.trim() : "";
13723
+ const imageUrl = typeof node.imageUrl === "string" ? node.imageUrl.trim() : "";
13724
+ if (node.type === "image" && (src || imageUrl)) count += 1;
13725
+ if (Array.isArray(node.children) && node.children.length > 0) {
13726
+ walk(node.children);
13727
+ }
13728
+ }
13729
+ };
13730
+ walk(page.children);
13731
+ return count;
13732
+ }
13733
+ waitForCanvasImages(container, expectedImageCount, maxWaitMs = 15e3, pollMs = 120) {
13734
+ return new Promise((resolve) => {
13735
+ const start = Date.now();
13736
+ let stableFrames = 0;
13737
+ let lastSummary = "";
13738
+ const isRenderableImage = (value) => value instanceof HTMLImageElement && value.complete && value.naturalWidth > 0 && value.naturalHeight > 0;
13739
+ const collectRenderableImages = (obj, seen) => {
13740
+ if (!obj || typeof obj !== "object") return;
13741
+ const candidates = [obj._element, obj._originalElement, obj._filteredEl, obj._cacheCanvasEl];
13742
+ for (const candidate of candidates) {
13743
+ if (isRenderableImage(candidate)) {
13744
+ seen.add(candidate);
13745
+ } else if (candidate instanceof HTMLImageElement) {
13746
+ return false;
13747
+ }
13748
+ }
13749
+ const nested = Array.isArray(obj._objects) ? obj._objects : Array.isArray(obj.objects) ? obj.objects : [];
13750
+ for (const child of nested) {
13751
+ if (collectRenderableImages(child, seen) === false) return false;
13752
+ }
13753
+ return true;
13754
+ };
13755
+ const getFabricCanvas = () => {
13756
+ const registry2 = window.__fabricCanvasRegistry;
13757
+ if (registry2 instanceof Map) {
13758
+ for (const entry of registry2.values()) {
13759
+ const canvas = entry == null ? void 0 : entry.canvas;
13760
+ const el = (canvas == null ? void 0 : canvas.lowerCanvasEl) || (canvas == null ? void 0 : canvas.upperCanvasEl);
13761
+ if (el && container.contains(el)) return canvas;
13762
+ }
13763
+ }
13764
+ return null;
13765
+ };
13766
+ const settle = () => requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
13767
+ const getImageDebugInfo = (obj, bucket) => {
13768
+ if (!obj || typeof obj !== "object") return;
13769
+ const candidates = [obj._element, obj._originalElement, obj._filteredEl, obj._cacheCanvasEl];
13770
+ for (const candidate of candidates) {
13771
+ if (candidate instanceof HTMLImageElement) {
13772
+ bucket.push({
13773
+ id: obj.__docuforgeId || obj.id || "unknown",
13774
+ src: (candidate.currentSrc || candidate.src || "").slice(0, 240),
13775
+ complete: candidate.complete,
13776
+ naturalWidth: candidate.naturalWidth,
13777
+ naturalHeight: candidate.naturalHeight
13778
+ });
13779
+ }
13780
+ }
13781
+ const nested = Array.isArray(obj._objects) ? obj._objects : Array.isArray(obj.objects) ? obj.objects : [];
13782
+ for (const child of nested) {
13783
+ getImageDebugInfo(child, bucket);
13784
+ }
13785
+ };
13786
+ const check = () => {
13787
+ const elapsed = Date.now() - start;
13788
+ const domImages = Array.from(container.querySelectorAll("img"));
13789
+ const allDomLoaded = domImages.every((img) => img.complete && img.naturalWidth > 0 && img.naturalHeight > 0);
13790
+ const fabricCanvas = getFabricCanvas();
13791
+ const fabricObjects = fabricCanvas && typeof fabricCanvas.getObjects === "function" ? fabricCanvas.getObjects() : [];
13792
+ const renderableImages = /* @__PURE__ */ new Set();
13793
+ const fabricReady = fabricObjects.every((obj) => collectRenderableImages(obj, renderableImages) !== false);
13794
+ const actualImageCount = Math.max(domImages.length, renderableImages.size);
13795
+ const canvasReady = !!fabricCanvas && !!(fabricCanvas.lowerCanvasEl || fabricCanvas.upperCanvasEl);
13796
+ const hasExpectedAssets = expectedImageCount === 0 ? true : actualImageCount >= expectedImageCount;
13797
+ const ready = allDomLoaded && fabricReady && hasExpectedAssets;
13798
+ const summary = `expected=${expectedImageCount} actual=${actualImageCount} dom=${domImages.length} fabricReady=${fabricReady} domReady=${allDomLoaded} canvasReady=${canvasReady}`;
13799
+ if (summary !== lastSummary) {
13800
+ lastSummary = summary;
13801
+ console.log(`[canvas-renderer][asset-wait] ${summary}`);
13802
+ }
13803
+ if (ready) {
13804
+ stableFrames += 1;
13805
+ if (stableFrames >= 2) {
13806
+ console.log(`[canvas-renderer][asset-wait] ready after ${elapsed}ms (${summary})`);
13807
+ settle();
13808
+ return;
13809
+ }
13810
+ } else {
13811
+ stableFrames = 0;
13812
+ }
13813
+ if (elapsed >= maxWaitMs) {
13814
+ const fabricImageDebug = [];
13815
+ for (const obj of fabricObjects) {
13816
+ getImageDebugInfo(obj, fabricImageDebug);
13817
+ }
13818
+ const domImageDebug = domImages.map((img, index) => ({
13819
+ index,
13820
+ src: (img.currentSrc || img.src || "").slice(0, 240),
13821
+ complete: img.complete,
13822
+ naturalWidth: img.naturalWidth,
13823
+ naturalHeight: img.naturalHeight
13824
+ }));
13825
+ console.warn(`[canvas-renderer][asset-wait-timeout] elapsed=${elapsed}ms ${summary}`);
13826
+ console.warn("[canvas-renderer][asset-wait-timeout][dom-images]", domImageDebug);
13827
+ console.warn("[canvas-renderer][asset-wait-timeout][fabric-images]", fabricImageDebug);
13828
+ settle();
13829
+ return;
13830
+ }
13831
+ setTimeout(check, pollMs);
13832
+ };
13833
+ setTimeout(check, 0);
13834
+ });
13835
+ }
13836
+ waitForCanvasScene(container, config, pageIndex, maxWaitMs = 8e3, pollMs = 50) {
13837
+ return new Promise((resolve) => {
13838
+ var _a, _b;
13839
+ const start = Date.now();
13840
+ const pageHasContent = (((_b = (_a = config.pages[pageIndex]) == null ? void 0 : _a.children) == null ? void 0 : _b.length) ?? 0) > 0;
13841
+ const settle = () => requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
13842
+ const check = () => {
13843
+ const fabricCanvas = this.getFabricCanvasFromContainer(container);
13844
+ const lowerCanvas = (fabricCanvas == null ? void 0 : fabricCanvas.lowerCanvasEl) || container.querySelector("canvas.lower-canvas, canvas");
13845
+ const objectCount = typeof (fabricCanvas == null ? void 0 : fabricCanvas.getObjects) === "function" ? fabricCanvas.getObjects().length : 0;
13846
+ const ready = !!lowerCanvas && (!pageHasContent || objectCount > 0);
13847
+ if (ready) {
13848
+ console.log(`[canvas-renderer][scene-wait] ready after ${Date.now() - start}ms (objects=${objectCount})`);
13849
+ settle();
13850
+ return;
13851
+ }
13852
+ if (Date.now() - start >= maxWaitMs) {
13853
+ console.warn(`[canvas-renderer][scene-wait-timeout] elapsed=${Date.now() - start}ms objects=${objectCount} pageHasContent=${pageHasContent}`);
13854
+ settle();
13855
+ return;
13856
+ }
13857
+ setTimeout(check, pollMs);
13858
+ };
13859
+ setTimeout(check, 0);
13860
+ });
13861
+ }
13862
+ async waitForRelevantFonts(config, maxWaitMs = 1800) {
13863
+ if (typeof document === "undefined" || !document.fonts) return;
13864
+ const descriptors = collectFontDescriptorsFromConfig(config);
13865
+ if (descriptors.length === 0) return;
13866
+ const loads = Promise.all(
13867
+ descriptors.map((descriptor) => {
13868
+ const stylePrefix = descriptor.style === "italic" ? "italic " : "";
13869
+ const spec = `${stylePrefix}${descriptor.weight} 16px "${descriptor.family}"`;
13870
+ return document.fonts.load(spec).catch(() => []);
13871
+ })
13872
+ ).then(() => void 0);
13873
+ await Promise.race([
13874
+ loads,
13875
+ new Promise((resolve) => setTimeout(resolve, maxWaitMs))
13876
+ ]);
13877
+ await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
14009
13878
  }
14010
- return files.regular;
14011
- }
14012
- function isItalicPath(files, path) {
14013
- return path === files.italic || path === files.boldItalic || path === files.lightItalic || path === files.mediumItalic || path === files.semiboldItalic;
14014
- }
14015
- function getJsPDFFontName(fontName) {
14016
- return fontName.replace(/\s+/g, "");
14017
- }
14018
- function getEmbeddedJsPDFFontName(fontName, weight, isItalic = false) {
14019
- const resolved = resolveFontWeight(weight);
14020
- const label = FONT_WEIGHT_LABELS[resolved];
14021
- const italicSuffix = isItalic ? "Italic" : "";
14022
- return `${getJsPDFFontName(fontName)}-${label}${italicSuffix}`;
14023
- }
14024
- function isFontAvailable(fontName) {
14025
- return fontName in FONT_FILES;
14026
- }
14027
- const ttfCache = /* @__PURE__ */ new Map();
14028
- async function fetchTTFAsBase64(url) {
14029
- const cached = ttfCache.get(url);
14030
- if (cached) return cached;
14031
- try {
14032
- const res = await fetch(url);
14033
- if (!res.ok) return null;
14034
- const buf = await res.arrayBuffer();
14035
- const bytes = new Uint8Array(buf);
14036
- let binary = "";
14037
- for (let i = 0; i < bytes.length; i++) {
14038
- binary += String.fromCharCode(bytes[i]);
13879
+ /**
13880
+ * Block until the webfonts referenced by `config` have actually loaded
13881
+ * (or `maxWaitMs` elapses). Used by the headless capture path BEFORE
13882
+ * mounting `PreviewCanvas`, so the synchronous `createText` auto-shrink
13883
+ * loop measures against final font metrics instead of fallback ones.
13884
+ *
13885
+ * Stronger than `ensureFontsForResolvedConfig` (which is fire-and-forget)
13886
+ * — this awaits each `document.fonts.load(spec)` AND `document.fonts.ready`,
13887
+ * racing the whole thing against `maxWaitMs` so a slow CDN can't hang the
13888
+ * renderer.
13889
+ */
13890
+ async awaitFontsForConfig(config, maxWaitMs) {
13891
+ if (typeof document === "undefined" || !document.fonts) return;
13892
+ void ensureFontsForResolvedConfig(config);
13893
+ await this.waitForRelevantFonts(config, maxWaitMs);
13894
+ await Promise.race([
13895
+ document.fonts.ready.catch(() => void 0).then(() => void 0),
13896
+ new Promise((r) => setTimeout(r, Math.min(500, maxWaitMs)))
13897
+ ]);
13898
+ }
13899
+ getNormalizedGradientStops(gradient) {
13900
+ const stops = Array.isArray(gradient == null ? void 0 : gradient.stops) ? gradient.stops.map((stop) => ({
13901
+ offset: Math.max(0, Math.min(1, Number((stop == null ? void 0 : stop.offset) ?? 0))),
13902
+ color: String((stop == null ? void 0 : stop.color) ?? "#ffffff")
13903
+ })).filter((stop) => Number.isFinite(stop.offset)).sort((a, b) => a.offset - b.offset) : [];
13904
+ if (stops.length === 0) return [];
13905
+ const normalized = [...stops];
13906
+ if (normalized[0].offset > 0) {
13907
+ normalized.unshift({ offset: 0, color: normalized[0].color });
14039
13908
  }
14040
- const b64 = btoa(binary);
14041
- ttfCache.set(url, b64);
14042
- return b64;
14043
- } catch {
14044
- return null;
13909
+ if (normalized[normalized.length - 1].offset < 1) {
13910
+ normalized.push({ offset: 1, color: normalized[normalized.length - 1].color });
13911
+ }
13912
+ return normalized;
14045
13913
  }
14046
- }
14047
- async function embedFont(pdf, fontName, weight, fontBaseUrl, isItalic = false) {
14048
- const fontFiles = FONT_FILES[fontName];
14049
- if (!fontFiles) return false;
14050
- const baseUrl = fontBaseUrl.endsWith("/") ? fontBaseUrl : fontBaseUrl + "/";
14051
- const resolvedWeight = resolveFontWeight(weight);
14052
- const fontPath = getFontPathForWeight(fontFiles, resolvedWeight, isItalic);
14053
- if (!fontPath) return false;
14054
- const hasItalicFile = isItalic && isItalicPath(fontFiles, fontPath);
14055
- const jsPdfFontName = getEmbeddedJsPDFFontName(fontName, weight, hasItalicFile);
14056
- const label = FONT_WEIGHT_LABELS[resolvedWeight];
14057
- const italicSuffix = hasItalicFile ? "Italic" : "";
14058
- const fileName = `${getJsPDFFontName(fontName)}-${label}${italicSuffix}.ttf`;
14059
- const url = baseUrl + fontPath;
14060
- try {
14061
- const b64 = await fetchTTFAsBase64(url);
14062
- if (!b64) return false;
14063
- pdf.addFileToVFS(fileName, b64);
14064
- pdf.addFont(fileName, jsPdfFontName, "normal");
14065
- if (fontName !== jsPdfFontName) {
14066
- try {
14067
- pdf.addFont(fileName, fontName, "normal");
14068
- } catch {
13914
+ paintPageBackground(ctx, page, width, height) {
13915
+ var _a, _b;
13916
+ const backgroundColor = ((_a = page == null ? void 0 : page.settings) == null ? void 0 : _a.backgroundColor) || "#ffffff";
13917
+ const gradient = (_b = page == null ? void 0 : page.settings) == null ? void 0 : _b.backgroundGradient;
13918
+ ctx.clearRect(0, 0, width, height);
13919
+ ctx.fillStyle = backgroundColor;
13920
+ ctx.fillRect(0, 0, width, height);
13921
+ const stops = this.getNormalizedGradientStops(gradient);
13922
+ if (stops.length < 2) return;
13923
+ try {
13924
+ let canvasGradient = null;
13925
+ if ((gradient == null ? void 0 : gradient.type) === "radial") {
13926
+ const cx = Number.isFinite(gradient == null ? void 0 : gradient.cx) ? gradient.cx : 0.5;
13927
+ const cy = Number.isFinite(gradient == null ? void 0 : gradient.cy) ? gradient.cy : 0.5;
13928
+ const centerX = width * cx;
13929
+ const centerY = height * cy;
13930
+ const radius = Math.max(
13931
+ Math.hypot(centerX, centerY),
13932
+ Math.hypot(width - centerX, centerY),
13933
+ Math.hypot(centerX, height - centerY),
13934
+ Math.hypot(width - centerX, height - centerY)
13935
+ );
13936
+ canvasGradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, radius);
13937
+ } else if ((gradient == null ? void 0 : gradient.type) === "conic" && typeof ctx.createConicGradient === "function") {
13938
+ const cx = Number.isFinite(gradient == null ? void 0 : gradient.cx) ? gradient.cx : 0.5;
13939
+ const cy = Number.isFinite(gradient == null ? void 0 : gradient.cy) ? gradient.cy : 0.5;
13940
+ const startAngle = (((gradient == null ? void 0 : gradient.angle) ?? 0) - 90) * Math.PI / 180;
13941
+ canvasGradient = ctx.createConicGradient(startAngle, width * cx, height * cy);
13942
+ } else {
13943
+ const angleDeg = (gradient == null ? void 0 : gradient.angle) ?? 90;
13944
+ const angleRad = angleDeg * Math.PI / 180;
13945
+ const sinA = Math.sin(angleRad);
13946
+ const cosA = Math.cos(angleRad);
13947
+ const midX = width / 2;
13948
+ const midY = height / 2;
13949
+ const corners = [
13950
+ [0, 0],
13951
+ [width, 0],
13952
+ [width, height],
13953
+ [0, height]
13954
+ ];
13955
+ const projections = corners.map(([x, y]) => x * sinA - y * cosA);
13956
+ const minProjection = Math.min(...projections);
13957
+ const maxProjection = Math.max(...projections);
13958
+ canvasGradient = ctx.createLinearGradient(
13959
+ midX + minProjection * sinA,
13960
+ midY - minProjection * cosA,
13961
+ midX + maxProjection * sinA,
13962
+ midY - maxProjection * cosA
13963
+ );
14069
13964
  }
13965
+ if (!canvasGradient) return;
13966
+ stops.forEach((stop) => canvasGradient.addColorStop(stop.offset, stop.color));
13967
+ ctx.fillStyle = canvasGradient;
13968
+ ctx.fillRect(0, 0, width, height);
13969
+ } catch {
14070
13970
  }
14071
- return true;
14072
- } catch (e) {
14073
- console.warn(`[pdf-fonts] Failed to embed ${fontName} w${weight}:`, e);
14074
- return false;
14075
13971
  }
14076
- }
14077
- async function embedFontsForConfig(pdf, config, fontBaseUrl) {
14078
- const fontKeys = /* @__PURE__ */ new Set();
14079
- const SEP = "";
14080
- const walkElements = (elements) => {
14081
- for (const el of elements) {
14082
- if (el.fontFamily) {
14083
- const w = resolveFontWeight(el.fontWeight ?? 400);
14084
- fontKeys.add(`${el.fontFamily}${SEP}${w}`);
14085
- }
14086
- if (el.styles && typeof el.styles === "object") {
14087
- for (const lineKey of Object.keys(el.styles)) {
14088
- const lineStyles = el.styles[lineKey];
14089
- if (lineStyles && typeof lineStyles === "object") {
14090
- for (const charKey of Object.keys(lineStyles)) {
14091
- const s = lineStyles[charKey];
14092
- if (s == null ? void 0 : s.fontFamily) {
14093
- const w = resolveFontWeight(s.fontWeight ?? 400);
14094
- fontKeys.add(`${s.fontFamily}${SEP}${w}`);
14095
- }
13972
+ async renderPageViaPreviewCanvas(config, pageIndex, pixelRatio, format, quality, options = {}) {
13973
+ const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
13974
+ const canvasWidth = config.canvas.width;
13975
+ const canvasHeight = config.canvas.height;
13976
+ const hasAutoShrink = configHasAutoShrinkText(config);
13977
+ let firstMountSettled = false;
13978
+ let lateFontSettleDetected = false;
13979
+ if (typeof document !== "undefined" && document.fonts && hasAutoShrink) {
13980
+ document.fonts.ready.then(() => {
13981
+ if (firstMountSettled) lateFontSettleDetected = true;
13982
+ }).catch(() => {
13983
+ });
13984
+ }
13985
+ return new Promise((resolve, reject) => {
13986
+ const container = document.createElement("div");
13987
+ container.style.cssText = `
13988
+ position: fixed; left: -99999px; top: -99999px;
13989
+ width: ${canvasWidth}px; height: ${canvasHeight}px;
13990
+ overflow: hidden; pointer-events: none; opacity: 0;
13991
+ `;
13992
+ document.body.appendChild(container);
13993
+ const timeout = setTimeout(() => {
13994
+ cleanup();
13995
+ reject(new Error("Render timeout (30s)"));
13996
+ }, 3e4);
13997
+ let root;
13998
+ let mountKey = 0;
13999
+ const cleanup = () => {
14000
+ clearTimeout(timeout);
14001
+ try {
14002
+ root.unmount();
14003
+ } catch {
14004
+ }
14005
+ container.remove();
14006
+ };
14007
+ const remountWithFreshKey = async () => {
14008
+ mountKey += 1;
14009
+ try {
14010
+ clearMeasurementCache();
14011
+ } catch {
14012
+ }
14013
+ try {
14014
+ clearFabricCharCache();
14015
+ } catch {
14016
+ }
14017
+ try {
14018
+ root.unmount();
14019
+ } catch {
14020
+ }
14021
+ root = createRoot(container);
14022
+ await new Promise((settle) => {
14023
+ const onReadyOnce = () => {
14024
+ this.waitForCanvasScene(container, config, pageIndex).then(async () => {
14025
+ const fabricInstance = this.getFabricCanvasFromContainer(container);
14026
+ const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
14027
+ await this.waitForCanvasImages(container, expectedImageCount);
14028
+ await this.waitForStableTextMetrics(container, config);
14029
+ await this.waitForCanvasScene(container, config, pageIndex);
14030
+ if (!fabricInstance) return settle();
14031
+ settle();
14032
+ }).catch(() => settle());
14033
+ };
14034
+ root.render(
14035
+ createElement(PreviewCanvas2, {
14036
+ key: `remount-${mountKey}`,
14037
+ config,
14038
+ pageIndex,
14039
+ zoom: pixelRatio,
14040
+ absoluteZoom: true,
14041
+ skipFontReadyWait: false,
14042
+ onReady: onReadyOnce
14043
+ })
14044
+ );
14045
+ });
14046
+ };
14047
+ const onReady = () => {
14048
+ this.waitForCanvasScene(container, config, pageIndex).then(async () => {
14049
+ try {
14050
+ const fabricInstance = this.getFabricCanvasFromContainer(container);
14051
+ const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
14052
+ await this.waitForCanvasImages(container, expectedImageCount);
14053
+ await this.waitForStableTextMetrics(container, config);
14054
+ await this.waitForCanvasScene(container, config, pageIndex);
14055
+ firstMountSettled = true;
14056
+ if (hasAutoShrink && lateFontSettleDetected) {
14057
+ console.log("[canvas-renderer][parity] late font-settle detected — remounting for auto-shrink reflow");
14058
+ await remountWithFreshKey();
14059
+ }
14060
+ const fabricCanvas = container.querySelector("canvas.upper-canvas, canvas");
14061
+ const sourceCanvas = (fabricInstance == null ? void 0 : fabricInstance.lowerCanvasEl) || container.querySelector("canvas.lower-canvas") || fabricCanvas;
14062
+ const fabricInstanceAfter = this.getFabricCanvasFromContainer(container) || fabricInstance;
14063
+ const sourceCanvasAfter = (fabricInstanceAfter == null ? void 0 : fabricInstanceAfter.lowerCanvasEl) || container.querySelector("canvas.lower-canvas") || sourceCanvas;
14064
+ if (!sourceCanvas) {
14065
+ cleanup();
14066
+ reject(new Error("No canvas element found after render"));
14067
+ return;
14068
+ }
14069
+ const exportCanvas = document.createElement("canvas");
14070
+ exportCanvas.width = sourceCanvasAfter.width;
14071
+ exportCanvas.height = sourceCanvasAfter.height;
14072
+ const exportCtx = exportCanvas.getContext("2d");
14073
+ if (!exportCtx) {
14074
+ cleanup();
14075
+ reject(new Error("Failed to create export canvas"));
14076
+ return;
14096
14077
  }
14078
+ exportCtx.save();
14079
+ exportCtx.scale(sourceCanvasAfter.width / canvasWidth, sourceCanvasAfter.height / canvasHeight);
14080
+ this.paintPageBackground(exportCtx, config.pages[pageIndex], canvasWidth, canvasHeight);
14081
+ exportCtx.restore();
14082
+ exportCtx.drawImage(sourceCanvasAfter, 0, 0);
14083
+ const mimeType = format === "jpeg" ? "image/jpeg" : format === "webp" ? "image/webp" : "image/png";
14084
+ const dataUrl = exportCanvas.toDataURL(mimeType, quality);
14085
+ cleanup();
14086
+ resolve(dataUrl);
14087
+ } catch (err) {
14088
+ cleanup();
14089
+ reject(err);
14097
14090
  }
14098
- }
14099
- }
14100
- if (el.children) walkElements(el.children);
14101
- if (el.objects) walkElements(el.objects);
14102
- }
14103
- };
14104
- for (const page of (config == null ? void 0 : config.pages) || []) {
14105
- if (page.children) walkElements(page.children);
14106
- if (page.elements) walkElements(page.elements);
14107
- }
14108
- fontKeys.add(`${FONT_FALLBACK_SYMBOLS}${SEP}400`);
14109
- for (const w of [300, 400, 500, 600, 700]) {
14110
- fontKeys.add(`${FONT_FALLBACK_DEVANAGARI}${SEP}${w}`);
14111
- }
14112
- const embedded = /* @__PURE__ */ new Set();
14113
- const tasks = [];
14114
- for (const key of fontKeys) {
14115
- const sep = key.indexOf(SEP);
14116
- const fontName = key.slice(0, sep);
14117
- const weight = parseInt(key.slice(sep + 1), 10);
14118
- if (!isFontAvailable(fontName)) continue;
14119
- tasks.push(
14120
- embedFont(pdf, fontName, weight, fontBaseUrl).then((ok) => {
14121
- if (ok) embedded.add(key);
14122
- })
14123
- );
14091
+ });
14092
+ };
14093
+ root = createRoot(container);
14094
+ root.render(
14095
+ createElement(PreviewCanvas2, {
14096
+ config,
14097
+ pageIndex,
14098
+ zoom: pixelRatio,
14099
+ absoluteZoom: true,
14100
+ skipFontReadyWait: false,
14101
+ onReady
14102
+ })
14103
+ );
14104
+ });
14124
14105
  }
14125
- await Promise.all(tasks);
14126
- console.log(`[pdf-fonts] Embedded ${embedded.size} font variants from config`);
14127
- return embedded;
14128
- }
14129
- function isDevanagari(char) {
14130
- const c = char.codePointAt(0) ?? 0;
14131
- return c >= 2304 && c <= 2431 || c >= 43232 && c <= 43263 || c >= 7376 && c <= 7423;
14132
- }
14133
- function containsDevanagari(text) {
14134
- if (!text) return false;
14135
- for (const char of text) {
14136
- if (isDevanagari(char)) return true;
14106
+ // ─── Internal: capture SVG from a rendered Fabric canvas ───
14107
+ //
14108
+ // APPROACH: Use the SAME PreviewCanvas that renders perfect PNGs, then call
14109
+ // Fabric's toSVG() on that canvas. This guarantees 100% layout parity —
14110
+ // the SVG is a vector snapshot of exactly what's on screen.
14111
+ //
14112
+ // The trick: before calling toSVG(), we temporarily neutralize the viewport
14113
+ // transform and retina scaling so Fabric emits coordinates in logical
14114
+ // document space (e.g. 612x792) instead of inflated pixel space.
14115
+ captureSvgViaPreviewCanvas(config, pageIndex, canvasWidth, canvasHeight) {
14116
+ return new Promise(async (resolve, reject) => {
14117
+ const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
14118
+ const hasAutoShrink = configHasAutoShrinkText(config);
14119
+ const container = document.createElement("div");
14120
+ container.style.cssText = `
14121
+ position: fixed; left: -99999px; top: -99999px;
14122
+ width: ${canvasWidth}px; height: ${canvasHeight}px;
14123
+ overflow: hidden; pointer-events: none; opacity: 0;
14124
+ `;
14125
+ document.body.appendChild(container);
14126
+ const timeout = setTimeout(() => {
14127
+ cleanup();
14128
+ reject(new Error("SVG render timeout (30s)"));
14129
+ }, 3e4);
14130
+ let root = null;
14131
+ let mountKey = 0;
14132
+ let didPreviewParityRemount = false;
14133
+ const cleanup = () => {
14134
+ clearTimeout(timeout);
14135
+ try {
14136
+ root == null ? void 0 : root.unmount();
14137
+ } catch {
14138
+ }
14139
+ container.remove();
14140
+ };
14141
+ const mountPreview = (readyHandler) => {
14142
+ root = createRoot(container);
14143
+ root.render(
14144
+ createElement(PreviewCanvas2, {
14145
+ key: `svg-capture-${mountKey}`,
14146
+ config,
14147
+ pageIndex,
14148
+ zoom: 1,
14149
+ absoluteZoom: true,
14150
+ skipFontReadyWait: false,
14151
+ onReady: readyHandler
14152
+ })
14153
+ );
14154
+ };
14155
+ const remountForPreviewParity = async () => {
14156
+ if (didPreviewParityRemount) return;
14157
+ didPreviewParityRemount = true;
14158
+ mountKey += 1;
14159
+ try {
14160
+ clearMeasurementCache();
14161
+ } catch {
14162
+ }
14163
+ try {
14164
+ clearFabricCharCache();
14165
+ } catch {
14166
+ }
14167
+ try {
14168
+ root == null ? void 0 : root.unmount();
14169
+ } catch {
14170
+ }
14171
+ await new Promise((settle) => {
14172
+ mountPreview(() => {
14173
+ this.waitForCanvasScene(container, config, pageIndex).then(async () => {
14174
+ const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
14175
+ await this.waitForCanvasImages(container, expectedImageCount);
14176
+ await this.waitForStableTextMetrics(container, config);
14177
+ await this.waitForCanvasScene(container, config, pageIndex);
14178
+ settle();
14179
+ }).catch(() => settle());
14180
+ });
14181
+ });
14182
+ };
14183
+ const onReady = () => {
14184
+ this.waitForCanvasScene(container, config, pageIndex).then(async () => {
14185
+ var _a, _b;
14186
+ try {
14187
+ const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
14188
+ await this.waitForCanvasImages(container, expectedImageCount);
14189
+ await this.waitForStableTextMetrics(container, config);
14190
+ await this.waitForCanvasScene(container, config, pageIndex);
14191
+ if (hasAutoShrink && !didPreviewParityRemount) {
14192
+ console.log("[canvas-renderer][svg-parity] remounting once to match PixldocsPreview auto-shrink stabilization");
14193
+ await remountForPreviewParity();
14194
+ }
14195
+ const fabricInstance = this.getFabricCanvasFromContainer(container);
14196
+ if (!fabricInstance) {
14197
+ cleanup();
14198
+ reject(new Error("No Fabric canvas instance found for SVG capture"));
14199
+ return;
14200
+ }
14201
+ const prevVPT = fabricInstance.viewportTransform ? [...fabricInstance.viewportTransform] : void 0;
14202
+ const prevSvgVPT = fabricInstance.svgViewportTransformation;
14203
+ const prevRetina = fabricInstance.enableRetinaScaling;
14204
+ const prevWidth = fabricInstance.width;
14205
+ const prevHeight = fabricInstance.height;
14206
+ fabricInstance.viewportTransform = [1, 0, 0, 1, 0, 0];
14207
+ fabricInstance.svgViewportTransformation = false;
14208
+ fabricInstance.enableRetinaScaling = false;
14209
+ fabricInstance.setDimensions(
14210
+ { width: canvasWidth, height: canvasHeight },
14211
+ { cssOnly: false, backstoreOnly: false }
14212
+ );
14213
+ const rawSvgString = fabricInstance.toSVG();
14214
+ const svgString = this.normalizeSvgDimensions(
14215
+ rawSvgString,
14216
+ canvasWidth,
14217
+ canvasHeight
14218
+ );
14219
+ fabricInstance.enableRetinaScaling = prevRetina;
14220
+ fabricInstance.setDimensions(
14221
+ { width: prevWidth, height: prevHeight },
14222
+ { cssOnly: false, backstoreOnly: false }
14223
+ );
14224
+ if (prevVPT) fabricInstance.viewportTransform = prevVPT;
14225
+ fabricInstance.svgViewportTransformation = prevSvgVPT;
14226
+ const page = config.pages[pageIndex];
14227
+ const backgroundColor = ((_a = page == null ? void 0 : page.settings) == null ? void 0 : _a.backgroundColor) || "#ffffff";
14228
+ const backgroundGradient = (_b = page == null ? void 0 : page.settings) == null ? void 0 : _b.backgroundGradient;
14229
+ cleanup();
14230
+ resolve({
14231
+ svg: svgString,
14232
+ width: canvasWidth,
14233
+ height: canvasHeight,
14234
+ backgroundColor,
14235
+ backgroundGradient
14236
+ });
14237
+ } catch (err) {
14238
+ cleanup();
14239
+ reject(err);
14240
+ }
14241
+ });
14242
+ };
14243
+ mountPreview(onReady);
14244
+ });
14137
14245
  }
14138
- return false;
14139
- }
14140
- function isBasicLatinOrLatin1(char) {
14141
- const c = char.codePointAt(0) ?? 0;
14142
- return c <= 591;
14143
- }
14144
- function classifyChar(char) {
14145
- if (isBasicLatinOrLatin1(char)) return "main";
14146
- if (isDevanagari(char)) return "devanagari";
14147
- return "symbol";
14148
- }
14149
- function splitIntoRuns(text) {
14150
- if (!text) return [];
14151
- const runs = [];
14152
- let currentType = null;
14153
- let currentText = "";
14154
- for (const char of text) {
14155
- const type = classifyChar(char);
14156
- if (type !== currentType && currentText) {
14157
- runs.push({ text: currentText, runType: currentType });
14158
- currentText = "";
14246
+ /**
14247
+ * Normalize the SVG's width/height/viewBox to match the logical page dimensions.
14248
+ * Fabric's toSVG() may output dimensions scaled by the canvas element's actual
14249
+ * pixel size (e.g., 2x due to devicePixelRatio), causing svg2pdf to render
14250
+ * content at the wrong scale. This rewrites the root <svg> attributes to ensure
14251
+ * the SVG coordinate system matches the intended page size exactly.
14252
+ */
14253
+ normalizeSvgDimensions(svg, targetWidth, targetHeight) {
14254
+ const widthMatch = svg.match(/<svg[^>]*\bwidth="([^"]+)"/i);
14255
+ const heightMatch = svg.match(/<svg[^>]*\bheight="([^"]+)"/i);
14256
+ const svgWidth = widthMatch ? parseFloat(widthMatch[1]) : targetWidth;
14257
+ const svgHeight = heightMatch ? parseFloat(heightMatch[1]) : targetHeight;
14258
+ console.log(
14259
+ `[canvas-renderer][svg-normalize] root ${svgWidth}x${svgHeight} → page ${targetWidth}x${targetHeight}`
14260
+ );
14261
+ let normalized = svg;
14262
+ if (/\bwidth="[^"]*"/i.test(normalized)) {
14263
+ normalized = normalized.replace(/(<svg[^>]*\b)width="[^"]*"/i, `$1width="${targetWidth}"`);
14264
+ } else {
14265
+ normalized = normalized.replace(/<svg\b/i, `<svg width="${targetWidth}"`);
14159
14266
  }
14160
- currentType = type;
14161
- currentText += char;
14162
- }
14163
- if (currentText && currentType) {
14164
- runs.push({ text: currentText, runType: currentType });
14165
- }
14166
- return runs;
14167
- }
14168
- function rewriteSvgFontsForJsPDF(svgStr) {
14169
- var _a, _b;
14170
- const parser = new DOMParser();
14171
- const doc = parser.parseFromString(svgStr, "image/svg+xml");
14172
- const textEls = doc.querySelectorAll("text, tspan, textPath");
14173
- const readStyleToken = (style, prop) => {
14174
- var _a2;
14175
- const match = style.match(new RegExp(`${prop}\\s*:\\s*([^;]+)`, "i"));
14176
- return ((_a2 = match == null ? void 0 : match[1]) == null ? void 0 : _a2.trim()) || null;
14177
- };
14178
- const resolveInheritedValue = (el, attr, styleProp = attr) => {
14179
- var _a2;
14180
- let current = el;
14181
- while (current) {
14182
- const attrVal = (_a2 = current.getAttribute(attr)) == null ? void 0 : _a2.trim();
14183
- if (attrVal) return attrVal;
14184
- const styleVal = readStyleToken(current.getAttribute("style") || "", styleProp);
14185
- if (styleVal) return styleVal;
14186
- current = current.parentElement;
14267
+ if (/\bheight="[^"]*"/i.test(normalized)) {
14268
+ normalized = normalized.replace(/(<svg[^>]*\b)height="[^"]*"/i, `$1height="${targetHeight}"`);
14269
+ } else {
14270
+ normalized = normalized.replace(/<svg\b/i, `<svg height="${targetHeight}"`);
14187
14271
  }
14188
- return null;
14189
- };
14190
- const resolveWeightNum = (weightRaw) => {
14191
- const parsedWeight = Number.parseInt(weightRaw, 10);
14192
- return Number.isFinite(parsedWeight) ? parsedWeight : /bold/i.test(weightRaw) ? 700 : /medium/i.test(weightRaw) ? 500 : /semi/i.test(weightRaw) ? 600 : /light/i.test(weightRaw) ? 300 : 400;
14193
- };
14194
- const buildStyleString = (existingStyle, fontName) => {
14195
- const stylePairs = existingStyle.split(";").map((part) => part.trim()).filter(Boolean).filter((part) => !/^font-family\s*:/i.test(part) && !/^font-weight\s*:/i.test(part) && !/^font-style\s*:/i.test(part));
14196
- stylePairs.push(`font-family: ${fontName}`);
14197
- stylePairs.push(`font-weight: normal`);
14198
- stylePairs.push(`font-style: normal`);
14199
- return stylePairs.join("; ");
14200
- };
14201
- for (const el of textEls) {
14202
- const inlineStyle = el.getAttribute("style") || "";
14203
- const rawFf = resolveInheritedValue(el, "font-family");
14204
- if (!rawFf) continue;
14205
- const clean = (_a = rawFf.split(",")[0]) == null ? void 0 : _a.replace(/['"]/g, "").trim();
14206
- if (!isFontAvailable(clean)) continue;
14207
- const weightRaw = resolveInheritedValue(el, "font-weight") || "400";
14208
- const styleRaw = resolveInheritedValue(el, "font-style") || "normal";
14209
- const weight = resolveWeightNum(weightRaw);
14210
- const resolved = resolveFontWeight(weight);
14211
- const isItalic = /italic|oblique/i.test(styleRaw);
14212
- const jsPdfName = getEmbeddedJsPDFFontName(clean, resolved, isItalic);
14213
- el.setAttribute("data-source-font-family", clean);
14214
- el.setAttribute("data-source-font-weight", String(resolved));
14215
- el.setAttribute("data-source-font-style", isItalic ? "italic" : "normal");
14216
- const directText = Array.from(el.childNodes).filter((n) => n.nodeType === 3).map((n) => n.textContent || "").join("");
14217
- const hasDevanagari = containsDevanagari(directText);
14218
- if (hasDevanagari && directText.length > 0) {
14219
- const devanagariWeight = resolveFontWeight(weight);
14220
- const devanagariJsPdfName = getEmbeddedJsPDFFontName(FONT_FALLBACK_DEVANAGARI, devanagariWeight);
14221
- const symbolJsPdfName = isFontAvailable(FONT_FALLBACK_SYMBOLS) ? getEmbeddedJsPDFFontName(FONT_FALLBACK_SYMBOLS, 400) : jsPdfName;
14222
- const childNodes = Array.from(el.childNodes);
14223
- for (const node of childNodes) {
14224
- if (node.nodeType !== 3 || !node.textContent) continue;
14225
- const runs = splitIntoRuns(node.textContent);
14226
- if (runs.length <= 1 && ((_b = runs[0]) == null ? void 0 : _b.runType) !== "devanagari") continue;
14227
- const fragment = doc.createDocumentFragment();
14228
- for (const run of runs) {
14229
- const tspan = doc.createElementNS("http://www.w3.org/2000/svg", "tspan");
14230
- let runFont;
14231
- if (run.runType === "devanagari") {
14232
- runFont = devanagariJsPdfName;
14233
- } else if (run.runType === "symbol") {
14234
- runFont = symbolJsPdfName;
14235
- } else {
14236
- runFont = jsPdfName;
14237
- }
14238
- tspan.setAttribute("font-family", runFont);
14239
- tspan.setAttribute("font-weight", "normal");
14240
- tspan.setAttribute("font-style", "normal");
14241
- tspan.textContent = run.text;
14242
- fragment.appendChild(tspan);
14243
- }
14244
- el.replaceChild(fragment, node);
14245
- }
14246
- el.setAttribute("font-family", jsPdfName);
14247
- el.setAttribute("font-weight", "normal");
14248
- el.setAttribute("font-style", "normal");
14249
- el.setAttribute("style", buildStyleString(inlineStyle, jsPdfName));
14272
+ const viewBox = `0 0 ${targetWidth} ${targetHeight}`;
14273
+ if (/\bviewBox="[^"]*"/i.test(normalized)) {
14274
+ normalized = normalized.replace(/viewBox="[^"]*"/i, `viewBox="${viewBox}"`);
14250
14275
  } else {
14251
- el.setAttribute("font-family", jsPdfName);
14252
- el.setAttribute("font-weight", "normal");
14253
- el.setAttribute("font-style", "normal");
14254
- el.setAttribute("style", buildStyleString(inlineStyle, jsPdfName));
14276
+ normalized = normalized.replace(/<svg\b/i, `<svg viewBox="${viewBox}"`);
14277
+ }
14278
+ normalized = normalized.replace(/="undefined"/g, '="0"');
14279
+ normalized = normalized.replace(/="NaN"/g, '="0"');
14280
+ if (/\bx="[^"]*"/i.test(normalized)) {
14281
+ normalized = normalized.replace(/(<svg[^>]*\b)x="[^"]*"/i, '$1x="0"');
14282
+ } else {
14283
+ normalized = normalized.replace(/<svg\b/i, '<svg x="0"');
14284
+ }
14285
+ if (/\by="[^"]*"/i.test(normalized)) {
14286
+ normalized = normalized.replace(/(<svg[^>]*\b)y="[^"]*"/i, '$1y="0"');
14287
+ } else {
14288
+ normalized = normalized.replace(/<svg\b/i, '<svg y="0"');
14289
+ }
14290
+ normalized = normalized.replace(/\bpreserveAspectRatio="[^"]*"/i, 'preserveAspectRatio="none"');
14291
+ if (!/\bpreserveAspectRatio="[^"]*"/i.test(normalized)) {
14292
+ normalized = normalized.replace(/<svg\b/i, '<svg preserveAspectRatio="none"');
14255
14293
  }
14294
+ return normalized;
14256
14295
  }
14257
- return new XMLSerializer().serializeToString(doc.documentElement);
14258
- }
14259
- function extractFontFamiliesFromSvgs(svgs) {
14260
- const families = /* @__PURE__ */ new Set();
14261
- const regex = /font-family[=:]\s*["']?([^"';},]+)/gi;
14262
- for (const svg of svgs) {
14263
- let m;
14264
- while ((m = regex.exec(svg)) !== null) {
14265
- const raw = m[1].trim().split(",")[0].trim().replace(/^['"]|['"]$/g, "");
14266
- if (raw && raw !== "serif" && raw !== "sans-serif" && raw !== "monospace") {
14267
- families.add(raw);
14296
+ /**
14297
+ * Find the Fabric.Canvas instance that belongs to a given container element,
14298
+ * using the global __fabricCanvasRegistry (set by PageCanvas).
14299
+ */
14300
+ getFabricCanvasFromContainer(container) {
14301
+ const registry2 = window.__fabricCanvasRegistry;
14302
+ if (registry2 instanceof Map) {
14303
+ for (const entry of registry2.values()) {
14304
+ const canvas = (entry == null ? void 0 : entry.canvas) || entry;
14305
+ if (!canvas || typeof canvas.toSVG !== "function") continue;
14306
+ const el = canvas.lowerCanvasEl || canvas.upperCanvasEl;
14307
+ if (el && container.contains(el)) return canvas;
14268
14308
  }
14269
14309
  }
14310
+ return null;
14270
14311
  }
14271
- return families;
14272
- }
14273
- async function embedFontsInPdf(pdf, fontFamilies, fontBaseUrl) {
14274
- const embedded = /* @__PURE__ */ new Set();
14275
- const weights = [300, 400, 500, 600, 700];
14276
- const tasks = [];
14277
- for (const family of fontFamilies) {
14278
- if (!isFontAvailable(family)) {
14279
- console.warn(`[pdf-fonts] No TTF mapping for "${family}" — will use Helvetica fallback`);
14280
- continue;
14281
- }
14282
- for (const w of weights) {
14283
- tasks.push(
14284
- embedFont(pdf, family, w, fontBaseUrl).then((ok) => {
14285
- if (ok) embedded.add(`${family}${w}`);
14286
- })
14287
- );
14312
+ async waitForStableTextMetrics(container, config) {
14313
+ var _a, _b, _c;
14314
+ if (typeof document !== "undefined") {
14315
+ void ensureFontsForResolvedConfig(config);
14316
+ await this.waitForRelevantFonts(config);
14288
14317
  }
14318
+ const fabricInstance = this.getFabricCanvasFromContainer(container);
14319
+ if (!(fabricInstance == null ? void 0 : fabricInstance.getObjects)) return;
14320
+ const waitForPaint = () => new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(() => r())));
14321
+ const primeCharBounds = (obj) => {
14322
+ if (obj instanceof fabric.Textbox) {
14323
+ const lines = obj._textLines;
14324
+ if (Array.isArray(lines)) {
14325
+ for (let i = 0; i < lines.length; i++) {
14326
+ try {
14327
+ obj.getLineWidth(i);
14328
+ } catch {
14329
+ }
14330
+ }
14331
+ }
14332
+ obj.dirty = true;
14333
+ return;
14334
+ }
14335
+ if (obj instanceof fabric.Group) {
14336
+ obj.getObjects().forEach(primeCharBounds);
14337
+ obj.dirty = true;
14338
+ }
14339
+ };
14340
+ fabricInstance.getObjects().forEach(primeCharBounds);
14341
+ (_a = fabricInstance.calcOffset) == null ? void 0 : _a.call(fabricInstance);
14342
+ (_b = fabricInstance.renderAll) == null ? void 0 : _b.call(fabricInstance);
14343
+ await waitForPaint();
14344
+ (_c = fabricInstance.renderAll) == null ? void 0 : _c.call(fabricInstance);
14345
+ await waitForPaint();
14289
14346
  }
14290
- await Promise.all(tasks);
14291
- console.log(`[pdf-fonts] Embedded ${embedded.size} font variants for ${fontFamilies.size} families`);
14292
- return embedded;
14293
14347
  }
14294
- const pdfFonts = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
14295
- __proto__: null,
14296
- FONT_FALLBACK_DEVANAGARI,
14297
- FONT_FALLBACK_SYMBOLS,
14298
- FONT_FILES,
14299
- FONT_WEIGHT_LABELS,
14300
- embedFont,
14301
- embedFontsForConfig,
14302
- embedFontsInPdf,
14303
- extractFontFamiliesFromSvgs,
14304
- getEmbeddedJsPDFFontName,
14305
- isFontAvailable,
14306
- resolveFontWeight,
14307
- rewriteSvgFontsForJsPDF
14308
- }, Symbol.toStringTag, { value: "Module" }));
14309
14348
  const SVG_DRAWABLE_TAGS = /* @__PURE__ */ new Set([
14310
14349
  "path",
14311
14350
  "rect",
@@ -15624,7 +15663,7 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
15624
15663
  const hasGradient = !!((_b = (_a = page.backgroundGradient) == null ? void 0 : _a.stops) == null ? void 0 : _b.length);
15625
15664
  drawPageBackground(pdf, i, page.width, page.height, page.backgroundColor, page.backgroundGradient);
15626
15665
  const shouldStripBg = stripPageBackground ?? hasGradient;
15627
- const shouldOutlineText = options.outlineText ?? true;
15666
+ const shouldOutlineText = options.outlineText === true;
15628
15667
  const pageSvg = page.svg;
15629
15668
  let processedSvg = await prepareLiveCanvasSvgForPdf(pageSvg, page.width, page.height, `page-${i + 1}`, {
15630
15669
  stripPageBackground: shouldStripBg