@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.js
CHANGED
|
@@ -5194,6 +5194,17 @@ function buildRoundedTrianglePath(w, h, rTop, rBR, rBL) {
|
|
|
5194
5194
|
];
|
|
5195
5195
|
return parts.join(" ");
|
|
5196
5196
|
}
|
|
5197
|
+
const roundDiag = (value) => {
|
|
5198
|
+
if (typeof value !== "number") return value;
|
|
5199
|
+
return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
|
|
5200
|
+
};
|
|
5201
|
+
const stringifyDiag = (payload) => {
|
|
5202
|
+
try {
|
|
5203
|
+
return JSON.stringify(payload, (_key, value) => roundDiag(value));
|
|
5204
|
+
} catch {
|
|
5205
|
+
return String(payload);
|
|
5206
|
+
}
|
|
5207
|
+
};
|
|
5197
5208
|
function buildRoundedRectPath(w, h, tl, tr, br, bl) {
|
|
5198
5209
|
return buildRoundedRectPath$1(w, h, getRoundedRectRadii(w, h, { rxTL: tl, rxTR: tr, rxBR: br, rxBL: bl }));
|
|
5199
5210
|
}
|
|
@@ -5304,11 +5315,11 @@ function createShape(element) {
|
|
|
5304
5315
|
}
|
|
5305
5316
|
}
|
|
5306
5317
|
function createText(element) {
|
|
5307
|
-
var _a;
|
|
5318
|
+
var _a, _b;
|
|
5308
5319
|
const overflowPolicy = element.overflowPolicy || "grow-and-push";
|
|
5309
5320
|
let text = element.text || "Text";
|
|
5310
5321
|
let fontSize = element.fontSize || 16;
|
|
5311
|
-
element.minFontSize || 8;
|
|
5322
|
+
const minFontSize = element.minFontSize || 8;
|
|
5312
5323
|
const maxLines = element.maxLines || 3;
|
|
5313
5324
|
const baseWidth = element.width && element.width > 0 ? element.width : 200;
|
|
5314
5325
|
const baseHeight = element.height;
|
|
@@ -5320,6 +5331,7 @@ function createText(element) {
|
|
|
5320
5331
|
const startFontSize = fontSize;
|
|
5321
5332
|
let breakReason = "min-font-size-reached";
|
|
5322
5333
|
let lastIter = null;
|
|
5334
|
+
const iterationSamples = [];
|
|
5323
5335
|
while (fontSize > 1) {
|
|
5324
5336
|
const testTextbox = new fabric.Textbox(text, {
|
|
5325
5337
|
width: fixedWidth,
|
|
@@ -5337,7 +5349,7 @@ function createText(element) {
|
|
|
5337
5349
|
const hasNoImplicitWrap = renderedLineCount <= explicitLineCount;
|
|
5338
5350
|
const fitsHeight = !baseHeight || textHeight <= baseHeight;
|
|
5339
5351
|
const widthMetrics = getTextboxWidthFitMetrics(testTextbox, fixedWidth);
|
|
5340
|
-
const { fitsWidth } = widthMetrics;
|
|
5352
|
+
const { maxLineWidth, widthDidGrow, fitsWidth } = widthMetrics;
|
|
5341
5353
|
if (debugAutoShrink) {
|
|
5342
5354
|
lastIter = {
|
|
5343
5355
|
fontSize,
|
|
@@ -5348,8 +5360,12 @@ function createText(element) {
|
|
|
5348
5360
|
fixedWidth,
|
|
5349
5361
|
hasNoImplicitWrap,
|
|
5350
5362
|
fitsHeight,
|
|
5351
|
-
fitsWidth
|
|
5363
|
+
fitsWidth,
|
|
5364
|
+
textLines: (testTextbox.textLines || []).map((line) => Array.isArray(line) ? line.join("") : String(line ?? ""))
|
|
5352
5365
|
};
|
|
5366
|
+
if (iterationSamples.length < 6 || fontSize <= minFontSize + 2) {
|
|
5367
|
+
iterationSamples.push(lastIter);
|
|
5368
|
+
}
|
|
5353
5369
|
}
|
|
5354
5370
|
if (hasNoImplicitWrap && fitsHeight && fitsWidth) {
|
|
5355
5371
|
breakReason = "fits";
|
|
@@ -5358,10 +5374,11 @@ function createText(element) {
|
|
|
5358
5374
|
fontSize--;
|
|
5359
5375
|
}
|
|
5360
5376
|
if (debugAutoShrink) {
|
|
5361
|
-
console.log("[auto-shrink][diag]"
|
|
5377
|
+
console.log("[auto-shrink][diag] " + stringifyDiag({
|
|
5362
5378
|
id: element.id,
|
|
5363
5379
|
name: element.name,
|
|
5364
|
-
text,
|
|
5380
|
+
text: text.slice(0, 180),
|
|
5381
|
+
textLength: text.length,
|
|
5365
5382
|
fontFamily: element.fontFamily,
|
|
5366
5383
|
fontWeight: element.fontWeight,
|
|
5367
5384
|
elementWidth: element.width,
|
|
@@ -5373,10 +5390,11 @@ function createText(element) {
|
|
|
5373
5390
|
startFontSize,
|
|
5374
5391
|
finalFontSize: fontSize,
|
|
5375
5392
|
breakReason,
|
|
5393
|
+
iterations: iterationSamples,
|
|
5376
5394
|
lastIter,
|
|
5377
5395
|
fontCheckRegular: typeof document !== "undefined" && document.fonts ? document.fonts.check(`16px "${element.fontFamily || "Open Sans"}"`) : null,
|
|
5378
5396
|
fontCheckBold: typeof document !== "undefined" && document.fonts ? document.fonts.check(`bold 16px "${element.fontFamily || "Open Sans"}"`) : null
|
|
5379
|
-
});
|
|
5397
|
+
}));
|
|
5380
5398
|
}
|
|
5381
5399
|
}
|
|
5382
5400
|
if (overflowPolicy === "max-lines-ellipsis") {
|
|
@@ -5451,6 +5469,22 @@ function createText(element) {
|
|
|
5451
5469
|
textbox.setCoords();
|
|
5452
5470
|
}
|
|
5453
5471
|
textbox.dirty = true;
|
|
5472
|
+
if (overflowPolicy === "auto-shrink" && typeof window !== "undefined" && window.__pixldocsDebugAutoShrink === true) {
|
|
5473
|
+
console.log("[auto-shrink][final-textbox] " + stringifyDiag({
|
|
5474
|
+
id: element.id,
|
|
5475
|
+
name: element.name,
|
|
5476
|
+
text: text.slice(0, 180),
|
|
5477
|
+
targetWidth,
|
|
5478
|
+
targetScaleX,
|
|
5479
|
+
targetScaleY,
|
|
5480
|
+
finalFontSize: fontSize,
|
|
5481
|
+
textboxWidth: textbox.width,
|
|
5482
|
+
textboxHeight: textbox.height,
|
|
5483
|
+
lineCount: ((_b = textbox.textLines) == null ? void 0 : _b.length) || 0,
|
|
5484
|
+
lines: (textbox.textLines || []).map((line) => Array.isArray(line) ? line.join("") : String(line ?? "")),
|
|
5485
|
+
widthMetrics: getTextboxWidthFitMetrics(textbox, targetWidth)
|
|
5486
|
+
}));
|
|
5487
|
+
}
|
|
5454
5488
|
applyTextBackground(textbox, extractTextBgConfig(element));
|
|
5455
5489
|
const shadow = buildTextShadow(element);
|
|
5456
5490
|
if (shadow) textbox.set("shadow", shadow);
|
|
@@ -11611,6 +11645,232 @@ function applyContentBoundsPagination(config) {
|
|
|
11611
11645
|
if (!mutated) return config;
|
|
11612
11646
|
return { ...config, pages: resultPages };
|
|
11613
11647
|
}
|
|
11648
|
+
function normalizeFontFamily(fontStack) {
|
|
11649
|
+
const first = fontStack.split(",")[0].trim();
|
|
11650
|
+
return first.replace(/^['"]|['"]$/g, "");
|
|
11651
|
+
}
|
|
11652
|
+
const loadedFonts = /* @__PURE__ */ new Set();
|
|
11653
|
+
const loadingPromises = /* @__PURE__ */ new Map();
|
|
11654
|
+
function withTimeout(promise, timeoutMs = 4e3) {
|
|
11655
|
+
let timeoutId;
|
|
11656
|
+
return Promise.race([
|
|
11657
|
+
promise,
|
|
11658
|
+
new Promise((resolve) => {
|
|
11659
|
+
timeoutId = setTimeout(resolve, timeoutMs);
|
|
11660
|
+
})
|
|
11661
|
+
]).finally(() => {
|
|
11662
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
11663
|
+
});
|
|
11664
|
+
}
|
|
11665
|
+
async function loadGoogleFontCSS(rawFontFamily) {
|
|
11666
|
+
if (!rawFontFamily || typeof document === "undefined") return;
|
|
11667
|
+
const fontFamily = normalizeFontFamily(rawFontFamily);
|
|
11668
|
+
if (!fontFamily) return;
|
|
11669
|
+
if (loadedFonts.has(fontFamily)) return;
|
|
11670
|
+
const existing = loadingPromises.get(fontFamily);
|
|
11671
|
+
if (existing) return existing;
|
|
11672
|
+
const promise = (async () => {
|
|
11673
|
+
try {
|
|
11674
|
+
const encoded = encodeURIComponent(fontFamily);
|
|
11675
|
+
const url = `https://fonts.googleapis.com/css?family=${encoded}:300,400,500,600,700&display=swap`;
|
|
11676
|
+
const link = document.createElement("link");
|
|
11677
|
+
link.rel = "stylesheet";
|
|
11678
|
+
link.href = url;
|
|
11679
|
+
link.crossOrigin = "anonymous";
|
|
11680
|
+
await new Promise((resolve, reject) => {
|
|
11681
|
+
link.onload = () => resolve();
|
|
11682
|
+
link.onerror = () => reject(new Error(`Failed to load font: ${fontFamily}`));
|
|
11683
|
+
document.head.appendChild(link);
|
|
11684
|
+
});
|
|
11685
|
+
loadedFonts.add(fontFamily);
|
|
11686
|
+
} catch (e) {
|
|
11687
|
+
console.warn(`[@pixldocs/canvas-renderer] Font load failed: ${fontFamily}`, e);
|
|
11688
|
+
}
|
|
11689
|
+
})();
|
|
11690
|
+
loadingPromises.set(fontFamily, promise);
|
|
11691
|
+
await promise;
|
|
11692
|
+
loadingPromises.delete(fontFamily);
|
|
11693
|
+
}
|
|
11694
|
+
function collectFontsFromConfig(config) {
|
|
11695
|
+
var _a;
|
|
11696
|
+
const fonts = /* @__PURE__ */ new Set();
|
|
11697
|
+
fonts.add("Open Sans");
|
|
11698
|
+
fonts.add("Hind");
|
|
11699
|
+
function walk(nodes) {
|
|
11700
|
+
var _a2;
|
|
11701
|
+
if (!nodes) return;
|
|
11702
|
+
for (const node of nodes) {
|
|
11703
|
+
if (node.fontFamily) fonts.add(normalizeFontFamily(node.fontFamily));
|
|
11704
|
+
if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) fonts.add(normalizeFontFamily(node.smartProps.fontFamily));
|
|
11705
|
+
if (node.styles && Array.isArray(node.styles)) {
|
|
11706
|
+
for (const lineStyle of node.styles) {
|
|
11707
|
+
if (lineStyle && typeof lineStyle === "object") {
|
|
11708
|
+
for (const charStyle of Object.values(lineStyle)) {
|
|
11709
|
+
if (charStyle == null ? void 0 : charStyle.fontFamily) fonts.add(normalizeFontFamily(charStyle.fontFamily));
|
|
11710
|
+
}
|
|
11711
|
+
}
|
|
11712
|
+
}
|
|
11713
|
+
}
|
|
11714
|
+
if (node.children) walk(node.children);
|
|
11715
|
+
}
|
|
11716
|
+
}
|
|
11717
|
+
for (const page of config.pages || []) {
|
|
11718
|
+
walk(page.children || []);
|
|
11719
|
+
}
|
|
11720
|
+
if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
|
|
11721
|
+
for (const def of Object.values(config.themeConfig.variables)) {
|
|
11722
|
+
if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
|
|
11723
|
+
if (def.label && /font/i.test(def.label)) {
|
|
11724
|
+
fonts.add(normalizeFontFamily(def.value));
|
|
11725
|
+
}
|
|
11726
|
+
}
|
|
11727
|
+
}
|
|
11728
|
+
}
|
|
11729
|
+
return fonts;
|
|
11730
|
+
}
|
|
11731
|
+
function collectFontDescriptorsFromConfig(config) {
|
|
11732
|
+
var _a;
|
|
11733
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11734
|
+
const descriptors = [];
|
|
11735
|
+
function add(family, weight, style) {
|
|
11736
|
+
const f = normalizeFontFamily(family);
|
|
11737
|
+
if (!f) return;
|
|
11738
|
+
const w = weight ?? 400;
|
|
11739
|
+
const s = style ?? "normal";
|
|
11740
|
+
const key = `${f}|${w}|${s}`;
|
|
11741
|
+
if (seen.has(key)) return;
|
|
11742
|
+
seen.add(key);
|
|
11743
|
+
descriptors.push({ family: f, weight: w, style: s });
|
|
11744
|
+
}
|
|
11745
|
+
function walk(nodes) {
|
|
11746
|
+
var _a2;
|
|
11747
|
+
if (!nodes) return;
|
|
11748
|
+
for (const node of nodes) {
|
|
11749
|
+
if (node.fontFamily) {
|
|
11750
|
+
add(node.fontFamily, node.fontWeight, node.fontStyle);
|
|
11751
|
+
if (node.type === "text") {
|
|
11752
|
+
for (const w of [300, 400, 500, 600, 700]) {
|
|
11753
|
+
add(node.fontFamily, w, node.fontStyle);
|
|
11754
|
+
}
|
|
11755
|
+
}
|
|
11756
|
+
}
|
|
11757
|
+
if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) {
|
|
11758
|
+
add(node.smartProps.fontFamily, node.smartProps.fontWeight, node.smartProps.fontStyle);
|
|
11759
|
+
}
|
|
11760
|
+
if (node.styles) {
|
|
11761
|
+
const styleEntries = Array.isArray(node.styles) ? node.styles : Object.values(node.styles);
|
|
11762
|
+
for (const lineStyle of styleEntries) {
|
|
11763
|
+
if (lineStyle && typeof lineStyle === "object") {
|
|
11764
|
+
for (const charStyle of Object.values(lineStyle)) {
|
|
11765
|
+
if (charStyle == null ? void 0 : charStyle.fontFamily) {
|
|
11766
|
+
add(charStyle.fontFamily, charStyle.fontWeight, charStyle.fontStyle);
|
|
11767
|
+
}
|
|
11768
|
+
}
|
|
11769
|
+
}
|
|
11770
|
+
}
|
|
11771
|
+
}
|
|
11772
|
+
if (node.children) walk(node.children);
|
|
11773
|
+
}
|
|
11774
|
+
}
|
|
11775
|
+
add("Open Sans", 400, "normal");
|
|
11776
|
+
add("Hind", 400, "normal");
|
|
11777
|
+
add("Hind", 700, "normal");
|
|
11778
|
+
for (const page of config.pages || []) {
|
|
11779
|
+
walk(page.children || []);
|
|
11780
|
+
}
|
|
11781
|
+
if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
|
|
11782
|
+
for (const def of Object.values(config.themeConfig.variables)) {
|
|
11783
|
+
if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
|
|
11784
|
+
if (def.label && /font/i.test(def.label)) {
|
|
11785
|
+
add(def.value);
|
|
11786
|
+
}
|
|
11787
|
+
}
|
|
11788
|
+
}
|
|
11789
|
+
}
|
|
11790
|
+
return descriptors;
|
|
11791
|
+
}
|
|
11792
|
+
async function ensureFontsForResolvedConfig(config) {
|
|
11793
|
+
if (typeof document === "undefined") return;
|
|
11794
|
+
const descriptors = collectFontDescriptorsFromConfig(config);
|
|
11795
|
+
const families = new Set(descriptors.map((d) => d.family));
|
|
11796
|
+
await withTimeout(Promise.all([...families].map((f) => loadGoogleFontCSS(f))), 3500);
|
|
11797
|
+
if (document.fonts) {
|
|
11798
|
+
descriptors.forEach((d) => {
|
|
11799
|
+
const stylePrefix = d.style === "italic" ? "italic " : "";
|
|
11800
|
+
const weightStr = String(d.weight);
|
|
11801
|
+
const spec = `${stylePrefix}${weightStr} 16px "${d.family}"`;
|
|
11802
|
+
document.fonts.load(spec).catch(() => {
|
|
11803
|
+
});
|
|
11804
|
+
});
|
|
11805
|
+
}
|
|
11806
|
+
}
|
|
11807
|
+
function configHasAutoShrinkText$1(config) {
|
|
11808
|
+
var _a;
|
|
11809
|
+
if (!((_a = config == null ? void 0 : config.pages) == null ? void 0 : _a.length)) return false;
|
|
11810
|
+
const walk = (nodes) => {
|
|
11811
|
+
for (const node of nodes || []) {
|
|
11812
|
+
if (!node) continue;
|
|
11813
|
+
if (node.type === "text" && node.overflowPolicy === "auto-shrink") return true;
|
|
11814
|
+
if (Array.isArray(node.children) && node.children.length && walk(node.children)) return true;
|
|
11815
|
+
}
|
|
11816
|
+
return false;
|
|
11817
|
+
};
|
|
11818
|
+
for (const page of config.pages) {
|
|
11819
|
+
if (walk(page.children || [])) return true;
|
|
11820
|
+
}
|
|
11821
|
+
return false;
|
|
11822
|
+
}
|
|
11823
|
+
async function awaitFontsForConfig(config, maxWaitMs) {
|
|
11824
|
+
if (typeof document === "undefined" || !document.fonts) return;
|
|
11825
|
+
await ensureFontsForResolvedConfig(config);
|
|
11826
|
+
const descriptors = collectFontDescriptorsFromConfig(config);
|
|
11827
|
+
if (descriptors.length === 0) return;
|
|
11828
|
+
const loads = Promise.all(
|
|
11829
|
+
descriptors.map((d) => {
|
|
11830
|
+
const stylePrefix = d.style === "italic" ? "italic " : "";
|
|
11831
|
+
const spec = `${stylePrefix}${d.weight} 16px "${d.family}"`;
|
|
11832
|
+
return document.fonts.load(spec).catch(() => []);
|
|
11833
|
+
})
|
|
11834
|
+
).then(() => void 0);
|
|
11835
|
+
await Promise.race([
|
|
11836
|
+
loads,
|
|
11837
|
+
new Promise((resolve) => setTimeout(resolve, maxWaitMs))
|
|
11838
|
+
]);
|
|
11839
|
+
await Promise.race([
|
|
11840
|
+
document.fonts.ready.catch(() => void 0).then(() => void 0),
|
|
11841
|
+
new Promise((r) => setTimeout(r, Math.min(500, maxWaitMs)))
|
|
11842
|
+
]);
|
|
11843
|
+
const checkSpecs = [];
|
|
11844
|
+
for (const d of descriptors) {
|
|
11845
|
+
const stylePrefix = d.style === "italic" ? "italic " : "";
|
|
11846
|
+
checkSpecs.push(`${stylePrefix}${d.weight} 16px "${d.family}"`);
|
|
11847
|
+
}
|
|
11848
|
+
const startedAt = Date.now();
|
|
11849
|
+
const pollBudget = Math.min(maxWaitMs, 2500);
|
|
11850
|
+
const allReady = () => {
|
|
11851
|
+
for (const spec of checkSpecs) {
|
|
11852
|
+
try {
|
|
11853
|
+
if (!document.fonts.check(spec)) return false;
|
|
11854
|
+
} catch {
|
|
11855
|
+
}
|
|
11856
|
+
}
|
|
11857
|
+
return true;
|
|
11858
|
+
};
|
|
11859
|
+
while (!allReady() && Date.now() - startedAt < pollBudget) {
|
|
11860
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
11861
|
+
}
|
|
11862
|
+
await new Promise(
|
|
11863
|
+
(resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))
|
|
11864
|
+
);
|
|
11865
|
+
}
|
|
11866
|
+
async function awaitFontsBeforeTextReflow(config) {
|
|
11867
|
+
if (typeof document === "undefined" || !configHasAutoShrinkText$1(config)) return;
|
|
11868
|
+
try {
|
|
11869
|
+
await awaitFontsForConfig(config, 4e3);
|
|
11870
|
+
} catch (error) {
|
|
11871
|
+
console.warn("[@pixldocs/canvas-renderer] Font wait before text reflow failed:", error);
|
|
11872
|
+
}
|
|
11873
|
+
}
|
|
11614
11874
|
function repeatablePageToSection(page) {
|
|
11615
11875
|
return {
|
|
11616
11876
|
id: page.id,
|
|
@@ -11804,6 +12064,7 @@ async function resolveTemplateData(options) {
|
|
|
11804
12064
|
}
|
|
11805
12065
|
}
|
|
11806
12066
|
const repeatablePagesInput = deriveRepeatablePagesFromTemplate(config, inlineFormSchema, mergedFormData);
|
|
12067
|
+
await awaitFontsBeforeTextReflow(config);
|
|
11807
12068
|
const resolvedConfig = applyFormDataToConfig(
|
|
11808
12069
|
config,
|
|
11809
12070
|
mappings,
|
|
@@ -11948,6 +12209,7 @@ async function resolveFromForm(options) {
|
|
|
11948
12209
|
};
|
|
11949
12210
|
collectFormats(formConfig.sections);
|
|
11950
12211
|
}
|
|
12212
|
+
await awaitFontsBeforeTextReflow(templateConfig);
|
|
11951
12213
|
let resolvedConfig = applyFormDataToConfig(
|
|
11952
12214
|
templateConfig,
|
|
11953
12215
|
mappings,
|
|
@@ -12126,224 +12388,6 @@ function paintRepeatableSections(config, repeatableSections) {
|
|
|
12126
12388
|
}
|
|
12127
12389
|
}
|
|
12128
12390
|
}
|
|
12129
|
-
function normalizeFontFamily(fontStack) {
|
|
12130
|
-
const first = fontStack.split(",")[0].trim();
|
|
12131
|
-
return first.replace(/^['"]|['"]$/g, "");
|
|
12132
|
-
}
|
|
12133
|
-
const loadedFonts = /* @__PURE__ */ new Set();
|
|
12134
|
-
const loadingPromises = /* @__PURE__ */ new Map();
|
|
12135
|
-
function withTimeout(promise, timeoutMs = 4e3) {
|
|
12136
|
-
let timeoutId;
|
|
12137
|
-
return Promise.race([
|
|
12138
|
-
promise,
|
|
12139
|
-
new Promise((resolve) => {
|
|
12140
|
-
timeoutId = setTimeout(resolve, timeoutMs);
|
|
12141
|
-
})
|
|
12142
|
-
]).finally(() => {
|
|
12143
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
12144
|
-
});
|
|
12145
|
-
}
|
|
12146
|
-
async function loadGoogleFontCSS(rawFontFamily) {
|
|
12147
|
-
if (!rawFontFamily || typeof document === "undefined") return;
|
|
12148
|
-
const fontFamily = normalizeFontFamily(rawFontFamily);
|
|
12149
|
-
if (!fontFamily) return;
|
|
12150
|
-
if (loadedFonts.has(fontFamily)) return;
|
|
12151
|
-
const existing = loadingPromises.get(fontFamily);
|
|
12152
|
-
if (existing) return existing;
|
|
12153
|
-
const promise = (async () => {
|
|
12154
|
-
try {
|
|
12155
|
-
const encoded = encodeURIComponent(fontFamily);
|
|
12156
|
-
const url = `https://fonts.googleapis.com/css?family=${encoded}:300,400,500,600,700&display=swap`;
|
|
12157
|
-
const link = document.createElement("link");
|
|
12158
|
-
link.rel = "stylesheet";
|
|
12159
|
-
link.href = url;
|
|
12160
|
-
link.crossOrigin = "anonymous";
|
|
12161
|
-
await new Promise((resolve, reject) => {
|
|
12162
|
-
link.onload = () => resolve();
|
|
12163
|
-
link.onerror = () => reject(new Error(`Failed to load font: ${fontFamily}`));
|
|
12164
|
-
document.head.appendChild(link);
|
|
12165
|
-
});
|
|
12166
|
-
loadedFonts.add(fontFamily);
|
|
12167
|
-
} catch (e) {
|
|
12168
|
-
console.warn(`[@pixldocs/canvas-renderer] Font load failed: ${fontFamily}`, e);
|
|
12169
|
-
}
|
|
12170
|
-
})();
|
|
12171
|
-
loadingPromises.set(fontFamily, promise);
|
|
12172
|
-
await promise;
|
|
12173
|
-
loadingPromises.delete(fontFamily);
|
|
12174
|
-
}
|
|
12175
|
-
function collectFontsFromConfig(config) {
|
|
12176
|
-
var _a;
|
|
12177
|
-
const fonts = /* @__PURE__ */ new Set();
|
|
12178
|
-
fonts.add("Open Sans");
|
|
12179
|
-
fonts.add("Hind");
|
|
12180
|
-
function walk(nodes) {
|
|
12181
|
-
var _a2;
|
|
12182
|
-
if (!nodes) return;
|
|
12183
|
-
for (const node of nodes) {
|
|
12184
|
-
if (node.fontFamily) fonts.add(normalizeFontFamily(node.fontFamily));
|
|
12185
|
-
if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) fonts.add(normalizeFontFamily(node.smartProps.fontFamily));
|
|
12186
|
-
if (node.styles && Array.isArray(node.styles)) {
|
|
12187
|
-
for (const lineStyle of node.styles) {
|
|
12188
|
-
if (lineStyle && typeof lineStyle === "object") {
|
|
12189
|
-
for (const charStyle of Object.values(lineStyle)) {
|
|
12190
|
-
if (charStyle == null ? void 0 : charStyle.fontFamily) fonts.add(normalizeFontFamily(charStyle.fontFamily));
|
|
12191
|
-
}
|
|
12192
|
-
}
|
|
12193
|
-
}
|
|
12194
|
-
}
|
|
12195
|
-
if (node.children) walk(node.children);
|
|
12196
|
-
}
|
|
12197
|
-
}
|
|
12198
|
-
for (const page of config.pages || []) {
|
|
12199
|
-
walk(page.children || []);
|
|
12200
|
-
}
|
|
12201
|
-
if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
|
|
12202
|
-
for (const def of Object.values(config.themeConfig.variables)) {
|
|
12203
|
-
if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
|
|
12204
|
-
if (def.label && /font/i.test(def.label)) {
|
|
12205
|
-
fonts.add(normalizeFontFamily(def.value));
|
|
12206
|
-
}
|
|
12207
|
-
}
|
|
12208
|
-
}
|
|
12209
|
-
}
|
|
12210
|
-
return fonts;
|
|
12211
|
-
}
|
|
12212
|
-
function collectFontDescriptorsFromConfig(config) {
|
|
12213
|
-
var _a;
|
|
12214
|
-
const seen = /* @__PURE__ */ new Set();
|
|
12215
|
-
const descriptors = [];
|
|
12216
|
-
function add(family, weight, style) {
|
|
12217
|
-
const f = normalizeFontFamily(family);
|
|
12218
|
-
if (!f) return;
|
|
12219
|
-
const w = weight ?? 400;
|
|
12220
|
-
const s = style ?? "normal";
|
|
12221
|
-
const key = `${f}|${w}|${s}`;
|
|
12222
|
-
if (seen.has(key)) return;
|
|
12223
|
-
seen.add(key);
|
|
12224
|
-
descriptors.push({ family: f, weight: w, style: s });
|
|
12225
|
-
}
|
|
12226
|
-
function walk(nodes) {
|
|
12227
|
-
var _a2;
|
|
12228
|
-
if (!nodes) return;
|
|
12229
|
-
for (const node of nodes) {
|
|
12230
|
-
if (node.fontFamily) {
|
|
12231
|
-
add(node.fontFamily, node.fontWeight, node.fontStyle);
|
|
12232
|
-
if (node.type === "text") {
|
|
12233
|
-
for (const w of [300, 400, 500, 600, 700]) {
|
|
12234
|
-
add(node.fontFamily, w, node.fontStyle);
|
|
12235
|
-
}
|
|
12236
|
-
}
|
|
12237
|
-
}
|
|
12238
|
-
if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) {
|
|
12239
|
-
add(node.smartProps.fontFamily, node.smartProps.fontWeight, node.smartProps.fontStyle);
|
|
12240
|
-
}
|
|
12241
|
-
if (node.styles) {
|
|
12242
|
-
const styleEntries = Array.isArray(node.styles) ? node.styles : Object.values(node.styles);
|
|
12243
|
-
for (const lineStyle of styleEntries) {
|
|
12244
|
-
if (lineStyle && typeof lineStyle === "object") {
|
|
12245
|
-
for (const charStyle of Object.values(lineStyle)) {
|
|
12246
|
-
if (charStyle == null ? void 0 : charStyle.fontFamily) {
|
|
12247
|
-
add(charStyle.fontFamily, charStyle.fontWeight, charStyle.fontStyle);
|
|
12248
|
-
}
|
|
12249
|
-
}
|
|
12250
|
-
}
|
|
12251
|
-
}
|
|
12252
|
-
}
|
|
12253
|
-
if (node.children) walk(node.children);
|
|
12254
|
-
}
|
|
12255
|
-
}
|
|
12256
|
-
add("Open Sans", 400, "normal");
|
|
12257
|
-
add("Hind", 400, "normal");
|
|
12258
|
-
add("Hind", 700, "normal");
|
|
12259
|
-
for (const page of config.pages || []) {
|
|
12260
|
-
walk(page.children || []);
|
|
12261
|
-
}
|
|
12262
|
-
if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
|
|
12263
|
-
for (const def of Object.values(config.themeConfig.variables)) {
|
|
12264
|
-
if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
|
|
12265
|
-
if (def.label && /font/i.test(def.label)) {
|
|
12266
|
-
add(def.value);
|
|
12267
|
-
}
|
|
12268
|
-
}
|
|
12269
|
-
}
|
|
12270
|
-
}
|
|
12271
|
-
return descriptors;
|
|
12272
|
-
}
|
|
12273
|
-
async function ensureFontsForResolvedConfig(config) {
|
|
12274
|
-
if (typeof document === "undefined") return;
|
|
12275
|
-
const descriptors = collectFontDescriptorsFromConfig(config);
|
|
12276
|
-
const families = new Set(descriptors.map((d) => d.family));
|
|
12277
|
-
await withTimeout(Promise.all([...families].map((f) => loadGoogleFontCSS(f))), 3500);
|
|
12278
|
-
if (document.fonts) {
|
|
12279
|
-
descriptors.forEach((d) => {
|
|
12280
|
-
const stylePrefix = d.style === "italic" ? "italic " : "";
|
|
12281
|
-
const weightStr = String(d.weight);
|
|
12282
|
-
const spec = `${stylePrefix}${weightStr} 16px "${d.family}"`;
|
|
12283
|
-
document.fonts.load(spec).catch(() => {
|
|
12284
|
-
});
|
|
12285
|
-
});
|
|
12286
|
-
}
|
|
12287
|
-
}
|
|
12288
|
-
function configHasAutoShrinkText$1(config) {
|
|
12289
|
-
var _a;
|
|
12290
|
-
if (!((_a = config == null ? void 0 : config.pages) == null ? void 0 : _a.length)) return false;
|
|
12291
|
-
const walk = (nodes) => {
|
|
12292
|
-
for (const node of nodes || []) {
|
|
12293
|
-
if (!node) continue;
|
|
12294
|
-
if (node.type === "text" && node.overflowPolicy === "auto-shrink") return true;
|
|
12295
|
-
if (Array.isArray(node.children) && node.children.length && walk(node.children)) return true;
|
|
12296
|
-
}
|
|
12297
|
-
return false;
|
|
12298
|
-
};
|
|
12299
|
-
for (const page of config.pages) {
|
|
12300
|
-
if (walk(page.children || [])) return true;
|
|
12301
|
-
}
|
|
12302
|
-
return false;
|
|
12303
|
-
}
|
|
12304
|
-
async function awaitFontsForConfig(config, maxWaitMs) {
|
|
12305
|
-
if (typeof document === "undefined" || !document.fonts) return;
|
|
12306
|
-
await ensureFontsForResolvedConfig(config);
|
|
12307
|
-
const descriptors = collectFontDescriptorsFromConfig(config);
|
|
12308
|
-
if (descriptors.length === 0) return;
|
|
12309
|
-
const loads = Promise.all(
|
|
12310
|
-
descriptors.map((d) => {
|
|
12311
|
-
const stylePrefix = d.style === "italic" ? "italic " : "";
|
|
12312
|
-
const spec = `${stylePrefix}${d.weight} 16px "${d.family}"`;
|
|
12313
|
-
return document.fonts.load(spec).catch(() => []);
|
|
12314
|
-
})
|
|
12315
|
-
).then(() => void 0);
|
|
12316
|
-
await Promise.race([
|
|
12317
|
-
loads,
|
|
12318
|
-
new Promise((resolve) => setTimeout(resolve, maxWaitMs))
|
|
12319
|
-
]);
|
|
12320
|
-
await Promise.race([
|
|
12321
|
-
document.fonts.ready.catch(() => void 0).then(() => void 0),
|
|
12322
|
-
new Promise((r) => setTimeout(r, Math.min(500, maxWaitMs)))
|
|
12323
|
-
]);
|
|
12324
|
-
const checkSpecs = [];
|
|
12325
|
-
for (const d of descriptors) {
|
|
12326
|
-
const stylePrefix = d.style === "italic" ? "italic " : "";
|
|
12327
|
-
checkSpecs.push(`${stylePrefix}${d.weight} 16px "${d.family}"`);
|
|
12328
|
-
}
|
|
12329
|
-
const startedAt = Date.now();
|
|
12330
|
-
const pollBudget = Math.min(maxWaitMs, 2500);
|
|
12331
|
-
const allReady = () => {
|
|
12332
|
-
for (const spec of checkSpecs) {
|
|
12333
|
-
try {
|
|
12334
|
-
if (!document.fonts.check(spec)) return false;
|
|
12335
|
-
} catch {
|
|
12336
|
-
}
|
|
12337
|
-
}
|
|
12338
|
-
return true;
|
|
12339
|
-
};
|
|
12340
|
-
while (!allReady() && Date.now() - startedAt < pollBudget) {
|
|
12341
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
12342
|
-
}
|
|
12343
|
-
await new Promise(
|
|
12344
|
-
(resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))
|
|
12345
|
-
);
|
|
12346
|
-
}
|
|
12347
12391
|
const PREVIEW_DEBUG_PREFIX = "[canvas-renderer][preview-debug]";
|
|
12348
12392
|
function countUnderlinedNodes(config) {
|
|
12349
12393
|
var _a;
|
|
@@ -12534,7 +12578,18 @@ function PixldocsPreview(props) {
|
|
|
12534
12578
|
!canvasSettled && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) })
|
|
12535
12579
|
] });
|
|
12536
12580
|
}
|
|
12537
|
-
const PACKAGE_VERSION = "0.5.
|
|
12581
|
+
const PACKAGE_VERSION = "0.5.78";
|
|
12582
|
+
const roundParityValue = (value) => {
|
|
12583
|
+
if (typeof value !== "number") return value;
|
|
12584
|
+
return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
|
|
12585
|
+
};
|
|
12586
|
+
function logJsonLine(tag, payload) {
|
|
12587
|
+
try {
|
|
12588
|
+
console.log(`${tag} ${JSON.stringify(payload, (_key, value) => roundParityValue(value))}`);
|
|
12589
|
+
} catch {
|
|
12590
|
+
console.log(tag, payload);
|
|
12591
|
+
}
|
|
12592
|
+
}
|
|
12538
12593
|
let __underlineFixInstalled = false;
|
|
12539
12594
|
function installUnderlineFix(fab) {
|
|
12540
12595
|
var _a;
|
|
@@ -13410,6 +13465,42 @@ class PixldocsRenderer {
|
|
|
13410
13465
|
}
|
|
13411
13466
|
return null;
|
|
13412
13467
|
}
|
|
13468
|
+
logFabricTextParitySnapshot(stage, fabricInstance) {
|
|
13469
|
+
var _a;
|
|
13470
|
+
if (typeof window === "undefined" || window.__pixldocsDebugAutoShrink !== true) return;
|
|
13471
|
+
const sample = [];
|
|
13472
|
+
const visit = (obj, groupPath = "") => {
|
|
13473
|
+
var _a2;
|
|
13474
|
+
if (!obj) return;
|
|
13475
|
+
if (obj instanceof fabric.Textbox) {
|
|
13476
|
+
const lineWidths = getCanvasMeasuredTextboxLineWidths(obj);
|
|
13477
|
+
sample.push({
|
|
13478
|
+
id: getObjectId(obj),
|
|
13479
|
+
groupPath,
|
|
13480
|
+
text: String(obj.text ?? "").slice(0, 180),
|
|
13481
|
+
left: obj.left,
|
|
13482
|
+
top: obj.top,
|
|
13483
|
+
width: obj.width,
|
|
13484
|
+
height: obj.height,
|
|
13485
|
+
scaleX: obj.scaleX,
|
|
13486
|
+
scaleY: obj.scaleY,
|
|
13487
|
+
fontSize: obj.fontSize,
|
|
13488
|
+
fontFamily: obj.fontFamily,
|
|
13489
|
+
fontWeight: obj.fontWeight,
|
|
13490
|
+
lineCount: ((_a2 = obj.textLines) == null ? void 0 : _a2.length) || 0,
|
|
13491
|
+
lines: (obj.textLines || []).map((line) => Array.isArray(line) ? line.join("") : String(line ?? "")),
|
|
13492
|
+
lineWidths,
|
|
13493
|
+
maxLineWidth: lineWidths.length ? Math.max(...lineWidths) : 0
|
|
13494
|
+
});
|
|
13495
|
+
}
|
|
13496
|
+
if (obj instanceof fabric.Group && typeof obj.getObjects === "function") {
|
|
13497
|
+
const nextPath = [groupPath, getObjectId(obj) || obj.type || "group"].filter(Boolean).join("/");
|
|
13498
|
+
obj.getObjects().forEach((child) => visit(child, nextPath));
|
|
13499
|
+
}
|
|
13500
|
+
};
|
|
13501
|
+
(_a = fabricInstance == null ? void 0 : fabricInstance.getObjects) == null ? void 0 : _a.call(fabricInstance).forEach((obj) => visit(obj));
|
|
13502
|
+
logJsonLine("[canvas-renderer][fabric-text-parity]", { stage, textboxes: sample.length, sample });
|
|
13503
|
+
}
|
|
13413
13504
|
async waitForStableTextMetrics(container, config) {
|
|
13414
13505
|
var _a, _b, _c;
|
|
13415
13506
|
if (typeof document !== "undefined") {
|
|
@@ -13418,6 +13509,7 @@ class PixldocsRenderer {
|
|
|
13418
13509
|
}
|
|
13419
13510
|
const fabricInstance = this.getFabricCanvasFromContainer(container);
|
|
13420
13511
|
if (!(fabricInstance == null ? void 0 : fabricInstance.getObjects)) return;
|
|
13512
|
+
this.logFabricTextParitySnapshot("before-stable-text-metrics", fabricInstance);
|
|
13421
13513
|
const waitForPaint = () => new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(() => r())));
|
|
13422
13514
|
const primeCharBounds = (obj) => {
|
|
13423
13515
|
if (obj instanceof fabric.Textbox) {
|
|
@@ -13444,6 +13536,7 @@ class PixldocsRenderer {
|
|
|
13444
13536
|
await waitForPaint();
|
|
13445
13537
|
(_c = fabricInstance.renderAll) == null ? void 0 : _c.call(fabricInstance);
|
|
13446
13538
|
await waitForPaint();
|
|
13539
|
+
this.logFabricTextParitySnapshot("after-stable-text-metrics", fabricInstance);
|
|
13447
13540
|
}
|
|
13448
13541
|
}
|
|
13449
13542
|
const FONT_WEIGHT_LABELS = {
|
|
@@ -14265,6 +14358,16 @@ const pdfFonts = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProp
|
|
|
14265
14358
|
resolveFontWeight,
|
|
14266
14359
|
rewriteSvgFontsForJsPDF
|
|
14267
14360
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
14361
|
+
function logParityJson(tag, stage, payload) {
|
|
14362
|
+
try {
|
|
14363
|
+
console.log(`${tag} ${stage} ${JSON.stringify(payload, (_key, value) => {
|
|
14364
|
+
if (typeof value !== "number") return value;
|
|
14365
|
+
return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
|
|
14366
|
+
})}`);
|
|
14367
|
+
} catch {
|
|
14368
|
+
console.log(`${tag} ${stage}`, payload);
|
|
14369
|
+
}
|
|
14370
|
+
}
|
|
14268
14371
|
function dumpSvgTextDiagnostics(svgStr, pageIndex, tag, stage, maxItems = 30) {
|
|
14269
14372
|
try {
|
|
14270
14373
|
if (typeof DOMParser === "undefined") return;
|
|
@@ -14287,7 +14390,7 @@ function dumpSvgTextDiagnostics(svgStr, pageIndex, tag, stage, maxItems = 30) {
|
|
|
14287
14390
|
svgViewBox,
|
|
14288
14391
|
textCount: texts.length
|
|
14289
14392
|
};
|
|
14290
|
-
|
|
14393
|
+
logParityJson(tag, stage, { kind: "summary", ...summary });
|
|
14291
14394
|
const sample = texts.slice(0, maxItems).map((t, idx) => {
|
|
14292
14395
|
var _a, _b;
|
|
14293
14396
|
const tspans = Array.from(t.querySelectorAll("tspan"));
|
|
@@ -14319,7 +14422,7 @@ function dumpSvgTextDiagnostics(svgStr, pageIndex, tag, stage, maxItems = 30) {
|
|
|
14319
14422
|
tspanSample: tspanInfo
|
|
14320
14423
|
};
|
|
14321
14424
|
});
|
|
14322
|
-
|
|
14425
|
+
logParityJson(tag, stage, { kind: "text-sample", page: pageIndex, count: sample.length, total: texts.length, sample });
|
|
14323
14426
|
} catch (err) {
|
|
14324
14427
|
console.warn(`${tag} ${stage} page=${pageIndex} dump threw`, err);
|
|
14325
14428
|
}
|