@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/dist/document.d.cts
CHANGED
|
@@ -322,14 +322,22 @@ interface FallbackFontLoaders {
|
|
|
322
322
|
emoji?: FontLoader | undefined;
|
|
323
323
|
symbols?: FontLoader | undefined;
|
|
324
324
|
cjk?: CjkFontLoader | undefined;
|
|
325
|
+
arabic?: FontLoader | undefined;
|
|
326
|
+
hebrew?: FontLoader | undefined;
|
|
325
327
|
}
|
|
326
328
|
/** Noto Sans JP (SIL OFL) from fontsource: 124 unicode-range subsets; only the needed ones load. */
|
|
327
329
|
declare const NOTO_CJK_BASE = "https://cdn.jsdelivr.net/npm/@fontsource/noto-sans-jp@5.3.0";
|
|
330
|
+
/** Noto Sans KR (SIL OFL) — Hangul, same unicode-range scheme. */
|
|
331
|
+
declare const NOTO_KR_BASE = "https://cdn.jsdelivr.net/npm/@fontsource/noto-sans-kr@5.3.0";
|
|
332
|
+
/** Noto Sans Arabic / Hebrew (SIL OFL) — one script subset each. */
|
|
333
|
+
declare const NOTO_ARABIC_URL = "https://cdn.jsdelivr.net/npm/@fontsource/noto-sans-arabic@5.3.0/files/noto-sans-arabic-arabic-400-normal.woff";
|
|
334
|
+
declare const NOTO_HEBREW_URL = "https://cdn.jsdelivr.net/npm/@fontsource/noto-sans-hebrew@5.3.0/files/noto-sans-hebrew-hebrew-400-normal.woff";
|
|
328
335
|
/** Parse a fontsource stylesheet: each face's file stem and its unicode ranges. */
|
|
329
336
|
declare function parseUnicodeRangeCss(css: string): Array<{
|
|
330
337
|
file: string;
|
|
331
338
|
ranges: Array<[number, number]>;
|
|
332
339
|
}>;
|
|
340
|
+
/** Hangul from Noto Sans KR, every other CJK character from Noto Sans JP. */
|
|
333
341
|
declare const defaultCjkFontLoader: CjkFontLoader;
|
|
334
342
|
/**
|
|
335
343
|
* Words pdfmake did not put on a page: a text node with no position was never
|
|
@@ -416,6 +424,8 @@ declare class CitationEngine {
|
|
|
416
424
|
/** A note style: every citation becomes a numbered note. */
|
|
417
425
|
readonly isNote: boolean;
|
|
418
426
|
private readonly numeric;
|
|
427
|
+
/** APA 7 §8.11: narrative citations join authors with "and", not "&". */
|
|
428
|
+
private readonly apaNarrativeAnd;
|
|
419
429
|
constructor(styleXml: string, localeXml: string, items: CslItem[], lang: string);
|
|
420
430
|
/** Cite a group; `noteIndex` is the note number for note styles (0 = in text). */
|
|
421
431
|
cite(refs: CiteRef[], opts?: {
|
|
@@ -671,4 +681,4 @@ declare function exportDocument(source: string, format: DocumentFormat, options?
|
|
|
671
681
|
/** Browser helper: save an export through a download link. */
|
|
672
682
|
declare function downloadDocumentExport(exp: DocumentExport): void;
|
|
673
683
|
|
|
674
|
-
export { BUNDLED_STYLE_IDS, type BibliographyEntry, type Block, CSL_STYLE_ALIASES, type Caption, CitationEngine, type CitationStyle, type CjkFontLoader, type CslFetcher, type CslItem, type CslName, DEFAULT_FOOTER, DOCUMENT_FORMATS, type DocSection, type DocTree, type DocumentExport, type DocumentFormat, type DocumentNotice, type DocumentSettings, type ExportDocumentOptions, type FallbackFontLoaders, type FontLoader, type ImageLoader, type ImageMap, type Inline, type ListItem, type Margins, type MathSpan, NOTO_CJK_BASE, NOTO_EMOJI_URL, NOTO_SYMBOLS_URL, type Orientation, PAGE_SIZES, type PageSizeName, type ParseDocumentOptions, type PrepareDocumentOptions, type ResolvedImage, type RunningTemplate, type TocEntry, type TypesetMath, buildPdfDefinition, countUnplacedWords, cslStyleId, defaultCjkFontLoader, defaultCslFetcher, defaultEmojiFontLoader, defaultImageLoader, defaultSymbolFontLoader, documentCss, documentToDocx, documentToEpub, documentToHtml, documentToPdf, downloadDocumentExport, exportDocument, extractMath, fillTemplate, loadCslLocale, loadCslStyle, mathKey, normalizeReference, parseCitationGroup, parseDocument, parseLength, parseLocator, parseMathML, parseUnicodeRangeCss, plainText, prepareDocument, renderBlocksHtml, renderPdfDefinition, resolveDocumentImages, resolveSettings, scanCitationKeys, splitFrontmatter, typesetDocumentMath };
|
|
684
|
+
export { BUNDLED_STYLE_IDS, type BibliographyEntry, type Block, CSL_STYLE_ALIASES, type Caption, CitationEngine, type CitationStyle, type CjkFontLoader, type CslFetcher, type CslItem, type CslName, DEFAULT_FOOTER, DOCUMENT_FORMATS, type DocSection, type DocTree, type DocumentExport, type DocumentFormat, type DocumentNotice, type DocumentSettings, type ExportDocumentOptions, type FallbackFontLoaders, type FontLoader, type ImageLoader, type ImageMap, type Inline, type ListItem, type Margins, type MathSpan, NOTO_ARABIC_URL, NOTO_CJK_BASE, NOTO_EMOJI_URL, NOTO_HEBREW_URL, NOTO_KR_BASE, NOTO_SYMBOLS_URL, type Orientation, PAGE_SIZES, type PageSizeName, type ParseDocumentOptions, type PrepareDocumentOptions, type ResolvedImage, type RunningTemplate, type TocEntry, type TypesetMath, buildPdfDefinition, countUnplacedWords, cslStyleId, defaultCjkFontLoader, defaultCslFetcher, defaultEmojiFontLoader, defaultImageLoader, defaultSymbolFontLoader, documentCss, documentToDocx, documentToEpub, documentToHtml, documentToPdf, downloadDocumentExport, exportDocument, extractMath, fillTemplate, loadCslLocale, loadCslStyle, mathKey, normalizeReference, parseCitationGroup, parseDocument, parseLength, parseLocator, parseMathML, parseUnicodeRangeCss, plainText, prepareDocument, renderBlocksHtml, renderPdfDefinition, resolveDocumentImages, resolveSettings, scanCitationKeys, splitFrontmatter, typesetDocumentMath };
|
package/dist/document.d.ts
CHANGED
|
@@ -322,14 +322,22 @@ interface FallbackFontLoaders {
|
|
|
322
322
|
emoji?: FontLoader | undefined;
|
|
323
323
|
symbols?: FontLoader | undefined;
|
|
324
324
|
cjk?: CjkFontLoader | undefined;
|
|
325
|
+
arabic?: FontLoader | undefined;
|
|
326
|
+
hebrew?: FontLoader | undefined;
|
|
325
327
|
}
|
|
326
328
|
/** Noto Sans JP (SIL OFL) from fontsource: 124 unicode-range subsets; only the needed ones load. */
|
|
327
329
|
declare const NOTO_CJK_BASE = "https://cdn.jsdelivr.net/npm/@fontsource/noto-sans-jp@5.3.0";
|
|
330
|
+
/** Noto Sans KR (SIL OFL) — Hangul, same unicode-range scheme. */
|
|
331
|
+
declare const NOTO_KR_BASE = "https://cdn.jsdelivr.net/npm/@fontsource/noto-sans-kr@5.3.0";
|
|
332
|
+
/** Noto Sans Arabic / Hebrew (SIL OFL) — one script subset each. */
|
|
333
|
+
declare const NOTO_ARABIC_URL = "https://cdn.jsdelivr.net/npm/@fontsource/noto-sans-arabic@5.3.0/files/noto-sans-arabic-arabic-400-normal.woff";
|
|
334
|
+
declare const NOTO_HEBREW_URL = "https://cdn.jsdelivr.net/npm/@fontsource/noto-sans-hebrew@5.3.0/files/noto-sans-hebrew-hebrew-400-normal.woff";
|
|
328
335
|
/** Parse a fontsource stylesheet: each face's file stem and its unicode ranges. */
|
|
329
336
|
declare function parseUnicodeRangeCss(css: string): Array<{
|
|
330
337
|
file: string;
|
|
331
338
|
ranges: Array<[number, number]>;
|
|
332
339
|
}>;
|
|
340
|
+
/** Hangul from Noto Sans KR, every other CJK character from Noto Sans JP. */
|
|
333
341
|
declare const defaultCjkFontLoader: CjkFontLoader;
|
|
334
342
|
/**
|
|
335
343
|
* Words pdfmake did not put on a page: a text node with no position was never
|
|
@@ -416,6 +424,8 @@ declare class CitationEngine {
|
|
|
416
424
|
/** A note style: every citation becomes a numbered note. */
|
|
417
425
|
readonly isNote: boolean;
|
|
418
426
|
private readonly numeric;
|
|
427
|
+
/** APA 7 §8.11: narrative citations join authors with "and", not "&". */
|
|
428
|
+
private readonly apaNarrativeAnd;
|
|
419
429
|
constructor(styleXml: string, localeXml: string, items: CslItem[], lang: string);
|
|
420
430
|
/** Cite a group; `noteIndex` is the note number for note styles (0 = in text). */
|
|
421
431
|
cite(refs: CiteRef[], opts?: {
|
|
@@ -671,4 +681,4 @@ declare function exportDocument(source: string, format: DocumentFormat, options?
|
|
|
671
681
|
/** Browser helper: save an export through a download link. */
|
|
672
682
|
declare function downloadDocumentExport(exp: DocumentExport): void;
|
|
673
683
|
|
|
674
|
-
export { BUNDLED_STYLE_IDS, type BibliographyEntry, type Block, CSL_STYLE_ALIASES, type Caption, CitationEngine, type CitationStyle, type CjkFontLoader, type CslFetcher, type CslItem, type CslName, DEFAULT_FOOTER, DOCUMENT_FORMATS, type DocSection, type DocTree, type DocumentExport, type DocumentFormat, type DocumentNotice, type DocumentSettings, type ExportDocumentOptions, type FallbackFontLoaders, type FontLoader, type ImageLoader, type ImageMap, type Inline, type ListItem, type Margins, type MathSpan, NOTO_CJK_BASE, NOTO_EMOJI_URL, NOTO_SYMBOLS_URL, type Orientation, PAGE_SIZES, type PageSizeName, type ParseDocumentOptions, type PrepareDocumentOptions, type ResolvedImage, type RunningTemplate, type TocEntry, type TypesetMath, buildPdfDefinition, countUnplacedWords, cslStyleId, defaultCjkFontLoader, defaultCslFetcher, defaultEmojiFontLoader, defaultImageLoader, defaultSymbolFontLoader, documentCss, documentToDocx, documentToEpub, documentToHtml, documentToPdf, downloadDocumentExport, exportDocument, extractMath, fillTemplate, loadCslLocale, loadCslStyle, mathKey, normalizeReference, parseCitationGroup, parseDocument, parseLength, parseLocator, parseMathML, parseUnicodeRangeCss, plainText, prepareDocument, renderBlocksHtml, renderPdfDefinition, resolveDocumentImages, resolveSettings, scanCitationKeys, splitFrontmatter, typesetDocumentMath };
|
|
684
|
+
export { BUNDLED_STYLE_IDS, type BibliographyEntry, type Block, CSL_STYLE_ALIASES, type Caption, CitationEngine, type CitationStyle, type CjkFontLoader, type CslFetcher, type CslItem, type CslName, DEFAULT_FOOTER, DOCUMENT_FORMATS, type DocSection, type DocTree, type DocumentExport, type DocumentFormat, type DocumentNotice, type DocumentSettings, type ExportDocumentOptions, type FallbackFontLoaders, type FontLoader, type ImageLoader, type ImageMap, type Inline, type ListItem, type Margins, type MathSpan, NOTO_ARABIC_URL, NOTO_CJK_BASE, NOTO_EMOJI_URL, NOTO_HEBREW_URL, NOTO_KR_BASE, NOTO_SYMBOLS_URL, type Orientation, PAGE_SIZES, type PageSizeName, type ParseDocumentOptions, type PrepareDocumentOptions, type ResolvedImage, type RunningTemplate, type TocEntry, type TypesetMath, buildPdfDefinition, countUnplacedWords, cslStyleId, defaultCjkFontLoader, defaultCslFetcher, defaultEmojiFontLoader, defaultImageLoader, defaultSymbolFontLoader, documentCss, documentToDocx, documentToEpub, documentToHtml, documentToPdf, downloadDocumentExport, exportDocument, extractMath, fillTemplate, loadCslLocale, loadCslStyle, mathKey, normalizeReference, parseCitationGroup, parseDocument, parseLength, parseLocator, parseMathML, parseUnicodeRangeCss, plainText, prepareDocument, renderBlocksHtml, renderPdfDefinition, resolveDocumentImages, resolveSettings, scanCitationKeys, splitFrontmatter, typesetDocumentMath };
|
package/dist/document.js
CHANGED
|
@@ -1,3 +1,68 @@
|
|
|
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
|
+
|
|
1
66
|
// src/document/math.ts
|
|
2
67
|
import { pairDisplayMath, singleDollarMathEnd } from "@ai-matrx/content-ir/source";
|
|
3
68
|
var MATH_OPEN = "\uE100";
|
|
@@ -2045,6 +2110,8 @@ var CitationEngine = class {
|
|
|
2045
2110
|
/** A note style: every citation becomes a numbered note. */
|
|
2046
2111
|
isNote;
|
|
2047
2112
|
numeric;
|
|
2113
|
+
/** APA 7 §8.11: narrative citations join authors with "and", not "&". */
|
|
2114
|
+
apaNarrativeAnd;
|
|
2048
2115
|
constructor(styleXml, localeXml, items, lang) {
|
|
2049
2116
|
const byId = new Map(items.map((i) => [i.id, toCslJson(i)]));
|
|
2050
2117
|
const sys = {
|
|
@@ -2055,6 +2122,7 @@ var CitationEngine = class {
|
|
|
2055
2122
|
this.engine = new Engine(sys, styleXml, cslLocaleTag(lang), true);
|
|
2056
2123
|
this.engine.setOutputFormat("html");
|
|
2057
2124
|
this.isNote = this.engine.opt.xclass === "note";
|
|
2125
|
+
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
2126
|
this.numeric = /citation-number/.test(styleXml.slice(styleXml.indexOf("<citation"), styleXml.indexOf("</citation>")));
|
|
2059
2127
|
}
|
|
2060
2128
|
/** Cite a group; `noteIndex` is the note number for note styles (0 = in text). */
|
|
@@ -2091,7 +2159,8 @@ var CitationEngine = class {
|
|
|
2091
2159
|
if (opts.narrative && refs.length === 1 && !this.isNote && !this.numeric) {
|
|
2092
2160
|
const author = make(items);
|
|
2093
2161
|
const rest = make(refs.map((r, i) => ({ ...items[i], "author-only": void 0, "suppress-author": true, id: r.key })));
|
|
2094
|
-
const
|
|
2162
|
+
const who = () => this.apaNarrativeAnd ? author.label.replace(/,? & /g, (m) => m.startsWith(",") ? ", and " : " and ") : author.label;
|
|
2163
|
+
const join = () => `${who()}${rest.label ? ` ${/^\(/.test(rest.label) ? rest.label : `(${rest.label})`}` : ""}`;
|
|
2095
2164
|
const listeners = [];
|
|
2096
2165
|
author.listeners.push(() => listeners.forEach((l) => l(join())));
|
|
2097
2166
|
rest.listeners.push(() => listeners.forEach((l) => l(join())));
|
|
@@ -2570,7 +2639,7 @@ function runs(nodes, c, st = {}) {
|
|
|
2570
2639
|
for (const n of nodes) {
|
|
2571
2640
|
switch (n.type) {
|
|
2572
2641
|
case "text":
|
|
2573
|
-
out.push(tr(n.value));
|
|
2642
|
+
out.push(RTL_RE.test(n.value) ? tr(n.value, { rightToLeft: true }) : tr(n.value));
|
|
2574
2643
|
break;
|
|
2575
2644
|
case "strong":
|
|
2576
2645
|
out.push(...runs(n.children, c, { ...st, bold: true }));
|
|
@@ -2722,6 +2791,10 @@ function numberingRef(c, ordered, start) {
|
|
|
2722
2791
|
}
|
|
2723
2792
|
return ref;
|
|
2724
2793
|
}
|
|
2794
|
+
function bidiPara(nodes) {
|
|
2795
|
+
const text = nodes.map((n) => "value" in n ? n.value : "").join("");
|
|
2796
|
+
return isRtlParagraph(text) ? { bidirectional: true } : {};
|
|
2797
|
+
}
|
|
2725
2798
|
function blocks(list, c, o = {}) {
|
|
2726
2799
|
const out = [];
|
|
2727
2800
|
for (const b of list) out.push(...block(b, c, o));
|
|
@@ -2746,6 +2819,7 @@ function block(b, c, o) {
|
|
|
2746
2819
|
const kids = [...b.number ? [new d.TextRun({ text: `${b.number} `, color: HEX.muted })] : [], ...runs(b.children, c)];
|
|
2747
2820
|
return [
|
|
2748
2821
|
new d.Paragraph({
|
|
2822
|
+
...bidiPara(b.children),
|
|
2749
2823
|
heading,
|
|
2750
2824
|
keepNext: true,
|
|
2751
2825
|
keepLines: true,
|
|
@@ -2755,7 +2829,7 @@ function block(b, c, o) {
|
|
|
2755
2829
|
];
|
|
2756
2830
|
}
|
|
2757
2831
|
case "paragraph":
|
|
2758
|
-
return [para(c, o, { children: runs(b.children, c), widowControl: true })];
|
|
2832
|
+
return [para(c, o, { children: runs(b.children, c), widowControl: true, ...bidiPara(b.children) })];
|
|
2759
2833
|
case "list": {
|
|
2760
2834
|
const level = o.listLevel ?? 0;
|
|
2761
2835
|
const ref = numberingRef(c, b.ordered, b.start);
|
|
@@ -2772,7 +2846,14 @@ function block(b, c, o) {
|
|
|
2772
2846
|
})
|
|
2773
2847
|
);
|
|
2774
2848
|
} else {
|
|
2775
|
-
out.push(
|
|
2849
|
+
out.push(
|
|
2850
|
+
new d.Paragraph({
|
|
2851
|
+
numbering: { reference: ref, level, instance },
|
|
2852
|
+
children: firstRuns,
|
|
2853
|
+
widowControl: true,
|
|
2854
|
+
...first?.type === "paragraph" ? bidiPara(first.children) : {}
|
|
2855
|
+
})
|
|
2856
|
+
);
|
|
2776
2857
|
}
|
|
2777
2858
|
const tail = first && first.type !== "paragraph" ? [first, ...rest] : rest;
|
|
2778
2859
|
for (const child of tail) {
|
|
@@ -2814,6 +2895,7 @@ function block(b, c, o) {
|
|
|
2814
2895
|
borders: { top: none, left: none, right: none, bottom: head ? { ...border, size: 10, color: HEX.heading } : border },
|
|
2815
2896
|
children: [
|
|
2816
2897
|
new d.Paragraph({
|
|
2898
|
+
...bidiPara(cells[i] ?? []),
|
|
2817
2899
|
alignment: alignOf(i),
|
|
2818
2900
|
keepNext: keep && !last,
|
|
2819
2901
|
children: runs(cells[i] ?? [], c, head ? { bold: true } : {})
|
|
@@ -3029,7 +3111,7 @@ async function documentToDocx(tree, options = {}) {
|
|
|
3029
3111
|
default: {
|
|
3030
3112
|
// East-Asian text gets a CJK face (Word and LibreOffice substitute an installed one).
|
|
3031
3113
|
document: {
|
|
3032
|
-
run: { font: { ascii: "Cambria", hAnsi: "Cambria", cs: "
|
|
3114
|
+
run: { font: { ascii: "Cambria", hAnsi: "Cambria", cs: "Arial", eastAsia: "Yu Gothic" }, size, sizeComplexScript: size, color: HEX.text },
|
|
3033
3115
|
paragraph: { spacing: { after: 140, line: 300 } }
|
|
3034
3116
|
}
|
|
3035
3117
|
},
|
|
@@ -3217,10 +3299,10 @@ function renderBlockHtml(b, x) {
|
|
|
3217
3299
|
switch (b.type) {
|
|
3218
3300
|
case "heading": {
|
|
3219
3301
|
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}>`;
|
|
3302
|
+
return `<h${b.level} id="${escapeHtml(b.id)}" dir="auto">${num}${renderInlineHtml(b.children, x)}</h${b.level}>`;
|
|
3221
3303
|
}
|
|
3222
3304
|
case "paragraph":
|
|
3223
|
-
return `<p>${renderInlineHtml(b.children, x)}</p>`;
|
|
3305
|
+
return `<p dir="auto">${renderInlineHtml(b.children, x)}</p>`;
|
|
3224
3306
|
case "list": {
|
|
3225
3307
|
const tag = b.ordered ? "ol" : "ul";
|
|
3226
3308
|
const start = b.ordered && b.start !== 1 ? ` start="${b.start}"` : "";
|
|
@@ -3228,12 +3310,12 @@ function renderBlockHtml(b, x) {
|
|
|
3228
3310
|
const items = b.items.map((i) => {
|
|
3229
3311
|
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
3312
|
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>`;
|
|
3313
|
+
return `<li dir="auto"${i.checked !== null ? ' class="doc-task"' : ""}>${box}${inner}</li>`;
|
|
3232
3314
|
}).join("");
|
|
3233
3315
|
return `<${tag}${start}${task ? ' class="doc-task-list"' : ""}>${items}</${tag}>`;
|
|
3234
3316
|
}
|
|
3235
3317
|
case "blockquote":
|
|
3236
|
-
return `<blockquote>${renderBlocksHtml(b.children, x)}</blockquote>`;
|
|
3318
|
+
return `<blockquote dir="auto">${renderBlocksHtml(b.children, x)}</blockquote>`;
|
|
3237
3319
|
case "code":
|
|
3238
3320
|
return `<pre class="doc-code"${b.lang ? ` data-language="${escapeHtml(b.lang)}"` : ""}><code>${escapeHtml(
|
|
3239
3321
|
b.value
|
|
@@ -3243,8 +3325,8 @@ function renderBlockHtml(b, x) {
|
|
|
3243
3325
|
const a = b.align[i];
|
|
3244
3326
|
return a ? ` style="text-align:${a}"` : "";
|
|
3245
3327
|
};
|
|
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>`;
|
|
3328
|
+
const head = `<thead><tr>${b.header.map((c, i) => `<th dir="auto"${al(i)}>${renderInlineHtml(c, x)}</th>`).join("")}</tr></thead>`;
|
|
3329
|
+
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
3330
|
const id = b.caption?.id ? ` id="tbl:${escapeHtml(b.caption.id)}"` : "";
|
|
3249
3331
|
return `<div class="doc-table-wrap"${id}><table>${b.caption ? captionHtml(b.caption, "caption", x) : ""}${head}${body}</table></div>`;
|
|
3250
3332
|
}
|
|
@@ -3714,7 +3796,7 @@ function breakRuns(input, width, t) {
|
|
|
3714
3796
|
for (let guard = 0; guard < 50 && atoms.length; guard++) {
|
|
3715
3797
|
const gw = atoms.reduce((n, a) => n + w(a), 0);
|
|
3716
3798
|
const sw = pendingSpace && line > 0 ? w(pendingSpace) : 0;
|
|
3717
|
-
if (line + sw + gw <= W || line === 0 && atoms.length !== 1) {
|
|
3799
|
+
if (line + sw + gw <= W || line === 0 && (atoms.length !== 1 || gw <= width)) {
|
|
3718
3800
|
if (sw && pendingSpace) out.push(pendingSpace);
|
|
3719
3801
|
out.push(...atoms);
|
|
3720
3802
|
line += sw + gw;
|
|
@@ -3838,11 +3920,14 @@ function withFallbackFonts(nodes, c) {
|
|
|
3838
3920
|
const fb = fallbackFor(cp);
|
|
3839
3921
|
if (fb) {
|
|
3840
3922
|
flush();
|
|
3841
|
-
|
|
3923
|
+
const prev = out[out.length - 1];
|
|
3924
|
+
if (prev && typeof prev === "object" && prev.font === fb && prev.__fb) prev.text = `${prev.text}${ch}`;
|
|
3925
|
+
else out.push({ ...n, text: ch, font: fb, bold: false, italics: false, __fb: true });
|
|
3842
3926
|
} else dropped = true;
|
|
3843
3927
|
}
|
|
3844
3928
|
flush();
|
|
3845
3929
|
}
|
|
3930
|
+
for (const o of out) if (typeof o === "object") delete o.__fb;
|
|
3846
3931
|
if (dropped && !c.glyphs.announced) {
|
|
3847
3932
|
c.glyphs.announced = true;
|
|
3848
3933
|
c.tree.notices.push({
|
|
@@ -3869,8 +3954,13 @@ function drawable(text, c) {
|
|
|
3869
3954
|
}
|
|
3870
3955
|
function bodyRuns(nodes, c, width = c.width) {
|
|
3871
3956
|
const r = runs2(nodes, c);
|
|
3872
|
-
|
|
3957
|
+
const text = plain(nodes);
|
|
3958
|
+
if (!RTL_RE.test(text)) return c.typo ? breakRuns(r, width, c.typo) : r;
|
|
3959
|
+
const typo = c.typo ?? (c.faces ? { split: (w) => [w], measure: c.faces.measure } : void 0);
|
|
3960
|
+
const lines = typo ? breakRuns(r, width, typo) : r;
|
|
3961
|
+
return visualOrder(lines, isRtlParagraph(text));
|
|
3873
3962
|
}
|
|
3963
|
+
var alignFor = (nodes) => isRtlParagraph(plain(nodes)) ? { alignment: "right" } : {};
|
|
3874
3964
|
function runs2(nodes, c, st = {}) {
|
|
3875
3965
|
const out = [];
|
|
3876
3966
|
const base = (raw) => {
|
|
@@ -4062,6 +4152,7 @@ function estimateLines(text, c, size = c.s.fontSize) {
|
|
|
4062
4152
|
function columnWidths(b, n, size, c) {
|
|
4063
4153
|
const measure = (text, bold) => c.faces ? c.faces.measure(text, { bold, fontSize: size }) : text.length * size * 0.5;
|
|
4064
4154
|
const pad = 10;
|
|
4155
|
+
const slack = 1;
|
|
4065
4156
|
const min = new Array(n).fill(0);
|
|
4066
4157
|
const max = new Array(n).fill(0);
|
|
4067
4158
|
const rows = [[b.header, true], ...b.rows.map((r) => [r, false])];
|
|
@@ -4069,8 +4160,8 @@ function columnWidths(b, n, size, c) {
|
|
|
4069
4160
|
for (let i = 0; i < n; i++) {
|
|
4070
4161
|
const t = plain(r[i] ?? []);
|
|
4071
4162
|
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);
|
|
4163
|
+
min[i] = Math.max(min[i], Math.min(longest * slack, c.width * 0.35) + pad);
|
|
4164
|
+
max[i] = Math.max(max[i], measure(t, head) * slack + pad);
|
|
4074
4165
|
}
|
|
4075
4166
|
}
|
|
4076
4167
|
const avail = c.width;
|
|
@@ -4134,8 +4225,11 @@ function block2(b, c) {
|
|
|
4134
4225
|
switch (b.type) {
|
|
4135
4226
|
case "heading": {
|
|
4136
4227
|
const rel = Math.min(6, b.level - c.minLevel + 1);
|
|
4228
|
+
const rtlHeading = RTL_RE.test(b.plain);
|
|
4229
|
+
const headRuns = rtlHeading ? visualOrder(runs2(b.children, c), isRtlParagraph(b.plain)) : runs2(b.children, c);
|
|
4137
4230
|
const node = {
|
|
4138
|
-
|
|
4231
|
+
...rtlHeading ? alignFor(b.children) : {},
|
|
4232
|
+
text: [...b.number ? [{ text: `${b.number} `, color: COLOR.muted }] : [], ...headRuns],
|
|
4139
4233
|
style: `h${Math.min(6, b.level)}`,
|
|
4140
4234
|
headlineLevel: b.level,
|
|
4141
4235
|
id: b.id
|
|
@@ -4149,12 +4243,12 @@ function block2(b, c) {
|
|
|
4149
4243
|
}
|
|
4150
4244
|
case "paragraph": {
|
|
4151
4245
|
const text = bodyRuns(b.children, c);
|
|
4152
|
-
const p = { text, style: "p" };
|
|
4246
|
+
const p = { text, style: "p", ...alignFor(b.children) };
|
|
4153
4247
|
return estimateLines(plain(b.children), c) <= 5 ? { stack: [p], unbreakable: true } : p;
|
|
4154
4248
|
}
|
|
4155
4249
|
case "list": {
|
|
4156
4250
|
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 });
|
|
4251
|
+
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
4252
|
if (it.checked !== null) return { ...taskItem(it.checked, content, c), listType: "none" };
|
|
4159
4253
|
return content.length === 1 ? content[0] : { stack: content };
|
|
4160
4254
|
});
|
|
@@ -4207,15 +4301,24 @@ function block2(b, c) {
|
|
|
4207
4301
|
case "table": {
|
|
4208
4302
|
const n = Math.max(b.header.length, ...b.rows.map((r) => r.length), 1);
|
|
4209
4303
|
const size = c.s.fontSize * 0.92;
|
|
4304
|
+
const widths = columnWidths(b, n, size, c);
|
|
4305
|
+
const cellRuns = (inl, i, head) => {
|
|
4306
|
+
const r = runs2(inl, c).map((x) => typeof x === "object" ? { ...x, fontSize: x.fontSize ?? size, ...head ? { bold: true } : {} } : x);
|
|
4307
|
+
const w = (widths[i] ?? c.width) - 10;
|
|
4308
|
+
if (RTL_RE.test(plain(inl))) {
|
|
4309
|
+
const typo = c.typo ?? (c.faces ? { split: (x) => [x], measure: c.faces.measure } : void 0);
|
|
4310
|
+
return visualOrder(typo ? breakRuns(r, w, typo) : r, isRtlParagraph(plain(inl)));
|
|
4311
|
+
}
|
|
4312
|
+
return c.typo && !head ? breakRuns(r, w, c.typo) : r;
|
|
4313
|
+
};
|
|
4210
4314
|
const cell = (inl, i, head) => ({
|
|
4211
|
-
text:
|
|
4315
|
+
text: cellRuns(inl ?? [], i, head),
|
|
4212
4316
|
bold: head,
|
|
4213
|
-
alignment: b.align[i] ?? "left",
|
|
4317
|
+
alignment: b.align[i] ?? (isRtlParagraph(plain(inl ?? [])) ? "right" : "left"),
|
|
4214
4318
|
fontSize: size,
|
|
4215
4319
|
color: head ? COLOR.heading : COLOR.text
|
|
4216
4320
|
});
|
|
4217
4321
|
const row = (r, head) => Array.from({ length: n }, (_, i) => cell(r[i], i, head));
|
|
4218
|
-
const widths = columnWidths(b, n, size, c);
|
|
4219
4322
|
const lineH = size * 1.3;
|
|
4220
4323
|
const pageH = c.pageHeight;
|
|
4221
4324
|
const rowHeight = (r) => Math.max(...r.map((inl, i) => estimateLines(plain(inl), { ...c, width: (widths[i] ?? c.width) - 10 }, size) * lineH)) + 8;
|
|
@@ -4461,12 +4564,16 @@ async function loadTypography(tree, loaders = {}) {
|
|
|
4461
4564
|
const base = await loadFaces(vfs, s.fontSize);
|
|
4462
4565
|
let needEmoji = false;
|
|
4463
4566
|
let needSymbols = false;
|
|
4567
|
+
let needArabic = false;
|
|
4568
|
+
let needHebrew = false;
|
|
4464
4569
|
const cjk = /* @__PURE__ */ new Set();
|
|
4465
4570
|
for (const ch of documentText(tree)) {
|
|
4466
4571
|
const cp = ch.codePointAt(0);
|
|
4467
4572
|
if (cp < 32 || cp === 8205 || cp === 65039 || base.body(cp)) continue;
|
|
4468
4573
|
if (EMOJI_CP.test(ch)) needEmoji = true;
|
|
4469
|
-
else if (isCjk(cp)) cjk.add(cp);
|
|
4574
|
+
else if (isCjk(cp) || isHangul(cp)) cjk.add(cp);
|
|
4575
|
+
else if (isArabic(cp)) needArabic = true;
|
|
4576
|
+
else if (isHebrew(cp)) needHebrew = true;
|
|
4470
4577
|
else needSymbols = true;
|
|
4471
4578
|
}
|
|
4472
4579
|
const fallbacks = [];
|
|
@@ -4478,6 +4585,14 @@ async function loadTypography(tree, loaders = {}) {
|
|
|
4478
4585
|
const b = await registerFace("NotoSymbols", "NotoSansMath.woff", loaders.symbols ?? defaultSymbolFontLoader, tree, "symbols");
|
|
4479
4586
|
if (b) fallbacks.push({ font: "NotoSymbols", bytes: b });
|
|
4480
4587
|
}
|
|
4588
|
+
if (needArabic) {
|
|
4589
|
+
const b = await registerFace("NotoArabic", "NotoSansArabic.woff", loaders.arabic ?? urlLoader(NOTO_ARABIC_URL), tree, "Arabic");
|
|
4590
|
+
if (b) fallbacks.push({ font: "NotoArabic", bytes: b });
|
|
4591
|
+
}
|
|
4592
|
+
if (needHebrew) {
|
|
4593
|
+
const b = await registerFace("NotoHebrew", "NotoSansHebrew.woff", loaders.hebrew ?? urlLoader(NOTO_HEBREW_URL), tree, "Hebrew");
|
|
4594
|
+
if (b) fallbacks.push({ font: "NotoHebrew", bytes: b });
|
|
4595
|
+
}
|
|
4481
4596
|
if (cjk.size) {
|
|
4482
4597
|
let subsets = [];
|
|
4483
4598
|
try {
|
|
@@ -4493,7 +4608,7 @@ async function loadTypography(tree, loaders = {}) {
|
|
|
4493
4608
|
if (!subsets.length) {
|
|
4494
4609
|
tree.notices.push({
|
|
4495
4610
|
code: "glyph-substituted",
|
|
4496
|
-
message: "The Japanese /
|
|
4611
|
+
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
4612
|
remedy: "Check the internet connection (the font comes from cdn.jsdelivr.net) and export again."
|
|
4498
4613
|
});
|
|
4499
4614
|
}
|
|
@@ -4521,6 +4636,12 @@ var urlLoader = (url) => async () => {
|
|
|
4521
4636
|
var defaultEmojiFontLoader = urlLoader(NOTO_EMOJI_URL);
|
|
4522
4637
|
var defaultSymbolFontLoader = urlLoader(NOTO_SYMBOLS_URL);
|
|
4523
4638
|
var NOTO_CJK_BASE = "https://cdn.jsdelivr.net/npm/@fontsource/noto-sans-jp@5.3.0";
|
|
4639
|
+
var NOTO_KR_BASE = "https://cdn.jsdelivr.net/npm/@fontsource/noto-sans-kr@5.3.0";
|
|
4640
|
+
var NOTO_ARABIC_URL = "https://cdn.jsdelivr.net/npm/@fontsource/noto-sans-arabic@5.3.0/files/noto-sans-arabic-arabic-400-normal.woff";
|
|
4641
|
+
var NOTO_HEBREW_URL = "https://cdn.jsdelivr.net/npm/@fontsource/noto-sans-hebrew@5.3.0/files/noto-sans-hebrew-hebrew-400-normal.woff";
|
|
4642
|
+
var isHangul = (cp) => cp >= 44032 && cp <= 55215 || cp >= 4352 && cp <= 4607 || cp >= 12592 && cp <= 12687;
|
|
4643
|
+
var isArabic = (cp) => cp >= 1536 && cp <= 1791 || cp >= 1872 && cp <= 1919 || cp >= 2208 && cp <= 2303 || cp >= 64336 && cp <= 65023 || cp >= 65136 && cp <= 65279;
|
|
4644
|
+
var isHebrew = (cp) => cp >= 1424 && cp <= 1535 || cp >= 64285 && cp <= 64335;
|
|
4524
4645
|
function parseUnicodeRangeCss(css) {
|
|
4525
4646
|
const out = [];
|
|
4526
4647
|
for (const block3 of css.split("@font-face").slice(1)) {
|
|
@@ -4536,19 +4657,25 @@ function parseUnicodeRangeCss(css) {
|
|
|
4536
4657
|
}
|
|
4537
4658
|
return out;
|
|
4538
4659
|
}
|
|
4539
|
-
|
|
4660
|
+
async function unicodeRangeFaces(base, cps) {
|
|
4540
4661
|
if (typeof fetch !== "function" || cps.length === 0) return [];
|
|
4541
|
-
const res = await fetch(`${
|
|
4662
|
+
const res = await fetch(`${base}/400.css`);
|
|
4542
4663
|
if (!res.ok) return [];
|
|
4543
4664
|
const faces = parseUnicodeRangeCss(await res.text()).filter((f) => cps.some((cp) => f.ranges.some(([a, b]) => cp >= a && cp <= b)));
|
|
4544
4665
|
const out = [];
|
|
4545
4666
|
await Promise.all(
|
|
4546
4667
|
faces.map(async (f) => {
|
|
4547
|
-
const r = await fetch(`${
|
|
4668
|
+
const r = await fetch(`${base}/files/${f.file}.woff`);
|
|
4548
4669
|
if (r.ok) out.push({ name: f.file, bytes: new Uint8Array(await r.arrayBuffer()) });
|
|
4549
4670
|
})
|
|
4550
4671
|
);
|
|
4551
4672
|
return out;
|
|
4673
|
+
}
|
|
4674
|
+
var defaultCjkFontLoader = async (cps) => {
|
|
4675
|
+
const kr = cps.filter(isHangul);
|
|
4676
|
+
const jp = cps.filter((cp) => !isHangul(cp));
|
|
4677
|
+
const [a, b] = await Promise.all([unicodeRangeFaces(NOTO_KR_BASE, kr), unicodeRangeFaces(NOTO_CJK_BASE, jp)]);
|
|
4678
|
+
return [...a, ...b];
|
|
4552
4679
|
};
|
|
4553
4680
|
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
4681
|
var EMOJI_CP = /\p{Extended_Pictographic}|\p{Regional_Indicator}|\u20E3/u;
|
|
@@ -4867,7 +4994,7 @@ function uniqueId(ctx, base) {
|
|
|
4867
4994
|
return id;
|
|
4868
4995
|
}
|
|
4869
4996
|
var CITE_GROUP_RE = /\[(-?@[^[\]]+)\]/g;
|
|
4870
|
-
var BARE_RE = /(^|[^\w@.\\])@((?:fig|tbl|sec|eq):[\w-]+|[A-Za-z0-9_](?:[\w:.#$%&+?<>~/-]*\w)?)
|
|
4997
|
+
var BARE_RE = /(^|[^\w@.\\])@((?:fig|tbl|sec|eq):[\w-]+|[A-Za-z0-9_](?:[\w:.#$%&+?<>~/-]*\w)?)(?: \[([^\[\]@]+)\])?/g;
|
|
4871
4998
|
function scanText(value, ctx) {
|
|
4872
4999
|
const out = [];
|
|
4873
5000
|
const pushText = (s) => {
|
|
@@ -4888,8 +5015,8 @@ function scanText(value, ctx) {
|
|
|
4888
5015
|
last2 = start + key.length + 1;
|
|
4889
5016
|
} else if (ctx.refs.has(key)) {
|
|
4890
5017
|
pushText(s.slice(last2, start));
|
|
4891
|
-
out.push(cite([{ key, suppressAuthor: false }], ctx, true));
|
|
4892
|
-
last2 = start + key.length + 1;
|
|
5018
|
+
out.push(cite([{ key, locator: m[3]?.trim(), suppressAuthor: false }], ctx, true));
|
|
5019
|
+
last2 = start + key.length + 1 + (m[3] !== void 0 ? m[3].length + 3 : 0);
|
|
4893
5020
|
}
|
|
4894
5021
|
}
|
|
4895
5022
|
pushText(s.slice(last2));
|
|
@@ -5630,8 +5757,11 @@ export {
|
|
|
5630
5757
|
CitationEngine,
|
|
5631
5758
|
DEFAULT_FOOTER,
|
|
5632
5759
|
DOCUMENT_FORMATS,
|
|
5760
|
+
NOTO_ARABIC_URL,
|
|
5633
5761
|
NOTO_CJK_BASE,
|
|
5634
5762
|
NOTO_EMOJI_URL,
|
|
5763
|
+
NOTO_HEBREW_URL,
|
|
5764
|
+
NOTO_KR_BASE,
|
|
5635
5765
|
NOTO_SYMBOLS_URL,
|
|
5636
5766
|
PAGE_SIZES,
|
|
5637
5767
|
buildPdfDefinition,
|