@pixldocs/canvas-renderer 0.5.110 → 0.5.112

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
@@ -144,6 +144,9 @@ export declare function extractFontFamiliesFromSvgs(svgs: string[]): Set<string>
144
144
  /** Font used for Devanagari / Hindi script */
145
145
  export declare const FONT_FALLBACK_DEVANAGARI = "Hind";
146
146
 
147
+ /** Font used only when the active font lacks math/operator glyphs. */
148
+ export declare const FONT_FALLBACK_MATH = "Noto Sans Math";
149
+
147
150
  /** Font used for symbols (● ◆ ★ etc.) */
148
151
  export declare const FONT_FALLBACK_SYMBOLS = "Noto Sans";
149
152
 
package/dist/index.js CHANGED
@@ -15052,6 +15052,7 @@ function resolveFontWeight(weight) {
15052
15052
  }
15053
15053
  const FONT_FALLBACK_SYMBOLS = "Noto Sans";
15054
15054
  const FONT_FALLBACK_DEVANAGARI = "Hind";
15055
+ const FONT_FALLBACK_MATH = "Noto Sans Math";
15055
15056
  const FONT_FILES = {
15056
15057
  "Playfair Display": {
15057
15058
  regular: "PlayfairDisplay-Regular.ttf",
@@ -15523,6 +15524,9 @@ const FONT_FILES = {
15523
15524
  medium: "Hind-Medium.ttf",
15524
15525
  semibold: "Hind-SemiBold.ttf"
15525
15526
  },
15527
+ "Noto Sans Math": {
15528
+ regular: "NotoSansMath-Regular.ttf"
15529
+ },
15526
15530
  "Cinzel": { regular: "Cinzel-Regular.ttf", bold: "Cinzel-Bold.ttf" },
15527
15531
  "Cormorant Garamond": {
15528
15532
  regular: "CormorantGaramond-Regular.ttf",
@@ -15625,6 +15629,54 @@ function isJsPdfEmbeddableTrueType(bytes) {
15625
15629
  }
15626
15630
  return false;
15627
15631
  }
15632
+ function base64ToBytes(b64) {
15633
+ const binary = atob(b64);
15634
+ const bytes = new Uint8Array(binary.length);
15635
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
15636
+ return bytes;
15637
+ }
15638
+ function extractSupportedCodePointsFromTtf(bytes) {
15639
+ const out = /* @__PURE__ */ new Set();
15640
+ if (bytes.length < 12) return out;
15641
+ const u16 = (o) => bytes[o] << 8 | bytes[o + 1];
15642
+ const i16 = (o) => {
15643
+ const v = u16(o);
15644
+ return v & 32768 ? v - 65536 : v;
15645
+ };
15646
+ const u32 = (o) => (bytes[o] << 24 | bytes[o + 1] << 16 | bytes[o + 2] << 8 | bytes[o + 3]) >>> 0;
15647
+ let cmap = 0;
15648
+ for (let i = 0, n = u16(4); i < n; i++) {
15649
+ const off = 12 + i * 16;
15650
+ if (String.fromCharCode(bytes[off], bytes[off + 1], bytes[off + 2], bytes[off + 3]) === "cmap") {
15651
+ cmap = u32(off + 8);
15652
+ break;
15653
+ }
15654
+ }
15655
+ if (!cmap) return out;
15656
+ const candidates = [];
15657
+ for (let i = 0, n = u16(cmap + 2); i < n; i++) {
15658
+ const enc = cmap + 4 + i * 8;
15659
+ const platform = u16(enc), encoding = u16(enc + 2), off = cmap + u32(enc + 4), format = u16(off);
15660
+ const score = (format === 12 ? 40 : format === 4 ? 30 : 0) + (platform === 3 && encoding === 10 ? 2 : platform === 0 ? 1 : 0);
15661
+ if (score) candidates.push({ format, off, score });
15662
+ }
15663
+ candidates.sort((a, b) => b.score - a.score);
15664
+ const best = candidates[0];
15665
+ if (!best) return out;
15666
+ if (best.format === 12) {
15667
+ for (let i = 0, n = u32(best.off + 12); i < n; i++) {
15668
+ const off = best.off + 16 + i * 12, start = u32(off), end = u32(off + 4);
15669
+ for (let cp = start; cp <= end && cp <= 1114111; cp++) out.add(cp);
15670
+ }
15671
+ } else if (best.format === 4) {
15672
+ const segCount = u16(best.off + 6) / 2, endCodes = best.off + 14, startCodes = endCodes + segCount * 2 + 2, deltas = startCodes + segCount * 2, ranges = deltas + segCount * 2;
15673
+ for (let i = 0; i < segCount; i++) for (let cp = u16(startCodes + i * 2), end = u16(endCodes + i * 2); cp <= end && cp !== 65535; cp++) {
15674
+ const ro = u16(ranges + i * 2);
15675
+ if (ro === 0 ? (cp + i16(deltas + i * 2) & 65535) !== 0 : u16(ranges + i * 2 + ro + (cp - u16(startCodes + i * 2)) * 2) !== 0) out.add(cp);
15676
+ }
15677
+ }
15678
+ return out;
15679
+ }
15628
15680
  async function embedFont(pdf, fontName, weight, fontBaseUrl, isItalic = false) {
15629
15681
  const fontFiles = FONT_FILES[fontName];
15630
15682
  if (!fontFiles) return false;
@@ -15641,6 +15693,7 @@ async function embedFont(pdf, fontName, weight, fontBaseUrl, isItalic = false) {
15641
15693
  try {
15642
15694
  const b64 = await fetchTTFAsBase64(url);
15643
15695
  if (!b64) return false;
15696
+ registeredVariantCoverage.set(variantKey(fontName, resolvedWeight, isItalic), extractSupportedCodePointsFromTtf(base64ToBytes(b64)));
15644
15697
  pdf.addFileToVFS(fileName, b64);
15645
15698
  pdf.addFont(fileName, jsPdfFontName, "normal");
15646
15699
  if (fontName !== jsPdfFontName) {
@@ -15661,6 +15714,7 @@ const googleFontNotFound = /* @__PURE__ */ new Set();
15661
15714
  const fontshareNotFound = /* @__PURE__ */ new Set();
15662
15715
  const remoteVariantKey = (family, weight, isItalic) => `${family}|${resolveFontWeight(weight)}|${isItalic ? "i" : "n"}`;
15663
15716
  const registeredVariants = /* @__PURE__ */ new Set();
15717
+ const registeredVariantCoverage = /* @__PURE__ */ new Map();
15664
15718
  const variantKey = (family, weight, italic) => `${family}|${resolveFontWeight(weight)}|${italic ? "i" : "n"}`;
15665
15719
  const resolveBestRegisteredVariant = (family, weight, italic) => {
15666
15720
  const want = resolveFontWeight(weight);
@@ -15671,6 +15725,12 @@ const resolveBestRegisteredVariant = (family, weight, italic) => {
15671
15725
  for (const w of nearest) if (registeredVariants.has(variantKey(family, w, !italic))) return { weight: w, italic: !italic };
15672
15726
  return null;
15673
15727
  };
15728
+ const doesVariantSupportChar = (family, weight, italic, char) => {
15729
+ var _a;
15730
+ const cp = char.codePointAt(0);
15731
+ if (cp == null) return false;
15732
+ return ((_a = registeredVariantCoverage.get(variantKey(family, weight, italic))) == null ? void 0 : _a.has(cp)) === true;
15733
+ };
15674
15734
  function bytesToBase64(bytes) {
15675
15735
  let binary = "";
15676
15736
  for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
@@ -15787,6 +15847,7 @@ function registerJsPdfFont(pdf, fontName, resolvedWeight, isItalic, base64) {
15787
15847
  const jsPdfFontName = getEmbeddedJsPDFFontName(fontName, resolvedWeight, isItalic);
15788
15848
  const fileName = `${getJsPDFFontName(fontName)}-${label}${italicSuffix}.ttf`;
15789
15849
  try {
15850
+ registeredVariantCoverage.set(variantKey(fontName, resolvedWeight, isItalic), extractSupportedCodePointsFromTtf(base64ToBytes(base64)));
15790
15851
  pdf.addFileToVFS(fileName, base64);
15791
15852
  pdf.addFont(fileName, jsPdfFontName, "normal");
15792
15853
  if (fontName !== jsPdfFontName) {
@@ -15883,6 +15944,7 @@ async function embedFontsForConfig(pdf, config, fontBaseUrl) {
15883
15944
  if (page.elements) walkElements(page.elements);
15884
15945
  }
15885
15946
  fontKeys.add(`${FONT_FALLBACK_SYMBOLS}${SEP}400${SEP}normal`);
15947
+ fontKeys.add(`${FONT_FALLBACK_MATH}${SEP}400${SEP}normal`);
15886
15948
  for (const w of [300, 400, 500, 600, 700]) {
15887
15949
  fontKeys.add(`${FONT_FALLBACK_DEVANAGARI}${SEP}${w}${SEP}normal`);
15888
15950
  }
@@ -15916,10 +15978,10 @@ function containsDevanagari(text) {
15916
15978
  }
15917
15979
  return false;
15918
15980
  }
15919
- function containsSymbol(text) {
15981
+ function containsSymbol(text, mainSupportsChar) {
15920
15982
  if (!text) return false;
15921
15983
  for (const char of text) {
15922
- if (classifyChar(char) === "symbol") return true;
15984
+ if (classifyChar(char, mainSupportsChar) !== "main") return true;
15923
15985
  }
15924
15986
  return false;
15925
15987
  }
@@ -15933,19 +15995,25 @@ function isCommonLatinPunctuation(char) {
15933
15995
  if (c === 8467 || c === 8482) return true;
15934
15996
  return false;
15935
15997
  }
15936
- function classifyChar(char) {
15998
+ function isMathOperator(char) {
15999
+ const c = char.codePointAt(0) ?? 0;
16000
+ return c >= 8592 && c <= 8959 || c >= 8960 && c <= 9215 || c >= 10176 && c <= 11007 || c >= 11008 && c <= 11097;
16001
+ }
16002
+ function classifyChar(char, mainSupportsChar) {
15937
16003
  if (isBasicLatinOrLatin1(char)) return "main";
15938
16004
  if (isCommonLatinPunctuation(char)) return "main";
16005
+ if (!isDevanagari(char) && (mainSupportsChar == null ? void 0 : mainSupportsChar(char))) return "main";
15939
16006
  if (isDevanagari(char)) return "devanagari";
16007
+ if (isMathOperator(char)) return "math";
15940
16008
  return "symbol";
15941
16009
  }
15942
- function splitIntoRuns(text) {
16010
+ function splitIntoRuns(text, mainSupportsChar) {
15943
16011
  if (!text) return [];
15944
16012
  const runs = [];
15945
16013
  let currentType = null;
15946
16014
  let currentText = "";
15947
16015
  for (const char of text) {
15948
- const type = classifyChar(char);
16016
+ const type = classifyChar(char, mainSupportsChar);
15949
16017
  if (type !== currentType && currentText) {
15950
16018
  runs.push({ text: currentText, runType: currentType });
15951
16019
  currentText = "";
@@ -16053,15 +16121,17 @@ function rewriteSvgFontsForJsPDF(svgStr) {
16053
16121
  }
16054
16122
  const directText = Array.from(el.childNodes).filter((n) => n.nodeType === 3).map((n) => n.textContent || "").join("");
16055
16123
  const hasDevanagari = containsDevanagari(directText);
16056
- const hasSymbol = containsSymbol(directText);
16124
+ const mainSupportsChar = (char) => doesVariantSupportChar(clean, resolved, actualItalic, char);
16125
+ const hasSymbol = containsSymbol(directText, mainSupportsChar);
16057
16126
  if ((hasDevanagari || hasSymbol) && directText.length > 0) {
16058
16127
  const devanagariWeight = resolveFontWeight(weight);
16059
16128
  const devanagariJsPdfName = getEmbeddedJsPDFFontName(FONT_FALLBACK_DEVANAGARI, devanagariWeight);
16060
16129
  const symbolJsPdfName = isFontAvailable(FONT_FALLBACK_SYMBOLS) ? getEmbeddedJsPDFFontName(FONT_FALLBACK_SYMBOLS, 400) : jsPdfName;
16130
+ const mathJsPdfName = isFontAvailable(FONT_FALLBACK_MATH) ? getEmbeddedJsPDFFontName(FONT_FALLBACK_MATH, 400) : symbolJsPdfName;
16061
16131
  const childNodes = Array.from(el.childNodes);
16062
16132
  for (const node of childNodes) {
16063
16133
  if (node.nodeType !== 3 || !node.textContent) continue;
16064
- const runs = splitIntoRuns(node.textContent);
16134
+ const runs = splitIntoRuns(node.textContent, mainSupportsChar);
16065
16135
  if (runs.length <= 1 && ((_b = runs[0]) == null ? void 0 : _b.runType) === "main") continue;
16066
16136
  const fragment = doc.createDocumentFragment();
16067
16137
  for (const run of runs) {
@@ -16071,6 +16141,8 @@ function rewriteSvgFontsForJsPDF(svgStr) {
16071
16141
  runFont = devanagariJsPdfName;
16072
16142
  } else if (run.runType === "symbol") {
16073
16143
  runFont = symbolJsPdfName;
16144
+ } else if (run.runType === "math") {
16145
+ runFont = mathJsPdfName;
16074
16146
  } else {
16075
16147
  runFont = jsPdfName;
16076
16148
  }
@@ -16177,6 +16249,7 @@ async function embedFontsInPdf(pdf, fontFamilies, fontBaseUrl) {
16177
16249
  const pdfFonts = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
16178
16250
  __proto__: null,
16179
16251
  FONT_FALLBACK_DEVANAGARI,
16252
+ FONT_FALLBACK_MATH,
16180
16253
  FONT_FALLBACK_SYMBOLS,
16181
16254
  FONT_FILES,
16182
16255
  FONT_WEIGHT_LABELS,
@@ -17653,7 +17726,7 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
17653
17726
  const hasGradient = !!((_b = (_a = page.backgroundGradient) == null ? void 0 : _a.stops) == null ? void 0 : _b.length);
17654
17727
  drawPageBackground(pdf, i, page.width, page.height, page.backgroundColor, page.backgroundGradient);
17655
17728
  const shouldStripBg = stripPageBackground ?? hasGradient;
17656
- const textMode = options.textMode ?? (options.outlineText === false ? "selectable" : "pixel-perfect");
17729
+ const textMode = options.textMode ?? (options.outlineText === true ? "pixel-perfect" : "selectable");
17657
17730
  const shouldOutlineText = textMode === "pixel-perfect" || textMode === "auto";
17658
17731
  const outlineSubMode = textMode === "auto" ? "complex-only" : "all";
17659
17732
  let pageSvg = page.svg;
@@ -17823,6 +17896,7 @@ function setAutoShrinkDebug(enabled) {
17823
17896
  }
17824
17897
  export {
17825
17898
  FONT_FALLBACK_DEVANAGARI,
17899
+ FONT_FALLBACK_MATH,
17826
17900
  FONT_FALLBACK_SYMBOLS,
17827
17901
  FONT_FILES,
17828
17902
  PACKAGE_VERSION,