@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/dist/document.js
CHANGED
|
@@ -1,62 +1,116 @@
|
|
|
1
|
+
// src/document/bidi.ts
|
|
2
|
+
import bidiFactory from "bidi-js";
|
|
3
|
+
var BIDI = /* @__PURE__ */ Symbol.for("ai-matrx.print.document.bidi");
|
|
4
|
+
function bidi() {
|
|
5
|
+
const g = globalThis;
|
|
6
|
+
if (!g[BIDI]) g[BIDI] = bidiFactory();
|
|
7
|
+
return g[BIDI];
|
|
8
|
+
}
|
|
9
|
+
var RTL_RE = /[-ࣿיִ-﷿ﹰ-]/;
|
|
10
|
+
function isRtlParagraph(text) {
|
|
11
|
+
if (!RTL_RE.test(text)) return false;
|
|
12
|
+
return (bidi().getEmbeddingLevels(text).paragraphs[0]?.level ?? 0) % 2 === 1;
|
|
13
|
+
}
|
|
14
|
+
function visualOrder(input, rtl) {
|
|
15
|
+
const b = bidi();
|
|
16
|
+
const runs3 = input.map((r) => typeof r === "string" ? { text: r } : { ...r, text: String(r.text ?? "") });
|
|
17
|
+
const lines = [[]];
|
|
18
|
+
runs3.forEach((r, i) => {
|
|
19
|
+
for (const m of r.text.matchAll(/\n|[^\S\n]+|[\u0590-\u08FF\uFB1D-\uFDFF\uFE70-\uFEFF]+|[^\s\u0590-\u08FF\uFB1D-\uFDFF\uFE70-\uFEFF]+/g)) {
|
|
20
|
+
if (m[0] === "\n") lines.push([]);
|
|
21
|
+
else lines[lines.length - 1].push({ run: i, text: m[0] });
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
const out = [];
|
|
25
|
+
lines.forEach((line, li) => {
|
|
26
|
+
if (li > 0) out.push({ ...runs3[0], text: "\n" });
|
|
27
|
+
const text = line.map((t) => t.text).join("");
|
|
28
|
+
if (!text) return;
|
|
29
|
+
const { levels } = b.getEmbeddingLevels(text, rtl ? "rtl" : "ltr");
|
|
30
|
+
let at = 0;
|
|
31
|
+
const tokens = line.map((t) => {
|
|
32
|
+
const level = levels[at] ?? 0;
|
|
33
|
+
let s = t.text;
|
|
34
|
+
if (level % 2 === 1) {
|
|
35
|
+
s = [...s].map((ch) => b.getMirroredCharacter(ch) ?? ch).join("");
|
|
36
|
+
if (!RTL_RE.test(s) && !/^\d/.test(s)) s = [...s].reverse().join("");
|
|
37
|
+
}
|
|
38
|
+
at += t.text.length;
|
|
39
|
+
return { ...t, text: s, level };
|
|
40
|
+
});
|
|
41
|
+
const max = Math.max(...tokens.map((t) => t.level));
|
|
42
|
+
const minOdd = Math.min(...tokens.map((t) => t.level % 2 === 1 ? t.level : Infinity));
|
|
43
|
+
for (let lvl = max; lvl >= minOdd && Number.isFinite(minOdd); lvl--) {
|
|
44
|
+
for (let i = 0; i < tokens.length; ) {
|
|
45
|
+
if (tokens[i].level < lvl) {
|
|
46
|
+
i++;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
let j = i;
|
|
50
|
+
while (j < tokens.length && tokens[j].level >= lvl) j++;
|
|
51
|
+
tokens.splice(i, j - i, ...tokens.slice(i, j).reverse());
|
|
52
|
+
i = j;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
for (const t of tokens) {
|
|
56
|
+
const last = out[out.length - 1];
|
|
57
|
+
const src = runs3[t.run];
|
|
58
|
+
if (last && last.text !== "\n" && last.__run === t.run) last.text += t.text;
|
|
59
|
+
else out.push({ ...src, text: t.text, __run: t.run });
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
for (const r of out) delete r.__run;
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/document/layout.ts
|
|
67
|
+
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("");
|
|
68
|
+
function contentColumnWidths(b, n, avail, measure, pad = 10) {
|
|
69
|
+
const min = new Array(n).fill(0);
|
|
70
|
+
const max = new Array(n).fill(0);
|
|
71
|
+
const rows = [[b.header, true], ...b.rows.map((r) => [r, false])];
|
|
72
|
+
for (const [r, head] of rows) {
|
|
73
|
+
for (let i = 0; i < n; i++) {
|
|
74
|
+
const t = plainInline(r[i] ?? []);
|
|
75
|
+
const longest = t.split(/\s+/).reduce((m, w) => Math.max(m, measure(w, head)), 0);
|
|
76
|
+
min[i] = Math.max(min[i], Math.min(longest, avail * 0.35) + pad);
|
|
77
|
+
max[i] = Math.max(max[i], measure(t, head) + pad);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const sumMax = max.reduce((a, x) => a + x, 0);
|
|
81
|
+
const sumMin = min.reduce((a, x) => a + x, 0);
|
|
82
|
+
if (sumMax <= avail) return max.map((m) => m + (avail - sumMax) / n);
|
|
83
|
+
if (sumMin >= avail) return min.map((m) => m / sumMin * avail);
|
|
84
|
+
const extra = avail - sumMin;
|
|
85
|
+
return min.map((m, i) => m + (max[i] - m) * (extra / (sumMax - sumMin)));
|
|
86
|
+
}
|
|
87
|
+
|
|
1
88
|
// src/document/math.ts
|
|
2
|
-
import {
|
|
89
|
+
import { listIslands, tokenizeSource } from "@ai-matrx/content-ir/source";
|
|
3
90
|
var MATH_OPEN = "\uE100";
|
|
4
91
|
var MATH_CLOSE = "\uE101";
|
|
5
92
|
var MATH_TOKEN_RE = /\uE100(\d+)\uE101/g;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (m[0].length === 0) re.lastIndex++;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
return ranges.sort((a, b) => a[0] - b[0]);
|
|
16
|
-
}
|
|
93
|
+
var DELIMITERS = {
|
|
94
|
+
$$: { open: 2, close: 2, display: true },
|
|
95
|
+
"\\[": { open: 2, close: 2, display: true },
|
|
96
|
+
$: { open: 1, close: 1, display: false },
|
|
97
|
+
"\\(": { open: 2, close: 2, display: false }
|
|
98
|
+
};
|
|
17
99
|
function extractMath(body) {
|
|
18
100
|
const spans = [];
|
|
19
101
|
if (!/[$\\]/.test(body)) return { text: body, spans };
|
|
20
|
-
const
|
|
21
|
-
const inCode = (i2) => code.some(([a, b]) => i2 >= a && i2 < b);
|
|
22
|
-
const display = pairDisplayMath(body);
|
|
102
|
+
const islands = listIslands(tokenizeSource(body)).filter((isl) => isl.complete && (isl.islandType === "math_block" || isl.islandType === "math_inline")).sort((a, b) => a.start - b.start);
|
|
23
103
|
let out = "";
|
|
24
|
-
let
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const close = display.get(i);
|
|
33
|
-
if (close !== void 0) {
|
|
34
|
-
out += token(body.slice(i + 2, close), true);
|
|
35
|
-
i = close + 2;
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
if (body[i + 1] !== "$" && body[i - 1] !== "$" && body[i - 1] !== "\\") {
|
|
39
|
-
const end = singleDollarMathEnd(body, i);
|
|
40
|
-
if (end > 0) {
|
|
41
|
-
out += token(body.slice(i + 1, end - 1), false);
|
|
42
|
-
i = end;
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
if (ch === "\\" && !inCode(i) && (body[i + 1] === "[" || body[i + 1] === "(")) {
|
|
48
|
-
const closer = body[i + 1] === "[" ? "\\]" : "\\)";
|
|
49
|
-
const end = body.indexOf(closer, i + 2);
|
|
50
|
-
if (end > i + 2 && end - i < 2e3 && (body[i + 1] === "[" || !body.slice(i, end).includes("\n\n"))) {
|
|
51
|
-
out += token(body.slice(i + 2, end), body[i + 1] === "[");
|
|
52
|
-
i = end + 2;
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
out += ch;
|
|
57
|
-
i++;
|
|
104
|
+
let at = 0;
|
|
105
|
+
for (const isl of islands) {
|
|
106
|
+
const d = DELIMITERS[String(isl.meta.delimiter ?? "")];
|
|
107
|
+
if (!d || isl.start < at) continue;
|
|
108
|
+
out += body.slice(at, isl.start);
|
|
109
|
+
spans.push({ tex: body.slice(isl.start + d.open, isl.end - d.close).trim(), display: d.display });
|
|
110
|
+
out += `${MATH_OPEN}${spans.length - 1}${MATH_CLOSE}`;
|
|
111
|
+
at = isl.end;
|
|
58
112
|
}
|
|
59
|
-
return { text: out, spans };
|
|
113
|
+
return { text: out + body.slice(at), spans };
|
|
60
114
|
}
|
|
61
115
|
var mathKey = (tex, display) => `${display ? "D" : "I"}:${tex}`;
|
|
62
116
|
var MATHJAX = /* @__PURE__ */ Symbol.for("ai-matrx.print.document.mathjax");
|
|
@@ -2045,6 +2099,8 @@ var CitationEngine = class {
|
|
|
2045
2099
|
/** A note style: every citation becomes a numbered note. */
|
|
2046
2100
|
isNote;
|
|
2047
2101
|
numeric;
|
|
2102
|
+
/** APA 7 §8.11: narrative citations join authors with "and", not "&". */
|
|
2103
|
+
apaNarrativeAnd;
|
|
2048
2104
|
constructor(styleXml, localeXml, items, lang) {
|
|
2049
2105
|
const byId = new Map(items.map((i) => [i.id, toCslJson(i)]));
|
|
2050
2106
|
const sys = {
|
|
@@ -2055,6 +2111,7 @@ var CitationEngine = class {
|
|
|
2055
2111
|
this.engine = new Engine(sys, styleXml, cslLocaleTag(lang), true);
|
|
2056
2112
|
this.engine.setOutputFormat("html");
|
|
2057
2113
|
this.isNote = this.engine.opt.xclass === "note";
|
|
2114
|
+
this.apaNarrativeAnd = /<title>[^<]*(?:\bAPA\b|American Psychological Association)/i.test(styleXml) || /<link[^>]*href="[^"]*\/styles\/apa(?:-[\w-]*)?"[^>]*rel="(?:self|template|independent-parent)"/i.test(styleXml);
|
|
2058
2115
|
this.numeric = /citation-number/.test(styleXml.slice(styleXml.indexOf("<citation"), styleXml.indexOf("</citation>")));
|
|
2059
2116
|
}
|
|
2060
2117
|
/** Cite a group; `noteIndex` is the note number for note styles (0 = in text). */
|
|
@@ -2091,7 +2148,8 @@ var CitationEngine = class {
|
|
|
2091
2148
|
if (opts.narrative && refs.length === 1 && !this.isNote && !this.numeric) {
|
|
2092
2149
|
const author = make(items);
|
|
2093
2150
|
const rest = make(refs.map((r, i) => ({ ...items[i], "author-only": void 0, "suppress-author": true, id: r.key })));
|
|
2094
|
-
const
|
|
2151
|
+
const who = () => this.apaNarrativeAnd ? author.label.replace(/,? & /g, (m) => m.startsWith(",") ? ", and " : " and ") : author.label;
|
|
2152
|
+
const join = () => `${who()}${rest.label ? ` ${/^\(/.test(rest.label) ? rest.label : `(${rest.label})`}` : ""}`;
|
|
2095
2153
|
const listeners = [];
|
|
2096
2154
|
author.listeners.push(() => listeners.forEach((l) => l(join())));
|
|
2097
2155
|
rest.listeners.push(() => listeners.forEach((l) => l(join())));
|
|
@@ -2570,7 +2628,7 @@ function runs(nodes, c, st = {}) {
|
|
|
2570
2628
|
for (const n of nodes) {
|
|
2571
2629
|
switch (n.type) {
|
|
2572
2630
|
case "text":
|
|
2573
|
-
out.push(tr(n.value));
|
|
2631
|
+
out.push(RTL_RE.test(n.value) ? tr(n.value, { rightToLeft: true }) : tr(n.value));
|
|
2574
2632
|
break;
|
|
2575
2633
|
case "strong":
|
|
2576
2634
|
out.push(...runs(n.children, c, { ...st, bold: true }));
|
|
@@ -2722,6 +2780,10 @@ function numberingRef(c, ordered, start) {
|
|
|
2722
2780
|
}
|
|
2723
2781
|
return ref;
|
|
2724
2782
|
}
|
|
2783
|
+
function bidiPara(nodes) {
|
|
2784
|
+
const text = nodes.map((n) => "value" in n ? n.value : "").join("");
|
|
2785
|
+
return isRtlParagraph(text) ? { bidirectional: true } : {};
|
|
2786
|
+
}
|
|
2725
2787
|
function blocks(list, c, o = {}) {
|
|
2726
2788
|
const out = [];
|
|
2727
2789
|
for (const b of list) out.push(...block(b, c, o));
|
|
@@ -2746,6 +2808,7 @@ function block(b, c, o) {
|
|
|
2746
2808
|
const kids = [...b.number ? [new d.TextRun({ text: `${b.number} `, color: HEX.muted })] : [], ...runs(b.children, c)];
|
|
2747
2809
|
return [
|
|
2748
2810
|
new d.Paragraph({
|
|
2811
|
+
...bidiPara(b.children),
|
|
2749
2812
|
heading,
|
|
2750
2813
|
keepNext: true,
|
|
2751
2814
|
keepLines: true,
|
|
@@ -2755,7 +2818,7 @@ function block(b, c, o) {
|
|
|
2755
2818
|
];
|
|
2756
2819
|
}
|
|
2757
2820
|
case "paragraph":
|
|
2758
|
-
return [para(c, o, { children: runs(b.children, c), widowControl: true })];
|
|
2821
|
+
return [para(c, o, { children: runs(b.children, c), widowControl: true, ...bidiPara(b.children) })];
|
|
2759
2822
|
case "list": {
|
|
2760
2823
|
const level = o.listLevel ?? 0;
|
|
2761
2824
|
const ref = numberingRef(c, b.ordered, b.start);
|
|
@@ -2772,7 +2835,14 @@ function block(b, c, o) {
|
|
|
2772
2835
|
})
|
|
2773
2836
|
);
|
|
2774
2837
|
} else {
|
|
2775
|
-
out.push(
|
|
2838
|
+
out.push(
|
|
2839
|
+
new d.Paragraph({
|
|
2840
|
+
numbering: { reference: ref, level, instance },
|
|
2841
|
+
children: firstRuns,
|
|
2842
|
+
widowControl: true,
|
|
2843
|
+
...first?.type === "paragraph" ? bidiPara(first.children) : {}
|
|
2844
|
+
})
|
|
2845
|
+
);
|
|
2776
2846
|
}
|
|
2777
2847
|
const tail = first && first.type !== "paragraph" ? [first, ...rest] : rest;
|
|
2778
2848
|
for (const child of tail) {
|
|
@@ -2800,6 +2870,8 @@ function block(b, c, o) {
|
|
|
2800
2870
|
case "table": {
|
|
2801
2871
|
const n = Math.max(b.header.length, ...b.rows.map((r) => r.length), 1);
|
|
2802
2872
|
const keep = b.rows.length <= 20;
|
|
2873
|
+
const cellPt = c.s.fontSize;
|
|
2874
|
+
const colTw = contentColumnWidths(b, n, c.width, (t, bold) => t.length * cellPt * (bold ? 0.55 : 0.5), 10).map((w) => Math.round(w * TW));
|
|
2803
2875
|
const alignOf = (i) => b.align[i] === "center" ? d.AlignmentType.CENTER : b.align[i] === "right" ? d.AlignmentType.RIGHT : d.AlignmentType.LEFT;
|
|
2804
2876
|
const border = { style: d.BorderStyle.SINGLE, size: 4, color: "E5E7EB" };
|
|
2805
2877
|
const none = { style: d.BorderStyle.NONE, size: 0, color: "FFFFFF" };
|
|
@@ -2809,11 +2881,13 @@ function block(b, c, o) {
|
|
|
2809
2881
|
children: Array.from(
|
|
2810
2882
|
{ length: n },
|
|
2811
2883
|
(_, i) => new d.TableCell({
|
|
2884
|
+
width: { size: colTw[i] ?? 0, type: d.WidthType.DXA },
|
|
2812
2885
|
...head ? { shading: { type: d.ShadingType.CLEAR, fill: "F3F4F6", color: "auto" } } : {},
|
|
2813
2886
|
margins: { top: 60, bottom: 60, left: 100, right: 100 },
|
|
2814
2887
|
borders: { top: none, left: none, right: none, bottom: head ? { ...border, size: 10, color: HEX.heading } : border },
|
|
2815
2888
|
children: [
|
|
2816
2889
|
new d.Paragraph({
|
|
2890
|
+
...bidiPara(cells[i] ?? []),
|
|
2817
2891
|
alignment: alignOf(i),
|
|
2818
2892
|
keepNext: keep && !last,
|
|
2819
2893
|
children: runs(cells[i] ?? [], c, head ? { bold: true } : {})
|
|
@@ -2826,7 +2900,9 @@ function block(b, c, o) {
|
|
|
2826
2900
|
if (b.caption) out.push(captionParagraph(b.caption, c, true));
|
|
2827
2901
|
out.push(
|
|
2828
2902
|
new d.Table({
|
|
2829
|
-
width: { size:
|
|
2903
|
+
width: { size: Math.round(c.width * TW), type: d.WidthType.DXA },
|
|
2904
|
+
columnWidths: colTw,
|
|
2905
|
+
layout: d.TableLayoutType.FIXED,
|
|
2830
2906
|
rows: [row(b.header, true, b.rows.length === 0), ...b.rows.map((r, i) => row(r, false, i === b.rows.length - 1))]
|
|
2831
2907
|
})
|
|
2832
2908
|
);
|
|
@@ -3029,7 +3105,7 @@ async function documentToDocx(tree, options = {}) {
|
|
|
3029
3105
|
default: {
|
|
3030
3106
|
// East-Asian text gets a CJK face (Word and LibreOffice substitute an installed one).
|
|
3031
3107
|
document: {
|
|
3032
|
-
run: { font: { ascii: "Cambria", hAnsi: "Cambria", cs: "
|
|
3108
|
+
run: { font: { ascii: "Cambria", hAnsi: "Cambria", cs: "Arial", eastAsia: "Yu Gothic" }, size, sizeComplexScript: size, color: HEX.text },
|
|
3033
3109
|
paragraph: { spacing: { after: 140, line: 300 } }
|
|
3034
3110
|
}
|
|
3035
3111
|
},
|
|
@@ -3217,10 +3293,10 @@ function renderBlockHtml(b, x) {
|
|
|
3217
3293
|
switch (b.type) {
|
|
3218
3294
|
case "heading": {
|
|
3219
3295
|
const num = b.number ? `<span class="doc-heading-number">${b.number}</span> ` : "";
|
|
3220
|
-
return `<h${b.level} id="${escapeHtml(b.id)}">${num}${renderInlineHtml(b.children, x)}</h${b.level}>`;
|
|
3296
|
+
return `<h${b.level} id="${escapeHtml(b.id)}" dir="auto">${num}${renderInlineHtml(b.children, x)}</h${b.level}>`;
|
|
3221
3297
|
}
|
|
3222
3298
|
case "paragraph":
|
|
3223
|
-
return `<p>${renderInlineHtml(b.children, x)}</p>`;
|
|
3299
|
+
return `<p dir="auto">${renderInlineHtml(b.children, x)}</p>`;
|
|
3224
3300
|
case "list": {
|
|
3225
3301
|
const tag = b.ordered ? "ol" : "ul";
|
|
3226
3302
|
const start = b.ordered && b.start !== 1 ? ` start="${b.start}"` : "";
|
|
@@ -3228,12 +3304,12 @@ function renderBlockHtml(b, x) {
|
|
|
3228
3304
|
const items = b.items.map((i) => {
|
|
3229
3305
|
const box = i.checked === null ? "" : `<span class="doc-task-box${i.checked ? " is-checked" : ""}" role="img" aria-label="${i.checked ? "done" : "not done"}"></span>`;
|
|
3230
3306
|
const inner = i.children.length === 1 && i.children[0]?.type === "paragraph" ? renderInlineHtml(i.children[0].children, x) : renderBlocksHtml(i.children, x);
|
|
3231
|
-
return `<li${i.checked !== null ? ' class="doc-task"' : ""}>${box}${inner}</li>`;
|
|
3307
|
+
return `<li dir="auto"${i.checked !== null ? ' class="doc-task"' : ""}>${box}${inner}</li>`;
|
|
3232
3308
|
}).join("");
|
|
3233
3309
|
return `<${tag}${start}${task ? ' class="doc-task-list"' : ""}>${items}</${tag}>`;
|
|
3234
3310
|
}
|
|
3235
3311
|
case "blockquote":
|
|
3236
|
-
return `<blockquote>${renderBlocksHtml(b.children, x)}</blockquote>`;
|
|
3312
|
+
return `<blockquote dir="auto">${renderBlocksHtml(b.children, x)}</blockquote>`;
|
|
3237
3313
|
case "code":
|
|
3238
3314
|
return `<pre class="doc-code"${b.lang ? ` data-language="${escapeHtml(b.lang)}"` : ""}><code>${escapeHtml(
|
|
3239
3315
|
b.value
|
|
@@ -3243,8 +3319,8 @@ function renderBlockHtml(b, x) {
|
|
|
3243
3319
|
const a = b.align[i];
|
|
3244
3320
|
return a ? ` style="text-align:${a}"` : "";
|
|
3245
3321
|
};
|
|
3246
|
-
const head = `<thead><tr>${b.header.map((c, i) => `<th${al(i)}>${renderInlineHtml(c, x)}</th>`).join("")}</tr></thead>`;
|
|
3247
|
-
const body = `<tbody>${b.rows.map((r) => `<tr>${r.map((c, i) => `<td${al(i)}>${renderInlineHtml(c, x)}</td>`).join("")}</tr>`).join("")}</tbody>`;
|
|
3322
|
+
const head = `<thead><tr>${b.header.map((c, i) => `<th dir="auto"${al(i)}>${renderInlineHtml(c, x)}</th>`).join("")}</tr></thead>`;
|
|
3323
|
+
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>`;
|
|
3248
3324
|
const id = b.caption?.id ? ` id="tbl:${escapeHtml(b.caption.id)}"` : "";
|
|
3249
3325
|
return `<div class="doc-table-wrap"${id}><table>${b.caption ? captionHtml(b.caption, "caption", x) : ""}${head}${body}</table></div>`;
|
|
3250
3326
|
}
|
|
@@ -3714,7 +3790,7 @@ function breakRuns(input, width, t) {
|
|
|
3714
3790
|
for (let guard = 0; guard < 50 && atoms.length; guard++) {
|
|
3715
3791
|
const gw = atoms.reduce((n, a) => n + w(a), 0);
|
|
3716
3792
|
const sw = pendingSpace && line > 0 ? w(pendingSpace) : 0;
|
|
3717
|
-
if (line + sw + gw <= W || line === 0 && atoms.length !== 1) {
|
|
3793
|
+
if (line + sw + gw <= W || line === 0 && (atoms.length !== 1 || gw <= width)) {
|
|
3718
3794
|
if (sw && pendingSpace) out.push(pendingSpace);
|
|
3719
3795
|
out.push(...atoms);
|
|
3720
3796
|
line += sw + gw;
|
|
@@ -3838,11 +3914,14 @@ function withFallbackFonts(nodes, c) {
|
|
|
3838
3914
|
const fb = fallbackFor(cp);
|
|
3839
3915
|
if (fb) {
|
|
3840
3916
|
flush();
|
|
3841
|
-
|
|
3917
|
+
const prev = out[out.length - 1];
|
|
3918
|
+
if (prev && typeof prev === "object" && prev.font === fb && prev.__fb) prev.text = `${prev.text}${ch}`;
|
|
3919
|
+
else out.push({ ...n, text: ch, font: fb, bold: false, italics: false, __fb: true });
|
|
3842
3920
|
} else dropped = true;
|
|
3843
3921
|
}
|
|
3844
3922
|
flush();
|
|
3845
3923
|
}
|
|
3924
|
+
for (const o of out) if (typeof o === "object") delete o.__fb;
|
|
3846
3925
|
if (dropped && !c.glyphs.announced) {
|
|
3847
3926
|
c.glyphs.announced = true;
|
|
3848
3927
|
c.tree.notices.push({
|
|
@@ -3869,8 +3948,13 @@ function drawable(text, c) {
|
|
|
3869
3948
|
}
|
|
3870
3949
|
function bodyRuns(nodes, c, width = c.width) {
|
|
3871
3950
|
const r = runs2(nodes, c);
|
|
3872
|
-
|
|
3951
|
+
const text = plain(nodes);
|
|
3952
|
+
if (!RTL_RE.test(text)) return c.typo ? breakRuns(r, width, c.typo) : r;
|
|
3953
|
+
const typo = c.typo ?? (c.faces ? { split: (w) => [w], measure: c.faces.measure } : void 0);
|
|
3954
|
+
const lines = typo ? breakRuns(r, width, typo) : r;
|
|
3955
|
+
return visualOrder(lines, isRtlParagraph(text));
|
|
3873
3956
|
}
|
|
3957
|
+
var alignFor = (nodes) => isRtlParagraph(plain(nodes)) ? { alignment: "right" } : {};
|
|
3874
3958
|
function runs2(nodes, c, st = {}) {
|
|
3875
3959
|
const out = [];
|
|
3876
3960
|
const base = (raw) => {
|
|
@@ -4061,25 +4145,7 @@ function estimateLines(text, c, size = c.s.fontSize) {
|
|
|
4061
4145
|
}
|
|
4062
4146
|
function columnWidths(b, n, size, c) {
|
|
4063
4147
|
const measure = (text, bold) => c.faces ? c.faces.measure(text, { bold, fontSize: size }) : text.length * size * 0.5;
|
|
4064
|
-
|
|
4065
|
-
const min = new Array(n).fill(0);
|
|
4066
|
-
const max = new Array(n).fill(0);
|
|
4067
|
-
const rows = [[b.header, true], ...b.rows.map((r) => [r, false])];
|
|
4068
|
-
for (const [r, head] of rows) {
|
|
4069
|
-
for (let i = 0; i < n; i++) {
|
|
4070
|
-
const t = plain(r[i] ?? []);
|
|
4071
|
-
const longest = t.split(/\s+/).reduce((m, w) => Math.max(m, measure(w, head)), 0);
|
|
4072
|
-
min[i] = Math.max(min[i], Math.min(longest, c.width * 0.35) + pad);
|
|
4073
|
-
max[i] = Math.max(max[i], measure(t, head) + pad);
|
|
4074
|
-
}
|
|
4075
|
-
}
|
|
4076
|
-
const avail = c.width;
|
|
4077
|
-
const sumMax = max.reduce((a, x) => a + x, 0);
|
|
4078
|
-
const sumMin = min.reduce((a, x) => a + x, 0);
|
|
4079
|
-
if (sumMax <= avail) return max.map((m) => m + (avail - sumMax) / n);
|
|
4080
|
-
if (sumMin >= avail) return min.map((m) => m / sumMin * avail);
|
|
4081
|
-
const extra = avail - sumMin;
|
|
4082
|
-
return min.map((m, i) => m + (max[i] - m) * (extra / (sumMax - sumMin)));
|
|
4148
|
+
return contentColumnWidths(b, n, c.width, measure);
|
|
4083
4149
|
}
|
|
4084
4150
|
var plain = (nodes) => nodes.map((n) => "value" in n ? n.value : "label" in n ? n.label : "children" in n ? plain(n.children) : "").join("");
|
|
4085
4151
|
function taskItem(checked, content, c) {
|
|
@@ -4134,8 +4200,11 @@ function block2(b, c) {
|
|
|
4134
4200
|
switch (b.type) {
|
|
4135
4201
|
case "heading": {
|
|
4136
4202
|
const rel = Math.min(6, b.level - c.minLevel + 1);
|
|
4203
|
+
const rtlHeading = RTL_RE.test(b.plain);
|
|
4204
|
+
const headRuns = rtlHeading ? visualOrder(runs2(b.children, c), isRtlParagraph(b.plain)) : runs2(b.children, c);
|
|
4137
4205
|
const node = {
|
|
4138
|
-
|
|
4206
|
+
...rtlHeading ? alignFor(b.children) : {},
|
|
4207
|
+
text: [...b.number ? [{ text: `${b.number} `, color: COLOR.muted }] : [], ...headRuns],
|
|
4139
4208
|
style: `h${Math.min(6, b.level)}`,
|
|
4140
4209
|
headlineLevel: b.level,
|
|
4141
4210
|
id: b.id
|
|
@@ -4149,12 +4218,12 @@ function block2(b, c) {
|
|
|
4149
4218
|
}
|
|
4150
4219
|
case "paragraph": {
|
|
4151
4220
|
const text = bodyRuns(b.children, c);
|
|
4152
|
-
const p = { text, style: "p" };
|
|
4221
|
+
const p = { text, style: "p", ...alignFor(b.children) };
|
|
4153
4222
|
return estimateLines(plain(b.children), c) <= 5 ? { stack: [p], unbreakable: true } : p;
|
|
4154
4223
|
}
|
|
4155
4224
|
case "list": {
|
|
4156
4225
|
const items = b.items.map((it) => {
|
|
4157
|
-
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 });
|
|
4226
|
+
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 });
|
|
4158
4227
|
if (it.checked !== null) return { ...taskItem(it.checked, content, c), listType: "none" };
|
|
4159
4228
|
return content.length === 1 ? content[0] : { stack: content };
|
|
4160
4229
|
});
|
|
@@ -4207,15 +4276,24 @@ function block2(b, c) {
|
|
|
4207
4276
|
case "table": {
|
|
4208
4277
|
const n = Math.max(b.header.length, ...b.rows.map((r) => r.length), 1);
|
|
4209
4278
|
const size = c.s.fontSize * 0.92;
|
|
4279
|
+
const widths = columnWidths(b, n, size, c);
|
|
4280
|
+
const cellRuns = (inl, i, head) => {
|
|
4281
|
+
const r = runs2(inl, c).map((x) => typeof x === "object" ? { ...x, fontSize: x.fontSize ?? size, ...head ? { bold: true } : {} } : x);
|
|
4282
|
+
const w = (widths[i] ?? c.width) - 10;
|
|
4283
|
+
if (RTL_RE.test(plain(inl))) {
|
|
4284
|
+
const typo = c.typo ?? (c.faces ? { split: (x) => [x], measure: c.faces.measure } : void 0);
|
|
4285
|
+
return visualOrder(typo ? breakRuns(r, w, typo) : r, isRtlParagraph(plain(inl)));
|
|
4286
|
+
}
|
|
4287
|
+
return c.typo && !head ? breakRuns(r, w, c.typo) : r;
|
|
4288
|
+
};
|
|
4210
4289
|
const cell = (inl, i, head) => ({
|
|
4211
|
-
text:
|
|
4290
|
+
text: cellRuns(inl ?? [], i, head),
|
|
4212
4291
|
bold: head,
|
|
4213
|
-
alignment: b.align[i] ?? "left",
|
|
4292
|
+
alignment: b.align[i] ?? (isRtlParagraph(plain(inl ?? [])) ? "right" : "left"),
|
|
4214
4293
|
fontSize: size,
|
|
4215
4294
|
color: head ? COLOR.heading : COLOR.text
|
|
4216
4295
|
});
|
|
4217
4296
|
const row = (r, head) => Array.from({ length: n }, (_, i) => cell(r[i], i, head));
|
|
4218
|
-
const widths = columnWidths(b, n, size, c);
|
|
4219
4297
|
const lineH = size * 1.3;
|
|
4220
4298
|
const pageH = c.pageHeight;
|
|
4221
4299
|
const rowHeight = (r) => Math.max(...r.map((inl, i) => estimateLines(plain(inl), { ...c, width: (widths[i] ?? c.width) - 10 }, size) * lineH)) + 8;
|
|
@@ -4461,12 +4539,16 @@ async function loadTypography(tree, loaders = {}) {
|
|
|
4461
4539
|
const base = await loadFaces(vfs, s.fontSize);
|
|
4462
4540
|
let needEmoji = false;
|
|
4463
4541
|
let needSymbols = false;
|
|
4542
|
+
let needArabic = false;
|
|
4543
|
+
let needHebrew = false;
|
|
4464
4544
|
const cjk = /* @__PURE__ */ new Set();
|
|
4465
4545
|
for (const ch of documentText(tree)) {
|
|
4466
4546
|
const cp = ch.codePointAt(0);
|
|
4467
4547
|
if (cp < 32 || cp === 8205 || cp === 65039 || base.body(cp)) continue;
|
|
4468
4548
|
if (EMOJI_CP.test(ch)) needEmoji = true;
|
|
4469
|
-
else if (isCjk(cp)) cjk.add(cp);
|
|
4549
|
+
else if (isCjk(cp) || isHangul(cp)) cjk.add(cp);
|
|
4550
|
+
else if (isArabic(cp)) needArabic = true;
|
|
4551
|
+
else if (isHebrew(cp)) needHebrew = true;
|
|
4470
4552
|
else needSymbols = true;
|
|
4471
4553
|
}
|
|
4472
4554
|
const fallbacks = [];
|
|
@@ -4478,6 +4560,14 @@ async function loadTypography(tree, loaders = {}) {
|
|
|
4478
4560
|
const b = await registerFace("NotoSymbols", "NotoSansMath.woff", loaders.symbols ?? defaultSymbolFontLoader, tree, "symbols");
|
|
4479
4561
|
if (b) fallbacks.push({ font: "NotoSymbols", bytes: b });
|
|
4480
4562
|
}
|
|
4563
|
+
if (needArabic) {
|
|
4564
|
+
const b = await registerFace("NotoArabic", "NotoSansArabic.woff", loaders.arabic ?? urlLoader(NOTO_ARABIC_URL), tree, "Arabic");
|
|
4565
|
+
if (b) fallbacks.push({ font: "NotoArabic", bytes: b });
|
|
4566
|
+
}
|
|
4567
|
+
if (needHebrew) {
|
|
4568
|
+
const b = await registerFace("NotoHebrew", "NotoSansHebrew.woff", loaders.hebrew ?? urlLoader(NOTO_HEBREW_URL), tree, "Hebrew");
|
|
4569
|
+
if (b) fallbacks.push({ font: "NotoHebrew", bytes: b });
|
|
4570
|
+
}
|
|
4481
4571
|
if (cjk.size) {
|
|
4482
4572
|
let subsets = [];
|
|
4483
4573
|
try {
|
|
@@ -4493,7 +4583,7 @@ async function loadTypography(tree, loaders = {}) {
|
|
|
4493
4583
|
if (!subsets.length) {
|
|
4494
4584
|
tree.notices.push({
|
|
4495
4585
|
code: "glyph-substituted",
|
|
4496
|
-
message: "The Japanese /
|
|
4586
|
+
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).",
|
|
4497
4587
|
remedy: "Check the internet connection (the font comes from cdn.jsdelivr.net) and export again."
|
|
4498
4588
|
});
|
|
4499
4589
|
}
|
|
@@ -4521,6 +4611,12 @@ var urlLoader = (url) => async () => {
|
|
|
4521
4611
|
var defaultEmojiFontLoader = urlLoader(NOTO_EMOJI_URL);
|
|
4522
4612
|
var defaultSymbolFontLoader = urlLoader(NOTO_SYMBOLS_URL);
|
|
4523
4613
|
var NOTO_CJK_BASE = "https://cdn.jsdelivr.net/npm/@fontsource/noto-sans-jp@5.3.0";
|
|
4614
|
+
var NOTO_KR_BASE = "https://cdn.jsdelivr.net/npm/@fontsource/noto-sans-kr@5.3.0";
|
|
4615
|
+
var NOTO_ARABIC_URL = "https://cdn.jsdelivr.net/npm/@fontsource/noto-sans-arabic@5.3.0/files/noto-sans-arabic-arabic-400-normal.woff";
|
|
4616
|
+
var NOTO_HEBREW_URL = "https://cdn.jsdelivr.net/npm/@fontsource/noto-sans-hebrew@5.3.0/files/noto-sans-hebrew-hebrew-400-normal.woff";
|
|
4617
|
+
var isHangul = (cp) => cp >= 44032 && cp <= 55215 || cp >= 4352 && cp <= 4607 || cp >= 12592 && cp <= 12687;
|
|
4618
|
+
var isArabic = (cp) => cp >= 1536 && cp <= 1791 || cp >= 1872 && cp <= 1919 || cp >= 2208 && cp <= 2303 || cp >= 64336 && cp <= 65023 || cp >= 65136 && cp <= 65279;
|
|
4619
|
+
var isHebrew = (cp) => cp >= 1424 && cp <= 1535 || cp >= 64285 && cp <= 64335;
|
|
4524
4620
|
function parseUnicodeRangeCss(css) {
|
|
4525
4621
|
const out = [];
|
|
4526
4622
|
for (const block3 of css.split("@font-face").slice(1)) {
|
|
@@ -4536,19 +4632,25 @@ function parseUnicodeRangeCss(css) {
|
|
|
4536
4632
|
}
|
|
4537
4633
|
return out;
|
|
4538
4634
|
}
|
|
4539
|
-
|
|
4635
|
+
async function unicodeRangeFaces(base, cps) {
|
|
4540
4636
|
if (typeof fetch !== "function" || cps.length === 0) return [];
|
|
4541
|
-
const res = await fetch(`${
|
|
4637
|
+
const res = await fetch(`${base}/400.css`);
|
|
4542
4638
|
if (!res.ok) return [];
|
|
4543
4639
|
const faces = parseUnicodeRangeCss(await res.text()).filter((f) => cps.some((cp) => f.ranges.some(([a, b]) => cp >= a && cp <= b)));
|
|
4544
4640
|
const out = [];
|
|
4545
4641
|
await Promise.all(
|
|
4546
4642
|
faces.map(async (f) => {
|
|
4547
|
-
const r = await fetch(`${
|
|
4643
|
+
const r = await fetch(`${base}/files/${f.file}.woff`);
|
|
4548
4644
|
if (r.ok) out.push({ name: f.file, bytes: new Uint8Array(await r.arrayBuffer()) });
|
|
4549
4645
|
})
|
|
4550
4646
|
);
|
|
4551
4647
|
return out;
|
|
4648
|
+
}
|
|
4649
|
+
var defaultCjkFontLoader = async (cps) => {
|
|
4650
|
+
const kr = cps.filter(isHangul);
|
|
4651
|
+
const jp = cps.filter((cp) => !isHangul(cp));
|
|
4652
|
+
const [a, b] = await Promise.all([unicodeRangeFaces(NOTO_KR_BASE, kr), unicodeRangeFaces(NOTO_CJK_BASE, jp)]);
|
|
4653
|
+
return [...a, ...b];
|
|
4552
4654
|
};
|
|
4553
4655
|
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;
|
|
4554
4656
|
var EMOJI_CP = /\p{Extended_Pictographic}|\p{Regional_Indicator}|\u20E3/u;
|
|
@@ -4867,7 +4969,7 @@ function uniqueId(ctx, base) {
|
|
|
4867
4969
|
return id;
|
|
4868
4970
|
}
|
|
4869
4971
|
var CITE_GROUP_RE = /\[(-?@[^[\]]+)\]/g;
|
|
4870
|
-
var BARE_RE = /(^|[^\w@.\\])@((?:fig|tbl|sec|eq):[\w-]+|[A-Za-z0-9_](?:[\w:.#$%&+?<>~/-]*\w)?)
|
|
4972
|
+
var BARE_RE = /(^|[^\w@.\\])@((?:fig|tbl|sec|eq):[\w-]+|[A-Za-z0-9_](?:[\w:.#$%&+?<>~/-]*\w)?)(?: \[([^\[\]@]+)\])?/g;
|
|
4871
4973
|
function scanText(value, ctx) {
|
|
4872
4974
|
const out = [];
|
|
4873
4975
|
const pushText = (s) => {
|
|
@@ -4888,8 +4990,8 @@ function scanText(value, ctx) {
|
|
|
4888
4990
|
last2 = start + key.length + 1;
|
|
4889
4991
|
} else if (ctx.refs.has(key)) {
|
|
4890
4992
|
pushText(s.slice(last2, start));
|
|
4891
|
-
out.push(cite([{ key, suppressAuthor: false }], ctx, true));
|
|
4892
|
-
last2 = start + key.length + 1;
|
|
4993
|
+
out.push(cite([{ key, locator: m[3]?.trim(), suppressAuthor: false }], ctx, true));
|
|
4994
|
+
last2 = start + key.length + 1 + (m[3] !== void 0 ? m[3].length + 3 : 0);
|
|
4893
4995
|
}
|
|
4894
4996
|
}
|
|
4895
4997
|
pushText(s.slice(last2));
|
|
@@ -5630,8 +5732,11 @@ export {
|
|
|
5630
5732
|
CitationEngine,
|
|
5631
5733
|
DEFAULT_FOOTER,
|
|
5632
5734
|
DOCUMENT_FORMATS,
|
|
5735
|
+
NOTO_ARABIC_URL,
|
|
5633
5736
|
NOTO_CJK_BASE,
|
|
5634
5737
|
NOTO_EMOJI_URL,
|
|
5738
|
+
NOTO_HEBREW_URL,
|
|
5739
|
+
NOTO_KR_BASE,
|
|
5635
5740
|
NOTO_SYMBOLS_URL,
|
|
5636
5741
|
PAGE_SIZES,
|
|
5637
5742
|
buildPdfDefinition,
|