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