@pixldocs/canvas-renderer 0.4.3 → 0.4.5

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.d.ts CHANGED
@@ -437,6 +437,9 @@ export declare function resolveTemplateData(options: ResolveOptions): Promise<Re
437
437
  * CRITICAL: Also sets font-weight to "normal" because the weight is already baked into
438
438
  * the jsPDF font name (e.g. JosefinSans-SemiBold). Without this, svg2pdf combines
439
439
  * font-weight + font-style into "600normal" which won't match fonts registered with style "normal".
440
+ *
441
+ * For Devanagari text: splits <tspan> elements containing mixed scripts into separate
442
+ * <tspan> children — Latin runs use the original font, Devanagari runs use FONT_FALLBACK_DEVANAGARI.
440
443
  */
441
444
  export declare function rewriteSvgFontsForJsPDF(svgStr: string): string;
442
445
 
package/dist/index.js CHANGED
@@ -2558,13 +2558,23 @@ const clearFabricCharCache = () => {
2558
2558
  };
2559
2559
  const clearFontCacheAndRerender = (canvas) => {
2560
2560
  clearFabricCharCache();
2561
- canvas.getObjects().forEach((obj) => {
2561
+ const fixTextbox = (obj) => {
2562
+ var _a;
2562
2563
  if (obj instanceof fabric.Textbox) {
2564
+ const savedWidth = obj.width;
2563
2565
  obj.dirty = true;
2564
2566
  obj.initDimensions();
2567
+ if (savedWidth != null && Math.abs((obj.width ?? 0) - savedWidth) > 0.01) {
2568
+ obj.width = savedWidth;
2569
+ }
2570
+ obj.setCoords();
2571
+ } else if (obj instanceof fabric.Group) {
2572
+ (_a = obj._objects) == null ? void 0 : _a.forEach(fixTextbox);
2573
+ obj.dirty = true;
2565
2574
  obj.setCoords();
2566
2575
  }
2567
- });
2576
+ };
2577
+ canvas.getObjects().forEach(fixTextbox);
2568
2578
  canvas.requestRenderAll();
2569
2579
  };
2570
2580
  const ensureFontLoaded = async (fontFamily) => {
@@ -11871,8 +11881,47 @@ async function embedFontsForConfig(pdf, config, fontBaseUrl) {
11871
11881
  console.log(`[pdf-fonts] Embedded ${embedded.size} font variants from config`);
11872
11882
  return embedded;
11873
11883
  }
11884
+ function isDevanagari(char) {
11885
+ const c = char.codePointAt(0) ?? 0;
11886
+ return c >= 2304 && c <= 2431 || c >= 43232 && c <= 43263 || c >= 7376 && c <= 7423;
11887
+ }
11888
+ function containsDevanagari(text) {
11889
+ if (!text) return false;
11890
+ for (const char of text) {
11891
+ if (isDevanagari(char)) return true;
11892
+ }
11893
+ return false;
11894
+ }
11895
+ function isBasicLatinOrLatin1(char) {
11896
+ const c = char.codePointAt(0) ?? 0;
11897
+ return c <= 591;
11898
+ }
11899
+ function classifyChar(char) {
11900
+ if (isBasicLatinOrLatin1(char)) return "main";
11901
+ if (isDevanagari(char)) return "devanagari";
11902
+ return "symbol";
11903
+ }
11904
+ function splitIntoRuns(text) {
11905
+ if (!text) return [];
11906
+ const runs = [];
11907
+ let currentType = null;
11908
+ let currentText = "";
11909
+ for (const char of text) {
11910
+ const type = classifyChar(char);
11911
+ if (type !== currentType && currentText) {
11912
+ runs.push({ text: currentText, runType: currentType });
11913
+ currentText = "";
11914
+ }
11915
+ currentType = type;
11916
+ currentText += char;
11917
+ }
11918
+ if (currentText && currentType) {
11919
+ runs.push({ text: currentText, runType: currentType });
11920
+ }
11921
+ return runs;
11922
+ }
11874
11923
  function rewriteSvgFontsForJsPDF(svgStr) {
11875
- var _a;
11924
+ var _a, _b;
11876
11925
  const parser = new DOMParser();
11877
11926
  const doc = parser.parseFromString(svgStr, "image/svg+xml");
11878
11927
  const textEls = doc.querySelectorAll("text, tspan, textPath");
@@ -11893,6 +11942,17 @@ function rewriteSvgFontsForJsPDF(svgStr) {
11893
11942
  }
11894
11943
  return null;
11895
11944
  };
11945
+ const resolveWeightNum = (weightRaw) => {
11946
+ const parsedWeight = Number.parseInt(weightRaw, 10);
11947
+ return Number.isFinite(parsedWeight) ? parsedWeight : /bold/i.test(weightRaw) ? 700 : /medium/i.test(weightRaw) ? 500 : /semi/i.test(weightRaw) ? 600 : /light/i.test(weightRaw) ? 300 : 400;
11948
+ };
11949
+ const buildStyleString = (existingStyle, fontName) => {
11950
+ const stylePairs = existingStyle.split(";").map((part) => part.trim()).filter(Boolean).filter((part) => !/^font-family\s*:/i.test(part) && !/^font-weight\s*:/i.test(part) && !/^font-style\s*:/i.test(part));
11951
+ stylePairs.push(`font-family: ${fontName}`);
11952
+ stylePairs.push(`font-weight: normal`);
11953
+ stylePairs.push(`font-style: normal`);
11954
+ return stylePairs.join("; ");
11955
+ };
11896
11956
  for (const el of textEls) {
11897
11957
  const inlineStyle = el.getAttribute("style") || "";
11898
11958
  const rawFf = resolveInheritedValue(el, "font-family");
@@ -11901,19 +11961,50 @@ function rewriteSvgFontsForJsPDF(svgStr) {
11901
11961
  if (!isFontAvailable(clean)) continue;
11902
11962
  const weightRaw = resolveInheritedValue(el, "font-weight") || "400";
11903
11963
  const styleRaw = resolveInheritedValue(el, "font-style") || "normal";
11904
- const parsedWeight = Number.parseInt(weightRaw, 10);
11905
- const weight = Number.isFinite(parsedWeight) ? parsedWeight : /bold/i.test(weightRaw) ? 700 : /medium/i.test(weightRaw) ? 500 : /semi/i.test(weightRaw) ? 600 : /light/i.test(weightRaw) ? 300 : 400;
11964
+ const weight = resolveWeightNum(weightRaw);
11906
11965
  const resolved = resolveFontWeight(weight);
11907
11966
  const isItalic = /italic|oblique/i.test(styleRaw);
11908
11967
  const jsPdfName = getEmbeddedJsPDFFontName(clean, resolved, isItalic);
11909
- el.setAttribute("font-family", jsPdfName);
11910
- el.setAttribute("font-weight", "normal");
11911
- el.setAttribute("font-style", "normal");
11912
- const stylePairs = inlineStyle.split(";").map((part) => part.trim()).filter(Boolean).filter((part) => !/^font-family\s*:/i.test(part) && !/^font-weight\s*:/i.test(part) && !/^font-style\s*:/i.test(part));
11913
- stylePairs.push(`font-family: ${jsPdfName}`);
11914
- stylePairs.push(`font-weight: normal`);
11915
- stylePairs.push(`font-style: normal`);
11916
- el.setAttribute("style", stylePairs.join("; "));
11968
+ const directText = Array.from(el.childNodes).filter((n) => n.nodeType === 3).map((n) => n.textContent || "").join("");
11969
+ const hasDevanagari = containsDevanagari(directText);
11970
+ if (hasDevanagari && directText.length > 0) {
11971
+ const devanagariWeight = resolveFontWeight(weight);
11972
+ const devanagariJsPdfName = getEmbeddedJsPDFFontName(FONT_FALLBACK_DEVANAGARI, devanagariWeight);
11973
+ const symbolJsPdfName = isFontAvailable(FONT_FALLBACK_SYMBOLS) ? getEmbeddedJsPDFFontName(FONT_FALLBACK_SYMBOLS, 400) : jsPdfName;
11974
+ const childNodes = Array.from(el.childNodes);
11975
+ for (const node of childNodes) {
11976
+ if (node.nodeType !== 3 || !node.textContent) continue;
11977
+ const runs = splitIntoRuns(node.textContent);
11978
+ if (runs.length <= 1 && ((_b = runs[0]) == null ? void 0 : _b.runType) !== "devanagari") continue;
11979
+ const fragment = doc.createDocumentFragment();
11980
+ for (const run of runs) {
11981
+ const tspan = doc.createElementNS("http://www.w3.org/2000/svg", "tspan");
11982
+ let runFont;
11983
+ if (run.runType === "devanagari") {
11984
+ runFont = devanagariJsPdfName;
11985
+ } else if (run.runType === "symbol") {
11986
+ runFont = symbolJsPdfName;
11987
+ } else {
11988
+ runFont = jsPdfName;
11989
+ }
11990
+ tspan.setAttribute("font-family", runFont);
11991
+ tspan.setAttribute("font-weight", "normal");
11992
+ tspan.setAttribute("font-style", "normal");
11993
+ tspan.textContent = run.text;
11994
+ fragment.appendChild(tspan);
11995
+ }
11996
+ el.replaceChild(fragment, node);
11997
+ }
11998
+ el.setAttribute("font-family", jsPdfName);
11999
+ el.setAttribute("font-weight", "normal");
12000
+ el.setAttribute("font-style", "normal");
12001
+ el.setAttribute("style", buildStyleString(inlineStyle, jsPdfName));
12002
+ } else {
12003
+ el.setAttribute("font-family", jsPdfName);
12004
+ el.setAttribute("font-weight", "normal");
12005
+ el.setAttribute("font-style", "normal");
12006
+ el.setAttribute("style", buildStyleString(inlineStyle, jsPdfName));
12007
+ }
11917
12008
  }
11918
12009
  return new XMLSerializer().serializeToString(doc.documentElement);
11919
12010
  }