@ai-matrx/print 0.8.2 → 0.8.4
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 +30 -0
- package/dist/document.cjs +235 -36
- package/dist/document.cjs.map +1 -1
- package/dist/document.d.cts +11 -1
- package/dist/document.d.ts +11 -1
- package/dist/document.js +235 -36
- package/dist/document.js.map +1 -1
- package/dist/markdown.cjs +40 -1
- package/dist/markdown.cjs.map +1 -1
- package/dist/markdown.d.cts +9 -1
- package/dist/markdown.d.ts +9 -1
- package/dist/markdown.js +40 -1
- package/dist/markdown.js.map +1 -1
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.4
|
|
4
|
+
|
|
5
|
+
- **Right-to-left scripts in PDF (Arabic, Hebrew).** Lines are put in visual
|
|
6
|
+
order by the Unicode Bidirectional Algorithm (bidi-js; words reversed at RTL
|
|
7
|
+
levels, numbers kept left-to-right, brackets mirrored), RTL paragraphs,
|
|
8
|
+
headings, list items and cells are right-aligned, and letters are shaped by
|
|
9
|
+
the font (Arabic joining forms). New fallback faces Noto Sans Arabic and
|
|
10
|
+
Noto Sans Hebrew (`fonts.arabic` / `fonts.hebrew`). HTML/EPUB blocks carry
|
|
11
|
+
`dir="auto"`; Word marks RTL paragraphs `bidi` and RTL runs `rtl`.
|
|
12
|
+
- **Korean** in PDF through Noto Sans KR (unicode-range subsets on demand,
|
|
13
|
+
same `fonts.cjk` loader; Hangul → KR, other CJK → JP).
|
|
14
|
+
- **Table cells are hyphenated** to their own column width in PDF; a word
|
|
15
|
+
that fits a line whole is never hyphenated.
|
|
16
|
+
- **APA 7 narrative citations use "and"** (`Kim and Alvarez (2021)`),
|
|
17
|
+
parenthetical keep "&"; `@key [p. 4]` gives a narrative citation with a
|
|
18
|
+
locator (pandoc syntax).
|
|
19
|
+
- Consumer action: none — refresh to latest.
|
|
20
|
+
|
|
21
|
+
## 0.8.3
|
|
22
|
+
|
|
23
|
+
- **Fixed:** an `<artifact>` envelope (how chat answers store tables and code)
|
|
24
|
+
printed as raw pipes in documents. The document pipeline now unwraps it with
|
|
25
|
+
the same rules the print skins use (`unwrapArtifactsToMarkdown`, exported
|
|
26
|
+
from `/markdown`): a table stays a table, code stays code.
|
|
27
|
+
- **Fixed (EPUB):** a `$$…$$` formula inside a sentence rendered as a `<div>`
|
|
28
|
+
inside `<p>` (epubcheck RSC-005); it is now a block-styled span.
|
|
29
|
+
- **Word:** East-Asian text carries a CJK font hint, so Word/LibreOffice pick
|
|
30
|
+
an installed Japanese/Chinese face.
|
|
31
|
+
- Consumer action: none — refresh to latest.
|
|
32
|
+
|
|
3
33
|
## 0.8.2
|
|
4
34
|
|
|
5
35
|
- **PDF font fallback covers Japanese / Chinese / Korean.** Characters the
|
package/dist/document.cjs
CHANGED
|
@@ -35,8 +35,11 @@ __export(document_exports, {
|
|
|
35
35
|
CitationEngine: () => CitationEngine,
|
|
36
36
|
DEFAULT_FOOTER: () => DEFAULT_FOOTER,
|
|
37
37
|
DOCUMENT_FORMATS: () => DOCUMENT_FORMATS,
|
|
38
|
+
NOTO_ARABIC_URL: () => NOTO_ARABIC_URL,
|
|
38
39
|
NOTO_CJK_BASE: () => NOTO_CJK_BASE,
|
|
39
40
|
NOTO_EMOJI_URL: () => NOTO_EMOJI_URL,
|
|
41
|
+
NOTO_HEBREW_URL: () => NOTO_HEBREW_URL,
|
|
42
|
+
NOTO_KR_BASE: () => NOTO_KR_BASE,
|
|
40
43
|
NOTO_SYMBOLS_URL: () => NOTO_SYMBOLS_URL,
|
|
41
44
|
PAGE_SIZES: () => PAGE_SIZES,
|
|
42
45
|
buildPdfDefinition: () => buildPdfDefinition,
|
|
@@ -78,6 +81,71 @@ __export(document_exports, {
|
|
|
78
81
|
});
|
|
79
82
|
module.exports = __toCommonJS(document_exports);
|
|
80
83
|
|
|
84
|
+
// src/document/bidi.ts
|
|
85
|
+
var import_bidi_js = __toESM(require("bidi-js"), 1);
|
|
86
|
+
var BIDI = /* @__PURE__ */ Symbol.for("ai-matrx.print.document.bidi");
|
|
87
|
+
function bidi() {
|
|
88
|
+
const g = globalThis;
|
|
89
|
+
if (!g[BIDI]) g[BIDI] = (0, import_bidi_js.default)();
|
|
90
|
+
return g[BIDI];
|
|
91
|
+
}
|
|
92
|
+
var RTL_RE = /[-ࣿיִ-﷿ﹰ-]/;
|
|
93
|
+
function isRtlParagraph(text) {
|
|
94
|
+
if (!RTL_RE.test(text)) return false;
|
|
95
|
+
return (bidi().getEmbeddingLevels(text).paragraphs[0]?.level ?? 0) % 2 === 1;
|
|
96
|
+
}
|
|
97
|
+
function visualOrder(input, rtl) {
|
|
98
|
+
const b = bidi();
|
|
99
|
+
const runs3 = input.map((r) => typeof r === "string" ? { text: r } : { ...r, text: String(r.text ?? "") });
|
|
100
|
+
const lines = [[]];
|
|
101
|
+
runs3.forEach((r, i) => {
|
|
102
|
+
for (const m of r.text.matchAll(/\n|[^\S\n]+|[\u0590-\u08FF\uFB1D-\uFDFF\uFE70-\uFEFF]+|[^\s\u0590-\u08FF\uFB1D-\uFDFF\uFE70-\uFEFF]+/g)) {
|
|
103
|
+
if (m[0] === "\n") lines.push([]);
|
|
104
|
+
else lines[lines.length - 1].push({ run: i, text: m[0] });
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
const out = [];
|
|
108
|
+
lines.forEach((line, li) => {
|
|
109
|
+
if (li > 0) out.push({ ...runs3[0], text: "\n" });
|
|
110
|
+
const text = line.map((t) => t.text).join("");
|
|
111
|
+
if (!text) return;
|
|
112
|
+
const { levels } = b.getEmbeddingLevels(text, rtl ? "rtl" : "ltr");
|
|
113
|
+
let at = 0;
|
|
114
|
+
const tokens = line.map((t) => {
|
|
115
|
+
const level = levels[at] ?? 0;
|
|
116
|
+
let s = t.text;
|
|
117
|
+
if (level % 2 === 1) {
|
|
118
|
+
s = [...s].map((ch) => b.getMirroredCharacter(ch) ?? ch).join("");
|
|
119
|
+
if (!RTL_RE.test(s) && !/^\d/.test(s)) s = [...s].reverse().join("");
|
|
120
|
+
}
|
|
121
|
+
at += t.text.length;
|
|
122
|
+
return { ...t, text: s, level };
|
|
123
|
+
});
|
|
124
|
+
const max = Math.max(...tokens.map((t) => t.level));
|
|
125
|
+
const minOdd = Math.min(...tokens.map((t) => t.level % 2 === 1 ? t.level : Infinity));
|
|
126
|
+
for (let lvl = max; lvl >= minOdd && Number.isFinite(minOdd); lvl--) {
|
|
127
|
+
for (let i = 0; i < tokens.length; ) {
|
|
128
|
+
if (tokens[i].level < lvl) {
|
|
129
|
+
i++;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
let j = i;
|
|
133
|
+
while (j < tokens.length && tokens[j].level >= lvl) j++;
|
|
134
|
+
tokens.splice(i, j - i, ...tokens.slice(i, j).reverse());
|
|
135
|
+
i = j;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
for (const t of tokens) {
|
|
139
|
+
const last = out[out.length - 1];
|
|
140
|
+
const src = runs3[t.run];
|
|
141
|
+
if (last && last.text !== "\n" && last.__run === t.run) last.text += t.text;
|
|
142
|
+
else out.push({ ...src, text: t.text, __run: t.run });
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
for (const r of out) delete r.__run;
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
|
|
81
149
|
// src/document/math.ts
|
|
82
150
|
var import_source = require("@ai-matrx/content-ir/source");
|
|
83
151
|
var MATH_OPEN = "\uE100";
|
|
@@ -2125,6 +2193,8 @@ var CitationEngine = class {
|
|
|
2125
2193
|
/** A note style: every citation becomes a numbered note. */
|
|
2126
2194
|
isNote;
|
|
2127
2195
|
numeric;
|
|
2196
|
+
/** APA 7 §8.11: narrative citations join authors with "and", not "&". */
|
|
2197
|
+
apaNarrativeAnd;
|
|
2128
2198
|
constructor(styleXml, localeXml, items, lang) {
|
|
2129
2199
|
const byId = new Map(items.map((i) => [i.id, toCslJson(i)]));
|
|
2130
2200
|
const sys = {
|
|
@@ -2135,6 +2205,7 @@ var CitationEngine = class {
|
|
|
2135
2205
|
this.engine = new Engine(sys, styleXml, cslLocaleTag(lang), true);
|
|
2136
2206
|
this.engine.setOutputFormat("html");
|
|
2137
2207
|
this.isNote = this.engine.opt.xclass === "note";
|
|
2208
|
+
this.apaNarrativeAnd = /<title>[^<]*(?:\bAPA\b|American Psychological Association)/i.test(styleXml) || /<link[^>]*href="[^"]*\/styles\/apa(?:-[\w-]*)?"[^>]*rel="(?:self|template|independent-parent)"/i.test(styleXml);
|
|
2138
2209
|
this.numeric = /citation-number/.test(styleXml.slice(styleXml.indexOf("<citation"), styleXml.indexOf("</citation>")));
|
|
2139
2210
|
}
|
|
2140
2211
|
/** Cite a group; `noteIndex` is the note number for note styles (0 = in text). */
|
|
@@ -2171,7 +2242,8 @@ var CitationEngine = class {
|
|
|
2171
2242
|
if (opts.narrative && refs.length === 1 && !this.isNote && !this.numeric) {
|
|
2172
2243
|
const author = make(items);
|
|
2173
2244
|
const rest = make(refs.map((r, i) => ({ ...items[i], "author-only": void 0, "suppress-author": true, id: r.key })));
|
|
2174
|
-
const
|
|
2245
|
+
const who = () => this.apaNarrativeAnd ? author.label.replace(/,? & /g, (m) => m.startsWith(",") ? ", and " : " and ") : author.label;
|
|
2246
|
+
const join = () => `${who()}${rest.label ? ` ${/^\(/.test(rest.label) ? rest.label : `(${rest.label})`}` : ""}`;
|
|
2175
2247
|
const listeners = [];
|
|
2176
2248
|
author.listeners.push(() => listeners.forEach((l) => l(join())));
|
|
2177
2249
|
rest.listeners.push(() => listeners.forEach((l) => l(join())));
|
|
@@ -2650,7 +2722,7 @@ function runs(nodes, c, st = {}) {
|
|
|
2650
2722
|
for (const n of nodes) {
|
|
2651
2723
|
switch (n.type) {
|
|
2652
2724
|
case "text":
|
|
2653
|
-
out.push(tr(n.value));
|
|
2725
|
+
out.push(RTL_RE.test(n.value) ? tr(n.value, { rightToLeft: true }) : tr(n.value));
|
|
2654
2726
|
break;
|
|
2655
2727
|
case "strong":
|
|
2656
2728
|
out.push(...runs(n.children, c, { ...st, bold: true }));
|
|
@@ -2802,6 +2874,10 @@ function numberingRef(c, ordered, start) {
|
|
|
2802
2874
|
}
|
|
2803
2875
|
return ref;
|
|
2804
2876
|
}
|
|
2877
|
+
function bidiPara(nodes) {
|
|
2878
|
+
const text = nodes.map((n) => "value" in n ? n.value : "").join("");
|
|
2879
|
+
return isRtlParagraph(text) ? { bidirectional: true } : {};
|
|
2880
|
+
}
|
|
2805
2881
|
function blocks(list, c, o = {}) {
|
|
2806
2882
|
const out = [];
|
|
2807
2883
|
for (const b of list) out.push(...block(b, c, o));
|
|
@@ -2826,6 +2902,7 @@ function block(b, c, o) {
|
|
|
2826
2902
|
const kids = [...b.number ? [new d.TextRun({ text: `${b.number} `, color: HEX.muted })] : [], ...runs(b.children, c)];
|
|
2827
2903
|
return [
|
|
2828
2904
|
new d.Paragraph({
|
|
2905
|
+
...bidiPara(b.children),
|
|
2829
2906
|
heading,
|
|
2830
2907
|
keepNext: true,
|
|
2831
2908
|
keepLines: true,
|
|
@@ -2835,7 +2912,7 @@ function block(b, c, o) {
|
|
|
2835
2912
|
];
|
|
2836
2913
|
}
|
|
2837
2914
|
case "paragraph":
|
|
2838
|
-
return [para(c, o, { children: runs(b.children, c), widowControl: true })];
|
|
2915
|
+
return [para(c, o, { children: runs(b.children, c), widowControl: true, ...bidiPara(b.children) })];
|
|
2839
2916
|
case "list": {
|
|
2840
2917
|
const level = o.listLevel ?? 0;
|
|
2841
2918
|
const ref = numberingRef(c, b.ordered, b.start);
|
|
@@ -2852,7 +2929,14 @@ function block(b, c, o) {
|
|
|
2852
2929
|
})
|
|
2853
2930
|
);
|
|
2854
2931
|
} else {
|
|
2855
|
-
out.push(
|
|
2932
|
+
out.push(
|
|
2933
|
+
new d.Paragraph({
|
|
2934
|
+
numbering: { reference: ref, level, instance },
|
|
2935
|
+
children: firstRuns,
|
|
2936
|
+
widowControl: true,
|
|
2937
|
+
...first?.type === "paragraph" ? bidiPara(first.children) : {}
|
|
2938
|
+
})
|
|
2939
|
+
);
|
|
2856
2940
|
}
|
|
2857
2941
|
const tail = first && first.type !== "paragraph" ? [first, ...rest] : rest;
|
|
2858
2942
|
for (const child of tail) {
|
|
@@ -2894,6 +2978,7 @@ function block(b, c, o) {
|
|
|
2894
2978
|
borders: { top: none, left: none, right: none, bottom: head ? { ...border, size: 10, color: HEX.heading } : border },
|
|
2895
2979
|
children: [
|
|
2896
2980
|
new d.Paragraph({
|
|
2981
|
+
...bidiPara(cells[i] ?? []),
|
|
2897
2982
|
alignment: alignOf(i),
|
|
2898
2983
|
keepNext: keep && !last,
|
|
2899
2984
|
children: runs(cells[i] ?? [], c, head ? { bold: true } : {})
|
|
@@ -3107,7 +3192,11 @@ async function documentToDocx(tree, options = {}) {
|
|
|
3107
3192
|
numbering: { config: [...c.numbering.entries()].map(([reference, levels2]) => ({ reference, levels: levels2 })) },
|
|
3108
3193
|
styles: {
|
|
3109
3194
|
default: {
|
|
3110
|
-
|
|
3195
|
+
// East-Asian text gets a CJK face (Word and LibreOffice substitute an installed one).
|
|
3196
|
+
document: {
|
|
3197
|
+
run: { font: { ascii: "Cambria", hAnsi: "Cambria", cs: "Arial", eastAsia: "Yu Gothic" }, size, sizeComplexScript: size, color: HEX.text },
|
|
3198
|
+
paragraph: { spacing: { after: 140, line: 300 } }
|
|
3199
|
+
}
|
|
3111
3200
|
},
|
|
3112
3201
|
paragraphStyles: [
|
|
3113
3202
|
heading("Heading1", "Heading 1", s.fontSize * 1.8, 0),
|
|
@@ -3250,7 +3339,7 @@ function renderInlineHtml(nodes, x) {
|
|
|
3250
3339
|
case "cite":
|
|
3251
3340
|
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>`;
|
|
3252
3341
|
case "math":
|
|
3253
|
-
return mathHtml(n.tex, n.display, x);
|
|
3342
|
+
return mathHtml(n.tex, n.display, x, true);
|
|
3254
3343
|
case "noteRef":
|
|
3255
3344
|
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>`;
|
|
3256
3345
|
default:
|
|
@@ -3258,10 +3347,11 @@ function renderInlineHtml(nodes, x) {
|
|
|
3258
3347
|
}
|
|
3259
3348
|
}).join("");
|
|
3260
3349
|
}
|
|
3261
|
-
function mathHtml(tex, display, x) {
|
|
3350
|
+
function mathHtml(tex, display, x, inText = false) {
|
|
3262
3351
|
const m = x.tree.math.get(mathKey(tex, display));
|
|
3263
3352
|
if (m?.mathml && !m.error) {
|
|
3264
|
-
|
|
3353
|
+
if (!display) return `<span class="doc-math">${m.mathml}</span>`;
|
|
3354
|
+
return inText ? `<span class="doc-math-display">${m.mathml}</span>` : `<div class="doc-math-display">${m.mathml}</div>`;
|
|
3265
3355
|
}
|
|
3266
3356
|
return `<code class="doc-math-tex">${(0, import_html_escape.escapeHtml)(display ? `$$${tex}$$` : `$${tex}$`)}</code>`;
|
|
3267
3357
|
}
|
|
@@ -3292,10 +3382,10 @@ function renderBlockHtml(b, x) {
|
|
|
3292
3382
|
switch (b.type) {
|
|
3293
3383
|
case "heading": {
|
|
3294
3384
|
const num = b.number ? `<span class="doc-heading-number">${b.number}</span> ` : "";
|
|
3295
|
-
return `<h${b.level} id="${(0, import_html_escape.escapeHtml)(b.id)}">${num}${renderInlineHtml(b.children, x)}</h${b.level}>`;
|
|
3385
|
+
return `<h${b.level} id="${(0, import_html_escape.escapeHtml)(b.id)}" dir="auto">${num}${renderInlineHtml(b.children, x)}</h${b.level}>`;
|
|
3296
3386
|
}
|
|
3297
3387
|
case "paragraph":
|
|
3298
|
-
return `<p>${renderInlineHtml(b.children, x)}</p>`;
|
|
3388
|
+
return `<p dir="auto">${renderInlineHtml(b.children, x)}</p>`;
|
|
3299
3389
|
case "list": {
|
|
3300
3390
|
const tag = b.ordered ? "ol" : "ul";
|
|
3301
3391
|
const start = b.ordered && b.start !== 1 ? ` start="${b.start}"` : "";
|
|
@@ -3303,12 +3393,12 @@ function renderBlockHtml(b, x) {
|
|
|
3303
3393
|
const items = b.items.map((i) => {
|
|
3304
3394
|
const box = i.checked === null ? "" : `<span class="doc-task-box${i.checked ? " is-checked" : ""}" role="img" aria-label="${i.checked ? "done" : "not done"}"></span>`;
|
|
3305
3395
|
const inner = i.children.length === 1 && i.children[0]?.type === "paragraph" ? renderInlineHtml(i.children[0].children, x) : renderBlocksHtml(i.children, x);
|
|
3306
|
-
return `<li${i.checked !== null ? ' class="doc-task"' : ""}>${box}${inner}</li>`;
|
|
3396
|
+
return `<li dir="auto"${i.checked !== null ? ' class="doc-task"' : ""}>${box}${inner}</li>`;
|
|
3307
3397
|
}).join("");
|
|
3308
3398
|
return `<${tag}${start}${task ? ' class="doc-task-list"' : ""}>${items}</${tag}>`;
|
|
3309
3399
|
}
|
|
3310
3400
|
case "blockquote":
|
|
3311
|
-
return `<blockquote>${renderBlocksHtml(b.children, x)}</blockquote>`;
|
|
3401
|
+
return `<blockquote dir="auto">${renderBlocksHtml(b.children, x)}</blockquote>`;
|
|
3312
3402
|
case "code":
|
|
3313
3403
|
return `<pre class="doc-code"${b.lang ? ` data-language="${(0, import_html_escape.escapeHtml)(b.lang)}"` : ""}><code>${(0, import_html_escape.escapeHtml)(
|
|
3314
3404
|
b.value
|
|
@@ -3318,8 +3408,8 @@ function renderBlockHtml(b, x) {
|
|
|
3318
3408
|
const a = b.align[i];
|
|
3319
3409
|
return a ? ` style="text-align:${a}"` : "";
|
|
3320
3410
|
};
|
|
3321
|
-
const head = `<thead><tr>${b.header.map((c, i) => `<th${al(i)}>${renderInlineHtml(c, x)}</th>`).join("")}</tr></thead>`;
|
|
3322
|
-
const body = `<tbody>${b.rows.map((r) => `<tr>${r.map((c, i) => `<td${al(i)}>${renderInlineHtml(c, x)}</td>`).join("")}</tr>`).join("")}</tbody>`;
|
|
3411
|
+
const head = `<thead><tr>${b.header.map((c, i) => `<th dir="auto"${al(i)}>${renderInlineHtml(c, x)}</th>`).join("")}</tr></thead>`;
|
|
3412
|
+
const body = `<tbody>${b.rows.map((r) => `<tr>${r.map((c, i) => `<td dir="auto"${al(i)}>${renderInlineHtml(c, x)}</td>`).join("")}</tr>`).join("")}</tbody>`;
|
|
3323
3413
|
const id = b.caption?.id ? ` id="tbl:${(0, import_html_escape.escapeHtml)(b.caption.id)}"` : "";
|
|
3324
3414
|
return `<div class="doc-table-wrap"${id}><table>${b.caption ? captionHtml(b.caption, "caption", x) : ""}${head}${body}</table></div>`;
|
|
3325
3415
|
}
|
|
@@ -3423,7 +3513,7 @@ table caption{caption-side:top;text-align:left;margin-bottom:4pt;}
|
|
|
3423
3513
|
.doc-figure figcaption{margin-top:4pt;}
|
|
3424
3514
|
.doc-image-missing{border:1px dashed var(--matrx-print-rule,#d1d5db);padding:16pt;color:var(--matrx-print-muted,#6b7280);}
|
|
3425
3515
|
.doc-inline-img{max-height:1.2em;vertical-align:middle;}
|
|
3426
|
-
.doc-math-display{margin:0.6em 0 0.9em;text-align:center;break-inside:avoid;overflow-x:auto;}
|
|
3516
|
+
.doc-math-display{display:block;margin:0.6em 0 0.9em;text-align:center;break-inside:avoid;overflow-x:auto;}
|
|
3427
3517
|
.doc-math-display math{font-size:1.15em;}
|
|
3428
3518
|
.doc-math-tex{color:var(--matrx-print-danger,#b91c1c);}
|
|
3429
3519
|
.doc-task-list{list-style:none;padding-left:0.2em;}
|
|
@@ -3502,7 +3592,7 @@ blockquote{margin:0 0 0.8em;padding-left:1em;border-left:3px solid #d1d5db;color
|
|
|
3502
3592
|
.doc-cover-logo{max-height:5em;}
|
|
3503
3593
|
.doc-cover-title{font-size:2em;}
|
|
3504
3594
|
.matrx-page-break{page-break-after:always;break-after:page;}
|
|
3505
|
-
.doc-math-display{margin:0.6em 0;text-align:center;}
|
|
3595
|
+
.doc-math-display{display:block;margin:0.6em 0;text-align:center;}
|
|
3506
3596
|
`.trim();
|
|
3507
3597
|
function extOf(mime) {
|
|
3508
3598
|
return mime === "image/png" ? "png" : mime === "image/gif" ? "gif" : "jpg";
|
|
@@ -3789,7 +3879,7 @@ function breakRuns(input, width, t) {
|
|
|
3789
3879
|
for (let guard = 0; guard < 50 && atoms.length; guard++) {
|
|
3790
3880
|
const gw = atoms.reduce((n, a) => n + w(a), 0);
|
|
3791
3881
|
const sw = pendingSpace && line > 0 ? w(pendingSpace) : 0;
|
|
3792
|
-
if (line + sw + gw <= W || line === 0 && atoms.length !== 1) {
|
|
3882
|
+
if (line + sw + gw <= W || line === 0 && (atoms.length !== 1 || gw <= width)) {
|
|
3793
3883
|
if (sw && pendingSpace) out.push(pendingSpace);
|
|
3794
3884
|
out.push(...atoms);
|
|
3795
3885
|
line += sw + gw;
|
|
@@ -3913,11 +4003,14 @@ function withFallbackFonts(nodes, c) {
|
|
|
3913
4003
|
const fb = fallbackFor(cp);
|
|
3914
4004
|
if (fb) {
|
|
3915
4005
|
flush();
|
|
3916
|
-
|
|
4006
|
+
const prev = out[out.length - 1];
|
|
4007
|
+
if (prev && typeof prev === "object" && prev.font === fb && prev.__fb) prev.text = `${prev.text}${ch}`;
|
|
4008
|
+
else out.push({ ...n, text: ch, font: fb, bold: false, italics: false, __fb: true });
|
|
3917
4009
|
} else dropped = true;
|
|
3918
4010
|
}
|
|
3919
4011
|
flush();
|
|
3920
4012
|
}
|
|
4013
|
+
for (const o of out) if (typeof o === "object") delete o.__fb;
|
|
3921
4014
|
if (dropped && !c.glyphs.announced) {
|
|
3922
4015
|
c.glyphs.announced = true;
|
|
3923
4016
|
c.tree.notices.push({
|
|
@@ -3944,8 +4037,13 @@ function drawable(text, c) {
|
|
|
3944
4037
|
}
|
|
3945
4038
|
function bodyRuns(nodes, c, width = c.width) {
|
|
3946
4039
|
const r = runs2(nodes, c);
|
|
3947
|
-
|
|
4040
|
+
const text = plain(nodes);
|
|
4041
|
+
if (!RTL_RE.test(text)) return c.typo ? breakRuns(r, width, c.typo) : r;
|
|
4042
|
+
const typo = c.typo ?? (c.faces ? { split: (w) => [w], measure: c.faces.measure } : void 0);
|
|
4043
|
+
const lines = typo ? breakRuns(r, width, typo) : r;
|
|
4044
|
+
return visualOrder(lines, isRtlParagraph(text));
|
|
3948
4045
|
}
|
|
4046
|
+
var alignFor = (nodes) => isRtlParagraph(plain(nodes)) ? { alignment: "right" } : {};
|
|
3949
4047
|
function runs2(nodes, c, st = {}) {
|
|
3950
4048
|
const out = [];
|
|
3951
4049
|
const base = (raw) => {
|
|
@@ -4137,6 +4235,7 @@ function estimateLines(text, c, size = c.s.fontSize) {
|
|
|
4137
4235
|
function columnWidths(b, n, size, c) {
|
|
4138
4236
|
const measure = (text, bold) => c.faces ? c.faces.measure(text, { bold, fontSize: size }) : text.length * size * 0.5;
|
|
4139
4237
|
const pad = 10;
|
|
4238
|
+
const slack = 1;
|
|
4140
4239
|
const min = new Array(n).fill(0);
|
|
4141
4240
|
const max = new Array(n).fill(0);
|
|
4142
4241
|
const rows = [[b.header, true], ...b.rows.map((r) => [r, false])];
|
|
@@ -4144,8 +4243,8 @@ function columnWidths(b, n, size, c) {
|
|
|
4144
4243
|
for (let i = 0; i < n; i++) {
|
|
4145
4244
|
const t = plain(r[i] ?? []);
|
|
4146
4245
|
const longest = t.split(/\s+/).reduce((m, w) => Math.max(m, measure(w, head)), 0);
|
|
4147
|
-
min[i] = Math.max(min[i], Math.min(longest, c.width * 0.35) + pad);
|
|
4148
|
-
max[i] = Math.max(max[i], measure(t, head) + pad);
|
|
4246
|
+
min[i] = Math.max(min[i], Math.min(longest * slack, c.width * 0.35) + pad);
|
|
4247
|
+
max[i] = Math.max(max[i], measure(t, head) * slack + pad);
|
|
4149
4248
|
}
|
|
4150
4249
|
}
|
|
4151
4250
|
const avail = c.width;
|
|
@@ -4209,8 +4308,11 @@ function block2(b, c) {
|
|
|
4209
4308
|
switch (b.type) {
|
|
4210
4309
|
case "heading": {
|
|
4211
4310
|
const rel = Math.min(6, b.level - c.minLevel + 1);
|
|
4311
|
+
const rtlHeading = RTL_RE.test(b.plain);
|
|
4312
|
+
const headRuns = rtlHeading ? visualOrder(runs2(b.children, c), isRtlParagraph(b.plain)) : runs2(b.children, c);
|
|
4212
4313
|
const node = {
|
|
4213
|
-
|
|
4314
|
+
...rtlHeading ? alignFor(b.children) : {},
|
|
4315
|
+
text: [...b.number ? [{ text: `${b.number} `, color: COLOR.muted }] : [], ...headRuns],
|
|
4214
4316
|
style: `h${Math.min(6, b.level)}`,
|
|
4215
4317
|
headlineLevel: b.level,
|
|
4216
4318
|
id: b.id
|
|
@@ -4224,12 +4326,12 @@ function block2(b, c) {
|
|
|
4224
4326
|
}
|
|
4225
4327
|
case "paragraph": {
|
|
4226
4328
|
const text = bodyRuns(b.children, c);
|
|
4227
|
-
const p = { text, style: "p" };
|
|
4329
|
+
const p = { text, style: "p", ...alignFor(b.children) };
|
|
4228
4330
|
return estimateLines(plain(b.children), c) <= 5 ? { stack: [p], unbreakable: true } : p;
|
|
4229
4331
|
}
|
|
4230
4332
|
case "list": {
|
|
4231
4333
|
const items = b.items.map((it) => {
|
|
4232
|
-
const content = it.children.length === 1 && it.children[0]?.type === "paragraph" ? [{ text: bodyRuns(it.children[0].children, c, c.width - 20 - (it.checked !== null ? 8 : 0)), margin: [0, 0, 0, 2] }] : blocks2(it.children, { ...c, width: c.width - 18 });
|
|
4334
|
+
const content = it.children.length === 1 && it.children[0]?.type === "paragraph" ? [{ text: bodyRuns(it.children[0].children, c, c.width - 20 - (it.checked !== null ? 8 : 0)), margin: [0, 0, 0, 2], ...alignFor(it.children[0].children) }] : blocks2(it.children, { ...c, width: c.width - 18 });
|
|
4233
4335
|
if (it.checked !== null) return { ...taskItem(it.checked, content, c), listType: "none" };
|
|
4234
4336
|
return content.length === 1 ? content[0] : { stack: content };
|
|
4235
4337
|
});
|
|
@@ -4282,15 +4384,24 @@ function block2(b, c) {
|
|
|
4282
4384
|
case "table": {
|
|
4283
4385
|
const n = Math.max(b.header.length, ...b.rows.map((r) => r.length), 1);
|
|
4284
4386
|
const size = c.s.fontSize * 0.92;
|
|
4387
|
+
const widths = columnWidths(b, n, size, c);
|
|
4388
|
+
const cellRuns = (inl, i, head) => {
|
|
4389
|
+
const r = runs2(inl, c).map((x) => typeof x === "object" ? { ...x, fontSize: x.fontSize ?? size, ...head ? { bold: true } : {} } : x);
|
|
4390
|
+
const w = (widths[i] ?? c.width) - 10;
|
|
4391
|
+
if (RTL_RE.test(plain(inl))) {
|
|
4392
|
+
const typo = c.typo ?? (c.faces ? { split: (x) => [x], measure: c.faces.measure } : void 0);
|
|
4393
|
+
return visualOrder(typo ? breakRuns(r, w, typo) : r, isRtlParagraph(plain(inl)));
|
|
4394
|
+
}
|
|
4395
|
+
return c.typo && !head ? breakRuns(r, w, c.typo) : r;
|
|
4396
|
+
};
|
|
4285
4397
|
const cell = (inl, i, head) => ({
|
|
4286
|
-
text:
|
|
4398
|
+
text: cellRuns(inl ?? [], i, head),
|
|
4287
4399
|
bold: head,
|
|
4288
|
-
alignment: b.align[i] ?? "left",
|
|
4400
|
+
alignment: b.align[i] ?? (isRtlParagraph(plain(inl ?? [])) ? "right" : "left"),
|
|
4289
4401
|
fontSize: size,
|
|
4290
4402
|
color: head ? COLOR.heading : COLOR.text
|
|
4291
4403
|
});
|
|
4292
4404
|
const row = (r, head) => Array.from({ length: n }, (_, i) => cell(r[i], i, head));
|
|
4293
|
-
const widths = columnWidths(b, n, size, c);
|
|
4294
4405
|
const lineH = size * 1.3;
|
|
4295
4406
|
const pageH = c.pageHeight;
|
|
4296
4407
|
const rowHeight = (r) => Math.max(...r.map((inl, i) => estimateLines(plain(inl), { ...c, width: (widths[i] ?? c.width) - 10 }, size) * lineH)) + 8;
|
|
@@ -4536,12 +4647,16 @@ async function loadTypography(tree, loaders = {}) {
|
|
|
4536
4647
|
const base = await loadFaces(vfs, s.fontSize);
|
|
4537
4648
|
let needEmoji = false;
|
|
4538
4649
|
let needSymbols = false;
|
|
4650
|
+
let needArabic = false;
|
|
4651
|
+
let needHebrew = false;
|
|
4539
4652
|
const cjk = /* @__PURE__ */ new Set();
|
|
4540
4653
|
for (const ch of documentText(tree)) {
|
|
4541
4654
|
const cp = ch.codePointAt(0);
|
|
4542
4655
|
if (cp < 32 || cp === 8205 || cp === 65039 || base.body(cp)) continue;
|
|
4543
4656
|
if (EMOJI_CP.test(ch)) needEmoji = true;
|
|
4544
|
-
else if (isCjk(cp)) cjk.add(cp);
|
|
4657
|
+
else if (isCjk(cp) || isHangul(cp)) cjk.add(cp);
|
|
4658
|
+
else if (isArabic(cp)) needArabic = true;
|
|
4659
|
+
else if (isHebrew(cp)) needHebrew = true;
|
|
4545
4660
|
else needSymbols = true;
|
|
4546
4661
|
}
|
|
4547
4662
|
const fallbacks = [];
|
|
@@ -4553,6 +4668,14 @@ async function loadTypography(tree, loaders = {}) {
|
|
|
4553
4668
|
const b = await registerFace("NotoSymbols", "NotoSansMath.woff", loaders.symbols ?? defaultSymbolFontLoader, tree, "symbols");
|
|
4554
4669
|
if (b) fallbacks.push({ font: "NotoSymbols", bytes: b });
|
|
4555
4670
|
}
|
|
4671
|
+
if (needArabic) {
|
|
4672
|
+
const b = await registerFace("NotoArabic", "NotoSansArabic.woff", loaders.arabic ?? urlLoader(NOTO_ARABIC_URL), tree, "Arabic");
|
|
4673
|
+
if (b) fallbacks.push({ font: "NotoArabic", bytes: b });
|
|
4674
|
+
}
|
|
4675
|
+
if (needHebrew) {
|
|
4676
|
+
const b = await registerFace("NotoHebrew", "NotoSansHebrew.woff", loaders.hebrew ?? urlLoader(NOTO_HEBREW_URL), tree, "Hebrew");
|
|
4677
|
+
if (b) fallbacks.push({ font: "NotoHebrew", bytes: b });
|
|
4678
|
+
}
|
|
4556
4679
|
if (cjk.size) {
|
|
4557
4680
|
let subsets = [];
|
|
4558
4681
|
try {
|
|
@@ -4568,7 +4691,7 @@ async function loadTypography(tree, loaders = {}) {
|
|
|
4568
4691
|
if (!subsets.length) {
|
|
4569
4692
|
tree.notices.push({
|
|
4570
4693
|
code: "glyph-substituted",
|
|
4571
|
-
message: "The Japanese /
|
|
4694
|
+
message: "The Chinese / Japanese / Korean font could not be loaded, so those characters were left out of the PDF (Word, EPUB and HTML keep them).",
|
|
4572
4695
|
remedy: "Check the internet connection (the font comes from cdn.jsdelivr.net) and export again."
|
|
4573
4696
|
});
|
|
4574
4697
|
}
|
|
@@ -4596,6 +4719,12 @@ var urlLoader = (url) => async () => {
|
|
|
4596
4719
|
var defaultEmojiFontLoader = urlLoader(NOTO_EMOJI_URL);
|
|
4597
4720
|
var defaultSymbolFontLoader = urlLoader(NOTO_SYMBOLS_URL);
|
|
4598
4721
|
var NOTO_CJK_BASE = "https://cdn.jsdelivr.net/npm/@fontsource/noto-sans-jp@5.3.0";
|
|
4722
|
+
var NOTO_KR_BASE = "https://cdn.jsdelivr.net/npm/@fontsource/noto-sans-kr@5.3.0";
|
|
4723
|
+
var NOTO_ARABIC_URL = "https://cdn.jsdelivr.net/npm/@fontsource/noto-sans-arabic@5.3.0/files/noto-sans-arabic-arabic-400-normal.woff";
|
|
4724
|
+
var NOTO_HEBREW_URL = "https://cdn.jsdelivr.net/npm/@fontsource/noto-sans-hebrew@5.3.0/files/noto-sans-hebrew-hebrew-400-normal.woff";
|
|
4725
|
+
var isHangul = (cp) => cp >= 44032 && cp <= 55215 || cp >= 4352 && cp <= 4607 || cp >= 12592 && cp <= 12687;
|
|
4726
|
+
var isArabic = (cp) => cp >= 1536 && cp <= 1791 || cp >= 1872 && cp <= 1919 || cp >= 2208 && cp <= 2303 || cp >= 64336 && cp <= 65023 || cp >= 65136 && cp <= 65279;
|
|
4727
|
+
var isHebrew = (cp) => cp >= 1424 && cp <= 1535 || cp >= 64285 && cp <= 64335;
|
|
4599
4728
|
function parseUnicodeRangeCss(css) {
|
|
4600
4729
|
const out = [];
|
|
4601
4730
|
for (const block3 of css.split("@font-face").slice(1)) {
|
|
@@ -4611,19 +4740,25 @@ function parseUnicodeRangeCss(css) {
|
|
|
4611
4740
|
}
|
|
4612
4741
|
return out;
|
|
4613
4742
|
}
|
|
4614
|
-
|
|
4743
|
+
async function unicodeRangeFaces(base, cps) {
|
|
4615
4744
|
if (typeof fetch !== "function" || cps.length === 0) return [];
|
|
4616
|
-
const res = await fetch(`${
|
|
4745
|
+
const res = await fetch(`${base}/400.css`);
|
|
4617
4746
|
if (!res.ok) return [];
|
|
4618
4747
|
const faces = parseUnicodeRangeCss(await res.text()).filter((f) => cps.some((cp) => f.ranges.some(([a, b]) => cp >= a && cp <= b)));
|
|
4619
4748
|
const out = [];
|
|
4620
4749
|
await Promise.all(
|
|
4621
4750
|
faces.map(async (f) => {
|
|
4622
|
-
const r = await fetch(`${
|
|
4751
|
+
const r = await fetch(`${base}/files/${f.file}.woff`);
|
|
4623
4752
|
if (r.ok) out.push({ name: f.file, bytes: new Uint8Array(await r.arrayBuffer()) });
|
|
4624
4753
|
})
|
|
4625
4754
|
);
|
|
4626
4755
|
return out;
|
|
4756
|
+
}
|
|
4757
|
+
var defaultCjkFontLoader = async (cps) => {
|
|
4758
|
+
const kr = cps.filter(isHangul);
|
|
4759
|
+
const jp = cps.filter((cp) => !isHangul(cp));
|
|
4760
|
+
const [a, b] = await Promise.all([unicodeRangeFaces(NOTO_KR_BASE, kr), unicodeRangeFaces(NOTO_CJK_BASE, jp)]);
|
|
4761
|
+
return [...a, ...b];
|
|
4627
4762
|
};
|
|
4628
4763
|
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;
|
|
4629
4764
|
var EMOJI_CP = /\p{Extended_Pictographic}|\p{Regional_Indicator}|\u20E3/u;
|
|
@@ -4792,6 +4927,70 @@ function removeThinkingContent(content) {
|
|
|
4792
4927
|
return result.trim();
|
|
4793
4928
|
}
|
|
4794
4929
|
var PAGE_BREAK_HTML = `<div class="${PAGE_BREAK_CLASS}" role="separator" aria-label="${PAGE_BREAK_LABEL}" data-label="${PAGE_BREAK_LABEL}"></div>`;
|
|
4930
|
+
var CODE_ARTIFACT_LANGUAGE = {
|
|
4931
|
+
html: "html",
|
|
4932
|
+
react: "tsx",
|
|
4933
|
+
svg: "svg",
|
|
4934
|
+
mermaid: "mermaid",
|
|
4935
|
+
diff: "diff",
|
|
4936
|
+
iframe: "html",
|
|
4937
|
+
code: ""
|
|
4938
|
+
};
|
|
4939
|
+
function parseTagAttributes(tag) {
|
|
4940
|
+
const attrs = {};
|
|
4941
|
+
for (const m of tag.matchAll(/([a-zA-Z_][\w-]*)\s*=\s*(?:"([^"]*)"|'([^']*)')/g)) {
|
|
4942
|
+
attrs[m[1].toLowerCase()] = m[2] ?? m[3] ?? "";
|
|
4943
|
+
}
|
|
4944
|
+
return attrs;
|
|
4945
|
+
}
|
|
4946
|
+
function unwrapArtifactsToMarkdown(markdown) {
|
|
4947
|
+
if (!/<artifact\b/i.test(markdown)) return markdown;
|
|
4948
|
+
return markdown.split(/(^[ \t]*(?:`{3,}|~{3,})[\s\S]*?^[ \t]*(?:`{3,}|~{3,})[ \t]*$)/m).map(
|
|
4949
|
+
(part, i) => i % 2 === 1 ? part : part.replace(/<artifact\b((?:[^>"']|"[^"]*"|'[^']*')*?)>([\s\S]*?)<\/artifact>/gi, (_m, tag, body) => {
|
|
4950
|
+
const attrs = parseTagAttributes(tag);
|
|
4951
|
+
const type = (attrs.type ?? "").toLowerCase();
|
|
4952
|
+
const trimmed = body.replace(/^\n+|\n+$/g, "");
|
|
4953
|
+
if (!trimmed.trim()) return "";
|
|
4954
|
+
if (/^\s*(`{3,}|~{3,})/.test(trimmed)) return `
|
|
4955
|
+
|
|
4956
|
+
${trimmed}
|
|
4957
|
+
|
|
4958
|
+
`;
|
|
4959
|
+
if (type in CODE_ARTIFACT_LANGUAGE) {
|
|
4960
|
+
const lang = CODE_ARTIFACT_LANGUAGE[type] || attrs.language || attrs.lang || "";
|
|
4961
|
+
return `
|
|
4962
|
+
|
|
4963
|
+
\`\`\`\`${lang}
|
|
4964
|
+
${trimmed}
|
|
4965
|
+
\`\`\`\`
|
|
4966
|
+
|
|
4967
|
+
`;
|
|
4968
|
+
}
|
|
4969
|
+
if (looksLikeJson(trimmed)) return `
|
|
4970
|
+
|
|
4971
|
+
\`\`\`\`json
|
|
4972
|
+
${JSON.stringify(JSON.parse(trimmed), null, 2)}
|
|
4973
|
+
\`\`\`\`
|
|
4974
|
+
|
|
4975
|
+
`;
|
|
4976
|
+
return `
|
|
4977
|
+
|
|
4978
|
+
${trimmed}
|
|
4979
|
+
|
|
4980
|
+
`;
|
|
4981
|
+
})
|
|
4982
|
+
).join("");
|
|
4983
|
+
}
|
|
4984
|
+
function looksLikeJson(body) {
|
|
4985
|
+
const t = body.trim();
|
|
4986
|
+
if (!/^[[{]/.test(t)) return false;
|
|
4987
|
+
try {
|
|
4988
|
+
JSON.parse(t);
|
|
4989
|
+
return true;
|
|
4990
|
+
} catch {
|
|
4991
|
+
return false;
|
|
4992
|
+
}
|
|
4993
|
+
}
|
|
4795
4994
|
var LINK_URL = String.raw`((?:[^()\s]|\([^()\s]*\))+)(?:\s+"[^"]*")?`;
|
|
4796
4995
|
var IMAGE_RE = new RegExp(String.raw`!\[([^\]]*)\]\(` + LINK_URL + String.raw`\)`, "g");
|
|
4797
4996
|
var LINK_RE = new RegExp(String.raw`\[([^\]]+)\]\(` + LINK_URL + String.raw`\)`, "g");
|
|
@@ -4878,7 +5077,7 @@ function uniqueId(ctx, base) {
|
|
|
4878
5077
|
return id;
|
|
4879
5078
|
}
|
|
4880
5079
|
var CITE_GROUP_RE = /\[(-?@[^[\]]+)\]/g;
|
|
4881
|
-
var BARE_RE = /(^|[^\w@.\\])@((?:fig|tbl|sec|eq):[\w-]+|[A-Za-z0-9_](?:[\w:.#$%&+?<>~/-]*\w)?)
|
|
5080
|
+
var BARE_RE = /(^|[^\w@.\\])@((?:fig|tbl|sec|eq):[\w-]+|[A-Za-z0-9_](?:[\w:.#$%&+?<>~/-]*\w)?)(?: \[([^\[\]@]+)\])?/g;
|
|
4882
5081
|
function scanText(value, ctx) {
|
|
4883
5082
|
const out = [];
|
|
4884
5083
|
const pushText = (s) => {
|
|
@@ -4899,8 +5098,8 @@ function scanText(value, ctx) {
|
|
|
4899
5098
|
last2 = start + key.length + 1;
|
|
4900
5099
|
} else if (ctx.refs.has(key)) {
|
|
4901
5100
|
pushText(s.slice(last2, start));
|
|
4902
|
-
out.push(cite([{ key, suppressAuthor: false }], ctx, true));
|
|
4903
|
-
last2 = start + key.length + 1;
|
|
5101
|
+
out.push(cite([{ key, locator: m[3]?.trim(), suppressAuthor: false }], ctx, true));
|
|
5102
|
+
last2 = start + key.length + 1 + (m[3] !== void 0 ? m[3].length + 3 : 0);
|
|
4904
5103
|
}
|
|
4905
5104
|
}
|
|
4906
5105
|
pushText(s.slice(last2));
|
|
@@ -5246,7 +5445,7 @@ function parseDocument(source, options = {}) {
|
|
|
5246
5445
|
const settings = resolveSettings(fm.data, sink, options);
|
|
5247
5446
|
for (const n of sink) notify(n);
|
|
5248
5447
|
const body = removeThinkingContent(fm.body);
|
|
5249
|
-
const lifted = extractMath(body);
|
|
5448
|
+
const lifted = extractMath(unwrapArtifactsToMarkdown(body));
|
|
5250
5449
|
const root = (0, import_mdast_util_from_markdown.fromMarkdown)(prepass(lifted.text), {
|
|
5251
5450
|
extensions: [(0, import_micromark_extension_gfm.gfm)()],
|
|
5252
5451
|
mdastExtensions: [(0, import_mdast_util_gfm.gfmFromMarkdown)()]
|