reamkit 0.1.0-alpha.0
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/LICENSE +21 -0
- package/README.md +95 -0
- package/dist/esm/bidi/algorithm.d.ts +9 -0
- package/dist/esm/bidi/algorithm.js +458 -0
- package/dist/esm/bidi/char-types.d.ts +2 -0
- package/dist/esm/bidi/char-types.js +439 -0
- package/dist/esm/bidi/index.d.ts +14 -0
- package/dist/esm/bidi/index.js +25 -0
- package/dist/esm/converter/docx-to-pdf.d.ts +15 -0
- package/dist/esm/converter/docx-to-pdf.js +227 -0
- package/dist/esm/converter/index.d.ts +4 -0
- package/dist/esm/converter/index.js +2 -0
- package/dist/esm/converter/xlsx-to-pdf.d.ts +13 -0
- package/dist/esm/converter/xlsx-to-pdf.js +515 -0
- package/dist/esm/crypto/asn1.d.ts +23 -0
- package/dist/esm/crypto/asn1.js +123 -0
- package/dist/esm/crypto/cms.d.ts +9 -0
- package/dist/esm/crypto/cms.js +34 -0
- package/dist/esm/crypto/index.d.ts +2 -0
- package/dist/esm/crypto/index.js +1 -0
- package/dist/esm/document-model/index.d.ts +1 -0
- package/dist/esm/document-model/index.js +0 -0
- package/dist/esm/document-model/types.d.ts +403 -0
- package/dist/esm/font/arabic-joining.d.ts +4 -0
- package/dist/esm/font/arabic-joining.js +97 -0
- package/dist/esm/font/binary-reader.d.ts +13 -0
- package/dist/esm/font/binary-reader.js +44 -0
- package/dist/esm/font/font-registry.d.ts +19 -0
- package/dist/esm/font/font-registry.js +39 -0
- package/dist/esm/font/index.d.ts +7 -0
- package/dist/esm/font/opentype-layout.d.ts +21 -0
- package/dist/esm/font/opentype-layout.js +329 -0
- package/dist/esm/font/ttf-parser.d.ts +29 -0
- package/dist/esm/font/ttf-parser.js +290 -0
- package/dist/esm/font/ttf-subset.d.ts +3 -0
- package/dist/esm/font/ttf-subset.js +201 -0
- package/dist/esm/fonts/index.d.ts +2 -0
- package/dist/esm/fonts/remote-fonts.d.ts +13 -0
- package/dist/esm/fonts/remote-fonts.js +94 -0
- package/dist/esm/hyphenation/index.d.ts +10 -0
- package/dist/esm/hyphenation/index.js +38 -0
- package/dist/esm/hyphenation/liang.d.ts +10 -0
- package/dist/esm/hyphenation/liang.js +79 -0
- package/dist/esm/hyphenation/patterns-en-us.d.ts +2 -0
- package/dist/esm/hyphenation/patterns-en-us.js +5 -0
- package/dist/esm/hyphenation/patterns-ru.d.ts +2 -0
- package/dist/esm/hyphenation/patterns-ru.js +5 -0
- package/dist/esm/index.d.ts +12 -0
- package/dist/esm/index.js +13 -0
- package/dist/esm/line-breaker/index.d.ts +2 -0
- package/dist/esm/line-breaker/knuth-plass.d.ts +30 -0
- package/dist/esm/line-breaker/knuth-plass.js +172 -0
- package/dist/esm/numbering/index.d.ts +1 -0
- package/dist/esm/numbering/state.d.ts +6 -0
- package/dist/esm/numbering/state.js +83 -0
- package/dist/esm/ooxml/drawingml/chart-parser.d.ts +3 -0
- package/dist/esm/ooxml/drawingml/chart-parser.js +218 -0
- package/dist/esm/ooxml/drawingml/colors.d.ts +17 -0
- package/dist/esm/ooxml/drawingml/colors.js +125 -0
- package/dist/esm/ooxml/drawingml/theme-parser.d.ts +1 -0
- package/dist/esm/ooxml/drawingml/theme-parser.js +55 -0
- package/dist/esm/ooxml/index.d.ts +1 -0
- package/dist/esm/ooxml/math/omml-parser.d.ts +4 -0
- package/dist/esm/ooxml/math/omml-parser.js +217 -0
- package/dist/esm/ooxml/spreadsheet/cell-reference.d.ts +6 -0
- package/dist/esm/ooxml/spreadsheet/cell-reference.js +26 -0
- package/dist/esm/ooxml/spreadsheet/defined-name-ref.d.ts +11 -0
- package/dist/esm/ooxml/spreadsheet/defined-name-ref.js +64 -0
- package/dist/esm/ooxml/spreadsheet/index.d.ts +12 -0
- package/dist/esm/ooxml/spreadsheet/index.js +7 -0
- package/dist/esm/ooxml/spreadsheet/number-format.d.ts +1 -0
- package/dist/esm/ooxml/spreadsheet/number-format.js +438 -0
- package/dist/esm/ooxml/spreadsheet/shared-strings-parser.d.ts +1 -0
- package/dist/esm/ooxml/spreadsheet/shared-strings-parser.js +47 -0
- package/dist/esm/ooxml/spreadsheet/styles-parser.d.ts +52 -0
- package/dist/esm/ooxml/spreadsheet/styles-parser.js +225 -0
- package/dist/esm/ooxml/spreadsheet/workbook-parser.d.ts +16 -0
- package/dist/esm/ooxml/spreadsheet/workbook-parser.js +90 -0
- package/dist/esm/ooxml/spreadsheet/worksheet-parser.d.ts +60 -0
- package/dist/esm/ooxml/spreadsheet/worksheet-parser.js +311 -0
- package/dist/esm/ooxml/wordproc/document-parser.d.ts +9 -0
- package/dist/esm/ooxml/wordproc/document-parser.js +274 -0
- package/dist/esm/ooxml/wordproc/drawing-parser.d.ts +33 -0
- package/dist/esm/ooxml/wordproc/drawing-parser.js +374 -0
- package/dist/esm/ooxml/wordproc/font-table.d.ts +15 -0
- package/dist/esm/ooxml/wordproc/font-table.js +82 -0
- package/dist/esm/ooxml/wordproc/index.d.ts +10 -0
- package/dist/esm/ooxml/wordproc/index.js +9 -0
- package/dist/esm/ooxml/wordproc/numbering-parser.d.ts +3 -0
- package/dist/esm/ooxml/wordproc/numbering-parser.js +89 -0
- package/dist/esm/ooxml/wordproc/paragraph-properties.d.ts +2 -0
- package/dist/esm/ooxml/wordproc/paragraph-properties.js +85 -0
- package/dist/esm/ooxml/wordproc/po-helpers.d.ts +14 -0
- package/dist/esm/ooxml/wordproc/po-helpers.js +76 -0
- package/dist/esm/ooxml/wordproc/po-to-flat.d.ts +2 -0
- package/dist/esm/ooxml/wordproc/po-to-flat.js +28 -0
- package/dist/esm/ooxml/wordproc/run-properties.d.ts +2 -0
- package/dist/esm/ooxml/wordproc/run-properties.js +82 -0
- package/dist/esm/ooxml/wordproc/settings-parser.d.ts +5 -0
- package/dist/esm/ooxml/wordproc/settings-parser.js +26 -0
- package/dist/esm/ooxml/wordproc/styles-parser.d.ts +3 -0
- package/dist/esm/ooxml/wordproc/styles-parser.js +67 -0
- package/dist/esm/ooxml/wordproc/table-parser.d.ts +3 -0
- package/dist/esm/ooxml/wordproc/table-parser.js +162 -0
- package/dist/esm/ooxml/wordproc/text-extractor.d.ts +1 -0
- package/dist/esm/ooxml/wordproc/text-extractor.js +1 -0
- package/dist/esm/ooxml/wordproc/xml-helpers.d.ts +9 -0
- package/dist/esm/ooxml/wordproc/xml-helpers.js +36 -0
- package/dist/esm/opc/core-properties.d.ts +11 -0
- package/dist/esm/opc/core-properties.js +51 -0
- package/dist/esm/opc/index.d.ts +5 -0
- package/dist/esm/opc/index.js +3 -0
- package/dist/esm/opc/package.d.ts +26 -0
- package/dist/esm/opc/package.js +113 -0
- package/dist/esm/opc/relationship-types.d.ts +3 -0
- package/dist/esm/opc/relationship-types.js +4 -0
- package/dist/esm/opc/relationships.d.ts +7 -0
- package/dist/esm/opc/relationships.js +28 -0
- package/dist/esm/pdf/arc-to-bezier.d.ts +5 -0
- package/dist/esm/pdf/arc-to-bezier.js +58 -0
- package/dist/esm/pdf/builtin-fonts.d.ts +2 -0
- package/dist/esm/pdf/chart-geometry.d.ts +64 -0
- package/dist/esm/pdf/chart-geometry.js +674 -0
- package/dist/esm/pdf/cid-font.d.ts +15 -0
- package/dist/esm/pdf/cid-font.js +174 -0
- package/dist/esm/pdf/embedded-file.d.ts +11 -0
- package/dist/esm/pdf/embedded-file.js +23 -0
- package/dist/esm/pdf/icc-profile.d.ts +1 -0
- package/dist/esm/pdf/icc-profile.js +166 -0
- package/dist/esm/pdf/image-xobject.d.ts +13 -0
- package/dist/esm/pdf/image-xobject.js +335 -0
- package/dist/esm/pdf/index.d.ts +11 -0
- package/dist/esm/pdf/index.js +5 -0
- package/dist/esm/pdf/math-layout.d.ts +45 -0
- package/dist/esm/pdf/math-layout.js +1138 -0
- package/dist/esm/pdf/objects.d.ts +32 -0
- package/dist/esm/pdf/objects.js +54 -0
- package/dist/esm/pdf/preset-geometry.d.ts +6 -0
- package/dist/esm/pdf/preset-geometry.js +199 -0
- package/dist/esm/pdf/serialize.d.ts +3 -0
- package/dist/esm/pdf/serialize.js +54 -0
- package/dist/esm/pdf/signature.d.ts +25 -0
- package/dist/esm/pdf/signature.js +126 -0
- package/dist/esm/pdf/struct-tree.d.ts +31 -0
- package/dist/esm/pdf/struct-tree.js +109 -0
- package/dist/esm/pdf/styled-page-renderer.d.ts +43 -0
- package/dist/esm/pdf/styled-page-renderer.js +2317 -0
- package/dist/esm/pdf/text-encoding.d.ts +2 -0
- package/dist/esm/pdf/text-page-renderer.d.ts +16 -0
- package/dist/esm/pdf/text-page-renderer.js +4 -0
- package/dist/esm/pdf/vector-graphics.d.ts +46 -0
- package/dist/esm/pdf/vector-graphics.js +103 -0
- package/dist/esm/pdf/writer.d.ts +10 -0
- package/dist/esm/pdf/writer.js +81 -0
- package/dist/esm/pdf/xmp.d.ts +13 -0
- package/dist/esm/pdf/xmp.js +38 -0
- package/dist/esm/style-cascade/index.d.ts +3 -0
- package/dist/esm/style-cascade/resolver.d.ts +4 -0
- package/dist/esm/style-cascade/resolver.js +96 -0
- package/dist/esm/style-cascade/types.d.ts +29 -0
- package/dist/esm/style-cascade/types.js +26 -0
- package/package.json +90 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alex Krassavin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Ream
|
|
2
|
+
|
|
3
|
+
> DOCX & XLSX → PDF, from scratch — no LibreOffice, no headless Office, no commercial SDK.
|
|
4
|
+
|
|
5
|
+
Convert Word (`.docx`) and Excel (`.xlsx`) documents to PDF, **in the browser** —
|
|
6
|
+
implemented from the **ECMA-376** (OOXML) and **ISO 32000** (PDF) specifications,
|
|
7
|
+
with no wrapper around LibreOffice, headless Office, or any commercial SDK. Pure
|
|
8
|
+
TypeScript/JavaScript working on `Uint8Array` in and `Uint8Array` out, so it also
|
|
9
|
+
runs unchanged in Node.js, serverless, and edge runtimes.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm install reamkit
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Runtime dependencies are minimal: `fflate` (ZIP/Deflate) and `fast-xml-parser`.
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
Give it the document bytes, get the PDF bytes back. No fonts to wire up — an open
|
|
22
|
+
substitute font (Roboto for sans, Tinos for serif, Cousine for monospace — the
|
|
23
|
+
same families LibreOffice substitutes) is fetched automatically based on the
|
|
24
|
+
document's referenced fonts:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { convertDocxToPdf } from 'reamkit';
|
|
28
|
+
|
|
29
|
+
// e.g. from an <input type="file"> or a fetch() — anything that yields bytes.
|
|
30
|
+
const docx = new Uint8Array(await file.arrayBuffer());
|
|
31
|
+
|
|
32
|
+
const pdf = await convertDocxToPdf(docx); // async — fetches a font if needed
|
|
33
|
+
|
|
34
|
+
// Hand the bytes to the browser: preview, download, upload, …
|
|
35
|
+
const url = URL.createObjectURL(new Blob([pdf], { type: 'application/pdf' }));
|
|
36
|
+
window.open(url);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Excel works the same way via `convertXlsxToPdf`. The input/output are plain
|
|
40
|
+
`Uint8Array`s, so wiring this to files, the network, or disk is up to you.
|
|
41
|
+
|
|
42
|
+
### Bring your own fonts (synchronous, no network)
|
|
43
|
+
|
|
44
|
+
To embed specific fonts — or to avoid the network entirely — pass the font bytes
|
|
45
|
+
in and use the synchronous variant:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { convertDocxToPdfSync } from 'reamkit';
|
|
49
|
+
|
|
50
|
+
const fonts = {
|
|
51
|
+
regular: new Uint8Array(await fetch('/fonts/MyFont-Regular.ttf').then((r) => r.arrayBuffer())),
|
|
52
|
+
bold: new Uint8Array(await fetch('/fonts/MyFont-Bold.ttf').then((r) => r.arrayBuffer())),
|
|
53
|
+
};
|
|
54
|
+
const pdf = convertDocxToPdfSync(docx, { fonts });
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The async `convertDocxToPdf` also accepts `fonts` (then it does no network I/O),
|
|
58
|
+
plus `fontFamily` to force a substitute and `fontFetch` to inject a custom
|
|
59
|
+
fetch implementation.
|
|
60
|
+
|
|
61
|
+
### Options
|
|
62
|
+
|
|
63
|
+
Both converters accept (beyond `fonts` / `fontBytes`):
|
|
64
|
+
|
|
65
|
+
- `info` — PDF `/Info` metadata (`title`, `author`, `subject`, …). Also read
|
|
66
|
+
automatically from the document's `docProps/core.xml`.
|
|
67
|
+
- `pdfA: 'PDF/A-1b'` — emit an archival PDF/A-1b file (embedded sRGB
|
|
68
|
+
OutputIntent, XMP, deterministic `/ID`, subset-tagged fonts, no transparency).
|
|
69
|
+
- `hyphenator` — a Liang hyphenator (see below) for nicer justified text.
|
|
70
|
+
|
|
71
|
+
### Hyphenation (optional)
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { getHyphenator } from 'reamkit';
|
|
75
|
+
const hyphenator = await getHyphenator('en-us'); // or 'ru'
|
|
76
|
+
const pdf = await convertDocxToPdf(docx, { hyphenator });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Advanced
|
|
80
|
+
|
|
81
|
+
`renderStyledPdf` drives the layout engine directly. The typed document model is
|
|
82
|
+
available from the `reamkit/document-model` subpath.
|
|
83
|
+
|
|
84
|
+
## Scope
|
|
85
|
+
|
|
86
|
+
Implemented: WordprocessingML text/styles/tables/lists/sections/headers-footers/
|
|
87
|
+
images, SpreadsheetML grids/number-formats/dates, Type0+CIDFontType2 font
|
|
88
|
+
embedding with subsetting, Knuth-Plass line breaking, Liang hyphenation,
|
|
89
|
+
OpenType ligatures + kerning, BiDi (UAX #9), and PDF/A-1b.
|
|
90
|
+
|
|
91
|
+
See `handoff.md` for the full feature matrix and known limitations.
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
[MIT](./LICENSE) © Alex Krassavin
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BidiClass } from './char-types.js';
|
|
2
|
+
export type Direction = 'ltr' | 'rtl' | 'auto';
|
|
3
|
+
export interface BidiResult {
|
|
4
|
+
readonly levels: ReadonlyArray<number>;
|
|
5
|
+
readonly paragraphLevel: number;
|
|
6
|
+
readonly types: ReadonlyArray<BidiClass>;
|
|
7
|
+
}
|
|
8
|
+
export declare function computeBidi(codePoints: ReadonlyArray<number>, dir: Direction): BidiResult;
|
|
9
|
+
export declare function reorderVisual(levels: ReadonlyArray<number>): Array<number>;
|
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
import { bidiClass } from "./char-types.js";
|
|
2
|
+
//#region src/bidi/algorithm.ts
|
|
3
|
+
var MAX_DEPTH = 125;
|
|
4
|
+
function matchingPDI(types, openIdx) {
|
|
5
|
+
let depth = 1;
|
|
6
|
+
for (let i = openIdx + 1; i < types.length; i++) {
|
|
7
|
+
const t = types[i];
|
|
8
|
+
if (t === "LRI" || t === "RLI" || t === "FSI") depth++;
|
|
9
|
+
else if (t === "PDI") {
|
|
10
|
+
depth--;
|
|
11
|
+
if (depth === 0) return i;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return types.length;
|
|
15
|
+
}
|
|
16
|
+
function computeBaseLevel(types, start, end) {
|
|
17
|
+
for (let i = start; i < end; i++) {
|
|
18
|
+
const t = types[i];
|
|
19
|
+
if (t === "LRI" || t === "RLI" || t === "FSI") {
|
|
20
|
+
i = matchingPDI(types, i);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (t === "L") return 0;
|
|
24
|
+
if (t === "R" || t === "AL") return 1;
|
|
25
|
+
}
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
function computeBidi(codePoints, dir) {
|
|
29
|
+
const n = codePoints.length;
|
|
30
|
+
const origTypes = new Array(n);
|
|
31
|
+
for (let i = 0; i < n; i++) origTypes[i] = bidiClass(codePoints[i]);
|
|
32
|
+
const paragraphLevel = dir === "ltr" ? 0 : dir === "rtl" ? 1 : computeBaseLevel(origTypes, 0, n);
|
|
33
|
+
const types = origTypes.slice();
|
|
34
|
+
const levels = new Array(n).fill(paragraphLevel);
|
|
35
|
+
const stack = [{
|
|
36
|
+
level: paragraphLevel,
|
|
37
|
+
override: "neutral",
|
|
38
|
+
isolate: false
|
|
39
|
+
}];
|
|
40
|
+
let overflowIsolate = 0;
|
|
41
|
+
let overflowEmbedding = 0;
|
|
42
|
+
let validIsolate = 0;
|
|
43
|
+
const nextOddLevel = (lvl) => lvl % 2 === 0 ? lvl + 1 : lvl + 2;
|
|
44
|
+
const nextEvenLevel = (lvl) => lvl % 2 === 0 ? lvl + 2 : lvl + 1;
|
|
45
|
+
const top = () => stack[stack.length - 1];
|
|
46
|
+
for (let i = 0; i < n; i++) {
|
|
47
|
+
const t = types[i];
|
|
48
|
+
switch (t) {
|
|
49
|
+
case "RLE":
|
|
50
|
+
case "LRE":
|
|
51
|
+
case "RLO":
|
|
52
|
+
case "LRO": {
|
|
53
|
+
const newLevel = t === "RLE" || t === "RLO" ? nextOddLevel(top().level) : nextEvenLevel(top().level);
|
|
54
|
+
levels[i] = top().level;
|
|
55
|
+
types[i] = "BN";
|
|
56
|
+
if (newLevel <= MAX_DEPTH && overflowIsolate === 0 && overflowEmbedding === 0) stack.push({
|
|
57
|
+
level: newLevel,
|
|
58
|
+
override: t === "RLO" ? "R" : t === "LRO" ? "L" : "neutral",
|
|
59
|
+
isolate: false
|
|
60
|
+
});
|
|
61
|
+
else if (overflowIsolate === 0) overflowEmbedding++;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
case "RLI":
|
|
65
|
+
case "LRI":
|
|
66
|
+
case "FSI": {
|
|
67
|
+
const ov = top().override;
|
|
68
|
+
levels[i] = top().level;
|
|
69
|
+
if (ov !== "neutral") types[i] = ov;
|
|
70
|
+
let isRTL = t === "RLI";
|
|
71
|
+
if (t === "FSI") {
|
|
72
|
+
const pdi = matchingPDI(types, i);
|
|
73
|
+
isRTL = computeBaseLevel(types, i + 1, pdi) === 1;
|
|
74
|
+
}
|
|
75
|
+
const newLevel = isRTL ? nextOddLevel(top().level) : nextEvenLevel(top().level);
|
|
76
|
+
if (newLevel <= MAX_DEPTH && overflowIsolate === 0 && overflowEmbedding === 0) {
|
|
77
|
+
validIsolate++;
|
|
78
|
+
stack.push({
|
|
79
|
+
level: newLevel,
|
|
80
|
+
override: "neutral",
|
|
81
|
+
isolate: true
|
|
82
|
+
});
|
|
83
|
+
} else overflowIsolate++;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
case "PDI": {
|
|
87
|
+
if (overflowIsolate > 0) overflowIsolate--;
|
|
88
|
+
else if (validIsolate > 0) {
|
|
89
|
+
overflowEmbedding = 0;
|
|
90
|
+
while (!top().isolate) stack.pop();
|
|
91
|
+
stack.pop();
|
|
92
|
+
validIsolate--;
|
|
93
|
+
}
|
|
94
|
+
const ov = top().override;
|
|
95
|
+
levels[i] = top().level;
|
|
96
|
+
if (ov !== "neutral") types[i] = ov;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
case "PDF":
|
|
100
|
+
levels[i] = top().level;
|
|
101
|
+
types[i] = "BN";
|
|
102
|
+
if (overflowIsolate > 0) {} else if (overflowEmbedding > 0) overflowEmbedding--;
|
|
103
|
+
else if (!top().isolate && stack.length >= 2) stack.pop();
|
|
104
|
+
break;
|
|
105
|
+
case "B":
|
|
106
|
+
levels[i] = paragraphLevel;
|
|
107
|
+
break;
|
|
108
|
+
default: {
|
|
109
|
+
const ov = top().override;
|
|
110
|
+
levels[i] = top().level;
|
|
111
|
+
if (ov !== "neutral") types[i] = ov;
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const sequences = buildIsolatingRunSequences(types, levels, paragraphLevel);
|
|
117
|
+
for (const seq of sequences) {
|
|
118
|
+
resolveWeakTypes(types, seq);
|
|
119
|
+
resolveNeutralTypes(types, levels, seq, codePoints);
|
|
120
|
+
resolveImplicitLevels(types, levels, seq);
|
|
121
|
+
}
|
|
122
|
+
applyL1(origTypes, types, levels, paragraphLevel);
|
|
123
|
+
return {
|
|
124
|
+
levels,
|
|
125
|
+
paragraphLevel,
|
|
126
|
+
types
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function isRemoved(t) {
|
|
130
|
+
return t === "BN" || t === "RLE" || t === "LRE" || t === "RLO" || t === "LRO" || t === "PDF";
|
|
131
|
+
}
|
|
132
|
+
function buildIsolatingRunSequences(types, levels, paragraphLevel) {
|
|
133
|
+
const n = types.length;
|
|
134
|
+
const runs = [];
|
|
135
|
+
let current = [];
|
|
136
|
+
let currentLevel = -1;
|
|
137
|
+
for (let i = 0; i < n; i++) {
|
|
138
|
+
if (isRemoved(types[i])) continue;
|
|
139
|
+
if (current.length === 0) {
|
|
140
|
+
current = [i];
|
|
141
|
+
currentLevel = levels[i];
|
|
142
|
+
} else if (levels[i] === currentLevel) current.push(i);
|
|
143
|
+
else {
|
|
144
|
+
runs.push(current);
|
|
145
|
+
current = [i];
|
|
146
|
+
currentLevel = levels[i];
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (current.length > 0) runs.push(current);
|
|
150
|
+
const runByFirst = /* @__PURE__ */ new Map();
|
|
151
|
+
for (let r = 0; r < runs.length; r++) runByFirst.set(runs[r][0], r);
|
|
152
|
+
const used = new Array(runs.length).fill(false);
|
|
153
|
+
const sequences = [];
|
|
154
|
+
for (let r = 0; r < runs.length; r++) {
|
|
155
|
+
if (used[r]) continue;
|
|
156
|
+
const firstIdx = runs[r][0];
|
|
157
|
+
if (types[firstIdx] === "PDI" && matchesAnInitiator(types, firstIdx)) continue;
|
|
158
|
+
const seqIndices = [];
|
|
159
|
+
let cur = r;
|
|
160
|
+
for (;;) {
|
|
161
|
+
used[cur] = true;
|
|
162
|
+
seqIndices.push(...runs[cur]);
|
|
163
|
+
const lastIdx = runs[cur][runs[cur].length - 1];
|
|
164
|
+
const lastType = types[lastIdx];
|
|
165
|
+
if (lastType === "LRI" || lastType === "RLI" || lastType === "FSI") {
|
|
166
|
+
const pdi = matchingPDI(types, lastIdx);
|
|
167
|
+
if (pdi < n) {
|
|
168
|
+
const nextRun = runByFirst.get(pdi);
|
|
169
|
+
if (nextRun !== void 0 && !used[nextRun]) {
|
|
170
|
+
cur = nextRun;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
const seqLevel = levels[seqIndices[0]];
|
|
178
|
+
const prevLevel = levelBefore(types, levels, seqIndices[0], paragraphLevel);
|
|
179
|
+
const lastSeqIdx = seqIndices[seqIndices.length - 1];
|
|
180
|
+
const lastType = types[lastSeqIdx];
|
|
181
|
+
let nextLevel;
|
|
182
|
+
if (lastType === "LRI" || lastType === "RLI" || lastType === "FSI") nextLevel = paragraphLevel;
|
|
183
|
+
else nextLevel = levelAfter(types, levels, lastSeqIdx, paragraphLevel);
|
|
184
|
+
const sos = Math.max(seqLevel, prevLevel) % 2 === 0 ? "L" : "R";
|
|
185
|
+
const eos = Math.max(seqLevel, nextLevel) % 2 === 0 ? "L" : "R";
|
|
186
|
+
sequences.push({
|
|
187
|
+
indices: seqIndices,
|
|
188
|
+
sos,
|
|
189
|
+
eos
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
return sequences;
|
|
193
|
+
}
|
|
194
|
+
function matchesAnInitiator(types, pdiIdx) {
|
|
195
|
+
let depth = 1;
|
|
196
|
+
for (let i = pdiIdx - 1; i >= 0; i--) {
|
|
197
|
+
const t = types[i];
|
|
198
|
+
if (t === "PDI") depth++;
|
|
199
|
+
else if (t === "LRI" || t === "RLI" || t === "FSI") {
|
|
200
|
+
depth--;
|
|
201
|
+
if (depth === 0) return true;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
function levelBefore(types, levels, idx, paragraphLevel) {
|
|
207
|
+
for (let i = idx - 1; i >= 0; i--) if (!isRemoved(types[i])) return levels[i];
|
|
208
|
+
return paragraphLevel;
|
|
209
|
+
}
|
|
210
|
+
function levelAfter(types, levels, idx, paragraphLevel) {
|
|
211
|
+
for (let i = idx + 1; i < types.length; i++) if (!isRemoved(types[i])) return levels[i];
|
|
212
|
+
return paragraphLevel;
|
|
213
|
+
}
|
|
214
|
+
function resolveWeakTypes(types, seq) {
|
|
215
|
+
const idx = seq.indices;
|
|
216
|
+
const isIsolateType = (t) => t === "LRI" || t === "RLI" || t === "FSI" || t === "PDI";
|
|
217
|
+
let prevType = seq.sos;
|
|
218
|
+
for (const i of idx) {
|
|
219
|
+
if (types[i] === "NSM") types[i] = isIsolateType(prevType) ? "ON" : prevType;
|
|
220
|
+
prevType = types[i];
|
|
221
|
+
}
|
|
222
|
+
let lastStrong = seq.sos;
|
|
223
|
+
for (const i of idx) {
|
|
224
|
+
const t = types[i];
|
|
225
|
+
if (t === "R" || t === "L" || t === "AL") lastStrong = t;
|
|
226
|
+
else if (t === "EN" && lastStrong === "AL") types[i] = "AN";
|
|
227
|
+
}
|
|
228
|
+
for (const i of idx) if (types[i] === "AL") types[i] = "R";
|
|
229
|
+
for (let k = 1; k < idx.length - 1; k++) {
|
|
230
|
+
const i = idx[k];
|
|
231
|
+
const t = types[i];
|
|
232
|
+
const prev = types[idx[k - 1]];
|
|
233
|
+
const next = types[idx[k + 1]];
|
|
234
|
+
if (t === "ES" && prev === "EN" && next === "EN") types[i] = "EN";
|
|
235
|
+
else if (t === "CS" && prev === "EN" && next === "EN") types[i] = "EN";
|
|
236
|
+
else if (t === "CS" && prev === "AN" && next === "AN") types[i] = "AN";
|
|
237
|
+
}
|
|
238
|
+
for (let k = 0; k < idx.length; k++) {
|
|
239
|
+
if (types[idx[k]] !== "ET") continue;
|
|
240
|
+
let runEnd = k;
|
|
241
|
+
while (runEnd < idx.length && types[idx[runEnd]] === "ET") runEnd++;
|
|
242
|
+
const before = k > 0 ? types[idx[k - 1]] : seq.sos;
|
|
243
|
+
const after = runEnd < idx.length ? types[idx[runEnd]] : seq.eos;
|
|
244
|
+
if (before === "EN" || after === "EN") for (let j = k; j < runEnd; j++) types[idx[j]] = "EN";
|
|
245
|
+
k = runEnd - 1;
|
|
246
|
+
}
|
|
247
|
+
for (const i of idx) {
|
|
248
|
+
const t = types[i];
|
|
249
|
+
if (t === "ES" || t === "ET" || t === "CS") types[i] = "ON";
|
|
250
|
+
}
|
|
251
|
+
lastStrong = seq.sos;
|
|
252
|
+
for (const i of idx) {
|
|
253
|
+
const t = types[i];
|
|
254
|
+
if (t === "R" || t === "L") lastStrong = t;
|
|
255
|
+
else if (t === "EN" && lastStrong === "L") types[i] = "L";
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
var BRACKET_OPEN_TO_CLOSE = new Map([
|
|
259
|
+
[40, 41],
|
|
260
|
+
[91, 93],
|
|
261
|
+
[123, 125],
|
|
262
|
+
[8261, 8262],
|
|
263
|
+
[8317, 8318],
|
|
264
|
+
[8333, 8334],
|
|
265
|
+
[8968, 8969],
|
|
266
|
+
[8970, 8971],
|
|
267
|
+
[9001, 9002],
|
|
268
|
+
[10214, 10215],
|
|
269
|
+
[10216, 10217],
|
|
270
|
+
[10218, 10219],
|
|
271
|
+
[10220, 10221],
|
|
272
|
+
[10222, 10223],
|
|
273
|
+
[10627, 10628],
|
|
274
|
+
[10629, 10630],
|
|
275
|
+
[10631, 10632],
|
|
276
|
+
[10633, 10634],
|
|
277
|
+
[10635, 10636],
|
|
278
|
+
[10637, 10638],
|
|
279
|
+
[10639, 10640],
|
|
280
|
+
[10641, 10642],
|
|
281
|
+
[10643, 10644],
|
|
282
|
+
[10645, 10646],
|
|
283
|
+
[10647, 10648],
|
|
284
|
+
[12296, 12297],
|
|
285
|
+
[12298, 12299],
|
|
286
|
+
[12300, 12301],
|
|
287
|
+
[12302, 12303],
|
|
288
|
+
[12304, 12305],
|
|
289
|
+
[12308, 12309],
|
|
290
|
+
[12310, 12311],
|
|
291
|
+
[12312, 12313],
|
|
292
|
+
[12314, 12315],
|
|
293
|
+
[65288, 65289],
|
|
294
|
+
[65339, 65341],
|
|
295
|
+
[65371, 65373],
|
|
296
|
+
[65375, 65376],
|
|
297
|
+
[65378, 65379]
|
|
298
|
+
]);
|
|
299
|
+
function canonBracket(cp) {
|
|
300
|
+
if (cp === 9001) return 12296;
|
|
301
|
+
if (cp === 9002) return 12297;
|
|
302
|
+
return cp;
|
|
303
|
+
}
|
|
304
|
+
var CLOSE_BRACKETS = /* @__PURE__ */ new Set();
|
|
305
|
+
for (const c of BRACKET_OPEN_TO_CLOSE.values()) CLOSE_BRACKETS.add(canonBracket(c));
|
|
306
|
+
function resolvePairedBrackets(types, levels, seq, codePoints) {
|
|
307
|
+
const idx = seq.indices;
|
|
308
|
+
if (idx.length === 0) return;
|
|
309
|
+
const e = levels[idx[0]] % 2 === 0 ? "L" : "R";
|
|
310
|
+
const o = e === "L" ? "R" : "L";
|
|
311
|
+
const stack = [];
|
|
312
|
+
const pairs = [];
|
|
313
|
+
for (let p = 0; p < idx.length; p++) {
|
|
314
|
+
if (types[idx[p]] !== "ON") continue;
|
|
315
|
+
const cp = canonBracket(codePoints[idx[p]]);
|
|
316
|
+
const closing = BRACKET_OPEN_TO_CLOSE.get(cp);
|
|
317
|
+
if (closing !== void 0) {
|
|
318
|
+
if (stack.length === 63) break;
|
|
319
|
+
stack.push({
|
|
320
|
+
close: canonBracket(closing),
|
|
321
|
+
pos: p
|
|
322
|
+
});
|
|
323
|
+
} else if (CLOSE_BRACKETS.has(cp)) {
|
|
324
|
+
for (let s = stack.length - 1; s >= 0; s--) if (stack[s].close === cp) {
|
|
325
|
+
pairs.push({
|
|
326
|
+
open: stack[s].pos,
|
|
327
|
+
close: p
|
|
328
|
+
});
|
|
329
|
+
stack.length = s;
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
pairs.sort((a, b) => a.open - b.open);
|
|
335
|
+
for (const { open, close } of pairs) {
|
|
336
|
+
let foundE = false;
|
|
337
|
+
let foundO = false;
|
|
338
|
+
for (let p = open + 1; p < close; p++) {
|
|
339
|
+
const d = directionOf(types[idx[p]]);
|
|
340
|
+
if (d === e) {
|
|
341
|
+
foundE = true;
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
if (d === o) foundO = true;
|
|
345
|
+
}
|
|
346
|
+
let setDir = null;
|
|
347
|
+
if (foundE) setDir = e;
|
|
348
|
+
else if (foundO) {
|
|
349
|
+
let prev = "other";
|
|
350
|
+
for (let p = open - 1; p >= 0; p--) {
|
|
351
|
+
const d = directionOf(types[idx[p]]);
|
|
352
|
+
if (d !== "other") {
|
|
353
|
+
prev = d;
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (prev === "other") prev = directionOf(seq.sos);
|
|
358
|
+
setDir = prev === o ? o : e;
|
|
359
|
+
}
|
|
360
|
+
if (setDir) {
|
|
361
|
+
types[idx[open]] = setDir;
|
|
362
|
+
types[idx[close]] = setDir;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function resolveNeutralTypes(types, levels, seq, codePoints) {
|
|
367
|
+
const idx = seq.indices;
|
|
368
|
+
const isNeutralOrIsolate = (t) => t === "B" || t === "S" || t === "WS" || t === "ON" || t === "LRI" || t === "RLI" || t === "FSI" || t === "PDI";
|
|
369
|
+
resolvePairedBrackets(types, levels, seq, codePoints);
|
|
370
|
+
let k = 0;
|
|
371
|
+
while (k < idx.length) {
|
|
372
|
+
if (!isNeutralOrIsolate(types[idx[k]])) {
|
|
373
|
+
k++;
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
let runEnd = k;
|
|
377
|
+
while (runEnd < idx.length && isNeutralOrIsolate(types[idx[runEnd]])) runEnd++;
|
|
378
|
+
const leftDir = directionOf(k > 0 ? types[idx[k - 1]] : seq.sos);
|
|
379
|
+
const rightDir = directionOf(runEnd < idx.length ? types[idx[runEnd]] : seq.eos);
|
|
380
|
+
let resolved;
|
|
381
|
+
if (leftDir === rightDir && (leftDir === "L" || leftDir === "R")) resolved = leftDir;
|
|
382
|
+
else resolved = levels[idx[k]] % 2 === 0 ? "L" : "R";
|
|
383
|
+
for (let j = k; j < runEnd; j++) types[idx[j]] = resolved;
|
|
384
|
+
k = runEnd;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
function directionOf(t) {
|
|
388
|
+
if (t === "L") return "L";
|
|
389
|
+
if (t === "R" || t === "EN" || t === "AN") return "R";
|
|
390
|
+
return "other";
|
|
391
|
+
}
|
|
392
|
+
function resolveImplicitLevels(types, levels, seq) {
|
|
393
|
+
for (const i of seq.indices) {
|
|
394
|
+
const t = types[i];
|
|
395
|
+
const lvl = levels[i];
|
|
396
|
+
if (lvl % 2 === 0) {
|
|
397
|
+
if (t === "R") levels[i] = lvl + 1;
|
|
398
|
+
else if (t === "AN" || t === "EN") levels[i] = lvl + 2;
|
|
399
|
+
} else if (t === "L" || t === "EN" || t === "AN") levels[i] = lvl + 1;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function applyL1(origTypes, types, levels, paragraphLevel) {
|
|
403
|
+
const n = levels.length;
|
|
404
|
+
const isResettableWS = (t) => t === "WS" || t === "LRI" || t === "RLI" || t === "FSI" || t === "PDI" || isRemoved(t);
|
|
405
|
+
let i = 0;
|
|
406
|
+
while (i < n) {
|
|
407
|
+
const ot = origTypes[i];
|
|
408
|
+
if (ot === "B" || ot === "S") {
|
|
409
|
+
levels[i] = paragraphLevel;
|
|
410
|
+
let j = i - 1;
|
|
411
|
+
while (j >= 0 && isResettableWS(origTypes[j])) {
|
|
412
|
+
levels[j] = paragraphLevel;
|
|
413
|
+
j--;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
i++;
|
|
417
|
+
}
|
|
418
|
+
let j = n - 1;
|
|
419
|
+
while (j >= 0 && isResettableWS(origTypes[j])) {
|
|
420
|
+
levels[j] = paragraphLevel;
|
|
421
|
+
j--;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
function reorderVisual(levels) {
|
|
425
|
+
const n = levels.length;
|
|
426
|
+
const order = Array.from({ length: n }, (_, i) => i);
|
|
427
|
+
if (n === 0) return order;
|
|
428
|
+
let highest = 0;
|
|
429
|
+
let lowestOdd = MAX_DEPTH + 1;
|
|
430
|
+
for (const lvl of levels) {
|
|
431
|
+
if (lvl > highest) highest = lvl;
|
|
432
|
+
if (lvl % 2 === 1 && lvl < lowestOdd) lowestOdd = lvl;
|
|
433
|
+
}
|
|
434
|
+
for (let level = highest; level >= lowestOdd; level--) {
|
|
435
|
+
let k = 0;
|
|
436
|
+
while (k < n) {
|
|
437
|
+
if (levels[order[k]] < level) {
|
|
438
|
+
k++;
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
let runEnd = k;
|
|
442
|
+
while (runEnd < n && levels[order[runEnd]] >= level) runEnd++;
|
|
443
|
+
let a = k;
|
|
444
|
+
let b = runEnd - 1;
|
|
445
|
+
while (a < b) {
|
|
446
|
+
const tmp = order[a];
|
|
447
|
+
order[a] = order[b];
|
|
448
|
+
order[b] = tmp;
|
|
449
|
+
a++;
|
|
450
|
+
b--;
|
|
451
|
+
}
|
|
452
|
+
k = runEnd;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return order;
|
|
456
|
+
}
|
|
457
|
+
//#endregion
|
|
458
|
+
export { computeBidi, reorderVisual };
|