@pixldocs/canvas-renderer 0.3.12 → 0.3.14
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 +179 -74
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +31 -0
- package/dist/index.js +179 -74
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -36,6 +36,8 @@ export declare interface CanvasSize {
|
|
|
36
36
|
height: number;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
export declare function collectFontDescriptorsFromConfig(config: TemplateConfig): FontDescriptor[];
|
|
40
|
+
|
|
39
41
|
/**
|
|
40
42
|
* Collect all font families used in a template config.
|
|
41
43
|
*/
|
|
@@ -52,6 +54,35 @@ export declare interface DynamicField {
|
|
|
52
54
|
[key: string]: any;
|
|
53
55
|
}
|
|
54
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Ensure all fonts required by a fully-resolved TemplateConfig are loaded
|
|
59
|
+
* and available to Fabric/Canvas before rendering.
|
|
60
|
+
*
|
|
61
|
+
* This is the **single API** consumers (and the renderer internally) should
|
|
62
|
+
* call to guarantee font parity with EC2 `/render-from-form`.
|
|
63
|
+
*
|
|
64
|
+
* It:
|
|
65
|
+
* 1. Walks ALL text nodes (including clones/repeatables) collecting
|
|
66
|
+
* fontFamily + fontWeight + fontStyle.
|
|
67
|
+
* 2. Loads each unique family via Google Fonts CSS v1 (idempotent).
|
|
68
|
+
* 3. Explicitly loads each weight+style combo via `document.fonts.load()`.
|
|
69
|
+
* 4. Awaits `document.fonts.ready` so Fabric never paints with fallback faces.
|
|
70
|
+
*
|
|
71
|
+
* Idempotent — safe to call multiple times for the same config.
|
|
72
|
+
*/
|
|
73
|
+
export declare function ensureFontsForResolvedConfig(config: TemplateConfig): Promise<void>;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Walk a fully-resolved TemplateConfig and collect every unique
|
|
77
|
+
* { fontFamily, fontWeight, fontStyle } tuple from all text nodes
|
|
78
|
+
* (including clones, repeatable children, and per-character Fabric styles).
|
|
79
|
+
*/
|
|
80
|
+
export declare interface FontDescriptor {
|
|
81
|
+
family: string;
|
|
82
|
+
weight: number | string;
|
|
83
|
+
style: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
55
86
|
export { InferredSection }
|
|
56
87
|
|
|
57
88
|
/**
|
package/dist/index.js
CHANGED
|
@@ -10369,6 +10369,165 @@ function paintRepeatableSections(config, repeatableSections) {
|
|
|
10369
10369
|
}
|
|
10370
10370
|
}
|
|
10371
10371
|
}
|
|
10372
|
+
function normalizeFontFamily(fontStack) {
|
|
10373
|
+
const first = fontStack.split(",")[0].trim();
|
|
10374
|
+
return first.replace(/^['"]|['"]$/g, "");
|
|
10375
|
+
}
|
|
10376
|
+
const loadedFonts = /* @__PURE__ */ new Set();
|
|
10377
|
+
const loadingPromises = /* @__PURE__ */ new Map();
|
|
10378
|
+
async function loadGoogleFontCSS(rawFontFamily) {
|
|
10379
|
+
if (!rawFontFamily || typeof document === "undefined") return;
|
|
10380
|
+
const fontFamily = normalizeFontFamily(rawFontFamily);
|
|
10381
|
+
if (!fontFamily) return;
|
|
10382
|
+
if (loadedFonts.has(fontFamily)) return;
|
|
10383
|
+
const existing = loadingPromises.get(fontFamily);
|
|
10384
|
+
if (existing) return existing;
|
|
10385
|
+
const promise = (async () => {
|
|
10386
|
+
var _a;
|
|
10387
|
+
try {
|
|
10388
|
+
if ((_a = document.fonts) == null ? void 0 : _a.check(`16px "${fontFamily}"`)) {
|
|
10389
|
+
loadedFonts.add(fontFamily);
|
|
10390
|
+
return;
|
|
10391
|
+
}
|
|
10392
|
+
const encoded = encodeURIComponent(fontFamily);
|
|
10393
|
+
const url = `https://fonts.googleapis.com/css?family=${encoded}:300,400,500,600,700&display=swap`;
|
|
10394
|
+
const link = document.createElement("link");
|
|
10395
|
+
link.rel = "stylesheet";
|
|
10396
|
+
link.href = url;
|
|
10397
|
+
link.crossOrigin = "anonymous";
|
|
10398
|
+
await new Promise((resolve, reject) => {
|
|
10399
|
+
link.onload = () => resolve();
|
|
10400
|
+
link.onerror = () => reject(new Error(`Failed to load font: ${fontFamily}`));
|
|
10401
|
+
document.head.appendChild(link);
|
|
10402
|
+
});
|
|
10403
|
+
if (document.fonts) {
|
|
10404
|
+
await document.fonts.load(`16px "${fontFamily}"`);
|
|
10405
|
+
await document.fonts.ready;
|
|
10406
|
+
}
|
|
10407
|
+
loadedFonts.add(fontFamily);
|
|
10408
|
+
} catch (e) {
|
|
10409
|
+
console.warn(`[@pixldocs/canvas-renderer] Font load failed: ${fontFamily}`, e);
|
|
10410
|
+
}
|
|
10411
|
+
})();
|
|
10412
|
+
loadingPromises.set(fontFamily, promise);
|
|
10413
|
+
await promise;
|
|
10414
|
+
loadingPromises.delete(fontFamily);
|
|
10415
|
+
}
|
|
10416
|
+
function collectFontsFromConfig(config) {
|
|
10417
|
+
var _a;
|
|
10418
|
+
const fonts = /* @__PURE__ */ new Set();
|
|
10419
|
+
fonts.add("Open Sans");
|
|
10420
|
+
fonts.add("Hind");
|
|
10421
|
+
function walk(nodes) {
|
|
10422
|
+
var _a2;
|
|
10423
|
+
if (!nodes) return;
|
|
10424
|
+
for (const node of nodes) {
|
|
10425
|
+
if (node.fontFamily) fonts.add(normalizeFontFamily(node.fontFamily));
|
|
10426
|
+
if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) fonts.add(normalizeFontFamily(node.smartProps.fontFamily));
|
|
10427
|
+
if (node.styles && Array.isArray(node.styles)) {
|
|
10428
|
+
for (const lineStyle of node.styles) {
|
|
10429
|
+
if (lineStyle && typeof lineStyle === "object") {
|
|
10430
|
+
for (const charStyle of Object.values(lineStyle)) {
|
|
10431
|
+
if (charStyle == null ? void 0 : charStyle.fontFamily) fonts.add(normalizeFontFamily(charStyle.fontFamily));
|
|
10432
|
+
}
|
|
10433
|
+
}
|
|
10434
|
+
}
|
|
10435
|
+
}
|
|
10436
|
+
if (node.children) walk(node.children);
|
|
10437
|
+
}
|
|
10438
|
+
}
|
|
10439
|
+
for (const page of config.pages || []) {
|
|
10440
|
+
walk(page.children || []);
|
|
10441
|
+
}
|
|
10442
|
+
if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
|
|
10443
|
+
for (const def of Object.values(config.themeConfig.variables)) {
|
|
10444
|
+
if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
|
|
10445
|
+
if (def.label && /font/i.test(def.label)) {
|
|
10446
|
+
fonts.add(normalizeFontFamily(def.value));
|
|
10447
|
+
}
|
|
10448
|
+
}
|
|
10449
|
+
}
|
|
10450
|
+
}
|
|
10451
|
+
return fonts;
|
|
10452
|
+
}
|
|
10453
|
+
function collectFontDescriptorsFromConfig(config) {
|
|
10454
|
+
var _a;
|
|
10455
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10456
|
+
const descriptors = [];
|
|
10457
|
+
function add(family, weight, style) {
|
|
10458
|
+
const f = normalizeFontFamily(family);
|
|
10459
|
+
if (!f) return;
|
|
10460
|
+
const w = weight ?? 400;
|
|
10461
|
+
const s = style ?? "normal";
|
|
10462
|
+
const key = `${f}|${w}|${s}`;
|
|
10463
|
+
if (seen.has(key)) return;
|
|
10464
|
+
seen.add(key);
|
|
10465
|
+
descriptors.push({ family: f, weight: w, style: s });
|
|
10466
|
+
}
|
|
10467
|
+
function walk(nodes) {
|
|
10468
|
+
var _a2;
|
|
10469
|
+
if (!nodes) return;
|
|
10470
|
+
for (const node of nodes) {
|
|
10471
|
+
if (node.fontFamily) {
|
|
10472
|
+
add(node.fontFamily, node.fontWeight, node.fontStyle);
|
|
10473
|
+
if (node.type === "text") {
|
|
10474
|
+
for (const w of [300, 400, 500, 600, 700]) {
|
|
10475
|
+
add(node.fontFamily, w, node.fontStyle);
|
|
10476
|
+
}
|
|
10477
|
+
}
|
|
10478
|
+
}
|
|
10479
|
+
if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) {
|
|
10480
|
+
add(node.smartProps.fontFamily, node.smartProps.fontWeight, node.smartProps.fontStyle);
|
|
10481
|
+
}
|
|
10482
|
+
if (node.styles) {
|
|
10483
|
+
const styleEntries = Array.isArray(node.styles) ? node.styles : Object.values(node.styles);
|
|
10484
|
+
for (const lineStyle of styleEntries) {
|
|
10485
|
+
if (lineStyle && typeof lineStyle === "object") {
|
|
10486
|
+
for (const charStyle of Object.values(lineStyle)) {
|
|
10487
|
+
if (charStyle == null ? void 0 : charStyle.fontFamily) {
|
|
10488
|
+
add(charStyle.fontFamily, charStyle.fontWeight, charStyle.fontStyle);
|
|
10489
|
+
}
|
|
10490
|
+
}
|
|
10491
|
+
}
|
|
10492
|
+
}
|
|
10493
|
+
}
|
|
10494
|
+
if (node.children) walk(node.children);
|
|
10495
|
+
}
|
|
10496
|
+
}
|
|
10497
|
+
add("Open Sans", 400, "normal");
|
|
10498
|
+
add("Hind", 400, "normal");
|
|
10499
|
+
add("Hind", 700, "normal");
|
|
10500
|
+
for (const page of config.pages || []) {
|
|
10501
|
+
walk(page.children || []);
|
|
10502
|
+
}
|
|
10503
|
+
if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
|
|
10504
|
+
for (const def of Object.values(config.themeConfig.variables)) {
|
|
10505
|
+
if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
|
|
10506
|
+
if (def.label && /font/i.test(def.label)) {
|
|
10507
|
+
add(def.value);
|
|
10508
|
+
}
|
|
10509
|
+
}
|
|
10510
|
+
}
|
|
10511
|
+
}
|
|
10512
|
+
return descriptors;
|
|
10513
|
+
}
|
|
10514
|
+
async function ensureFontsForResolvedConfig(config) {
|
|
10515
|
+
if (typeof document === "undefined") return;
|
|
10516
|
+
const descriptors = collectFontDescriptorsFromConfig(config);
|
|
10517
|
+
const families = new Set(descriptors.map((d) => d.family));
|
|
10518
|
+
await Promise.all([...families].map((f) => loadGoogleFontCSS(f)));
|
|
10519
|
+
if (document.fonts) {
|
|
10520
|
+
const loadPromises = descriptors.map((d) => {
|
|
10521
|
+
const stylePrefix = d.style === "italic" ? "italic " : "";
|
|
10522
|
+
const weightStr = String(d.weight);
|
|
10523
|
+
const spec = `${stylePrefix}${weightStr} 16px "${d.family}"`;
|
|
10524
|
+
return document.fonts.load(spec).catch(() => {
|
|
10525
|
+
});
|
|
10526
|
+
});
|
|
10527
|
+
await Promise.all(loadPromises);
|
|
10528
|
+
await document.fonts.ready;
|
|
10529
|
+
}
|
|
10530
|
+
}
|
|
10372
10531
|
function PixldocsPreview(props) {
|
|
10373
10532
|
const {
|
|
10374
10533
|
pageIndex = 0,
|
|
@@ -10386,6 +10545,7 @@ function PixldocsPreview(props) {
|
|
|
10386
10545
|
}, [imageProxyUrl]);
|
|
10387
10546
|
const [resolvedConfig, setResolvedConfig] = useState(null);
|
|
10388
10547
|
const [isLoading, setIsLoading] = useState(false);
|
|
10548
|
+
const [fontsReady, setFontsReady] = useState(false);
|
|
10389
10549
|
const isResolveMode = !("config" in props && props.config);
|
|
10390
10550
|
useEffect(() => {
|
|
10391
10551
|
if (!isResolveMode) {
|
|
@@ -10406,7 +10566,17 @@ function PixldocsPreview(props) {
|
|
|
10406
10566
|
}).then((resolved) => {
|
|
10407
10567
|
if (!cancelled) {
|
|
10408
10568
|
setResolvedConfig(resolved.config);
|
|
10409
|
-
|
|
10569
|
+
ensureFontsForResolvedConfig(resolved.config).then(() => {
|
|
10570
|
+
if (!cancelled) {
|
|
10571
|
+
setFontsReady(true);
|
|
10572
|
+
setIsLoading(false);
|
|
10573
|
+
}
|
|
10574
|
+
}).catch(() => {
|
|
10575
|
+
if (!cancelled) {
|
|
10576
|
+
setFontsReady(true);
|
|
10577
|
+
setIsLoading(false);
|
|
10578
|
+
}
|
|
10579
|
+
});
|
|
10410
10580
|
}
|
|
10411
10581
|
}).catch((err) => {
|
|
10412
10582
|
if (!cancelled) {
|
|
@@ -10426,6 +10596,11 @@ function PixldocsPreview(props) {
|
|
|
10426
10596
|
isResolveMode ? props.themeId : void 0
|
|
10427
10597
|
]);
|
|
10428
10598
|
const config = isResolveMode ? resolvedConfig : props.config;
|
|
10599
|
+
useEffect(() => {
|
|
10600
|
+
if (isResolveMode || !config) return;
|
|
10601
|
+
setFontsReady(false);
|
|
10602
|
+
ensureFontsForResolvedConfig(config).then(() => setFontsReady(true)).catch(() => setFontsReady(true));
|
|
10603
|
+
}, [isResolveMode, config]);
|
|
10429
10604
|
if (isLoading) {
|
|
10430
10605
|
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..." }) });
|
|
10431
10606
|
}
|
|
@@ -10442,77 +10617,6 @@ function PixldocsPreview(props) {
|
|
|
10442
10617
|
}
|
|
10443
10618
|
) });
|
|
10444
10619
|
}
|
|
10445
|
-
function normalizeFontFamily(fontStack) {
|
|
10446
|
-
const first = fontStack.split(",")[0].trim();
|
|
10447
|
-
return first.replace(/^['"]|['"]$/g, "");
|
|
10448
|
-
}
|
|
10449
|
-
const loadedFonts = /* @__PURE__ */ new Set();
|
|
10450
|
-
const loadingPromises = /* @__PURE__ */ new Map();
|
|
10451
|
-
async function loadGoogleFontCSS(rawFontFamily) {
|
|
10452
|
-
if (!rawFontFamily || typeof document === "undefined") return;
|
|
10453
|
-
const fontFamily = normalizeFontFamily(rawFontFamily);
|
|
10454
|
-
if (!fontFamily) return;
|
|
10455
|
-
if (loadedFonts.has(fontFamily)) return;
|
|
10456
|
-
const existing = loadingPromises.get(fontFamily);
|
|
10457
|
-
if (existing) return existing;
|
|
10458
|
-
const promise = (async () => {
|
|
10459
|
-
var _a;
|
|
10460
|
-
try {
|
|
10461
|
-
if ((_a = document.fonts) == null ? void 0 : _a.check(`16px "${fontFamily}"`)) {
|
|
10462
|
-
loadedFonts.add(fontFamily);
|
|
10463
|
-
return;
|
|
10464
|
-
}
|
|
10465
|
-
const encoded = encodeURIComponent(fontFamily);
|
|
10466
|
-
const url = `https://fonts.googleapis.com/css?family=${encoded}:300,400,500,600,700&display=swap`;
|
|
10467
|
-
const link = document.createElement("link");
|
|
10468
|
-
link.rel = "stylesheet";
|
|
10469
|
-
link.href = url;
|
|
10470
|
-
link.crossOrigin = "anonymous";
|
|
10471
|
-
await new Promise((resolve, reject) => {
|
|
10472
|
-
link.onload = () => resolve();
|
|
10473
|
-
link.onerror = () => reject(new Error(`Failed to load font: ${fontFamily}`));
|
|
10474
|
-
document.head.appendChild(link);
|
|
10475
|
-
});
|
|
10476
|
-
if (document.fonts) {
|
|
10477
|
-
await document.fonts.load(`16px "${fontFamily}"`);
|
|
10478
|
-
await document.fonts.ready;
|
|
10479
|
-
}
|
|
10480
|
-
loadedFonts.add(fontFamily);
|
|
10481
|
-
} catch (e) {
|
|
10482
|
-
console.warn(`[@pixldocs/canvas-renderer] Font load failed: ${fontFamily}`, e);
|
|
10483
|
-
}
|
|
10484
|
-
})();
|
|
10485
|
-
loadingPromises.set(fontFamily, promise);
|
|
10486
|
-
await promise;
|
|
10487
|
-
loadingPromises.delete(fontFamily);
|
|
10488
|
-
}
|
|
10489
|
-
function collectFontsFromConfig(config) {
|
|
10490
|
-
var _a;
|
|
10491
|
-
const fonts = /* @__PURE__ */ new Set();
|
|
10492
|
-
fonts.add("Open Sans");
|
|
10493
|
-
function walk(nodes) {
|
|
10494
|
-
var _a2;
|
|
10495
|
-
if (!nodes) return;
|
|
10496
|
-
for (const node of nodes) {
|
|
10497
|
-
if (node.fontFamily) fonts.add(normalizeFontFamily(node.fontFamily));
|
|
10498
|
-
if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) fonts.add(normalizeFontFamily(node.smartProps.fontFamily));
|
|
10499
|
-
if (node.children) walk(node.children);
|
|
10500
|
-
}
|
|
10501
|
-
}
|
|
10502
|
-
for (const page of config.pages || []) {
|
|
10503
|
-
walk(page.children || []);
|
|
10504
|
-
}
|
|
10505
|
-
if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
|
|
10506
|
-
for (const def of Object.values(config.themeConfig.variables)) {
|
|
10507
|
-
if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
|
|
10508
|
-
if (def.label && /font/i.test(def.label)) {
|
|
10509
|
-
fonts.add(normalizeFontFamily(def.value));
|
|
10510
|
-
}
|
|
10511
|
-
}
|
|
10512
|
-
}
|
|
10513
|
-
}
|
|
10514
|
-
return fonts;
|
|
10515
|
-
}
|
|
10516
10620
|
class PixldocsRenderer {
|
|
10517
10621
|
constructor(config) {
|
|
10518
10622
|
__publicField(this, "config");
|
|
@@ -10533,8 +10637,7 @@ class PixldocsRenderer {
|
|
|
10533
10637
|
if (!page) {
|
|
10534
10638
|
throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
|
|
10535
10639
|
}
|
|
10536
|
-
|
|
10537
|
-
await Promise.all([...fonts].map((f) => loadGoogleFontCSS(f)));
|
|
10640
|
+
await ensureFontsForResolvedConfig(templateConfig);
|
|
10538
10641
|
const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
|
|
10539
10642
|
setPackageApiUrl2(this.config.imageProxyUrl);
|
|
10540
10643
|
const dataUrl = await this.renderPageViaPreviewCanvas(
|
|
@@ -10762,7 +10865,9 @@ export {
|
|
|
10762
10865
|
PixldocsPreview,
|
|
10763
10866
|
PixldocsRenderer,
|
|
10764
10867
|
applyThemeToConfig,
|
|
10868
|
+
collectFontDescriptorsFromConfig,
|
|
10765
10869
|
collectFontsFromConfig,
|
|
10870
|
+
ensureFontsForResolvedConfig,
|
|
10766
10871
|
loadGoogleFontCSS,
|
|
10767
10872
|
normalizeFontFamily,
|
|
10768
10873
|
resolveFromForm,
|