@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 CHANGED
@@ -10388,6 +10388,165 @@ function paintRepeatableSections(config, repeatableSections) {
10388
10388
  }
10389
10389
  }
10390
10390
  }
10391
+ function normalizeFontFamily(fontStack) {
10392
+ const first = fontStack.split(",")[0].trim();
10393
+ return first.replace(/^['"]|['"]$/g, "");
10394
+ }
10395
+ const loadedFonts = /* @__PURE__ */ new Set();
10396
+ const loadingPromises = /* @__PURE__ */ new Map();
10397
+ async function loadGoogleFontCSS(rawFontFamily) {
10398
+ if (!rawFontFamily || typeof document === "undefined") return;
10399
+ const fontFamily = normalizeFontFamily(rawFontFamily);
10400
+ if (!fontFamily) return;
10401
+ if (loadedFonts.has(fontFamily)) return;
10402
+ const existing = loadingPromises.get(fontFamily);
10403
+ if (existing) return existing;
10404
+ const promise = (async () => {
10405
+ var _a;
10406
+ try {
10407
+ if ((_a = document.fonts) == null ? void 0 : _a.check(`16px "${fontFamily}"`)) {
10408
+ loadedFonts.add(fontFamily);
10409
+ return;
10410
+ }
10411
+ const encoded = encodeURIComponent(fontFamily);
10412
+ const url = `https://fonts.googleapis.com/css?family=${encoded}:300,400,500,600,700&display=swap`;
10413
+ const link = document.createElement("link");
10414
+ link.rel = "stylesheet";
10415
+ link.href = url;
10416
+ link.crossOrigin = "anonymous";
10417
+ await new Promise((resolve, reject) => {
10418
+ link.onload = () => resolve();
10419
+ link.onerror = () => reject(new Error(`Failed to load font: ${fontFamily}`));
10420
+ document.head.appendChild(link);
10421
+ });
10422
+ if (document.fonts) {
10423
+ await document.fonts.load(`16px "${fontFamily}"`);
10424
+ await document.fonts.ready;
10425
+ }
10426
+ loadedFonts.add(fontFamily);
10427
+ } catch (e) {
10428
+ console.warn(`[@pixldocs/canvas-renderer] Font load failed: ${fontFamily}`, e);
10429
+ }
10430
+ })();
10431
+ loadingPromises.set(fontFamily, promise);
10432
+ await promise;
10433
+ loadingPromises.delete(fontFamily);
10434
+ }
10435
+ function collectFontsFromConfig(config) {
10436
+ var _a;
10437
+ const fonts = /* @__PURE__ */ new Set();
10438
+ fonts.add("Open Sans");
10439
+ fonts.add("Hind");
10440
+ function walk(nodes) {
10441
+ var _a2;
10442
+ if (!nodes) return;
10443
+ for (const node of nodes) {
10444
+ if (node.fontFamily) fonts.add(normalizeFontFamily(node.fontFamily));
10445
+ if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) fonts.add(normalizeFontFamily(node.smartProps.fontFamily));
10446
+ if (node.styles && Array.isArray(node.styles)) {
10447
+ for (const lineStyle of node.styles) {
10448
+ if (lineStyle && typeof lineStyle === "object") {
10449
+ for (const charStyle of Object.values(lineStyle)) {
10450
+ if (charStyle == null ? void 0 : charStyle.fontFamily) fonts.add(normalizeFontFamily(charStyle.fontFamily));
10451
+ }
10452
+ }
10453
+ }
10454
+ }
10455
+ if (node.children) walk(node.children);
10456
+ }
10457
+ }
10458
+ for (const page of config.pages || []) {
10459
+ walk(page.children || []);
10460
+ }
10461
+ if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
10462
+ for (const def of Object.values(config.themeConfig.variables)) {
10463
+ if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
10464
+ if (def.label && /font/i.test(def.label)) {
10465
+ fonts.add(normalizeFontFamily(def.value));
10466
+ }
10467
+ }
10468
+ }
10469
+ }
10470
+ return fonts;
10471
+ }
10472
+ function collectFontDescriptorsFromConfig(config) {
10473
+ var _a;
10474
+ const seen = /* @__PURE__ */ new Set();
10475
+ const descriptors = [];
10476
+ function add(family, weight, style) {
10477
+ const f = normalizeFontFamily(family);
10478
+ if (!f) return;
10479
+ const w = weight ?? 400;
10480
+ const s = style ?? "normal";
10481
+ const key = `${f}|${w}|${s}`;
10482
+ if (seen.has(key)) return;
10483
+ seen.add(key);
10484
+ descriptors.push({ family: f, weight: w, style: s });
10485
+ }
10486
+ function walk(nodes) {
10487
+ var _a2;
10488
+ if (!nodes) return;
10489
+ for (const node of nodes) {
10490
+ if (node.fontFamily) {
10491
+ add(node.fontFamily, node.fontWeight, node.fontStyle);
10492
+ if (node.type === "text") {
10493
+ for (const w of [300, 400, 500, 600, 700]) {
10494
+ add(node.fontFamily, w, node.fontStyle);
10495
+ }
10496
+ }
10497
+ }
10498
+ if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) {
10499
+ add(node.smartProps.fontFamily, node.smartProps.fontWeight, node.smartProps.fontStyle);
10500
+ }
10501
+ if (node.styles) {
10502
+ const styleEntries = Array.isArray(node.styles) ? node.styles : Object.values(node.styles);
10503
+ for (const lineStyle of styleEntries) {
10504
+ if (lineStyle && typeof lineStyle === "object") {
10505
+ for (const charStyle of Object.values(lineStyle)) {
10506
+ if (charStyle == null ? void 0 : charStyle.fontFamily) {
10507
+ add(charStyle.fontFamily, charStyle.fontWeight, charStyle.fontStyle);
10508
+ }
10509
+ }
10510
+ }
10511
+ }
10512
+ }
10513
+ if (node.children) walk(node.children);
10514
+ }
10515
+ }
10516
+ add("Open Sans", 400, "normal");
10517
+ add("Hind", 400, "normal");
10518
+ add("Hind", 700, "normal");
10519
+ for (const page of config.pages || []) {
10520
+ walk(page.children || []);
10521
+ }
10522
+ if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
10523
+ for (const def of Object.values(config.themeConfig.variables)) {
10524
+ if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
10525
+ if (def.label && /font/i.test(def.label)) {
10526
+ add(def.value);
10527
+ }
10528
+ }
10529
+ }
10530
+ }
10531
+ return descriptors;
10532
+ }
10533
+ async function ensureFontsForResolvedConfig(config) {
10534
+ if (typeof document === "undefined") return;
10535
+ const descriptors = collectFontDescriptorsFromConfig(config);
10536
+ const families = new Set(descriptors.map((d) => d.family));
10537
+ await Promise.all([...families].map((f) => loadGoogleFontCSS(f)));
10538
+ if (document.fonts) {
10539
+ const loadPromises = descriptors.map((d) => {
10540
+ const stylePrefix = d.style === "italic" ? "italic " : "";
10541
+ const weightStr = String(d.weight);
10542
+ const spec = `${stylePrefix}${weightStr} 16px "${d.family}"`;
10543
+ return document.fonts.load(spec).catch(() => {
10544
+ });
10545
+ });
10546
+ await Promise.all(loadPromises);
10547
+ await document.fonts.ready;
10548
+ }
10549
+ }
10391
10550
  function PixldocsPreview(props) {
10392
10551
  const {
10393
10552
  pageIndex = 0,
@@ -10405,6 +10564,7 @@ function PixldocsPreview(props) {
10405
10564
  }, [imageProxyUrl]);
10406
10565
  const [resolvedConfig, setResolvedConfig] = react.useState(null);
10407
10566
  const [isLoading, setIsLoading] = react.useState(false);
10567
+ const [fontsReady, setFontsReady] = react.useState(false);
10408
10568
  const isResolveMode = !("config" in props && props.config);
10409
10569
  react.useEffect(() => {
10410
10570
  if (!isResolveMode) {
@@ -10425,7 +10585,17 @@ function PixldocsPreview(props) {
10425
10585
  }).then((resolved) => {
10426
10586
  if (!cancelled) {
10427
10587
  setResolvedConfig(resolved.config);
10428
- setIsLoading(false);
10588
+ ensureFontsForResolvedConfig(resolved.config).then(() => {
10589
+ if (!cancelled) {
10590
+ setFontsReady(true);
10591
+ setIsLoading(false);
10592
+ }
10593
+ }).catch(() => {
10594
+ if (!cancelled) {
10595
+ setFontsReady(true);
10596
+ setIsLoading(false);
10597
+ }
10598
+ });
10429
10599
  }
10430
10600
  }).catch((err) => {
10431
10601
  if (!cancelled) {
@@ -10445,6 +10615,11 @@ function PixldocsPreview(props) {
10445
10615
  isResolveMode ? props.themeId : void 0
10446
10616
  ]);
10447
10617
  const config = isResolveMode ? resolvedConfig : props.config;
10618
+ react.useEffect(() => {
10619
+ if (isResolveMode || !config) return;
10620
+ setFontsReady(false);
10621
+ ensureFontsForResolvedConfig(config).then(() => setFontsReady(true)).catch(() => setFontsReady(true));
10622
+ }, [isResolveMode, config]);
10448
10623
  if (isLoading) {
10449
10624
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style: { ...style, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) });
10450
10625
  }
@@ -10461,77 +10636,6 @@ function PixldocsPreview(props) {
10461
10636
  }
10462
10637
  ) });
10463
10638
  }
10464
- function normalizeFontFamily(fontStack) {
10465
- const first = fontStack.split(",")[0].trim();
10466
- return first.replace(/^['"]|['"]$/g, "");
10467
- }
10468
- const loadedFonts = /* @__PURE__ */ new Set();
10469
- const loadingPromises = /* @__PURE__ */ new Map();
10470
- async function loadGoogleFontCSS(rawFontFamily) {
10471
- if (!rawFontFamily || typeof document === "undefined") return;
10472
- const fontFamily = normalizeFontFamily(rawFontFamily);
10473
- if (!fontFamily) return;
10474
- if (loadedFonts.has(fontFamily)) return;
10475
- const existing = loadingPromises.get(fontFamily);
10476
- if (existing) return existing;
10477
- const promise = (async () => {
10478
- var _a;
10479
- try {
10480
- if ((_a = document.fonts) == null ? void 0 : _a.check(`16px "${fontFamily}"`)) {
10481
- loadedFonts.add(fontFamily);
10482
- return;
10483
- }
10484
- const encoded = encodeURIComponent(fontFamily);
10485
- const url = `https://fonts.googleapis.com/css?family=${encoded}:300,400,500,600,700&display=swap`;
10486
- const link = document.createElement("link");
10487
- link.rel = "stylesheet";
10488
- link.href = url;
10489
- link.crossOrigin = "anonymous";
10490
- await new Promise((resolve, reject) => {
10491
- link.onload = () => resolve();
10492
- link.onerror = () => reject(new Error(`Failed to load font: ${fontFamily}`));
10493
- document.head.appendChild(link);
10494
- });
10495
- if (document.fonts) {
10496
- await document.fonts.load(`16px "${fontFamily}"`);
10497
- await document.fonts.ready;
10498
- }
10499
- loadedFonts.add(fontFamily);
10500
- } catch (e) {
10501
- console.warn(`[@pixldocs/canvas-renderer] Font load failed: ${fontFamily}`, e);
10502
- }
10503
- })();
10504
- loadingPromises.set(fontFamily, promise);
10505
- await promise;
10506
- loadingPromises.delete(fontFamily);
10507
- }
10508
- function collectFontsFromConfig(config) {
10509
- var _a;
10510
- const fonts = /* @__PURE__ */ new Set();
10511
- fonts.add("Open Sans");
10512
- function walk(nodes) {
10513
- var _a2;
10514
- if (!nodes) return;
10515
- for (const node of nodes) {
10516
- if (node.fontFamily) fonts.add(normalizeFontFamily(node.fontFamily));
10517
- if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) fonts.add(normalizeFontFamily(node.smartProps.fontFamily));
10518
- if (node.children) walk(node.children);
10519
- }
10520
- }
10521
- for (const page of config.pages || []) {
10522
- walk(page.children || []);
10523
- }
10524
- if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
10525
- for (const def of Object.values(config.themeConfig.variables)) {
10526
- if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
10527
- if (def.label && /font/i.test(def.label)) {
10528
- fonts.add(normalizeFontFamily(def.value));
10529
- }
10530
- }
10531
- }
10532
- }
10533
- return fonts;
10534
- }
10535
10639
  class PixldocsRenderer {
10536
10640
  constructor(config) {
10537
10641
  __publicField(this, "config");
@@ -10552,8 +10656,7 @@ class PixldocsRenderer {
10552
10656
  if (!page) {
10553
10657
  throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
10554
10658
  }
10555
- const fonts = collectFontsFromConfig(templateConfig);
10556
- await Promise.all([...fonts].map((f) => loadGoogleFontCSS(f)));
10659
+ await ensureFontsForResolvedConfig(templateConfig);
10557
10660
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
10558
10661
  setPackageApiUrl2(this.config.imageProxyUrl);
10559
10662
  const dataUrl = await this.renderPageViaPreviewCanvas(
@@ -10780,7 +10883,9 @@ class PixldocsRenderer {
10780
10883
  exports.PixldocsPreview = PixldocsPreview;
10781
10884
  exports.PixldocsRenderer = PixldocsRenderer;
10782
10885
  exports.applyThemeToConfig = applyThemeToConfig;
10886
+ exports.collectFontDescriptorsFromConfig = collectFontDescriptorsFromConfig;
10783
10887
  exports.collectFontsFromConfig = collectFontsFromConfig;
10888
+ exports.ensureFontsForResolvedConfig = ensureFontsForResolvedConfig;
10784
10889
  exports.loadGoogleFontCSS = loadGoogleFontCSS;
10785
10890
  exports.normalizeFontFamily = normalizeFontFamily;
10786
10891
  exports.resolveFromForm = resolveFromForm;