@pixldocs/canvas-renderer 0.5.76 → 0.5.78
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 +331 -228
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +331 -228
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -5213,6 +5213,17 @@ function buildRoundedTrianglePath(w, h, rTop, rBR, rBL) {
|
|
|
5213
5213
|
];
|
|
5214
5214
|
return parts.join(" ");
|
|
5215
5215
|
}
|
|
5216
|
+
const roundDiag = (value) => {
|
|
5217
|
+
if (typeof value !== "number") return value;
|
|
5218
|
+
return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
|
|
5219
|
+
};
|
|
5220
|
+
const stringifyDiag = (payload) => {
|
|
5221
|
+
try {
|
|
5222
|
+
return JSON.stringify(payload, (_key, value) => roundDiag(value));
|
|
5223
|
+
} catch {
|
|
5224
|
+
return String(payload);
|
|
5225
|
+
}
|
|
5226
|
+
};
|
|
5216
5227
|
function buildRoundedRectPath(w, h, tl, tr, br, bl) {
|
|
5217
5228
|
return buildRoundedRectPath$1(w, h, getRoundedRectRadii(w, h, { rxTL: tl, rxTR: tr, rxBR: br, rxBL: bl }));
|
|
5218
5229
|
}
|
|
@@ -5323,11 +5334,11 @@ function createShape(element) {
|
|
|
5323
5334
|
}
|
|
5324
5335
|
}
|
|
5325
5336
|
function createText(element) {
|
|
5326
|
-
var _a;
|
|
5337
|
+
var _a, _b;
|
|
5327
5338
|
const overflowPolicy = element.overflowPolicy || "grow-and-push";
|
|
5328
5339
|
let text = element.text || "Text";
|
|
5329
5340
|
let fontSize = element.fontSize || 16;
|
|
5330
|
-
element.minFontSize || 8;
|
|
5341
|
+
const minFontSize = element.minFontSize || 8;
|
|
5331
5342
|
const maxLines = element.maxLines || 3;
|
|
5332
5343
|
const baseWidth = element.width && element.width > 0 ? element.width : 200;
|
|
5333
5344
|
const baseHeight = element.height;
|
|
@@ -5339,6 +5350,7 @@ function createText(element) {
|
|
|
5339
5350
|
const startFontSize = fontSize;
|
|
5340
5351
|
let breakReason = "min-font-size-reached";
|
|
5341
5352
|
let lastIter = null;
|
|
5353
|
+
const iterationSamples = [];
|
|
5342
5354
|
while (fontSize > 1) {
|
|
5343
5355
|
const testTextbox = new fabric__namespace.Textbox(text, {
|
|
5344
5356
|
width: fixedWidth,
|
|
@@ -5356,7 +5368,7 @@ function createText(element) {
|
|
|
5356
5368
|
const hasNoImplicitWrap = renderedLineCount <= explicitLineCount;
|
|
5357
5369
|
const fitsHeight = !baseHeight || textHeight <= baseHeight;
|
|
5358
5370
|
const widthMetrics = getTextboxWidthFitMetrics(testTextbox, fixedWidth);
|
|
5359
|
-
const { fitsWidth } = widthMetrics;
|
|
5371
|
+
const { maxLineWidth, widthDidGrow, fitsWidth } = widthMetrics;
|
|
5360
5372
|
if (debugAutoShrink) {
|
|
5361
5373
|
lastIter = {
|
|
5362
5374
|
fontSize,
|
|
@@ -5367,8 +5379,12 @@ function createText(element) {
|
|
|
5367
5379
|
fixedWidth,
|
|
5368
5380
|
hasNoImplicitWrap,
|
|
5369
5381
|
fitsHeight,
|
|
5370
|
-
fitsWidth
|
|
5382
|
+
fitsWidth,
|
|
5383
|
+
textLines: (testTextbox.textLines || []).map((line) => Array.isArray(line) ? line.join("") : String(line ?? ""))
|
|
5371
5384
|
};
|
|
5385
|
+
if (iterationSamples.length < 6 || fontSize <= minFontSize + 2) {
|
|
5386
|
+
iterationSamples.push(lastIter);
|
|
5387
|
+
}
|
|
5372
5388
|
}
|
|
5373
5389
|
if (hasNoImplicitWrap && fitsHeight && fitsWidth) {
|
|
5374
5390
|
breakReason = "fits";
|
|
@@ -5377,10 +5393,11 @@ function createText(element) {
|
|
|
5377
5393
|
fontSize--;
|
|
5378
5394
|
}
|
|
5379
5395
|
if (debugAutoShrink) {
|
|
5380
|
-
console.log("[auto-shrink][diag]"
|
|
5396
|
+
console.log("[auto-shrink][diag] " + stringifyDiag({
|
|
5381
5397
|
id: element.id,
|
|
5382
5398
|
name: element.name,
|
|
5383
|
-
text,
|
|
5399
|
+
text: text.slice(0, 180),
|
|
5400
|
+
textLength: text.length,
|
|
5384
5401
|
fontFamily: element.fontFamily,
|
|
5385
5402
|
fontWeight: element.fontWeight,
|
|
5386
5403
|
elementWidth: element.width,
|
|
@@ -5392,10 +5409,11 @@ function createText(element) {
|
|
|
5392
5409
|
startFontSize,
|
|
5393
5410
|
finalFontSize: fontSize,
|
|
5394
5411
|
breakReason,
|
|
5412
|
+
iterations: iterationSamples,
|
|
5395
5413
|
lastIter,
|
|
5396
5414
|
fontCheckRegular: typeof document !== "undefined" && document.fonts ? document.fonts.check(`16px "${element.fontFamily || "Open Sans"}"`) : null,
|
|
5397
5415
|
fontCheckBold: typeof document !== "undefined" && document.fonts ? document.fonts.check(`bold 16px "${element.fontFamily || "Open Sans"}"`) : null
|
|
5398
|
-
});
|
|
5416
|
+
}));
|
|
5399
5417
|
}
|
|
5400
5418
|
}
|
|
5401
5419
|
if (overflowPolicy === "max-lines-ellipsis") {
|
|
@@ -5470,6 +5488,22 @@ function createText(element) {
|
|
|
5470
5488
|
textbox.setCoords();
|
|
5471
5489
|
}
|
|
5472
5490
|
textbox.dirty = true;
|
|
5491
|
+
if (overflowPolicy === "auto-shrink" && typeof window !== "undefined" && window.__pixldocsDebugAutoShrink === true) {
|
|
5492
|
+
console.log("[auto-shrink][final-textbox] " + stringifyDiag({
|
|
5493
|
+
id: element.id,
|
|
5494
|
+
name: element.name,
|
|
5495
|
+
text: text.slice(0, 180),
|
|
5496
|
+
targetWidth,
|
|
5497
|
+
targetScaleX,
|
|
5498
|
+
targetScaleY,
|
|
5499
|
+
finalFontSize: fontSize,
|
|
5500
|
+
textboxWidth: textbox.width,
|
|
5501
|
+
textboxHeight: textbox.height,
|
|
5502
|
+
lineCount: ((_b = textbox.textLines) == null ? void 0 : _b.length) || 0,
|
|
5503
|
+
lines: (textbox.textLines || []).map((line) => Array.isArray(line) ? line.join("") : String(line ?? "")),
|
|
5504
|
+
widthMetrics: getTextboxWidthFitMetrics(textbox, targetWidth)
|
|
5505
|
+
}));
|
|
5506
|
+
}
|
|
5473
5507
|
applyTextBackground(textbox, extractTextBgConfig(element));
|
|
5474
5508
|
const shadow = buildTextShadow(element);
|
|
5475
5509
|
if (shadow) textbox.set("shadow", shadow);
|
|
@@ -11630,6 +11664,232 @@ function applyContentBoundsPagination(config) {
|
|
|
11630
11664
|
if (!mutated) return config;
|
|
11631
11665
|
return { ...config, pages: resultPages };
|
|
11632
11666
|
}
|
|
11667
|
+
function normalizeFontFamily(fontStack) {
|
|
11668
|
+
const first = fontStack.split(",")[0].trim();
|
|
11669
|
+
return first.replace(/^['"]|['"]$/g, "");
|
|
11670
|
+
}
|
|
11671
|
+
const loadedFonts = /* @__PURE__ */ new Set();
|
|
11672
|
+
const loadingPromises = /* @__PURE__ */ new Map();
|
|
11673
|
+
function withTimeout(promise, timeoutMs = 4e3) {
|
|
11674
|
+
let timeoutId;
|
|
11675
|
+
return Promise.race([
|
|
11676
|
+
promise,
|
|
11677
|
+
new Promise((resolve) => {
|
|
11678
|
+
timeoutId = setTimeout(resolve, timeoutMs);
|
|
11679
|
+
})
|
|
11680
|
+
]).finally(() => {
|
|
11681
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
11682
|
+
});
|
|
11683
|
+
}
|
|
11684
|
+
async function loadGoogleFontCSS(rawFontFamily) {
|
|
11685
|
+
if (!rawFontFamily || typeof document === "undefined") return;
|
|
11686
|
+
const fontFamily = normalizeFontFamily(rawFontFamily);
|
|
11687
|
+
if (!fontFamily) return;
|
|
11688
|
+
if (loadedFonts.has(fontFamily)) return;
|
|
11689
|
+
const existing = loadingPromises.get(fontFamily);
|
|
11690
|
+
if (existing) return existing;
|
|
11691
|
+
const promise = (async () => {
|
|
11692
|
+
try {
|
|
11693
|
+
const encoded = encodeURIComponent(fontFamily);
|
|
11694
|
+
const url = `https://fonts.googleapis.com/css?family=${encoded}:300,400,500,600,700&display=swap`;
|
|
11695
|
+
const link = document.createElement("link");
|
|
11696
|
+
link.rel = "stylesheet";
|
|
11697
|
+
link.href = url;
|
|
11698
|
+
link.crossOrigin = "anonymous";
|
|
11699
|
+
await new Promise((resolve, reject) => {
|
|
11700
|
+
link.onload = () => resolve();
|
|
11701
|
+
link.onerror = () => reject(new Error(`Failed to load font: ${fontFamily}`));
|
|
11702
|
+
document.head.appendChild(link);
|
|
11703
|
+
});
|
|
11704
|
+
loadedFonts.add(fontFamily);
|
|
11705
|
+
} catch (e) {
|
|
11706
|
+
console.warn(`[@pixldocs/canvas-renderer] Font load failed: ${fontFamily}`, e);
|
|
11707
|
+
}
|
|
11708
|
+
})();
|
|
11709
|
+
loadingPromises.set(fontFamily, promise);
|
|
11710
|
+
await promise;
|
|
11711
|
+
loadingPromises.delete(fontFamily);
|
|
11712
|
+
}
|
|
11713
|
+
function collectFontsFromConfig(config) {
|
|
11714
|
+
var _a;
|
|
11715
|
+
const fonts = /* @__PURE__ */ new Set();
|
|
11716
|
+
fonts.add("Open Sans");
|
|
11717
|
+
fonts.add("Hind");
|
|
11718
|
+
function walk(nodes) {
|
|
11719
|
+
var _a2;
|
|
11720
|
+
if (!nodes) return;
|
|
11721
|
+
for (const node of nodes) {
|
|
11722
|
+
if (node.fontFamily) fonts.add(normalizeFontFamily(node.fontFamily));
|
|
11723
|
+
if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) fonts.add(normalizeFontFamily(node.smartProps.fontFamily));
|
|
11724
|
+
if (node.styles && Array.isArray(node.styles)) {
|
|
11725
|
+
for (const lineStyle of node.styles) {
|
|
11726
|
+
if (lineStyle && typeof lineStyle === "object") {
|
|
11727
|
+
for (const charStyle of Object.values(lineStyle)) {
|
|
11728
|
+
if (charStyle == null ? void 0 : charStyle.fontFamily) fonts.add(normalizeFontFamily(charStyle.fontFamily));
|
|
11729
|
+
}
|
|
11730
|
+
}
|
|
11731
|
+
}
|
|
11732
|
+
}
|
|
11733
|
+
if (node.children) walk(node.children);
|
|
11734
|
+
}
|
|
11735
|
+
}
|
|
11736
|
+
for (const page of config.pages || []) {
|
|
11737
|
+
walk(page.children || []);
|
|
11738
|
+
}
|
|
11739
|
+
if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
|
|
11740
|
+
for (const def of Object.values(config.themeConfig.variables)) {
|
|
11741
|
+
if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
|
|
11742
|
+
if (def.label && /font/i.test(def.label)) {
|
|
11743
|
+
fonts.add(normalizeFontFamily(def.value));
|
|
11744
|
+
}
|
|
11745
|
+
}
|
|
11746
|
+
}
|
|
11747
|
+
}
|
|
11748
|
+
return fonts;
|
|
11749
|
+
}
|
|
11750
|
+
function collectFontDescriptorsFromConfig(config) {
|
|
11751
|
+
var _a;
|
|
11752
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11753
|
+
const descriptors = [];
|
|
11754
|
+
function add(family, weight, style) {
|
|
11755
|
+
const f = normalizeFontFamily(family);
|
|
11756
|
+
if (!f) return;
|
|
11757
|
+
const w = weight ?? 400;
|
|
11758
|
+
const s = style ?? "normal";
|
|
11759
|
+
const key = `${f}|${w}|${s}`;
|
|
11760
|
+
if (seen.has(key)) return;
|
|
11761
|
+
seen.add(key);
|
|
11762
|
+
descriptors.push({ family: f, weight: w, style: s });
|
|
11763
|
+
}
|
|
11764
|
+
function walk(nodes) {
|
|
11765
|
+
var _a2;
|
|
11766
|
+
if (!nodes) return;
|
|
11767
|
+
for (const node of nodes) {
|
|
11768
|
+
if (node.fontFamily) {
|
|
11769
|
+
add(node.fontFamily, node.fontWeight, node.fontStyle);
|
|
11770
|
+
if (node.type === "text") {
|
|
11771
|
+
for (const w of [300, 400, 500, 600, 700]) {
|
|
11772
|
+
add(node.fontFamily, w, node.fontStyle);
|
|
11773
|
+
}
|
|
11774
|
+
}
|
|
11775
|
+
}
|
|
11776
|
+
if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) {
|
|
11777
|
+
add(node.smartProps.fontFamily, node.smartProps.fontWeight, node.smartProps.fontStyle);
|
|
11778
|
+
}
|
|
11779
|
+
if (node.styles) {
|
|
11780
|
+
const styleEntries = Array.isArray(node.styles) ? node.styles : Object.values(node.styles);
|
|
11781
|
+
for (const lineStyle of styleEntries) {
|
|
11782
|
+
if (lineStyle && typeof lineStyle === "object") {
|
|
11783
|
+
for (const charStyle of Object.values(lineStyle)) {
|
|
11784
|
+
if (charStyle == null ? void 0 : charStyle.fontFamily) {
|
|
11785
|
+
add(charStyle.fontFamily, charStyle.fontWeight, charStyle.fontStyle);
|
|
11786
|
+
}
|
|
11787
|
+
}
|
|
11788
|
+
}
|
|
11789
|
+
}
|
|
11790
|
+
}
|
|
11791
|
+
if (node.children) walk(node.children);
|
|
11792
|
+
}
|
|
11793
|
+
}
|
|
11794
|
+
add("Open Sans", 400, "normal");
|
|
11795
|
+
add("Hind", 400, "normal");
|
|
11796
|
+
add("Hind", 700, "normal");
|
|
11797
|
+
for (const page of config.pages || []) {
|
|
11798
|
+
walk(page.children || []);
|
|
11799
|
+
}
|
|
11800
|
+
if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
|
|
11801
|
+
for (const def of Object.values(config.themeConfig.variables)) {
|
|
11802
|
+
if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
|
|
11803
|
+
if (def.label && /font/i.test(def.label)) {
|
|
11804
|
+
add(def.value);
|
|
11805
|
+
}
|
|
11806
|
+
}
|
|
11807
|
+
}
|
|
11808
|
+
}
|
|
11809
|
+
return descriptors;
|
|
11810
|
+
}
|
|
11811
|
+
async function ensureFontsForResolvedConfig(config) {
|
|
11812
|
+
if (typeof document === "undefined") return;
|
|
11813
|
+
const descriptors = collectFontDescriptorsFromConfig(config);
|
|
11814
|
+
const families = new Set(descriptors.map((d) => d.family));
|
|
11815
|
+
await withTimeout(Promise.all([...families].map((f) => loadGoogleFontCSS(f))), 3500);
|
|
11816
|
+
if (document.fonts) {
|
|
11817
|
+
descriptors.forEach((d) => {
|
|
11818
|
+
const stylePrefix = d.style === "italic" ? "italic " : "";
|
|
11819
|
+
const weightStr = String(d.weight);
|
|
11820
|
+
const spec = `${stylePrefix}${weightStr} 16px "${d.family}"`;
|
|
11821
|
+
document.fonts.load(spec).catch(() => {
|
|
11822
|
+
});
|
|
11823
|
+
});
|
|
11824
|
+
}
|
|
11825
|
+
}
|
|
11826
|
+
function configHasAutoShrinkText$1(config) {
|
|
11827
|
+
var _a;
|
|
11828
|
+
if (!((_a = config == null ? void 0 : config.pages) == null ? void 0 : _a.length)) return false;
|
|
11829
|
+
const walk = (nodes) => {
|
|
11830
|
+
for (const node of nodes || []) {
|
|
11831
|
+
if (!node) continue;
|
|
11832
|
+
if (node.type === "text" && node.overflowPolicy === "auto-shrink") return true;
|
|
11833
|
+
if (Array.isArray(node.children) && node.children.length && walk(node.children)) return true;
|
|
11834
|
+
}
|
|
11835
|
+
return false;
|
|
11836
|
+
};
|
|
11837
|
+
for (const page of config.pages) {
|
|
11838
|
+
if (walk(page.children || [])) return true;
|
|
11839
|
+
}
|
|
11840
|
+
return false;
|
|
11841
|
+
}
|
|
11842
|
+
async function awaitFontsForConfig(config, maxWaitMs) {
|
|
11843
|
+
if (typeof document === "undefined" || !document.fonts) return;
|
|
11844
|
+
await ensureFontsForResolvedConfig(config);
|
|
11845
|
+
const descriptors = collectFontDescriptorsFromConfig(config);
|
|
11846
|
+
if (descriptors.length === 0) return;
|
|
11847
|
+
const loads = Promise.all(
|
|
11848
|
+
descriptors.map((d) => {
|
|
11849
|
+
const stylePrefix = d.style === "italic" ? "italic " : "";
|
|
11850
|
+
const spec = `${stylePrefix}${d.weight} 16px "${d.family}"`;
|
|
11851
|
+
return document.fonts.load(spec).catch(() => []);
|
|
11852
|
+
})
|
|
11853
|
+
).then(() => void 0);
|
|
11854
|
+
await Promise.race([
|
|
11855
|
+
loads,
|
|
11856
|
+
new Promise((resolve) => setTimeout(resolve, maxWaitMs))
|
|
11857
|
+
]);
|
|
11858
|
+
await Promise.race([
|
|
11859
|
+
document.fonts.ready.catch(() => void 0).then(() => void 0),
|
|
11860
|
+
new Promise((r) => setTimeout(r, Math.min(500, maxWaitMs)))
|
|
11861
|
+
]);
|
|
11862
|
+
const checkSpecs = [];
|
|
11863
|
+
for (const d of descriptors) {
|
|
11864
|
+
const stylePrefix = d.style === "italic" ? "italic " : "";
|
|
11865
|
+
checkSpecs.push(`${stylePrefix}${d.weight} 16px "${d.family}"`);
|
|
11866
|
+
}
|
|
11867
|
+
const startedAt = Date.now();
|
|
11868
|
+
const pollBudget = Math.min(maxWaitMs, 2500);
|
|
11869
|
+
const allReady = () => {
|
|
11870
|
+
for (const spec of checkSpecs) {
|
|
11871
|
+
try {
|
|
11872
|
+
if (!document.fonts.check(spec)) return false;
|
|
11873
|
+
} catch {
|
|
11874
|
+
}
|
|
11875
|
+
}
|
|
11876
|
+
return true;
|
|
11877
|
+
};
|
|
11878
|
+
while (!allReady() && Date.now() - startedAt < pollBudget) {
|
|
11879
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
11880
|
+
}
|
|
11881
|
+
await new Promise(
|
|
11882
|
+
(resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))
|
|
11883
|
+
);
|
|
11884
|
+
}
|
|
11885
|
+
async function awaitFontsBeforeTextReflow(config) {
|
|
11886
|
+
if (typeof document === "undefined" || !configHasAutoShrinkText$1(config)) return;
|
|
11887
|
+
try {
|
|
11888
|
+
await awaitFontsForConfig(config, 4e3);
|
|
11889
|
+
} catch (error) {
|
|
11890
|
+
console.warn("[@pixldocs/canvas-renderer] Font wait before text reflow failed:", error);
|
|
11891
|
+
}
|
|
11892
|
+
}
|
|
11633
11893
|
function repeatablePageToSection(page) {
|
|
11634
11894
|
return {
|
|
11635
11895
|
id: page.id,
|
|
@@ -11823,6 +12083,7 @@ async function resolveTemplateData(options) {
|
|
|
11823
12083
|
}
|
|
11824
12084
|
}
|
|
11825
12085
|
const repeatablePagesInput = deriveRepeatablePagesFromTemplate(config, inlineFormSchema, mergedFormData);
|
|
12086
|
+
await awaitFontsBeforeTextReflow(config);
|
|
11826
12087
|
const resolvedConfig = applyFormDataToConfig(
|
|
11827
12088
|
config,
|
|
11828
12089
|
mappings,
|
|
@@ -11967,6 +12228,7 @@ async function resolveFromForm(options) {
|
|
|
11967
12228
|
};
|
|
11968
12229
|
collectFormats(formConfig.sections);
|
|
11969
12230
|
}
|
|
12231
|
+
await awaitFontsBeforeTextReflow(templateConfig);
|
|
11970
12232
|
let resolvedConfig = applyFormDataToConfig(
|
|
11971
12233
|
templateConfig,
|
|
11972
12234
|
mappings,
|
|
@@ -12145,224 +12407,6 @@ function paintRepeatableSections(config, repeatableSections) {
|
|
|
12145
12407
|
}
|
|
12146
12408
|
}
|
|
12147
12409
|
}
|
|
12148
|
-
function normalizeFontFamily(fontStack) {
|
|
12149
|
-
const first = fontStack.split(",")[0].trim();
|
|
12150
|
-
return first.replace(/^['"]|['"]$/g, "");
|
|
12151
|
-
}
|
|
12152
|
-
const loadedFonts = /* @__PURE__ */ new Set();
|
|
12153
|
-
const loadingPromises = /* @__PURE__ */ new Map();
|
|
12154
|
-
function withTimeout(promise, timeoutMs = 4e3) {
|
|
12155
|
-
let timeoutId;
|
|
12156
|
-
return Promise.race([
|
|
12157
|
-
promise,
|
|
12158
|
-
new Promise((resolve) => {
|
|
12159
|
-
timeoutId = setTimeout(resolve, timeoutMs);
|
|
12160
|
-
})
|
|
12161
|
-
]).finally(() => {
|
|
12162
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
12163
|
-
});
|
|
12164
|
-
}
|
|
12165
|
-
async function loadGoogleFontCSS(rawFontFamily) {
|
|
12166
|
-
if (!rawFontFamily || typeof document === "undefined") return;
|
|
12167
|
-
const fontFamily = normalizeFontFamily(rawFontFamily);
|
|
12168
|
-
if (!fontFamily) return;
|
|
12169
|
-
if (loadedFonts.has(fontFamily)) return;
|
|
12170
|
-
const existing = loadingPromises.get(fontFamily);
|
|
12171
|
-
if (existing) return existing;
|
|
12172
|
-
const promise = (async () => {
|
|
12173
|
-
try {
|
|
12174
|
-
const encoded = encodeURIComponent(fontFamily);
|
|
12175
|
-
const url = `https://fonts.googleapis.com/css?family=${encoded}:300,400,500,600,700&display=swap`;
|
|
12176
|
-
const link = document.createElement("link");
|
|
12177
|
-
link.rel = "stylesheet";
|
|
12178
|
-
link.href = url;
|
|
12179
|
-
link.crossOrigin = "anonymous";
|
|
12180
|
-
await new Promise((resolve, reject) => {
|
|
12181
|
-
link.onload = () => resolve();
|
|
12182
|
-
link.onerror = () => reject(new Error(`Failed to load font: ${fontFamily}`));
|
|
12183
|
-
document.head.appendChild(link);
|
|
12184
|
-
});
|
|
12185
|
-
loadedFonts.add(fontFamily);
|
|
12186
|
-
} catch (e) {
|
|
12187
|
-
console.warn(`[@pixldocs/canvas-renderer] Font load failed: ${fontFamily}`, e);
|
|
12188
|
-
}
|
|
12189
|
-
})();
|
|
12190
|
-
loadingPromises.set(fontFamily, promise);
|
|
12191
|
-
await promise;
|
|
12192
|
-
loadingPromises.delete(fontFamily);
|
|
12193
|
-
}
|
|
12194
|
-
function collectFontsFromConfig(config) {
|
|
12195
|
-
var _a;
|
|
12196
|
-
const fonts = /* @__PURE__ */ new Set();
|
|
12197
|
-
fonts.add("Open Sans");
|
|
12198
|
-
fonts.add("Hind");
|
|
12199
|
-
function walk(nodes) {
|
|
12200
|
-
var _a2;
|
|
12201
|
-
if (!nodes) return;
|
|
12202
|
-
for (const node of nodes) {
|
|
12203
|
-
if (node.fontFamily) fonts.add(normalizeFontFamily(node.fontFamily));
|
|
12204
|
-
if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) fonts.add(normalizeFontFamily(node.smartProps.fontFamily));
|
|
12205
|
-
if (node.styles && Array.isArray(node.styles)) {
|
|
12206
|
-
for (const lineStyle of node.styles) {
|
|
12207
|
-
if (lineStyle && typeof lineStyle === "object") {
|
|
12208
|
-
for (const charStyle of Object.values(lineStyle)) {
|
|
12209
|
-
if (charStyle == null ? void 0 : charStyle.fontFamily) fonts.add(normalizeFontFamily(charStyle.fontFamily));
|
|
12210
|
-
}
|
|
12211
|
-
}
|
|
12212
|
-
}
|
|
12213
|
-
}
|
|
12214
|
-
if (node.children) walk(node.children);
|
|
12215
|
-
}
|
|
12216
|
-
}
|
|
12217
|
-
for (const page of config.pages || []) {
|
|
12218
|
-
walk(page.children || []);
|
|
12219
|
-
}
|
|
12220
|
-
if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
|
|
12221
|
-
for (const def of Object.values(config.themeConfig.variables)) {
|
|
12222
|
-
if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
|
|
12223
|
-
if (def.label && /font/i.test(def.label)) {
|
|
12224
|
-
fonts.add(normalizeFontFamily(def.value));
|
|
12225
|
-
}
|
|
12226
|
-
}
|
|
12227
|
-
}
|
|
12228
|
-
}
|
|
12229
|
-
return fonts;
|
|
12230
|
-
}
|
|
12231
|
-
function collectFontDescriptorsFromConfig(config) {
|
|
12232
|
-
var _a;
|
|
12233
|
-
const seen = /* @__PURE__ */ new Set();
|
|
12234
|
-
const descriptors = [];
|
|
12235
|
-
function add(family, weight, style) {
|
|
12236
|
-
const f = normalizeFontFamily(family);
|
|
12237
|
-
if (!f) return;
|
|
12238
|
-
const w = weight ?? 400;
|
|
12239
|
-
const s = style ?? "normal";
|
|
12240
|
-
const key = `${f}|${w}|${s}`;
|
|
12241
|
-
if (seen.has(key)) return;
|
|
12242
|
-
seen.add(key);
|
|
12243
|
-
descriptors.push({ family: f, weight: w, style: s });
|
|
12244
|
-
}
|
|
12245
|
-
function walk(nodes) {
|
|
12246
|
-
var _a2;
|
|
12247
|
-
if (!nodes) return;
|
|
12248
|
-
for (const node of nodes) {
|
|
12249
|
-
if (node.fontFamily) {
|
|
12250
|
-
add(node.fontFamily, node.fontWeight, node.fontStyle);
|
|
12251
|
-
if (node.type === "text") {
|
|
12252
|
-
for (const w of [300, 400, 500, 600, 700]) {
|
|
12253
|
-
add(node.fontFamily, w, node.fontStyle);
|
|
12254
|
-
}
|
|
12255
|
-
}
|
|
12256
|
-
}
|
|
12257
|
-
if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) {
|
|
12258
|
-
add(node.smartProps.fontFamily, node.smartProps.fontWeight, node.smartProps.fontStyle);
|
|
12259
|
-
}
|
|
12260
|
-
if (node.styles) {
|
|
12261
|
-
const styleEntries = Array.isArray(node.styles) ? node.styles : Object.values(node.styles);
|
|
12262
|
-
for (const lineStyle of styleEntries) {
|
|
12263
|
-
if (lineStyle && typeof lineStyle === "object") {
|
|
12264
|
-
for (const charStyle of Object.values(lineStyle)) {
|
|
12265
|
-
if (charStyle == null ? void 0 : charStyle.fontFamily) {
|
|
12266
|
-
add(charStyle.fontFamily, charStyle.fontWeight, charStyle.fontStyle);
|
|
12267
|
-
}
|
|
12268
|
-
}
|
|
12269
|
-
}
|
|
12270
|
-
}
|
|
12271
|
-
}
|
|
12272
|
-
if (node.children) walk(node.children);
|
|
12273
|
-
}
|
|
12274
|
-
}
|
|
12275
|
-
add("Open Sans", 400, "normal");
|
|
12276
|
-
add("Hind", 400, "normal");
|
|
12277
|
-
add("Hind", 700, "normal");
|
|
12278
|
-
for (const page of config.pages || []) {
|
|
12279
|
-
walk(page.children || []);
|
|
12280
|
-
}
|
|
12281
|
-
if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
|
|
12282
|
-
for (const def of Object.values(config.themeConfig.variables)) {
|
|
12283
|
-
if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
|
|
12284
|
-
if (def.label && /font/i.test(def.label)) {
|
|
12285
|
-
add(def.value);
|
|
12286
|
-
}
|
|
12287
|
-
}
|
|
12288
|
-
}
|
|
12289
|
-
}
|
|
12290
|
-
return descriptors;
|
|
12291
|
-
}
|
|
12292
|
-
async function ensureFontsForResolvedConfig(config) {
|
|
12293
|
-
if (typeof document === "undefined") return;
|
|
12294
|
-
const descriptors = collectFontDescriptorsFromConfig(config);
|
|
12295
|
-
const families = new Set(descriptors.map((d) => d.family));
|
|
12296
|
-
await withTimeout(Promise.all([...families].map((f) => loadGoogleFontCSS(f))), 3500);
|
|
12297
|
-
if (document.fonts) {
|
|
12298
|
-
descriptors.forEach((d) => {
|
|
12299
|
-
const stylePrefix = d.style === "italic" ? "italic " : "";
|
|
12300
|
-
const weightStr = String(d.weight);
|
|
12301
|
-
const spec = `${stylePrefix}${weightStr} 16px "${d.family}"`;
|
|
12302
|
-
document.fonts.load(spec).catch(() => {
|
|
12303
|
-
});
|
|
12304
|
-
});
|
|
12305
|
-
}
|
|
12306
|
-
}
|
|
12307
|
-
function configHasAutoShrinkText$1(config) {
|
|
12308
|
-
var _a;
|
|
12309
|
-
if (!((_a = config == null ? void 0 : config.pages) == null ? void 0 : _a.length)) return false;
|
|
12310
|
-
const walk = (nodes) => {
|
|
12311
|
-
for (const node of nodes || []) {
|
|
12312
|
-
if (!node) continue;
|
|
12313
|
-
if (node.type === "text" && node.overflowPolicy === "auto-shrink") return true;
|
|
12314
|
-
if (Array.isArray(node.children) && node.children.length && walk(node.children)) return true;
|
|
12315
|
-
}
|
|
12316
|
-
return false;
|
|
12317
|
-
};
|
|
12318
|
-
for (const page of config.pages) {
|
|
12319
|
-
if (walk(page.children || [])) return true;
|
|
12320
|
-
}
|
|
12321
|
-
return false;
|
|
12322
|
-
}
|
|
12323
|
-
async function awaitFontsForConfig(config, maxWaitMs) {
|
|
12324
|
-
if (typeof document === "undefined" || !document.fonts) return;
|
|
12325
|
-
await ensureFontsForResolvedConfig(config);
|
|
12326
|
-
const descriptors = collectFontDescriptorsFromConfig(config);
|
|
12327
|
-
if (descriptors.length === 0) return;
|
|
12328
|
-
const loads = Promise.all(
|
|
12329
|
-
descriptors.map((d) => {
|
|
12330
|
-
const stylePrefix = d.style === "italic" ? "italic " : "";
|
|
12331
|
-
const spec = `${stylePrefix}${d.weight} 16px "${d.family}"`;
|
|
12332
|
-
return document.fonts.load(spec).catch(() => []);
|
|
12333
|
-
})
|
|
12334
|
-
).then(() => void 0);
|
|
12335
|
-
await Promise.race([
|
|
12336
|
-
loads,
|
|
12337
|
-
new Promise((resolve) => setTimeout(resolve, maxWaitMs))
|
|
12338
|
-
]);
|
|
12339
|
-
await Promise.race([
|
|
12340
|
-
document.fonts.ready.catch(() => void 0).then(() => void 0),
|
|
12341
|
-
new Promise((r) => setTimeout(r, Math.min(500, maxWaitMs)))
|
|
12342
|
-
]);
|
|
12343
|
-
const checkSpecs = [];
|
|
12344
|
-
for (const d of descriptors) {
|
|
12345
|
-
const stylePrefix = d.style === "italic" ? "italic " : "";
|
|
12346
|
-
checkSpecs.push(`${stylePrefix}${d.weight} 16px "${d.family}"`);
|
|
12347
|
-
}
|
|
12348
|
-
const startedAt = Date.now();
|
|
12349
|
-
const pollBudget = Math.min(maxWaitMs, 2500);
|
|
12350
|
-
const allReady = () => {
|
|
12351
|
-
for (const spec of checkSpecs) {
|
|
12352
|
-
try {
|
|
12353
|
-
if (!document.fonts.check(spec)) return false;
|
|
12354
|
-
} catch {
|
|
12355
|
-
}
|
|
12356
|
-
}
|
|
12357
|
-
return true;
|
|
12358
|
-
};
|
|
12359
|
-
while (!allReady() && Date.now() - startedAt < pollBudget) {
|
|
12360
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
12361
|
-
}
|
|
12362
|
-
await new Promise(
|
|
12363
|
-
(resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))
|
|
12364
|
-
);
|
|
12365
|
-
}
|
|
12366
12410
|
const PREVIEW_DEBUG_PREFIX = "[canvas-renderer][preview-debug]";
|
|
12367
12411
|
function countUnderlinedNodes(config) {
|
|
12368
12412
|
var _a;
|
|
@@ -12553,7 +12597,18 @@ function PixldocsPreview(props) {
|
|
|
12553
12597
|
!canvasSettled && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) })
|
|
12554
12598
|
] });
|
|
12555
12599
|
}
|
|
12556
|
-
const PACKAGE_VERSION = "0.5.
|
|
12600
|
+
const PACKAGE_VERSION = "0.5.78";
|
|
12601
|
+
const roundParityValue = (value) => {
|
|
12602
|
+
if (typeof value !== "number") return value;
|
|
12603
|
+
return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
|
|
12604
|
+
};
|
|
12605
|
+
function logJsonLine(tag, payload) {
|
|
12606
|
+
try {
|
|
12607
|
+
console.log(`${tag} ${JSON.stringify(payload, (_key, value) => roundParityValue(value))}`);
|
|
12608
|
+
} catch {
|
|
12609
|
+
console.log(tag, payload);
|
|
12610
|
+
}
|
|
12611
|
+
}
|
|
12557
12612
|
let __underlineFixInstalled = false;
|
|
12558
12613
|
function installUnderlineFix(fab) {
|
|
12559
12614
|
var _a;
|
|
@@ -13429,6 +13484,42 @@ class PixldocsRenderer {
|
|
|
13429
13484
|
}
|
|
13430
13485
|
return null;
|
|
13431
13486
|
}
|
|
13487
|
+
logFabricTextParitySnapshot(stage, fabricInstance) {
|
|
13488
|
+
var _a;
|
|
13489
|
+
if (typeof window === "undefined" || window.__pixldocsDebugAutoShrink !== true) return;
|
|
13490
|
+
const sample = [];
|
|
13491
|
+
const visit = (obj, groupPath = "") => {
|
|
13492
|
+
var _a2;
|
|
13493
|
+
if (!obj) return;
|
|
13494
|
+
if (obj instanceof fabric__namespace.Textbox) {
|
|
13495
|
+
const lineWidths = getCanvasMeasuredTextboxLineWidths(obj);
|
|
13496
|
+
sample.push({
|
|
13497
|
+
id: getObjectId(obj),
|
|
13498
|
+
groupPath,
|
|
13499
|
+
text: String(obj.text ?? "").slice(0, 180),
|
|
13500
|
+
left: obj.left,
|
|
13501
|
+
top: obj.top,
|
|
13502
|
+
width: obj.width,
|
|
13503
|
+
height: obj.height,
|
|
13504
|
+
scaleX: obj.scaleX,
|
|
13505
|
+
scaleY: obj.scaleY,
|
|
13506
|
+
fontSize: obj.fontSize,
|
|
13507
|
+
fontFamily: obj.fontFamily,
|
|
13508
|
+
fontWeight: obj.fontWeight,
|
|
13509
|
+
lineCount: ((_a2 = obj.textLines) == null ? void 0 : _a2.length) || 0,
|
|
13510
|
+
lines: (obj.textLines || []).map((line) => Array.isArray(line) ? line.join("") : String(line ?? "")),
|
|
13511
|
+
lineWidths,
|
|
13512
|
+
maxLineWidth: lineWidths.length ? Math.max(...lineWidths) : 0
|
|
13513
|
+
});
|
|
13514
|
+
}
|
|
13515
|
+
if (obj instanceof fabric__namespace.Group && typeof obj.getObjects === "function") {
|
|
13516
|
+
const nextPath = [groupPath, getObjectId(obj) || obj.type || "group"].filter(Boolean).join("/");
|
|
13517
|
+
obj.getObjects().forEach((child) => visit(child, nextPath));
|
|
13518
|
+
}
|
|
13519
|
+
};
|
|
13520
|
+
(_a = fabricInstance == null ? void 0 : fabricInstance.getObjects) == null ? void 0 : _a.call(fabricInstance).forEach((obj) => visit(obj));
|
|
13521
|
+
logJsonLine("[canvas-renderer][fabric-text-parity]", { stage, textboxes: sample.length, sample });
|
|
13522
|
+
}
|
|
13432
13523
|
async waitForStableTextMetrics(container, config) {
|
|
13433
13524
|
var _a, _b, _c;
|
|
13434
13525
|
if (typeof document !== "undefined") {
|
|
@@ -13437,6 +13528,7 @@ class PixldocsRenderer {
|
|
|
13437
13528
|
}
|
|
13438
13529
|
const fabricInstance = this.getFabricCanvasFromContainer(container);
|
|
13439
13530
|
if (!(fabricInstance == null ? void 0 : fabricInstance.getObjects)) return;
|
|
13531
|
+
this.logFabricTextParitySnapshot("before-stable-text-metrics", fabricInstance);
|
|
13440
13532
|
const waitForPaint = () => new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(() => r())));
|
|
13441
13533
|
const primeCharBounds = (obj) => {
|
|
13442
13534
|
if (obj instanceof fabric__namespace.Textbox) {
|
|
@@ -13463,6 +13555,7 @@ class PixldocsRenderer {
|
|
|
13463
13555
|
await waitForPaint();
|
|
13464
13556
|
(_c = fabricInstance.renderAll) == null ? void 0 : _c.call(fabricInstance);
|
|
13465
13557
|
await waitForPaint();
|
|
13558
|
+
this.logFabricTextParitySnapshot("after-stable-text-metrics", fabricInstance);
|
|
13466
13559
|
}
|
|
13467
13560
|
}
|
|
13468
13561
|
const FONT_WEIGHT_LABELS = {
|
|
@@ -14284,6 +14377,16 @@ const pdfFonts = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProp
|
|
|
14284
14377
|
resolveFontWeight,
|
|
14285
14378
|
rewriteSvgFontsForJsPDF
|
|
14286
14379
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
14380
|
+
function logParityJson(tag, stage, payload) {
|
|
14381
|
+
try {
|
|
14382
|
+
console.log(`${tag} ${stage} ${JSON.stringify(payload, (_key, value) => {
|
|
14383
|
+
if (typeof value !== "number") return value;
|
|
14384
|
+
return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
|
|
14385
|
+
})}`);
|
|
14386
|
+
} catch {
|
|
14387
|
+
console.log(`${tag} ${stage}`, payload);
|
|
14388
|
+
}
|
|
14389
|
+
}
|
|
14287
14390
|
function dumpSvgTextDiagnostics(svgStr, pageIndex, tag, stage, maxItems = 30) {
|
|
14288
14391
|
try {
|
|
14289
14392
|
if (typeof DOMParser === "undefined") return;
|
|
@@ -14306,7 +14409,7 @@ function dumpSvgTextDiagnostics(svgStr, pageIndex, tag, stage, maxItems = 30) {
|
|
|
14306
14409
|
svgViewBox,
|
|
14307
14410
|
textCount: texts.length
|
|
14308
14411
|
};
|
|
14309
|
-
|
|
14412
|
+
logParityJson(tag, stage, { kind: "summary", ...summary });
|
|
14310
14413
|
const sample = texts.slice(0, maxItems).map((t, idx) => {
|
|
14311
14414
|
var _a, _b;
|
|
14312
14415
|
const tspans = Array.from(t.querySelectorAll("tspan"));
|
|
@@ -14338,7 +14441,7 @@ function dumpSvgTextDiagnostics(svgStr, pageIndex, tag, stage, maxItems = 30) {
|
|
|
14338
14441
|
tspanSample: tspanInfo
|
|
14339
14442
|
};
|
|
14340
14443
|
});
|
|
14341
|
-
|
|
14444
|
+
logParityJson(tag, stage, { kind: "text-sample", page: pageIndex, count: sample.length, total: texts.length, sample });
|
|
14342
14445
|
} catch (err) {
|
|
14343
14446
|
console.warn(`${tag} ${stage} page=${pageIndex} dump threw`, err);
|
|
14344
14447
|
}
|