@ai-matrx/print 0.8.3 → 0.8.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/CHANGELOG.md +26 -0
- package/dist/document.cjs +202 -97
- package/dist/document.cjs.map +1 -1
- package/dist/document.d.cts +21 -2
- package/dist/document.d.ts +21 -2
- package/dist/document.js +203 -98
- package/dist/document.js.map +1 -1
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.5
|
|
4
|
+
|
|
5
|
+
- **Word tables use content-aware column widths** (the same rule as the PDF,
|
|
6
|
+
now shared in `src/document/layout.ts`; fixed table layout). A 10-column
|
|
7
|
+
landscape table no longer splits its width equally.
|
|
8
|
+
- A `$$...$$` placeholder in prose prints as text (via content-ir's rule).
|
|
9
|
+
- Consumer action: none — refresh to latest.
|
|
10
|
+
|
|
11
|
+
## 0.8.4
|
|
12
|
+
|
|
13
|
+
- **Right-to-left scripts in PDF (Arabic, Hebrew).** Lines are put in visual
|
|
14
|
+
order by the Unicode Bidirectional Algorithm (bidi-js; words reversed at RTL
|
|
15
|
+
levels, numbers kept left-to-right, brackets mirrored), RTL paragraphs,
|
|
16
|
+
headings, list items and cells are right-aligned, and letters are shaped by
|
|
17
|
+
the font (Arabic joining forms). New fallback faces Noto Sans Arabic and
|
|
18
|
+
Noto Sans Hebrew (`fonts.arabic` / `fonts.hebrew`). HTML/EPUB blocks carry
|
|
19
|
+
`dir="auto"`; Word marks RTL paragraphs `bidi` and RTL runs `rtl`.
|
|
20
|
+
- **Korean** in PDF through Noto Sans KR (unicode-range subsets on demand,
|
|
21
|
+
same `fonts.cjk` loader; Hangul → KR, other CJK → JP).
|
|
22
|
+
- **Table cells are hyphenated** to their own column width in PDF; a word
|
|
23
|
+
that fits a line whole is never hyphenated.
|
|
24
|
+
- **APA 7 narrative citations use "and"** (`Kim and Alvarez (2021)`),
|
|
25
|
+
parenthetical keep "&"; `@key [p. 4]` gives a narrative citation with a
|
|
26
|
+
locator (pandoc syntax).
|
|
27
|
+
- Consumer action: none — refresh to latest.
|
|
28
|
+
|
|
3
29
|
## 0.8.3
|
|
4
30
|
|
|
5
31
|
- **Fixed:** an `<artifact>` envelope (how chat answers store tables and code)
|
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,65 +81,119 @@ __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
|
+
|
|
149
|
+
// src/document/layout.ts
|
|
150
|
+
var plainInline = (nodes) => nodes.map((n) => "value" in n ? n.value : "label" in n ? n.label : "tex" in n ? n.tex : "children" in n ? plainInline(n.children) : "").join("");
|
|
151
|
+
function contentColumnWidths(b, n, avail, measure, pad = 10) {
|
|
152
|
+
const min = new Array(n).fill(0);
|
|
153
|
+
const max = new Array(n).fill(0);
|
|
154
|
+
const rows = [[b.header, true], ...b.rows.map((r) => [r, false])];
|
|
155
|
+
for (const [r, head] of rows) {
|
|
156
|
+
for (let i = 0; i < n; i++) {
|
|
157
|
+
const t = plainInline(r[i] ?? []);
|
|
158
|
+
const longest = t.split(/\s+/).reduce((m, w) => Math.max(m, measure(w, head)), 0);
|
|
159
|
+
min[i] = Math.max(min[i], Math.min(longest, avail * 0.35) + pad);
|
|
160
|
+
max[i] = Math.max(max[i], measure(t, head) + pad);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const sumMax = max.reduce((a, x) => a + x, 0);
|
|
164
|
+
const sumMin = min.reduce((a, x) => a + x, 0);
|
|
165
|
+
if (sumMax <= avail) return max.map((m) => m + (avail - sumMax) / n);
|
|
166
|
+
if (sumMin >= avail) return min.map((m) => m / sumMin * avail);
|
|
167
|
+
const extra = avail - sumMin;
|
|
168
|
+
return min.map((m, i) => m + (max[i] - m) * (extra / (sumMax - sumMin)));
|
|
169
|
+
}
|
|
170
|
+
|
|
81
171
|
// src/document/math.ts
|
|
82
172
|
var import_source = require("@ai-matrx/content-ir/source");
|
|
83
173
|
var MATH_OPEN = "\uE100";
|
|
84
174
|
var MATH_CLOSE = "\uE101";
|
|
85
175
|
var MATH_TOKEN_RE = /\uE100(\d+)\uE101/g;
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (m[0].length === 0) re.lastIndex++;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return ranges.sort((a, b) => a[0] - b[0]);
|
|
96
|
-
}
|
|
176
|
+
var DELIMITERS = {
|
|
177
|
+
$$: { open: 2, close: 2, display: true },
|
|
178
|
+
"\\[": { open: 2, close: 2, display: true },
|
|
179
|
+
$: { open: 1, close: 1, display: false },
|
|
180
|
+
"\\(": { open: 2, close: 2, display: false }
|
|
181
|
+
};
|
|
97
182
|
function extractMath(body) {
|
|
98
183
|
const spans = [];
|
|
99
184
|
if (!/[$\\]/.test(body)) return { text: body, spans };
|
|
100
|
-
const
|
|
101
|
-
const inCode = (i2) => code.some(([a, b]) => i2 >= a && i2 < b);
|
|
102
|
-
const display = (0, import_source.pairDisplayMath)(body);
|
|
185
|
+
const islands = (0, import_source.listIslands)((0, import_source.tokenizeSource)(body)).filter((isl) => isl.complete && (isl.islandType === "math_block" || isl.islandType === "math_inline")).sort((a, b) => a.start - b.start);
|
|
103
186
|
let out = "";
|
|
104
|
-
let
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const close = display.get(i);
|
|
113
|
-
if (close !== void 0) {
|
|
114
|
-
out += token(body.slice(i + 2, close), true);
|
|
115
|
-
i = close + 2;
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
if (body[i + 1] !== "$" && body[i - 1] !== "$" && body[i - 1] !== "\\") {
|
|
119
|
-
const end = (0, import_source.singleDollarMathEnd)(body, i);
|
|
120
|
-
if (end > 0) {
|
|
121
|
-
out += token(body.slice(i + 1, end - 1), false);
|
|
122
|
-
i = end;
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
if (ch === "\\" && !inCode(i) && (body[i + 1] === "[" || body[i + 1] === "(")) {
|
|
128
|
-
const closer = body[i + 1] === "[" ? "\\]" : "\\)";
|
|
129
|
-
const end = body.indexOf(closer, i + 2);
|
|
130
|
-
if (end > i + 2 && end - i < 2e3 && (body[i + 1] === "[" || !body.slice(i, end).includes("\n\n"))) {
|
|
131
|
-
out += token(body.slice(i + 2, end), body[i + 1] === "[");
|
|
132
|
-
i = end + 2;
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
out += ch;
|
|
137
|
-
i++;
|
|
187
|
+
let at = 0;
|
|
188
|
+
for (const isl of islands) {
|
|
189
|
+
const d = DELIMITERS[String(isl.meta.delimiter ?? "")];
|
|
190
|
+
if (!d || isl.start < at) continue;
|
|
191
|
+
out += body.slice(at, isl.start);
|
|
192
|
+
spans.push({ tex: body.slice(isl.start + d.open, isl.end - d.close).trim(), display: d.display });
|
|
193
|
+
out += `${MATH_OPEN}${spans.length - 1}${MATH_CLOSE}`;
|
|
194
|
+
at = isl.end;
|
|
138
195
|
}
|
|
139
|
-
return { text: out, spans };
|
|
196
|
+
return { text: out + body.slice(at), spans };
|
|
140
197
|
}
|
|
141
198
|
var mathKey = (tex, display) => `${display ? "D" : "I"}:${tex}`;
|
|
142
199
|
var MATHJAX = /* @__PURE__ */ Symbol.for("ai-matrx.print.document.mathjax");
|
|
@@ -2125,6 +2182,8 @@ var CitationEngine = class {
|
|
|
2125
2182
|
/** A note style: every citation becomes a numbered note. */
|
|
2126
2183
|
isNote;
|
|
2127
2184
|
numeric;
|
|
2185
|
+
/** APA 7 §8.11: narrative citations join authors with "and", not "&". */
|
|
2186
|
+
apaNarrativeAnd;
|
|
2128
2187
|
constructor(styleXml, localeXml, items, lang) {
|
|
2129
2188
|
const byId = new Map(items.map((i) => [i.id, toCslJson(i)]));
|
|
2130
2189
|
const sys = {
|
|
@@ -2135,6 +2194,7 @@ var CitationEngine = class {
|
|
|
2135
2194
|
this.engine = new Engine(sys, styleXml, cslLocaleTag(lang), true);
|
|
2136
2195
|
this.engine.setOutputFormat("html");
|
|
2137
2196
|
this.isNote = this.engine.opt.xclass === "note";
|
|
2197
|
+
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
2198
|
this.numeric = /citation-number/.test(styleXml.slice(styleXml.indexOf("<citation"), styleXml.indexOf("</citation>")));
|
|
2139
2199
|
}
|
|
2140
2200
|
/** Cite a group; `noteIndex` is the note number for note styles (0 = in text). */
|
|
@@ -2171,7 +2231,8 @@ var CitationEngine = class {
|
|
|
2171
2231
|
if (opts.narrative && refs.length === 1 && !this.isNote && !this.numeric) {
|
|
2172
2232
|
const author = make(items);
|
|
2173
2233
|
const rest = make(refs.map((r, i) => ({ ...items[i], "author-only": void 0, "suppress-author": true, id: r.key })));
|
|
2174
|
-
const
|
|
2234
|
+
const who = () => this.apaNarrativeAnd ? author.label.replace(/,? & /g, (m) => m.startsWith(",") ? ", and " : " and ") : author.label;
|
|
2235
|
+
const join = () => `${who()}${rest.label ? ` ${/^\(/.test(rest.label) ? rest.label : `(${rest.label})`}` : ""}`;
|
|
2175
2236
|
const listeners = [];
|
|
2176
2237
|
author.listeners.push(() => listeners.forEach((l) => l(join())));
|
|
2177
2238
|
rest.listeners.push(() => listeners.forEach((l) => l(join())));
|
|
@@ -2650,7 +2711,7 @@ function runs(nodes, c, st = {}) {
|
|
|
2650
2711
|
for (const n of nodes) {
|
|
2651
2712
|
switch (n.type) {
|
|
2652
2713
|
case "text":
|
|
2653
|
-
out.push(tr(n.value));
|
|
2714
|
+
out.push(RTL_RE.test(n.value) ? tr(n.value, { rightToLeft: true }) : tr(n.value));
|
|
2654
2715
|
break;
|
|
2655
2716
|
case "strong":
|
|
2656
2717
|
out.push(...runs(n.children, c, { ...st, bold: true }));
|
|
@@ -2802,6 +2863,10 @@ function numberingRef(c, ordered, start) {
|
|
|
2802
2863
|
}
|
|
2803
2864
|
return ref;
|
|
2804
2865
|
}
|
|
2866
|
+
function bidiPara(nodes) {
|
|
2867
|
+
const text = nodes.map((n) => "value" in n ? n.value : "").join("");
|
|
2868
|
+
return isRtlParagraph(text) ? { bidirectional: true } : {};
|
|
2869
|
+
}
|
|
2805
2870
|
function blocks(list, c, o = {}) {
|
|
2806
2871
|
const out = [];
|
|
2807
2872
|
for (const b of list) out.push(...block(b, c, o));
|
|
@@ -2826,6 +2891,7 @@ function block(b, c, o) {
|
|
|
2826
2891
|
const kids = [...b.number ? [new d.TextRun({ text: `${b.number} `, color: HEX.muted })] : [], ...runs(b.children, c)];
|
|
2827
2892
|
return [
|
|
2828
2893
|
new d.Paragraph({
|
|
2894
|
+
...bidiPara(b.children),
|
|
2829
2895
|
heading,
|
|
2830
2896
|
keepNext: true,
|
|
2831
2897
|
keepLines: true,
|
|
@@ -2835,7 +2901,7 @@ function block(b, c, o) {
|
|
|
2835
2901
|
];
|
|
2836
2902
|
}
|
|
2837
2903
|
case "paragraph":
|
|
2838
|
-
return [para(c, o, { children: runs(b.children, c), widowControl: true })];
|
|
2904
|
+
return [para(c, o, { children: runs(b.children, c), widowControl: true, ...bidiPara(b.children) })];
|
|
2839
2905
|
case "list": {
|
|
2840
2906
|
const level = o.listLevel ?? 0;
|
|
2841
2907
|
const ref = numberingRef(c, b.ordered, b.start);
|
|
@@ -2852,7 +2918,14 @@ function block(b, c, o) {
|
|
|
2852
2918
|
})
|
|
2853
2919
|
);
|
|
2854
2920
|
} else {
|
|
2855
|
-
out.push(
|
|
2921
|
+
out.push(
|
|
2922
|
+
new d.Paragraph({
|
|
2923
|
+
numbering: { reference: ref, level, instance },
|
|
2924
|
+
children: firstRuns,
|
|
2925
|
+
widowControl: true,
|
|
2926
|
+
...first?.type === "paragraph" ? bidiPara(first.children) : {}
|
|
2927
|
+
})
|
|
2928
|
+
);
|
|
2856
2929
|
}
|
|
2857
2930
|
const tail = first && first.type !== "paragraph" ? [first, ...rest] : rest;
|
|
2858
2931
|
for (const child of tail) {
|
|
@@ -2880,6 +2953,8 @@ function block(b, c, o) {
|
|
|
2880
2953
|
case "table": {
|
|
2881
2954
|
const n = Math.max(b.header.length, ...b.rows.map((r) => r.length), 1);
|
|
2882
2955
|
const keep = b.rows.length <= 20;
|
|
2956
|
+
const cellPt = c.s.fontSize;
|
|
2957
|
+
const colTw = contentColumnWidths(b, n, c.width, (t, bold) => t.length * cellPt * (bold ? 0.55 : 0.5), 10).map((w) => Math.round(w * TW));
|
|
2883
2958
|
const alignOf = (i) => b.align[i] === "center" ? d.AlignmentType.CENTER : b.align[i] === "right" ? d.AlignmentType.RIGHT : d.AlignmentType.LEFT;
|
|
2884
2959
|
const border = { style: d.BorderStyle.SINGLE, size: 4, color: "E5E7EB" };
|
|
2885
2960
|
const none = { style: d.BorderStyle.NONE, size: 0, color: "FFFFFF" };
|
|
@@ -2889,11 +2964,13 @@ function block(b, c, o) {
|
|
|
2889
2964
|
children: Array.from(
|
|
2890
2965
|
{ length: n },
|
|
2891
2966
|
(_, i) => new d.TableCell({
|
|
2967
|
+
width: { size: colTw[i] ?? 0, type: d.WidthType.DXA },
|
|
2892
2968
|
...head ? { shading: { type: d.ShadingType.CLEAR, fill: "F3F4F6", color: "auto" } } : {},
|
|
2893
2969
|
margins: { top: 60, bottom: 60, left: 100, right: 100 },
|
|
2894
2970
|
borders: { top: none, left: none, right: none, bottom: head ? { ...border, size: 10, color: HEX.heading } : border },
|
|
2895
2971
|
children: [
|
|
2896
2972
|
new d.Paragraph({
|
|
2973
|
+
...bidiPara(cells[i] ?? []),
|
|
2897
2974
|
alignment: alignOf(i),
|
|
2898
2975
|
keepNext: keep && !last,
|
|
2899
2976
|
children: runs(cells[i] ?? [], c, head ? { bold: true } : {})
|
|
@@ -2906,7 +2983,9 @@ function block(b, c, o) {
|
|
|
2906
2983
|
if (b.caption) out.push(captionParagraph(b.caption, c, true));
|
|
2907
2984
|
out.push(
|
|
2908
2985
|
new d.Table({
|
|
2909
|
-
width: { size:
|
|
2986
|
+
width: { size: Math.round(c.width * TW), type: d.WidthType.DXA },
|
|
2987
|
+
columnWidths: colTw,
|
|
2988
|
+
layout: d.TableLayoutType.FIXED,
|
|
2910
2989
|
rows: [row(b.header, true, b.rows.length === 0), ...b.rows.map((r, i) => row(r, false, i === b.rows.length - 1))]
|
|
2911
2990
|
})
|
|
2912
2991
|
);
|
|
@@ -3109,7 +3188,7 @@ async function documentToDocx(tree, options = {}) {
|
|
|
3109
3188
|
default: {
|
|
3110
3189
|
// East-Asian text gets a CJK face (Word and LibreOffice substitute an installed one).
|
|
3111
3190
|
document: {
|
|
3112
|
-
run: { font: { ascii: "Cambria", hAnsi: "Cambria", cs: "
|
|
3191
|
+
run: { font: { ascii: "Cambria", hAnsi: "Cambria", cs: "Arial", eastAsia: "Yu Gothic" }, size, sizeComplexScript: size, color: HEX.text },
|
|
3113
3192
|
paragraph: { spacing: { after: 140, line: 300 } }
|
|
3114
3193
|
}
|
|
3115
3194
|
},
|
|
@@ -3297,10 +3376,10 @@ function renderBlockHtml(b, x) {
|
|
|
3297
3376
|
switch (b.type) {
|
|
3298
3377
|
case "heading": {
|
|
3299
3378
|
const num = b.number ? `<span class="doc-heading-number">${b.number}</span> ` : "";
|
|
3300
|
-
return `<h${b.level} id="${(0, import_html_escape.escapeHtml)(b.id)}">${num}${renderInlineHtml(b.children, x)}</h${b.level}>`;
|
|
3379
|
+
return `<h${b.level} id="${(0, import_html_escape.escapeHtml)(b.id)}" dir="auto">${num}${renderInlineHtml(b.children, x)}</h${b.level}>`;
|
|
3301
3380
|
}
|
|
3302
3381
|
case "paragraph":
|
|
3303
|
-
return `<p>${renderInlineHtml(b.children, x)}</p>`;
|
|
3382
|
+
return `<p dir="auto">${renderInlineHtml(b.children, x)}</p>`;
|
|
3304
3383
|
case "list": {
|
|
3305
3384
|
const tag = b.ordered ? "ol" : "ul";
|
|
3306
3385
|
const start = b.ordered && b.start !== 1 ? ` start="${b.start}"` : "";
|
|
@@ -3308,12 +3387,12 @@ function renderBlockHtml(b, x) {
|
|
|
3308
3387
|
const items = b.items.map((i) => {
|
|
3309
3388
|
const box = i.checked === null ? "" : `<span class="doc-task-box${i.checked ? " is-checked" : ""}" role="img" aria-label="${i.checked ? "done" : "not done"}"></span>`;
|
|
3310
3389
|
const inner = i.children.length === 1 && i.children[0]?.type === "paragraph" ? renderInlineHtml(i.children[0].children, x) : renderBlocksHtml(i.children, x);
|
|
3311
|
-
return `<li${i.checked !== null ? ' class="doc-task"' : ""}>${box}${inner}</li>`;
|
|
3390
|
+
return `<li dir="auto"${i.checked !== null ? ' class="doc-task"' : ""}>${box}${inner}</li>`;
|
|
3312
3391
|
}).join("");
|
|
3313
3392
|
return `<${tag}${start}${task ? ' class="doc-task-list"' : ""}>${items}</${tag}>`;
|
|
3314
3393
|
}
|
|
3315
3394
|
case "blockquote":
|
|
3316
|
-
return `<blockquote>${renderBlocksHtml(b.children, x)}</blockquote>`;
|
|
3395
|
+
return `<blockquote dir="auto">${renderBlocksHtml(b.children, x)}</blockquote>`;
|
|
3317
3396
|
case "code":
|
|
3318
3397
|
return `<pre class="doc-code"${b.lang ? ` data-language="${(0, import_html_escape.escapeHtml)(b.lang)}"` : ""}><code>${(0, import_html_escape.escapeHtml)(
|
|
3319
3398
|
b.value
|
|
@@ -3323,8 +3402,8 @@ function renderBlockHtml(b, x) {
|
|
|
3323
3402
|
const a = b.align[i];
|
|
3324
3403
|
return a ? ` style="text-align:${a}"` : "";
|
|
3325
3404
|
};
|
|
3326
|
-
const head = `<thead><tr>${b.header.map((c, i) => `<th${al(i)}>${renderInlineHtml(c, x)}</th>`).join("")}</tr></thead>`;
|
|
3327
|
-
const body = `<tbody>${b.rows.map((r) => `<tr>${r.map((c, i) => `<td${al(i)}>${renderInlineHtml(c, x)}</td>`).join("")}</tr>`).join("")}</tbody>`;
|
|
3405
|
+
const head = `<thead><tr>${b.header.map((c, i) => `<th dir="auto"${al(i)}>${renderInlineHtml(c, x)}</th>`).join("")}</tr></thead>`;
|
|
3406
|
+
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>`;
|
|
3328
3407
|
const id = b.caption?.id ? ` id="tbl:${(0, import_html_escape.escapeHtml)(b.caption.id)}"` : "";
|
|
3329
3408
|
return `<div class="doc-table-wrap"${id}><table>${b.caption ? captionHtml(b.caption, "caption", x) : ""}${head}${body}</table></div>`;
|
|
3330
3409
|
}
|
|
@@ -3794,7 +3873,7 @@ function breakRuns(input, width, t) {
|
|
|
3794
3873
|
for (let guard = 0; guard < 50 && atoms.length; guard++) {
|
|
3795
3874
|
const gw = atoms.reduce((n, a) => n + w(a), 0);
|
|
3796
3875
|
const sw = pendingSpace && line > 0 ? w(pendingSpace) : 0;
|
|
3797
|
-
if (line + sw + gw <= W || line === 0 && atoms.length !== 1) {
|
|
3876
|
+
if (line + sw + gw <= W || line === 0 && (atoms.length !== 1 || gw <= width)) {
|
|
3798
3877
|
if (sw && pendingSpace) out.push(pendingSpace);
|
|
3799
3878
|
out.push(...atoms);
|
|
3800
3879
|
line += sw + gw;
|
|
@@ -3918,11 +3997,14 @@ function withFallbackFonts(nodes, c) {
|
|
|
3918
3997
|
const fb = fallbackFor(cp);
|
|
3919
3998
|
if (fb) {
|
|
3920
3999
|
flush();
|
|
3921
|
-
|
|
4000
|
+
const prev = out[out.length - 1];
|
|
4001
|
+
if (prev && typeof prev === "object" && prev.font === fb && prev.__fb) prev.text = `${prev.text}${ch}`;
|
|
4002
|
+
else out.push({ ...n, text: ch, font: fb, bold: false, italics: false, __fb: true });
|
|
3922
4003
|
} else dropped = true;
|
|
3923
4004
|
}
|
|
3924
4005
|
flush();
|
|
3925
4006
|
}
|
|
4007
|
+
for (const o of out) if (typeof o === "object") delete o.__fb;
|
|
3926
4008
|
if (dropped && !c.glyphs.announced) {
|
|
3927
4009
|
c.glyphs.announced = true;
|
|
3928
4010
|
c.tree.notices.push({
|
|
@@ -3949,8 +4031,13 @@ function drawable(text, c) {
|
|
|
3949
4031
|
}
|
|
3950
4032
|
function bodyRuns(nodes, c, width = c.width) {
|
|
3951
4033
|
const r = runs2(nodes, c);
|
|
3952
|
-
|
|
4034
|
+
const text = plain(nodes);
|
|
4035
|
+
if (!RTL_RE.test(text)) return c.typo ? breakRuns(r, width, c.typo) : r;
|
|
4036
|
+
const typo = c.typo ?? (c.faces ? { split: (w) => [w], measure: c.faces.measure } : void 0);
|
|
4037
|
+
const lines = typo ? breakRuns(r, width, typo) : r;
|
|
4038
|
+
return visualOrder(lines, isRtlParagraph(text));
|
|
3953
4039
|
}
|
|
4040
|
+
var alignFor = (nodes) => isRtlParagraph(plain(nodes)) ? { alignment: "right" } : {};
|
|
3954
4041
|
function runs2(nodes, c, st = {}) {
|
|
3955
4042
|
const out = [];
|
|
3956
4043
|
const base = (raw) => {
|
|
@@ -4141,25 +4228,7 @@ function estimateLines(text, c, size = c.s.fontSize) {
|
|
|
4141
4228
|
}
|
|
4142
4229
|
function columnWidths(b, n, size, c) {
|
|
4143
4230
|
const measure = (text, bold) => c.faces ? c.faces.measure(text, { bold, fontSize: size }) : text.length * size * 0.5;
|
|
4144
|
-
|
|
4145
|
-
const min = new Array(n).fill(0);
|
|
4146
|
-
const max = new Array(n).fill(0);
|
|
4147
|
-
const rows = [[b.header, true], ...b.rows.map((r) => [r, false])];
|
|
4148
|
-
for (const [r, head] of rows) {
|
|
4149
|
-
for (let i = 0; i < n; i++) {
|
|
4150
|
-
const t = plain(r[i] ?? []);
|
|
4151
|
-
const longest = t.split(/\s+/).reduce((m, w) => Math.max(m, measure(w, head)), 0);
|
|
4152
|
-
min[i] = Math.max(min[i], Math.min(longest, c.width * 0.35) + pad);
|
|
4153
|
-
max[i] = Math.max(max[i], measure(t, head) + pad);
|
|
4154
|
-
}
|
|
4155
|
-
}
|
|
4156
|
-
const avail = c.width;
|
|
4157
|
-
const sumMax = max.reduce((a, x) => a + x, 0);
|
|
4158
|
-
const sumMin = min.reduce((a, x) => a + x, 0);
|
|
4159
|
-
if (sumMax <= avail) return max.map((m) => m + (avail - sumMax) / n);
|
|
4160
|
-
if (sumMin >= avail) return min.map((m) => m / sumMin * avail);
|
|
4161
|
-
const extra = avail - sumMin;
|
|
4162
|
-
return min.map((m, i) => m + (max[i] - m) * (extra / (sumMax - sumMin)));
|
|
4231
|
+
return contentColumnWidths(b, n, c.width, measure);
|
|
4163
4232
|
}
|
|
4164
4233
|
var plain = (nodes) => nodes.map((n) => "value" in n ? n.value : "label" in n ? n.label : "children" in n ? plain(n.children) : "").join("");
|
|
4165
4234
|
function taskItem(checked, content, c) {
|
|
@@ -4214,8 +4283,11 @@ function block2(b, c) {
|
|
|
4214
4283
|
switch (b.type) {
|
|
4215
4284
|
case "heading": {
|
|
4216
4285
|
const rel = Math.min(6, b.level - c.minLevel + 1);
|
|
4286
|
+
const rtlHeading = RTL_RE.test(b.plain);
|
|
4287
|
+
const headRuns = rtlHeading ? visualOrder(runs2(b.children, c), isRtlParagraph(b.plain)) : runs2(b.children, c);
|
|
4217
4288
|
const node = {
|
|
4218
|
-
|
|
4289
|
+
...rtlHeading ? alignFor(b.children) : {},
|
|
4290
|
+
text: [...b.number ? [{ text: `${b.number} `, color: COLOR.muted }] : [], ...headRuns],
|
|
4219
4291
|
style: `h${Math.min(6, b.level)}`,
|
|
4220
4292
|
headlineLevel: b.level,
|
|
4221
4293
|
id: b.id
|
|
@@ -4229,12 +4301,12 @@ function block2(b, c) {
|
|
|
4229
4301
|
}
|
|
4230
4302
|
case "paragraph": {
|
|
4231
4303
|
const text = bodyRuns(b.children, c);
|
|
4232
|
-
const p = { text, style: "p" };
|
|
4304
|
+
const p = { text, style: "p", ...alignFor(b.children) };
|
|
4233
4305
|
return estimateLines(plain(b.children), c) <= 5 ? { stack: [p], unbreakable: true } : p;
|
|
4234
4306
|
}
|
|
4235
4307
|
case "list": {
|
|
4236
4308
|
const items = b.items.map((it) => {
|
|
4237
|
-
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 });
|
|
4309
|
+
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 });
|
|
4238
4310
|
if (it.checked !== null) return { ...taskItem(it.checked, content, c), listType: "none" };
|
|
4239
4311
|
return content.length === 1 ? content[0] : { stack: content };
|
|
4240
4312
|
});
|
|
@@ -4287,15 +4359,24 @@ function block2(b, c) {
|
|
|
4287
4359
|
case "table": {
|
|
4288
4360
|
const n = Math.max(b.header.length, ...b.rows.map((r) => r.length), 1);
|
|
4289
4361
|
const size = c.s.fontSize * 0.92;
|
|
4362
|
+
const widths = columnWidths(b, n, size, c);
|
|
4363
|
+
const cellRuns = (inl, i, head) => {
|
|
4364
|
+
const r = runs2(inl, c).map((x) => typeof x === "object" ? { ...x, fontSize: x.fontSize ?? size, ...head ? { bold: true } : {} } : x);
|
|
4365
|
+
const w = (widths[i] ?? c.width) - 10;
|
|
4366
|
+
if (RTL_RE.test(plain(inl))) {
|
|
4367
|
+
const typo = c.typo ?? (c.faces ? { split: (x) => [x], measure: c.faces.measure } : void 0);
|
|
4368
|
+
return visualOrder(typo ? breakRuns(r, w, typo) : r, isRtlParagraph(plain(inl)));
|
|
4369
|
+
}
|
|
4370
|
+
return c.typo && !head ? breakRuns(r, w, c.typo) : r;
|
|
4371
|
+
};
|
|
4290
4372
|
const cell = (inl, i, head) => ({
|
|
4291
|
-
text:
|
|
4373
|
+
text: cellRuns(inl ?? [], i, head),
|
|
4292
4374
|
bold: head,
|
|
4293
|
-
alignment: b.align[i] ?? "left",
|
|
4375
|
+
alignment: b.align[i] ?? (isRtlParagraph(plain(inl ?? [])) ? "right" : "left"),
|
|
4294
4376
|
fontSize: size,
|
|
4295
4377
|
color: head ? COLOR.heading : COLOR.text
|
|
4296
4378
|
});
|
|
4297
4379
|
const row = (r, head) => Array.from({ length: n }, (_, i) => cell(r[i], i, head));
|
|
4298
|
-
const widths = columnWidths(b, n, size, c);
|
|
4299
4380
|
const lineH = size * 1.3;
|
|
4300
4381
|
const pageH = c.pageHeight;
|
|
4301
4382
|
const rowHeight = (r) => Math.max(...r.map((inl, i) => estimateLines(plain(inl), { ...c, width: (widths[i] ?? c.width) - 10 }, size) * lineH)) + 8;
|
|
@@ -4541,12 +4622,16 @@ async function loadTypography(tree, loaders = {}) {
|
|
|
4541
4622
|
const base = await loadFaces(vfs, s.fontSize);
|
|
4542
4623
|
let needEmoji = false;
|
|
4543
4624
|
let needSymbols = false;
|
|
4625
|
+
let needArabic = false;
|
|
4626
|
+
let needHebrew = false;
|
|
4544
4627
|
const cjk = /* @__PURE__ */ new Set();
|
|
4545
4628
|
for (const ch of documentText(tree)) {
|
|
4546
4629
|
const cp = ch.codePointAt(0);
|
|
4547
4630
|
if (cp < 32 || cp === 8205 || cp === 65039 || base.body(cp)) continue;
|
|
4548
4631
|
if (EMOJI_CP.test(ch)) needEmoji = true;
|
|
4549
|
-
else if (isCjk(cp)) cjk.add(cp);
|
|
4632
|
+
else if (isCjk(cp) || isHangul(cp)) cjk.add(cp);
|
|
4633
|
+
else if (isArabic(cp)) needArabic = true;
|
|
4634
|
+
else if (isHebrew(cp)) needHebrew = true;
|
|
4550
4635
|
else needSymbols = true;
|
|
4551
4636
|
}
|
|
4552
4637
|
const fallbacks = [];
|
|
@@ -4558,6 +4643,14 @@ async function loadTypography(tree, loaders = {}) {
|
|
|
4558
4643
|
const b = await registerFace("NotoSymbols", "NotoSansMath.woff", loaders.symbols ?? defaultSymbolFontLoader, tree, "symbols");
|
|
4559
4644
|
if (b) fallbacks.push({ font: "NotoSymbols", bytes: b });
|
|
4560
4645
|
}
|
|
4646
|
+
if (needArabic) {
|
|
4647
|
+
const b = await registerFace("NotoArabic", "NotoSansArabic.woff", loaders.arabic ?? urlLoader(NOTO_ARABIC_URL), tree, "Arabic");
|
|
4648
|
+
if (b) fallbacks.push({ font: "NotoArabic", bytes: b });
|
|
4649
|
+
}
|
|
4650
|
+
if (needHebrew) {
|
|
4651
|
+
const b = await registerFace("NotoHebrew", "NotoSansHebrew.woff", loaders.hebrew ?? urlLoader(NOTO_HEBREW_URL), tree, "Hebrew");
|
|
4652
|
+
if (b) fallbacks.push({ font: "NotoHebrew", bytes: b });
|
|
4653
|
+
}
|
|
4561
4654
|
if (cjk.size) {
|
|
4562
4655
|
let subsets = [];
|
|
4563
4656
|
try {
|
|
@@ -4573,7 +4666,7 @@ async function loadTypography(tree, loaders = {}) {
|
|
|
4573
4666
|
if (!subsets.length) {
|
|
4574
4667
|
tree.notices.push({
|
|
4575
4668
|
code: "glyph-substituted",
|
|
4576
|
-
message: "The Japanese /
|
|
4669
|
+
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).",
|
|
4577
4670
|
remedy: "Check the internet connection (the font comes from cdn.jsdelivr.net) and export again."
|
|
4578
4671
|
});
|
|
4579
4672
|
}
|
|
@@ -4601,6 +4694,12 @@ var urlLoader = (url) => async () => {
|
|
|
4601
4694
|
var defaultEmojiFontLoader = urlLoader(NOTO_EMOJI_URL);
|
|
4602
4695
|
var defaultSymbolFontLoader = urlLoader(NOTO_SYMBOLS_URL);
|
|
4603
4696
|
var NOTO_CJK_BASE = "https://cdn.jsdelivr.net/npm/@fontsource/noto-sans-jp@5.3.0";
|
|
4697
|
+
var NOTO_KR_BASE = "https://cdn.jsdelivr.net/npm/@fontsource/noto-sans-kr@5.3.0";
|
|
4698
|
+
var NOTO_ARABIC_URL = "https://cdn.jsdelivr.net/npm/@fontsource/noto-sans-arabic@5.3.0/files/noto-sans-arabic-arabic-400-normal.woff";
|
|
4699
|
+
var NOTO_HEBREW_URL = "https://cdn.jsdelivr.net/npm/@fontsource/noto-sans-hebrew@5.3.0/files/noto-sans-hebrew-hebrew-400-normal.woff";
|
|
4700
|
+
var isHangul = (cp) => cp >= 44032 && cp <= 55215 || cp >= 4352 && cp <= 4607 || cp >= 12592 && cp <= 12687;
|
|
4701
|
+
var isArabic = (cp) => cp >= 1536 && cp <= 1791 || cp >= 1872 && cp <= 1919 || cp >= 2208 && cp <= 2303 || cp >= 64336 && cp <= 65023 || cp >= 65136 && cp <= 65279;
|
|
4702
|
+
var isHebrew = (cp) => cp >= 1424 && cp <= 1535 || cp >= 64285 && cp <= 64335;
|
|
4604
4703
|
function parseUnicodeRangeCss(css) {
|
|
4605
4704
|
const out = [];
|
|
4606
4705
|
for (const block3 of css.split("@font-face").slice(1)) {
|
|
@@ -4616,19 +4715,25 @@ function parseUnicodeRangeCss(css) {
|
|
|
4616
4715
|
}
|
|
4617
4716
|
return out;
|
|
4618
4717
|
}
|
|
4619
|
-
|
|
4718
|
+
async function unicodeRangeFaces(base, cps) {
|
|
4620
4719
|
if (typeof fetch !== "function" || cps.length === 0) return [];
|
|
4621
|
-
const res = await fetch(`${
|
|
4720
|
+
const res = await fetch(`${base}/400.css`);
|
|
4622
4721
|
if (!res.ok) return [];
|
|
4623
4722
|
const faces = parseUnicodeRangeCss(await res.text()).filter((f) => cps.some((cp) => f.ranges.some(([a, b]) => cp >= a && cp <= b)));
|
|
4624
4723
|
const out = [];
|
|
4625
4724
|
await Promise.all(
|
|
4626
4725
|
faces.map(async (f) => {
|
|
4627
|
-
const r = await fetch(`${
|
|
4726
|
+
const r = await fetch(`${base}/files/${f.file}.woff`);
|
|
4628
4727
|
if (r.ok) out.push({ name: f.file, bytes: new Uint8Array(await r.arrayBuffer()) });
|
|
4629
4728
|
})
|
|
4630
4729
|
);
|
|
4631
4730
|
return out;
|
|
4731
|
+
}
|
|
4732
|
+
var defaultCjkFontLoader = async (cps) => {
|
|
4733
|
+
const kr = cps.filter(isHangul);
|
|
4734
|
+
const jp = cps.filter((cp) => !isHangul(cp));
|
|
4735
|
+
const [a, b] = await Promise.all([unicodeRangeFaces(NOTO_KR_BASE, kr), unicodeRangeFaces(NOTO_CJK_BASE, jp)]);
|
|
4736
|
+
return [...a, ...b];
|
|
4632
4737
|
};
|
|
4633
4738
|
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;
|
|
4634
4739
|
var EMOJI_CP = /\p{Extended_Pictographic}|\p{Regional_Indicator}|\u20E3/u;
|
|
@@ -4947,7 +5052,7 @@ function uniqueId(ctx, base) {
|
|
|
4947
5052
|
return id;
|
|
4948
5053
|
}
|
|
4949
5054
|
var CITE_GROUP_RE = /\[(-?@[^[\]]+)\]/g;
|
|
4950
|
-
var BARE_RE = /(^|[^\w@.\\])@((?:fig|tbl|sec|eq):[\w-]+|[A-Za-z0-9_](?:[\w:.#$%&+?<>~/-]*\w)?)
|
|
5055
|
+
var BARE_RE = /(^|[^\w@.\\])@((?:fig|tbl|sec|eq):[\w-]+|[A-Za-z0-9_](?:[\w:.#$%&+?<>~/-]*\w)?)(?: \[([^\[\]@]+)\])?/g;
|
|
4951
5056
|
function scanText(value, ctx) {
|
|
4952
5057
|
const out = [];
|
|
4953
5058
|
const pushText = (s) => {
|
|
@@ -4968,8 +5073,8 @@ function scanText(value, ctx) {
|
|
|
4968
5073
|
last2 = start + key.length + 1;
|
|
4969
5074
|
} else if (ctx.refs.has(key)) {
|
|
4970
5075
|
pushText(s.slice(last2, start));
|
|
4971
|
-
out.push(cite([{ key, suppressAuthor: false }], ctx, true));
|
|
4972
|
-
last2 = start + key.length + 1;
|
|
5076
|
+
out.push(cite([{ key, locator: m[3]?.trim(), suppressAuthor: false }], ctx, true));
|
|
5077
|
+
last2 = start + key.length + 1 + (m[3] !== void 0 ? m[3].length + 3 : 0);
|
|
4973
5078
|
}
|
|
4974
5079
|
}
|
|
4975
5080
|
pushText(s.slice(last2));
|