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