@ai-matrx/print 0.8.1 → 0.8.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.3
4
+
5
+ - **Fixed:** an `<artifact>` envelope (how chat answers store tables and code)
6
+ printed as raw pipes in documents. The document pipeline now unwraps it with
7
+ the same rules the print skins use (`unwrapArtifactsToMarkdown`, exported
8
+ from `/markdown`): a table stays a table, code stays code.
9
+ - **Fixed (EPUB):** a `$$…$$` formula inside a sentence rendered as a `<div>`
10
+ inside `<p>` (epubcheck RSC-005); it is now a block-styled span.
11
+ - **Word:** East-Asian text carries a CJK font hint, so Word/LibreOffice pick
12
+ an installed Japanese/Chinese face.
13
+ - Consumer action: none — refresh to latest.
14
+
15
+ ## 0.8.2
16
+
17
+ - **PDF font fallback covers Japanese / Chinese / Korean.** Characters the
18
+ body, emoji and symbol faces cannot draw now fall back to Noto Sans JP
19
+ (SIL OFL, fontsource) — loading only the unicode-range subsets the document
20
+ uses (`fonts.cjk` loader; default jsDelivr). Found on a real conversation
21
+ whose citations carried Japanese source titles. A glyph is announced only
22
+ when no face in the chain can draw it.
23
+ - Consumer action: none — refresh to latest.
24
+
3
25
  ## 0.8.1
4
26
 
5
27
  - **Fixed (Word):** exporting DOCX laid out a whole PDF to number the table of
package/dist/document.cjs CHANGED
@@ -35,12 +35,14 @@ __export(document_exports, {
35
35
  CitationEngine: () => CitationEngine,
36
36
  DEFAULT_FOOTER: () => DEFAULT_FOOTER,
37
37
  DOCUMENT_FORMATS: () => DOCUMENT_FORMATS,
38
+ NOTO_CJK_BASE: () => NOTO_CJK_BASE,
38
39
  NOTO_EMOJI_URL: () => NOTO_EMOJI_URL,
39
40
  NOTO_SYMBOLS_URL: () => NOTO_SYMBOLS_URL,
40
41
  PAGE_SIZES: () => PAGE_SIZES,
41
42
  buildPdfDefinition: () => buildPdfDefinition,
42
43
  countUnplacedWords: () => countUnplacedWords,
43
44
  cslStyleId: () => cslStyleId,
45
+ defaultCjkFontLoader: () => defaultCjkFontLoader,
44
46
  defaultCslFetcher: () => defaultCslFetcher,
45
47
  defaultEmojiFontLoader: () => defaultEmojiFontLoader,
46
48
  defaultImageLoader: () => defaultImageLoader,
@@ -63,6 +65,7 @@ __export(document_exports, {
63
65
  parseLength: () => parseLength,
64
66
  parseLocator: () => parseLocator,
65
67
  parseMathML: () => parseMathML,
68
+ parseUnicodeRangeCss: () => parseUnicodeRangeCss,
66
69
  plainText: () => plainText,
67
70
  prepareDocument: () => prepareDocument,
68
71
  renderBlocksHtml: () => renderBlocksHtml,
@@ -3104,7 +3107,11 @@ async function documentToDocx(tree, options = {}) {
3104
3107
  numbering: { config: [...c.numbering.entries()].map(([reference, levels2]) => ({ reference, levels: levels2 })) },
3105
3108
  styles: {
3106
3109
  default: {
3107
- document: { run: { font: "Cambria", size, color: HEX.text }, paragraph: { spacing: { after: 140, line: 300 } } }
3110
+ // East-Asian text gets a CJK face (Word and LibreOffice substitute an installed one).
3111
+ document: {
3112
+ run: { font: { ascii: "Cambria", hAnsi: "Cambria", cs: "Cambria", eastAsia: "Yu Gothic" }, size, color: HEX.text },
3113
+ paragraph: { spacing: { after: 140, line: 300 } }
3114
+ }
3108
3115
  },
3109
3116
  paragraphStyles: [
3110
3117
  heading("Heading1", "Heading 1", s.fontSize * 1.8, 0),
@@ -3247,7 +3254,7 @@ function renderInlineHtml(nodes, x) {
3247
3254
  case "cite":
3248
3255
  return n.resolved ? `<a class="doc-cite" href="${(0, import_html_escape.escapeHtml)(href("ref-" + (n.keys[0] ?? ""), x))}">${(0, import_html_escape.escapeHtml)(n.label)}</a>` : `<span class="doc-cite doc-cite-missing">${(0, import_html_escape.escapeHtml)(n.label)}</span>`;
3249
3256
  case "math":
3250
- return mathHtml(n.tex, n.display, x);
3257
+ return mathHtml(n.tex, n.display, x, true);
3251
3258
  case "noteRef":
3252
3259
  return `<sup class="doc-note-ref"><a href="${(0, import_html_escape.escapeHtml)(href(`note-${n.n}`, x))}" id="noteref-${n.n}">${n.n}</a></sup>`;
3253
3260
  default:
@@ -3255,10 +3262,11 @@ function renderInlineHtml(nodes, x) {
3255
3262
  }
3256
3263
  }).join("");
3257
3264
  }
3258
- function mathHtml(tex, display, x) {
3265
+ function mathHtml(tex, display, x, inText = false) {
3259
3266
  const m = x.tree.math.get(mathKey(tex, display));
3260
3267
  if (m?.mathml && !m.error) {
3261
- return display ? `<div class="doc-math-display">${m.mathml}</div>` : `<span class="doc-math">${m.mathml}</span>`;
3268
+ if (!display) return `<span class="doc-math">${m.mathml}</span>`;
3269
+ return inText ? `<span class="doc-math-display">${m.mathml}</span>` : `<div class="doc-math-display">${m.mathml}</div>`;
3262
3270
  }
3263
3271
  return `<code class="doc-math-tex">${(0, import_html_escape.escapeHtml)(display ? `$$${tex}$$` : `$${tex}$`)}</code>`;
3264
3272
  }
@@ -3420,7 +3428,7 @@ table caption{caption-side:top;text-align:left;margin-bottom:4pt;}
3420
3428
  .doc-figure figcaption{margin-top:4pt;}
3421
3429
  .doc-image-missing{border:1px dashed var(--matrx-print-rule,#d1d5db);padding:16pt;color:var(--matrx-print-muted,#6b7280);}
3422
3430
  .doc-inline-img{max-height:1.2em;vertical-align:middle;}
3423
- .doc-math-display{margin:0.6em 0 0.9em;text-align:center;break-inside:avoid;overflow-x:auto;}
3431
+ .doc-math-display{display:block;margin:0.6em 0 0.9em;text-align:center;break-inside:avoid;overflow-x:auto;}
3424
3432
  .doc-math-display math{font-size:1.15em;}
3425
3433
  .doc-math-tex{color:var(--matrx-print-danger,#b91c1c);}
3426
3434
  .doc-task-list{list-style:none;padding-left:0.2em;}
@@ -3499,7 +3507,7 @@ blockquote{margin:0 0 0.8em;padding-left:1em;border-left:3px solid #d1d5db;color
3499
3507
  .doc-cover-logo{max-height:5em;}
3500
3508
  .doc-cover-title{font-size:2em;}
3501
3509
  .matrx-page-break{page-break-after:always;break-after:page;}
3502
- .doc-math-display{margin:0.6em 0;text-align:center;}
3510
+ .doc-math-display{display:block;margin:0.6em 0;text-align:center;}
3503
3511
  `.trim();
3504
3512
  function extOf(mime) {
3505
3513
  return mime === "image/png" ? "png" : mime === "image/gif" ? "gif" : "jpg";
@@ -4533,12 +4541,13 @@ async function loadTypography(tree, loaders = {}) {
4533
4541
  const base = await loadFaces(vfs, s.fontSize);
4534
4542
  let needEmoji = false;
4535
4543
  let needSymbols = false;
4544
+ const cjk = /* @__PURE__ */ new Set();
4536
4545
  for (const ch of documentText(tree)) {
4537
4546
  const cp = ch.codePointAt(0);
4538
4547
  if (cp < 32 || cp === 8205 || cp === 65039 || base.body(cp)) continue;
4539
4548
  if (EMOJI_CP.test(ch)) needEmoji = true;
4549
+ else if (isCjk(cp)) cjk.add(cp);
4540
4550
  else needSymbols = true;
4541
- if (needEmoji && needSymbols) break;
4542
4551
  }
4543
4552
  const fallbacks = [];
4544
4553
  if (needEmoji) {
@@ -4549,6 +4558,26 @@ async function loadTypography(tree, loaders = {}) {
4549
4558
  const b = await registerFace("NotoSymbols", "NotoSansMath.woff", loaders.symbols ?? defaultSymbolFontLoader, tree, "symbols");
4550
4559
  if (b) fallbacks.push({ font: "NotoSymbols", bytes: b });
4551
4560
  }
4561
+ if (cjk.size) {
4562
+ let subsets = [];
4563
+ try {
4564
+ subsets = await (loaders.cjk ?? defaultCjkFontLoader)([...cjk]);
4565
+ } catch {
4566
+ subsets = [];
4567
+ }
4568
+ for (const sub of subsets) {
4569
+ const font = `NotoCJK_${sub.name.replace(/[^\w]/g, "_")}`;
4570
+ const b = await registerFace(font, `${sub.name}.woff`, async () => sub.bytes, tree, "Japanese / Chinese");
4571
+ if (b) fallbacks.push({ font, bytes: b });
4572
+ }
4573
+ if (!subsets.length) {
4574
+ tree.notices.push({
4575
+ code: "glyph-substituted",
4576
+ message: "The Japanese / Chinese font could not be loaded, so those characters were left out of the PDF (Word, EPUB and HTML keep them).",
4577
+ remedy: "Check the internet connection (the font comes from cdn.jsdelivr.net) and export again."
4578
+ });
4579
+ }
4580
+ }
4552
4581
  const faces = fallbacks.length ? await loadFaces(vfs, s.fontSize, fallbacks) : base;
4553
4582
  if (!s.hyphenate) return { faces };
4554
4583
  const split = await loadHyphenator(s.lang);
@@ -4571,6 +4600,37 @@ var urlLoader = (url) => async () => {
4571
4600
  };
4572
4601
  var defaultEmojiFontLoader = urlLoader(NOTO_EMOJI_URL);
4573
4602
  var defaultSymbolFontLoader = urlLoader(NOTO_SYMBOLS_URL);
4603
+ var NOTO_CJK_BASE = "https://cdn.jsdelivr.net/npm/@fontsource/noto-sans-jp@5.3.0";
4604
+ function parseUnicodeRangeCss(css) {
4605
+ const out = [];
4606
+ for (const block3 of css.split("@font-face").slice(1)) {
4607
+ const file = /url\(\.\/files\/([\w-]+)\.woff\)/.exec(block3)?.[1];
4608
+ const range = /unicode-range:\s*([^;]+);/.exec(block3)?.[1];
4609
+ if (!file || !range) continue;
4610
+ const ranges = range.split(",").map((r) => {
4611
+ const [a, b] = r.trim().replace(/^U\+/i, "").split("-");
4612
+ const lo = parseInt(a, 16);
4613
+ return [lo, b ? parseInt(b, 16) : lo];
4614
+ });
4615
+ out.push({ file, ranges });
4616
+ }
4617
+ return out;
4618
+ }
4619
+ var defaultCjkFontLoader = async (cps) => {
4620
+ if (typeof fetch !== "function" || cps.length === 0) return [];
4621
+ const res = await fetch(`${NOTO_CJK_BASE}/400.css`);
4622
+ if (!res.ok) return [];
4623
+ const faces = parseUnicodeRangeCss(await res.text()).filter((f) => cps.some((cp) => f.ranges.some(([a, b]) => cp >= a && cp <= b)));
4624
+ const out = [];
4625
+ await Promise.all(
4626
+ faces.map(async (f) => {
4627
+ const r = await fetch(`${NOTO_CJK_BASE}/files/${f.file}.woff`);
4628
+ if (r.ok) out.push({ name: f.file, bytes: new Uint8Array(await r.arrayBuffer()) });
4629
+ })
4630
+ );
4631
+ return out;
4632
+ };
4633
+ var isCjk = (cp) => cp >= 11904 && cp <= 40959 || cp >= 44032 && cp <= 55215 || cp >= 63744 && cp <= 64255 || cp >= 65072 && cp <= 65103 || cp >= 65280 && cp <= 65519 || cp >= 131072 && cp <= 201551;
4574
4634
  var EMOJI_CP = /\p{Extended_Pictographic}|\p{Regional_Indicator}|\u20E3/u;
4575
4635
  function documentText(tree) {
4576
4636
  const s = tree.settings;
@@ -4737,6 +4797,70 @@ function removeThinkingContent(content) {
4737
4797
  return result.trim();
4738
4798
  }
4739
4799
  var PAGE_BREAK_HTML = `<div class="${PAGE_BREAK_CLASS}" role="separator" aria-label="${PAGE_BREAK_LABEL}" data-label="${PAGE_BREAK_LABEL}"></div>`;
4800
+ var CODE_ARTIFACT_LANGUAGE = {
4801
+ html: "html",
4802
+ react: "tsx",
4803
+ svg: "svg",
4804
+ mermaid: "mermaid",
4805
+ diff: "diff",
4806
+ iframe: "html",
4807
+ code: ""
4808
+ };
4809
+ function parseTagAttributes(tag) {
4810
+ const attrs = {};
4811
+ for (const m of tag.matchAll(/([a-zA-Z_][\w-]*)\s*=\s*(?:"([^"]*)"|'([^']*)')/g)) {
4812
+ attrs[m[1].toLowerCase()] = m[2] ?? m[3] ?? "";
4813
+ }
4814
+ return attrs;
4815
+ }
4816
+ function unwrapArtifactsToMarkdown(markdown) {
4817
+ if (!/<artifact\b/i.test(markdown)) return markdown;
4818
+ return markdown.split(/(^[ \t]*(?:`{3,}|~{3,})[\s\S]*?^[ \t]*(?:`{3,}|~{3,})[ \t]*$)/m).map(
4819
+ (part, i) => i % 2 === 1 ? part : part.replace(/<artifact\b((?:[^>"']|"[^"]*"|'[^']*')*?)>([\s\S]*?)<\/artifact>/gi, (_m, tag, body) => {
4820
+ const attrs = parseTagAttributes(tag);
4821
+ const type = (attrs.type ?? "").toLowerCase();
4822
+ const trimmed = body.replace(/^\n+|\n+$/g, "");
4823
+ if (!trimmed.trim()) return "";
4824
+ if (/^\s*(`{3,}|~{3,})/.test(trimmed)) return `
4825
+
4826
+ ${trimmed}
4827
+
4828
+ `;
4829
+ if (type in CODE_ARTIFACT_LANGUAGE) {
4830
+ const lang = CODE_ARTIFACT_LANGUAGE[type] || attrs.language || attrs.lang || "";
4831
+ return `
4832
+
4833
+ \`\`\`\`${lang}
4834
+ ${trimmed}
4835
+ \`\`\`\`
4836
+
4837
+ `;
4838
+ }
4839
+ if (looksLikeJson(trimmed)) return `
4840
+
4841
+ \`\`\`\`json
4842
+ ${JSON.stringify(JSON.parse(trimmed), null, 2)}
4843
+ \`\`\`\`
4844
+
4845
+ `;
4846
+ return `
4847
+
4848
+ ${trimmed}
4849
+
4850
+ `;
4851
+ })
4852
+ ).join("");
4853
+ }
4854
+ function looksLikeJson(body) {
4855
+ const t = body.trim();
4856
+ if (!/^[[{]/.test(t)) return false;
4857
+ try {
4858
+ JSON.parse(t);
4859
+ return true;
4860
+ } catch {
4861
+ return false;
4862
+ }
4863
+ }
4740
4864
  var LINK_URL = String.raw`((?:[^()\s]|\([^()\s]*\))+)(?:\s+"[^"]*")?`;
4741
4865
  var IMAGE_RE = new RegExp(String.raw`!\[([^\]]*)\]\(` + LINK_URL + String.raw`\)`, "g");
4742
4866
  var LINK_RE = new RegExp(String.raw`\[([^\]]+)\]\(` + LINK_URL + String.raw`\)`, "g");
@@ -5191,7 +5315,7 @@ function parseDocument(source, options = {}) {
5191
5315
  const settings = resolveSettings(fm.data, sink, options);
5192
5316
  for (const n of sink) notify(n);
5193
5317
  const body = removeThinkingContent(fm.body);
5194
- const lifted = extractMath(body);
5318
+ const lifted = extractMath(unwrapArtifactsToMarkdown(body));
5195
5319
  const root = (0, import_mdast_util_from_markdown.fromMarkdown)(prepass(lifted.text), {
5196
5320
  extensions: [(0, import_micromark_extension_gfm.gfm)()],
5197
5321
  mdastExtensions: [(0, import_mdast_util_gfm.gfmFromMarkdown)()]