@pixldocs/canvas-renderer 0.5.95 → 0.5.97

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.js CHANGED
@@ -396,33 +396,6 @@ function measureTextHeight(element) {
396
396
  return fallbackEstimateHeight(element);
397
397
  }
398
398
  }
399
- function getMinTextWidth(element) {
400
- if (element.type !== "text" || !element.text) {
401
- return typeof element.width === "number" ? element.width : 200;
402
- }
403
- try {
404
- const textbox = new fabric.Textbox(element.text, {
405
- width: 1e4,
406
- fontSize: element.fontSize || 16,
407
- fontFamily: element.fontFamily || "Open Sans",
408
- fontWeight: element.fontWeight || 400,
409
- fontStyle: element.fontStyle || "normal",
410
- lineHeight: element.lineHeight || 1.2,
411
- charSpacing: element.charSpacing || 0,
412
- splitByGrapheme: element.splitByGrapheme ?? element.wordWrap === "break-word",
413
- left: 0,
414
- top: 0
415
- });
416
- textbox.initDimensions();
417
- const lineWidths = textbox.__lineWidths;
418
- if (lineWidths && lineWidths.length > 0) {
419
- const maxLine = Math.max(...lineWidths);
420
- return Math.ceil(maxLine) + 2;
421
- }
422
- } catch (_) {
423
- }
424
- return typeof element.width === "number" ? element.width : 200;
425
- }
426
399
  function fallbackEstimateHeight(element) {
427
400
  if (element.type !== "text" || !element.text) {
428
401
  return element.height || 20;
@@ -2778,11 +2751,22 @@ const waitUntilFontsAvailable = async (fontFamilies, options) => {
2778
2751
  }
2779
2752
  };
2780
2753
  const clearFabricCharCache = () => {
2754
+ var _a;
2781
2755
  const fabricAny = fabric;
2756
+ if (fabricAny.cache && typeof fabricAny.cache.clearFontCache === "function") {
2757
+ fabricAny.cache.clearFontCache();
2758
+ }
2759
+ if (((_a = fabricAny.cache) == null ? void 0 : _a.charWidthsCache) instanceof Map) {
2760
+ fabricAny.cache.charWidthsCache.clear();
2761
+ }
2782
2762
  if (typeof fabricAny.charWidthsCache === "object" && fabricAny.charWidthsCache !== null) {
2783
- Object.keys(fabricAny.charWidthsCache).forEach((key) => {
2784
- delete fabricAny.charWidthsCache[key];
2785
- });
2763
+ if (fabricAny.charWidthsCache instanceof Map) {
2764
+ fabricAny.charWidthsCache.clear();
2765
+ } else {
2766
+ Object.keys(fabricAny.charWidthsCache).forEach((key) => {
2767
+ delete fabricAny.charWidthsCache[key];
2768
+ });
2769
+ }
2786
2770
  }
2787
2771
  if (typeof fabric.util !== "undefined" && typeof fabric.util.clearFabricFontCache === "function") {
2788
2772
  fabric.util.clearFabricFontCache();
@@ -5557,6 +5541,8 @@ function createText(element) {
5557
5541
  const targetScaleY = element.scaleY ?? 1;
5558
5542
  const textbox = new fabric.Textbox(text, {
5559
5543
  width: targetWidth,
5544
+ minWidth: 1,
5545
+ dynamicMinWidth: 0,
5560
5546
  scaleX: targetScaleX,
5561
5547
  scaleY: targetScaleY,
5562
5548
  fontSize,
@@ -5577,6 +5563,8 @@ function createText(element) {
5577
5563
  textbox.initDimensions();
5578
5564
  textbox.set({
5579
5565
  width: targetWidth,
5566
+ minWidth: 1,
5567
+ dynamicMinWidth: 0,
5580
5568
  scaleX: targetScaleX,
5581
5569
  scaleY: targetScaleY
5582
5570
  });
@@ -5586,6 +5574,7 @@ function createText(element) {
5586
5574
  const scaleYAfterSet = textbox.scaleY ?? 1;
5587
5575
  if (Math.abs(widthAfterSet - targetWidth) > 0.01 || Math.abs(scaleXAfterSet - targetScaleX) > 0.01 || Math.abs(scaleYAfterSet - targetScaleY) > 0.01) {
5588
5576
  textbox.width = targetWidth;
5577
+ textbox.dynamicMinWidth = 0;
5589
5578
  textbox.scaleX = targetScaleX;
5590
5579
  textbox.scaleY = targetScaleY;
5591
5580
  textbox.setCoords();
@@ -6432,6 +6421,48 @@ const PageCanvas = forwardRef(
6432
6421
  if (!canvas2) return false;
6433
6422
  return !!canvas2._currentTransform || !!canvas2.__isUserTransforming;
6434
6423
  }, []);
6424
+ const reflowTextboxesFromCurrentElements = useCallback((canvas2) => {
6425
+ const elementById = new Map(elementsRef.current.map((el) => [el.id, el]));
6426
+ let didReflow = false;
6427
+ const reflowObject = (obj) => {
6428
+ var _a;
6429
+ if (obj instanceof fabric.Group) {
6430
+ (_a = obj._objects) == null ? void 0 : _a.forEach(reflowObject);
6431
+ obj.dirty = true;
6432
+ return;
6433
+ }
6434
+ if (!(obj instanceof fabric.Textbox)) return;
6435
+ const id = getObjectId(obj);
6436
+ const element = id ? elementById.get(id) : void 0;
6437
+ if (!element) return;
6438
+ const targetWidth = Math.max(1, Number(element.width) > 0 ? Number(element.width) : Number(obj.width ?? 200));
6439
+ const overflowPolicy = element.overflowPolicy || "grow-and-push";
6440
+ const splitByGrapheme = overflowPolicy === "auto-shrink" ? false : element.splitByGrapheme ?? element.wordWrap === "break-word";
6441
+ obj.set({
6442
+ width: targetWidth,
6443
+ minWidth: 1,
6444
+ dynamicMinWidth: 0,
6445
+ text: element.text || "Text",
6446
+ fontSize: element.fontSize || 16,
6447
+ fontFamily: element.fontFamily || "Open Sans",
6448
+ fontWeight: element.fontWeight || 400,
6449
+ fontStyle: element.fontStyle || "normal",
6450
+ lineHeight: element.lineHeight || 1.2,
6451
+ charSpacing: element.charSpacing || 0,
6452
+ splitByGrapheme
6453
+ });
6454
+ obj.initDimensions();
6455
+ if (Math.abs((obj.width ?? 0) - targetWidth) > 0.01) {
6456
+ obj.width = targetWidth;
6457
+ }
6458
+ obj.dynamicMinWidth = 0;
6459
+ obj.setCoords();
6460
+ obj.dirty = true;
6461
+ didReflow = true;
6462
+ };
6463
+ canvas2.getObjects().forEach(reflowObject);
6464
+ if (didReflow) canvas2.requestRenderAll();
6465
+ }, []);
6435
6466
  useEffect(() => {
6436
6467
  if (!canvasElRef.current) return;
6437
6468
  const zoom2 = workspaceZoom || 1;
@@ -6512,6 +6543,8 @@ const PageCanvas = forwardRef(
6512
6543
  };
6513
6544
  initFonts();
6514
6545
  const persistTextDimensionsAfterFontLoad = (canvas2) => {
6546
+ reflowTextboxesFromCurrentElements(canvas2);
6547
+ if (isPreviewMode) return;
6515
6548
  const state = useEditorStore.getState();
6516
6549
  const page = state.canvas.pages.find((p) => p.id === pageId);
6517
6550
  if (!page) return;
@@ -9108,10 +9141,11 @@ const PageCanvas = forwardRef(
9108
9141
  text = result;
9109
9142
  }
9110
9143
  }
9111
- const minWidth = getMinTextWidth(element);
9112
- const textboxWidth = overflowPolicy === "auto-shrink" ? fixedWidth : Math.max(storedWidth, minWidth);
9144
+ const textboxWidth = fixedWidth;
9113
9145
  obj.set({
9114
9146
  width: textboxWidth,
9147
+ minWidth: 1,
9148
+ dynamicMinWidth: 0,
9115
9149
  fontSize,
9116
9150
  fontFamily: element.fontFamily || "Open Sans",
9117
9151
  fill: element.fill || "#1a1a1a",
@@ -9131,6 +9165,7 @@ const PageCanvas = forwardRef(
9131
9165
  if (Math.abs((obj.width ?? 0) - textboxWidth) > 0.01) {
9132
9166
  obj.width = textboxWidth;
9133
9167
  }
9168
+ obj.dynamicMinWidth = 0;
9134
9169
  obj.setCoords();
9135
9170
  obj.dirty = true;
9136
9171
  try {
@@ -12919,7 +12954,6 @@ function PixldocsPreview(props) {
12919
12954
  const [isLoading, setIsLoading] = useState(false);
12920
12955
  const [fontsReady, setFontsReady] = useState(false);
12921
12956
  const [canvasSettled, setCanvasSettled] = useState(false);
12922
- const [stabilizationPass, setStabilizationPass] = useState(0);
12923
12957
  const isResolveMode = !("config" in props && props.config);
12924
12958
  useEffect(() => {
12925
12959
  if (!isResolveMode) {
@@ -12990,21 +13024,16 @@ function PixldocsPreview(props) {
12990
13024
  isResolveMode ? props.themeId : void 0
12991
13025
  ]);
12992
13026
  const config = isResolveMode ? resolvedConfig : props.config;
12993
- const previewKey = useMemo(
12994
- () => `${pageIndex}-${stabilizationPass}`,
12995
- [pageIndex, stabilizationPass]
12996
- );
13027
+ const previewKey = useMemo(() => `${pageIndex}`, [pageIndex]);
12997
13028
  useEffect(() => {
12998
13029
  if (isResolveMode) return;
12999
13030
  if (!config) {
13000
13031
  setFontsReady(false);
13001
13032
  setCanvasSettled(false);
13002
- setStabilizationPass(0);
13003
13033
  return;
13004
13034
  }
13005
13035
  setFontsReady(false);
13006
13036
  setCanvasSettled(false);
13007
- setStabilizationPass(0);
13008
13037
  let cancelled = false;
13009
13038
  const hasAutoShrink = configHasAutoShrinkText$1(config);
13010
13039
  const waitMs = hasAutoShrink ? 4e3 : 1800;
@@ -13027,16 +13056,10 @@ function PixldocsPreview(props) {
13027
13056
  };
13028
13057
  }, [isResolveMode, config]);
13029
13058
  const handleCanvasReady = useCallback(() => {
13030
- if (stabilizationPass === 0) {
13031
- console.log(PREVIEW_DEBUG_PREFIX, "canvas-ready-pass", { pageIndex, stabilizationPass, action: "stabilize-again" });
13032
- setCanvasSettled(false);
13033
- setStabilizationPass(1);
13034
- return;
13035
- }
13036
- console.log(PREVIEW_DEBUG_PREFIX, "canvas-ready-pass", { pageIndex, stabilizationPass, action: "settled" });
13059
+ console.log(PREVIEW_DEBUG_PREFIX, "canvas-ready", { pageIndex, action: "settled" });
13037
13060
  setCanvasSettled(true);
13038
13061
  onReady == null ? void 0 : onReady();
13039
- }, [onReady, pageIndex, stabilizationPass]);
13062
+ }, [onReady, pageIndex]);
13040
13063
  if (isLoading) {
13041
13064
  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..." }) });
13042
13065
  }
@@ -14558,6 +14581,10 @@ function getEmbeddedJsPDFFontName(fontName, weight, isItalic = false) {
14558
14581
  function isFontAvailable(fontName) {
14559
14582
  return fontName in FONT_FILES;
14560
14583
  }
14584
+ const registeredFamilies = /* @__PURE__ */ new Set();
14585
+ function isFamilyEmbedded(family) {
14586
+ return registeredFamilies.has(family);
14587
+ }
14561
14588
  const ttfCache = /* @__PURE__ */ new Map();
14562
14589
  async function fetchTTFAsBase64(url) {
14563
14590
  const cached = ttfCache.get(url);
@@ -14630,12 +14657,133 @@ async function embedFont(pdf, fontName, weight, fontBaseUrl, isItalic = false) {
14630
14657
  } catch {
14631
14658
  }
14632
14659
  }
14660
+ registeredFamilies.add(fontName);
14633
14661
  return true;
14634
14662
  } catch (e) {
14635
14663
  console.warn(`[pdf-fonts] Failed to embed ${fontName} w${weight}:`, e);
14636
14664
  return false;
14637
14665
  }
14638
14666
  }
14667
+ const googleFontNotFound = /* @__PURE__ */ new Set();
14668
+ const fontshareNotFound = /* @__PURE__ */ new Set();
14669
+ function bytesToBase64(bytes) {
14670
+ let binary = "";
14671
+ for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
14672
+ return btoa(binary);
14673
+ }
14674
+ async function fetchGoogleFontTTF(fontFamily, weight, isItalic = false) {
14675
+ const cacheKey = `gf:${fontFamily}:${weight}:${isItalic ? "i" : "n"}`;
14676
+ if (ttfCache.has(cacheKey)) return ttfCache.get(cacheKey);
14677
+ if (googleFontNotFound.has(fontFamily)) return null;
14678
+ try {
14679
+ const ital = isItalic ? "1" : "0";
14680
+ const cssUrl = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(
14681
+ fontFamily
14682
+ )}:ital,wght@${ital},${weight}&display=swap`;
14683
+ const cssRes = await fetch(cssUrl, {
14684
+ headers: {
14685
+ "User-Agent": "Mozilla/5.0 (Linux; U; Android 2.2; en-us) AppleWebKit/533.1 (KHTML, like Gecko)"
14686
+ }
14687
+ });
14688
+ if (!cssRes.ok) {
14689
+ if (cssRes.status === 400 || cssRes.status === 404) googleFontNotFound.add(fontFamily);
14690
+ return null;
14691
+ }
14692
+ const css = await cssRes.text();
14693
+ const urlMatch = css.match(/url\(([^)]+)\)\s+format\(['"]?truetype['"]?\)/i) || css.match(/url\(([^)]+)\)/);
14694
+ if (!urlMatch) return null;
14695
+ const ttfUrl = urlMatch[1].replace(/['"]/g, "");
14696
+ const ttfRes = await fetch(ttfUrl);
14697
+ if (!ttfRes.ok) return null;
14698
+ const buf = await ttfRes.arrayBuffer();
14699
+ const bytes = new Uint8Array(buf);
14700
+ if (!isJsPdfEmbeddableTrueType(bytes)) {
14701
+ console.warn(`[pdf-fonts] Google Fonts returned a non-TTF for ${fontFamily} (${weight}); skipping`);
14702
+ return null;
14703
+ }
14704
+ const b64 = bytesToBase64(bytes);
14705
+ ttfCache.set(cacheKey, b64);
14706
+ return b64;
14707
+ } catch (err) {
14708
+ console.warn(`[pdf-fonts] fetchGoogleFontTTF failed for ${fontFamily} (${weight}):`, err);
14709
+ return null;
14710
+ }
14711
+ }
14712
+ async function fetchFontshareTTF(fontFamily, weight, isItalic = false) {
14713
+ const cacheKey = `fs:${fontFamily}:${weight}:${isItalic ? "i" : "n"}`;
14714
+ if (ttfCache.has(cacheKey)) return ttfCache.get(cacheKey);
14715
+ if (fontshareNotFound.has(fontFamily)) return null;
14716
+ const slug = fontFamily.trim().toLowerCase().replace(/\s+/g, "-");
14717
+ try {
14718
+ const styleSuffix = isItalic ? "i" : "";
14719
+ const cssUrl = `https://api.fontshare.com/v2/css?f[]=${slug}@${weight}${styleSuffix}&display=swap`;
14720
+ const cssRes = await fetch(cssUrl);
14721
+ if (!cssRes.ok) {
14722
+ if (cssRes.status === 400 || cssRes.status === 404) fontshareNotFound.add(fontFamily);
14723
+ return null;
14724
+ }
14725
+ const css = await cssRes.text();
14726
+ const ttMatch = css.match(/url\(([^)]+)\)\s+format\(['"]?truetype['"]?\)/i);
14727
+ if (!ttMatch) {
14728
+ fontshareNotFound.add(fontFamily);
14729
+ return null;
14730
+ }
14731
+ let ttfUrl = ttMatch[1].replace(/['"]/g, "").trim();
14732
+ if (ttfUrl.startsWith("//")) ttfUrl = `https:${ttfUrl}`;
14733
+ const ttfRes = await fetch(ttfUrl);
14734
+ if (!ttfRes.ok) return null;
14735
+ const buf = await ttfRes.arrayBuffer();
14736
+ const bytes = new Uint8Array(buf);
14737
+ if (!isJsPdfEmbeddableTrueType(bytes)) return null;
14738
+ const b64 = bytesToBase64(bytes);
14739
+ ttfCache.set(cacheKey, b64);
14740
+ return b64;
14741
+ } catch (err) {
14742
+ console.warn(`[pdf-fonts] fetchFontshareTTF failed for ${fontFamily} (${weight}):`, err);
14743
+ return null;
14744
+ }
14745
+ }
14746
+ function registerJsPdfFont(pdf, fontName, resolvedWeight, isItalic, base64) {
14747
+ const label = FONT_WEIGHT_LABELS[resolvedWeight];
14748
+ const italicSuffix = isItalic ? "Italic" : "";
14749
+ const jsPdfFontName = getEmbeddedJsPDFFontName(fontName, resolvedWeight, isItalic);
14750
+ const fileName = `${getJsPDFFontName(fontName)}-${label}${italicSuffix}.ttf`;
14751
+ try {
14752
+ pdf.addFileToVFS(fileName, base64);
14753
+ pdf.addFont(fileName, jsPdfFontName, "normal");
14754
+ if (fontName !== jsPdfFontName) {
14755
+ try {
14756
+ pdf.addFont(fileName, fontName, "normal");
14757
+ } catch {
14758
+ }
14759
+ }
14760
+ registeredFamilies.add(fontName);
14761
+ return true;
14762
+ } catch (err) {
14763
+ console.warn(`[pdf-fonts] registerJsPdfFont failed for ${fontName}:`, err);
14764
+ return false;
14765
+ }
14766
+ }
14767
+ async function embedFontWithGoogleFallback(pdf, fontName, weight = 400, fontBaseUrl, isItalic = false) {
14768
+ if (FONT_FILES[fontName]) {
14769
+ const ok = await embedFont(pdf, fontName, weight, fontBaseUrl, isItalic);
14770
+ if (ok) return true;
14771
+ }
14772
+ const resolved = resolveFontWeight(weight);
14773
+ const fsB64 = await fetchFontshareTTF(fontName, resolved, isItalic);
14774
+ if (fsB64) return registerJsPdfFont(pdf, fontName, resolved, isItalic, fsB64);
14775
+ if (isItalic) {
14776
+ const fsUpright = await fetchFontshareTTF(fontName, resolved, false);
14777
+ if (fsUpright) return registerJsPdfFont(pdf, fontName, resolved, isItalic, fsUpright);
14778
+ }
14779
+ const b64 = await fetchGoogleFontTTF(fontName, resolved, isItalic);
14780
+ if (b64) return registerJsPdfFont(pdf, fontName, resolved, isItalic, b64);
14781
+ if (isItalic) {
14782
+ const uprightB64 = await fetchGoogleFontTTF(fontName, resolved, false);
14783
+ if (uprightB64) return registerJsPdfFont(pdf, fontName, resolved, isItalic, uprightB64);
14784
+ }
14785
+ return false;
14786
+ }
14639
14787
  async function embedFontsForConfig(pdf, config, fontBaseUrl) {
14640
14788
  const fontKeys = /* @__PURE__ */ new Set();
14641
14789
  const SEP = "";
@@ -14794,7 +14942,7 @@ function rewriteSvgFontsForJsPDF(svgStr) {
14794
14942
  const rawFf = resolveInheritedValue(el, "font-family");
14795
14943
  if (!rawFf) continue;
14796
14944
  const clean = (_a = rawFf.split(",")[0]) == null ? void 0 : _a.replace(/['"]/g, "").trim();
14797
- if (!isFontAvailable(clean)) continue;
14945
+ if (!isFontAvailable(clean) && !isFamilyEmbedded(clean)) continue;
14798
14946
  const weightRaw = resolveInheritedValue(el, "font-weight") || "400";
14799
14947
  const styleRaw = resolveInheritedValue(el, "font-style") || "normal";
14800
14948
  const weight = resolveWeightNum(weightRaw);
@@ -14877,14 +15025,19 @@ async function embedFontsInPdf(pdf, fontFamilies, fontBaseUrl) {
14877
15025
  fontFamilies.add(FONT_FALLBACK_SYMBOLS);
14878
15026
  fontFamilies.add(FONT_FALLBACK_DEVANAGARI);
14879
15027
  for (const family of fontFamilies) {
14880
- if (!isFontAvailable(family)) {
14881
- console.warn(`[pdf-fonts] No TTF mapping for "${family}" — will use Helvetica fallback`);
14882
- continue;
14883
- }
14884
- for (const w of weights) {
15028
+ if (isFontAvailable(family)) {
15029
+ for (const w of weights) {
15030
+ tasks.push(
15031
+ embedFont(pdf, family, w, fontBaseUrl).then((ok) => {
15032
+ if (ok) embedded.add(`${family}${w}`);
15033
+ })
15034
+ );
15035
+ }
15036
+ } else {
14885
15037
  tasks.push(
14886
- embedFont(pdf, family, w, fontBaseUrl).then((ok) => {
14887
- if (ok) embedded.add(`${family}${w}`);
15038
+ embedFontWithGoogleFallback(pdf, family, 400, fontBaseUrl, false).then((ok) => {
15039
+ if (ok) embedded.add(`${family}400`);
15040
+ else console.warn(`[pdf-fonts] No TTF (local/Google/Fontshare) for "${family}" — will use Helvetica fallback`);
14888
15041
  })
14889
15042
  );
14890
15043
  }
@@ -14900,6 +15053,7 @@ const pdfFonts = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProp
14900
15053
  FONT_FILES,
14901
15054
  FONT_WEIGHT_LABELS,
14902
15055
  embedFont,
15056
+ embedFontWithGoogleFallback,
14903
15057
  embedFontsForConfig,
14904
15058
  embedFontsInPdf,
14905
15059
  extractFontFamiliesFromSvgs,