@ai-matrx/print 0.8.3 → 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 +18 -0
- package/dist/document.cjs +160 -30
- 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 +160 -30
- package/dist/document.js.map +1 -1
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
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
|
+
|
|
3
21
|
## 0.8.3
|
|
4
22
|
|
|
5
23
|
- **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,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 } : {})
|
|
@@ -3109,7 +3194,7 @@ async function documentToDocx(tree, options = {}) {
|
|
|
3109
3194
|
default: {
|
|
3110
3195
|
// East-Asian text gets a CJK face (Word and LibreOffice substitute an installed one).
|
|
3111
3196
|
document: {
|
|
3112
|
-
run: { font: { ascii: "Cambria", hAnsi: "Cambria", cs: "
|
|
3197
|
+
run: { font: { ascii: "Cambria", hAnsi: "Cambria", cs: "Arial", eastAsia: "Yu Gothic" }, size, sizeComplexScript: size, color: HEX.text },
|
|
3113
3198
|
paragraph: { spacing: { after: 140, line: 300 } }
|
|
3114
3199
|
}
|
|
3115
3200
|
},
|
|
@@ -3297,10 +3382,10 @@ function renderBlockHtml(b, x) {
|
|
|
3297
3382
|
switch (b.type) {
|
|
3298
3383
|
case "heading": {
|
|
3299
3384
|
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}>`;
|
|
3385
|
+
return `<h${b.level} id="${(0, import_html_escape.escapeHtml)(b.id)}" dir="auto">${num}${renderInlineHtml(b.children, x)}</h${b.level}>`;
|
|
3301
3386
|
}
|
|
3302
3387
|
case "paragraph":
|
|
3303
|
-
return `<p>${renderInlineHtml(b.children, x)}</p>`;
|
|
3388
|
+
return `<p dir="auto">${renderInlineHtml(b.children, x)}</p>`;
|
|
3304
3389
|
case "list": {
|
|
3305
3390
|
const tag = b.ordered ? "ol" : "ul";
|
|
3306
3391
|
const start = b.ordered && b.start !== 1 ? ` start="${b.start}"` : "";
|
|
@@ -3308,12 +3393,12 @@ function renderBlockHtml(b, x) {
|
|
|
3308
3393
|
const items = b.items.map((i) => {
|
|
3309
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>`;
|
|
3310
3395
|
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>`;
|
|
3396
|
+
return `<li dir="auto"${i.checked !== null ? ' class="doc-task"' : ""}>${box}${inner}</li>`;
|
|
3312
3397
|
}).join("");
|
|
3313
3398
|
return `<${tag}${start}${task ? ' class="doc-task-list"' : ""}>${items}</${tag}>`;
|
|
3314
3399
|
}
|
|
3315
3400
|
case "blockquote":
|
|
3316
|
-
return `<blockquote>${renderBlocksHtml(b.children, x)}</blockquote>`;
|
|
3401
|
+
return `<blockquote dir="auto">${renderBlocksHtml(b.children, x)}</blockquote>`;
|
|
3317
3402
|
case "code":
|
|
3318
3403
|
return `<pre class="doc-code"${b.lang ? ` data-language="${(0, import_html_escape.escapeHtml)(b.lang)}"` : ""}><code>${(0, import_html_escape.escapeHtml)(
|
|
3319
3404
|
b.value
|
|
@@ -3323,8 +3408,8 @@ function renderBlockHtml(b, x) {
|
|
|
3323
3408
|
const a = b.align[i];
|
|
3324
3409
|
return a ? ` style="text-align:${a}"` : "";
|
|
3325
3410
|
};
|
|
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>`;
|
|
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>`;
|
|
3328
3413
|
const id = b.caption?.id ? ` id="tbl:${(0, import_html_escape.escapeHtml)(b.caption.id)}"` : "";
|
|
3329
3414
|
return `<div class="doc-table-wrap"${id}><table>${b.caption ? captionHtml(b.caption, "caption", x) : ""}${head}${body}</table></div>`;
|
|
3330
3415
|
}
|
|
@@ -3794,7 +3879,7 @@ function breakRuns(input, width, t) {
|
|
|
3794
3879
|
for (let guard = 0; guard < 50 && atoms.length; guard++) {
|
|
3795
3880
|
const gw = atoms.reduce((n, a) => n + w(a), 0);
|
|
3796
3881
|
const sw = pendingSpace && line > 0 ? w(pendingSpace) : 0;
|
|
3797
|
-
if (line + sw + gw <= W || line === 0 && atoms.length !== 1) {
|
|
3882
|
+
if (line + sw + gw <= W || line === 0 && (atoms.length !== 1 || gw <= width)) {
|
|
3798
3883
|
if (sw && pendingSpace) out.push(pendingSpace);
|
|
3799
3884
|
out.push(...atoms);
|
|
3800
3885
|
line += sw + gw;
|
|
@@ -3918,11 +4003,14 @@ function withFallbackFonts(nodes, c) {
|
|
|
3918
4003
|
const fb = fallbackFor(cp);
|
|
3919
4004
|
if (fb) {
|
|
3920
4005
|
flush();
|
|
3921
|
-
|
|
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 });
|
|
3922
4009
|
} else dropped = true;
|
|
3923
4010
|
}
|
|
3924
4011
|
flush();
|
|
3925
4012
|
}
|
|
4013
|
+
for (const o of out) if (typeof o === "object") delete o.__fb;
|
|
3926
4014
|
if (dropped && !c.glyphs.announced) {
|
|
3927
4015
|
c.glyphs.announced = true;
|
|
3928
4016
|
c.tree.notices.push({
|
|
@@ -3949,8 +4037,13 @@ function drawable(text, c) {
|
|
|
3949
4037
|
}
|
|
3950
4038
|
function bodyRuns(nodes, c, width = c.width) {
|
|
3951
4039
|
const r = runs2(nodes, c);
|
|
3952
|
-
|
|
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));
|
|
3953
4045
|
}
|
|
4046
|
+
var alignFor = (nodes) => isRtlParagraph(plain(nodes)) ? { alignment: "right" } : {};
|
|
3954
4047
|
function runs2(nodes, c, st = {}) {
|
|
3955
4048
|
const out = [];
|
|
3956
4049
|
const base = (raw) => {
|
|
@@ -4142,6 +4235,7 @@ function estimateLines(text, c, size = c.s.fontSize) {
|
|
|
4142
4235
|
function columnWidths(b, n, size, c) {
|
|
4143
4236
|
const measure = (text, bold) => c.faces ? c.faces.measure(text, { bold, fontSize: size }) : text.length * size * 0.5;
|
|
4144
4237
|
const pad = 10;
|
|
4238
|
+
const slack = 1;
|
|
4145
4239
|
const min = new Array(n).fill(0);
|
|
4146
4240
|
const max = new Array(n).fill(0);
|
|
4147
4241
|
const rows = [[b.header, true], ...b.rows.map((r) => [r, false])];
|
|
@@ -4149,8 +4243,8 @@ function columnWidths(b, n, size, c) {
|
|
|
4149
4243
|
for (let i = 0; i < n; i++) {
|
|
4150
4244
|
const t = plain(r[i] ?? []);
|
|
4151
4245
|
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);
|
|
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);
|
|
4154
4248
|
}
|
|
4155
4249
|
}
|
|
4156
4250
|
const avail = c.width;
|
|
@@ -4214,8 +4308,11 @@ function block2(b, c) {
|
|
|
4214
4308
|
switch (b.type) {
|
|
4215
4309
|
case "heading": {
|
|
4216
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);
|
|
4217
4313
|
const node = {
|
|
4218
|
-
|
|
4314
|
+
...rtlHeading ? alignFor(b.children) : {},
|
|
4315
|
+
text: [...b.number ? [{ text: `${b.number} `, color: COLOR.muted }] : [], ...headRuns],
|
|
4219
4316
|
style: `h${Math.min(6, b.level)}`,
|
|
4220
4317
|
headlineLevel: b.level,
|
|
4221
4318
|
id: b.id
|
|
@@ -4229,12 +4326,12 @@ function block2(b, c) {
|
|
|
4229
4326
|
}
|
|
4230
4327
|
case "paragraph": {
|
|
4231
4328
|
const text = bodyRuns(b.children, c);
|
|
4232
|
-
const p = { text, style: "p" };
|
|
4329
|
+
const p = { text, style: "p", ...alignFor(b.children) };
|
|
4233
4330
|
return estimateLines(plain(b.children), c) <= 5 ? { stack: [p], unbreakable: true } : p;
|
|
4234
4331
|
}
|
|
4235
4332
|
case "list": {
|
|
4236
4333
|
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 });
|
|
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 });
|
|
4238
4335
|
if (it.checked !== null) return { ...taskItem(it.checked, content, c), listType: "none" };
|
|
4239
4336
|
return content.length === 1 ? content[0] : { stack: content };
|
|
4240
4337
|
});
|
|
@@ -4287,15 +4384,24 @@ function block2(b, c) {
|
|
|
4287
4384
|
case "table": {
|
|
4288
4385
|
const n = Math.max(b.header.length, ...b.rows.map((r) => r.length), 1);
|
|
4289
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
|
+
};
|
|
4290
4397
|
const cell = (inl, i, head) => ({
|
|
4291
|
-
text:
|
|
4398
|
+
text: cellRuns(inl ?? [], i, head),
|
|
4292
4399
|
bold: head,
|
|
4293
|
-
alignment: b.align[i] ?? "left",
|
|
4400
|
+
alignment: b.align[i] ?? (isRtlParagraph(plain(inl ?? [])) ? "right" : "left"),
|
|
4294
4401
|
fontSize: size,
|
|
4295
4402
|
color: head ? COLOR.heading : COLOR.text
|
|
4296
4403
|
});
|
|
4297
4404
|
const row = (r, head) => Array.from({ length: n }, (_, i) => cell(r[i], i, head));
|
|
4298
|
-
const widths = columnWidths(b, n, size, c);
|
|
4299
4405
|
const lineH = size * 1.3;
|
|
4300
4406
|
const pageH = c.pageHeight;
|
|
4301
4407
|
const rowHeight = (r) => Math.max(...r.map((inl, i) => estimateLines(plain(inl), { ...c, width: (widths[i] ?? c.width) - 10 }, size) * lineH)) + 8;
|
|
@@ -4541,12 +4647,16 @@ async function loadTypography(tree, loaders = {}) {
|
|
|
4541
4647
|
const base = await loadFaces(vfs, s.fontSize);
|
|
4542
4648
|
let needEmoji = false;
|
|
4543
4649
|
let needSymbols = false;
|
|
4650
|
+
let needArabic = false;
|
|
4651
|
+
let needHebrew = false;
|
|
4544
4652
|
const cjk = /* @__PURE__ */ new Set();
|
|
4545
4653
|
for (const ch of documentText(tree)) {
|
|
4546
4654
|
const cp = ch.codePointAt(0);
|
|
4547
4655
|
if (cp < 32 || cp === 8205 || cp === 65039 || base.body(cp)) continue;
|
|
4548
4656
|
if (EMOJI_CP.test(ch)) needEmoji = true;
|
|
4549
|
-
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;
|
|
4550
4660
|
else needSymbols = true;
|
|
4551
4661
|
}
|
|
4552
4662
|
const fallbacks = [];
|
|
@@ -4558,6 +4668,14 @@ async function loadTypography(tree, loaders = {}) {
|
|
|
4558
4668
|
const b = await registerFace("NotoSymbols", "NotoSansMath.woff", loaders.symbols ?? defaultSymbolFontLoader, tree, "symbols");
|
|
4559
4669
|
if (b) fallbacks.push({ font: "NotoSymbols", bytes: b });
|
|
4560
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
|
+
}
|
|
4561
4679
|
if (cjk.size) {
|
|
4562
4680
|
let subsets = [];
|
|
4563
4681
|
try {
|
|
@@ -4573,7 +4691,7 @@ async function loadTypography(tree, loaders = {}) {
|
|
|
4573
4691
|
if (!subsets.length) {
|
|
4574
4692
|
tree.notices.push({
|
|
4575
4693
|
code: "glyph-substituted",
|
|
4576
|
-
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).",
|
|
4577
4695
|
remedy: "Check the internet connection (the font comes from cdn.jsdelivr.net) and export again."
|
|
4578
4696
|
});
|
|
4579
4697
|
}
|
|
@@ -4601,6 +4719,12 @@ var urlLoader = (url) => async () => {
|
|
|
4601
4719
|
var defaultEmojiFontLoader = urlLoader(NOTO_EMOJI_URL);
|
|
4602
4720
|
var defaultSymbolFontLoader = urlLoader(NOTO_SYMBOLS_URL);
|
|
4603
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;
|
|
4604
4728
|
function parseUnicodeRangeCss(css) {
|
|
4605
4729
|
const out = [];
|
|
4606
4730
|
for (const block3 of css.split("@font-face").slice(1)) {
|
|
@@ -4616,19 +4740,25 @@ function parseUnicodeRangeCss(css) {
|
|
|
4616
4740
|
}
|
|
4617
4741
|
return out;
|
|
4618
4742
|
}
|
|
4619
|
-
|
|
4743
|
+
async function unicodeRangeFaces(base, cps) {
|
|
4620
4744
|
if (typeof fetch !== "function" || cps.length === 0) return [];
|
|
4621
|
-
const res = await fetch(`${
|
|
4745
|
+
const res = await fetch(`${base}/400.css`);
|
|
4622
4746
|
if (!res.ok) return [];
|
|
4623
4747
|
const faces = parseUnicodeRangeCss(await res.text()).filter((f) => cps.some((cp) => f.ranges.some(([a, b]) => cp >= a && cp <= b)));
|
|
4624
4748
|
const out = [];
|
|
4625
4749
|
await Promise.all(
|
|
4626
4750
|
faces.map(async (f) => {
|
|
4627
|
-
const r = await fetch(`${
|
|
4751
|
+
const r = await fetch(`${base}/files/${f.file}.woff`);
|
|
4628
4752
|
if (r.ok) out.push({ name: f.file, bytes: new Uint8Array(await r.arrayBuffer()) });
|
|
4629
4753
|
})
|
|
4630
4754
|
);
|
|
4631
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];
|
|
4632
4762
|
};
|
|
4633
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;
|
|
4634
4764
|
var EMOJI_CP = /\p{Extended_Pictographic}|\p{Regional_Indicator}|\u20E3/u;
|
|
@@ -4947,7 +5077,7 @@ function uniqueId(ctx, base) {
|
|
|
4947
5077
|
return id;
|
|
4948
5078
|
}
|
|
4949
5079
|
var CITE_GROUP_RE = /\[(-?@[^[\]]+)\]/g;
|
|
4950
|
-
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;
|
|
4951
5081
|
function scanText(value, ctx) {
|
|
4952
5082
|
const out = [];
|
|
4953
5083
|
const pushText = (s) => {
|
|
@@ -4968,8 +5098,8 @@ function scanText(value, ctx) {
|
|
|
4968
5098
|
last2 = start + key.length + 1;
|
|
4969
5099
|
} else if (ctx.refs.has(key)) {
|
|
4970
5100
|
pushText(s.slice(last2, start));
|
|
4971
|
-
out.push(cite([{ key, suppressAuthor: false }], ctx, true));
|
|
4972
|
-
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);
|
|
4973
5103
|
}
|
|
4974
5104
|
}
|
|
4975
5105
|
pushText(s.slice(last2));
|