@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/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 { pairDisplayMath, singleDollarMathEnd } from "@ai-matrx/content-ir/source";
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
- function codeRanges(text) {
7
- const ranges = [];
8
- for (const re of [/```[\s\S]*?(?:```|$)/g, /~~~[\s\S]*?(?:~~~|$)/g, /`[^`\n]*`/g]) {
9
- let m;
10
- while ((m = re.exec(text)) !== null) {
11
- ranges.push([m.index, m.index + m[0].length]);
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 code = codeRanges(body);
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 i = 0;
25
- const token = (tex, isDisplay) => {
26
- spans.push({ tex: tex.trim(), display: isDisplay });
27
- return `${MATH_OPEN}${spans.length - 1}${MATH_CLOSE}`;
28
- };
29
- while (i < body.length) {
30
- const ch = body[i];
31
- if (ch === "$" && !inCode(i)) {
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 join = () => `${author.label}${rest.label ? ` ${/^\(/.test(rest.label) ? rest.label : `(${rest.label})`}` : ""}`;
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(new d.Paragraph({ numbering: { reference: ref, level, instance }, children: firstRuns, widowControl: true }));
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: 100, type: d.WidthType.PERCENTAGE },
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: "Cambria", eastAsia: "Yu Gothic" }, size, color: HEX.text },
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
- out.push({ ...n, text: ch, font: fb, bold: false, italics: false });
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
- return c.typo ? breakRuns(r, width, c.typo) : r;
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
- const pad = 10;
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
- text: [...b.number ? [{ text: `${b.number} `, color: COLOR.muted }] : [], ...runs2(b.children, c)],
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: runs2(inl ?? [], c),
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 / Chinese font could not be loaded, so those characters were left out of the PDF (Word, EPUB and HTML keep them).",
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
- var defaultCjkFontLoader = async (cps) => {
4635
+ async function unicodeRangeFaces(base, cps) {
4540
4636
  if (typeof fetch !== "function" || cps.length === 0) return [];
4541
- const res = await fetch(`${NOTO_CJK_BASE}/400.css`);
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(`${NOTO_CJK_BASE}/files/${f.file}.woff`);
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)?)/g;
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,