@pixldocs/canvas-renderer 0.4.4 → 0.4.6

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
@@ -11881,8 +11881,47 @@ async function embedFontsForConfig(pdf, config, fontBaseUrl) {
11881
11881
  console.log(`[pdf-fonts] Embedded ${embedded.size} font variants from config`);
11882
11882
  return embedded;
11883
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
+ }
11884
11923
  function rewriteSvgFontsForJsPDF(svgStr) {
11885
- var _a;
11924
+ var _a, _b;
11886
11925
  const parser = new DOMParser();
11887
11926
  const doc = parser.parseFromString(svgStr, "image/svg+xml");
11888
11927
  const textEls = doc.querySelectorAll("text, tspan, textPath");
@@ -11903,6 +11942,17 @@ function rewriteSvgFontsForJsPDF(svgStr) {
11903
11942
  }
11904
11943
  return null;
11905
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
+ };
11906
11956
  for (const el of textEls) {
11907
11957
  const inlineStyle = el.getAttribute("style") || "";
11908
11958
  const rawFf = resolveInheritedValue(el, "font-family");
@@ -11911,19 +11961,50 @@ function rewriteSvgFontsForJsPDF(svgStr) {
11911
11961
  if (!isFontAvailable(clean)) continue;
11912
11962
  const weightRaw = resolveInheritedValue(el, "font-weight") || "400";
11913
11963
  const styleRaw = resolveInheritedValue(el, "font-style") || "normal";
11914
- const parsedWeight = Number.parseInt(weightRaw, 10);
11915
- 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);
11916
11965
  const resolved = resolveFontWeight(weight);
11917
11966
  const isItalic = /italic|oblique/i.test(styleRaw);
11918
11967
  const jsPdfName = getEmbeddedJsPDFFontName(clean, resolved, isItalic);
11919
- el.setAttribute("font-family", jsPdfName);
11920
- el.setAttribute("font-weight", "normal");
11921
- el.setAttribute("font-style", "normal");
11922
- 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));
11923
- stylePairs.push(`font-family: ${jsPdfName}`);
11924
- stylePairs.push(`font-weight: normal`);
11925
- stylePairs.push(`font-style: normal`);
11926
- 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
+ }
11927
12008
  }
11928
12009
  return new XMLSerializer().serializeToString(doc.documentElement);
11929
12010
  }