reamkit 1.33.2 → 1.34.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/README.md +13 -10
- package/dist/esm/core/converter/facade.js +1 -0
- package/dist/esm/core/converter/ream.d.ts +6 -6
- package/dist/esm/core/converter/ream.js +11 -5
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +15 -0
- package/dist/esm/core/drawingml/text-warp.d.ts +34 -0
- package/dist/esm/core/drawingml/text-warp.js +49 -1
- package/dist/esm/core/font/index.d.ts +1 -1
- package/dist/esm/core/font/index.js +8 -0
- package/dist/esm/core/font/indic-shaper.d.ts +47 -0
- package/dist/esm/core/font/indic-shaper.js +1146 -0
- package/dist/esm/core/font/indic-table.d.ts +12 -0
- package/dist/esm/core/font/indic-table.js +463 -0
- package/dist/esm/core/font/measure.d.ts +45 -4
- package/dist/esm/core/font/measure.js +253 -14
- package/dist/esm/core/font/opentype-lookups.d.ts +214 -0
- package/dist/esm/core/font/opentype-lookups.js +1112 -0
- package/dist/esm/core/fonts/families.d.ts +4 -0
- package/dist/esm/core/fonts/families.js +43 -12
- package/dist/esm/core/fonts/provider.js +1 -0
- package/dist/esm/core/fonts/remote-fonts.d.ts +4 -2
- package/dist/esm/core/fonts/remote-fonts.js +36 -0
- package/dist/esm/core/fonts/scripts.d.ts +19 -0
- package/dist/esm/core/fonts/scripts.js +96 -1
- package/dist/esm/core/numbering/apply.js +12 -8
- package/dist/esm/core/style-cascade/index.d.ts +1 -1
- package/dist/esm/core/style-cascade/index.js +2 -2
- package/dist/esm/core/style-cascade/resolver.d.ts +9 -0
- package/dist/esm/core/style-cascade/resolver.js +31 -3
- package/dist/esm/core/style-cascade/table.js +3 -8
- package/dist/esm/excel/xlsx-to-pdf.js +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/layout/line-paint.d.ts +5 -4
- package/dist/esm/layout/line-paint.js +6 -5
- package/dist/esm/layout/page-doc.d.ts +26 -0
- package/dist/esm/layout/styled-layout.d.ts +8 -1
- package/dist/esm/layout/styled-layout.js +459 -112
- package/dist/esm/layout/undrawn.d.ts +18 -0
- package/dist/esm/layout/undrawn.js +59 -0
- package/dist/esm/pdf/cid-font.d.ts +5 -0
- package/dist/esm/pdf/cid-font.js +8 -2
- package/dist/esm/pdf/styled-page-emitter.js +117 -28
- package/dist/esm/pdf/text-page-renderer.js +1 -0
- package/dist/esm/pdf-reader/content.d.ts +1 -1
- package/dist/esm/pdf-reader/content.js +48 -13
- package/dist/esm/pdf-reader/embedded-fonts.js +1 -0
- package/dist/esm/pdf-reader/encodings.d.ts +9 -0
- package/dist/esm/pdf-reader/encodings.js +66 -1
- package/dist/esm/pdf-reader/face-outlines.js +1 -0
- package/dist/esm/pdf-reader/text.d.ts +10 -0
- package/dist/esm/pdf-reader/text.js +25 -3
- package/dist/esm/pdf-reader/vector.js +2 -2
- package/dist/esm/pptx/embedded-fonts.js +1 -0
- package/dist/esm/pptx/sp-helpers.js +4 -1
- package/dist/esm/svg/svg-text.js +30 -7
- package/dist/esm/word/doc/doc-reader.js +148 -57
- package/dist/esm/word/doc/doc-text.d.ts +80 -11
- package/dist/esm/word/doc/doc-text.js +438 -75
- package/dist/esm/word/document-parser.js +48 -28
- package/dist/esm/word/docx-to-pdf.d.ts +5 -5
- package/dist/esm/word/docx-to-pdf.js +9 -7
- package/dist/esm/word/docx-writer.js +3 -5
- package/dist/esm/word/font-embed.js +1 -0
- package/dist/esm/word/font-table.js +1 -0
- package/dist/esm/word/numbering-parser.js +3 -0
- package/dist/esm/word/paragraph-properties.js +2 -2
- package/dist/esm/word/run-properties.js +2 -2
- package/dist/esm/word/xml-helpers.d.ts +18 -0
- package/dist/esm/word/xml-helpers.js +48 -1
- package/package.json +2 -2
|
@@ -0,0 +1,1146 @@
|
|
|
1
|
+
import { COMBINING, DECOMPOSITIONS, EXTENDED, MAIN, SINGLES, VEDIC } from "./indic-table.js";
|
|
2
|
+
import { OtlFace, joinSources, mergeClusters, pickScript, planLookups, position, substitute, wouldSubstitute } from "./opentype-lookups.js";
|
|
3
|
+
//#region src/core/font/indic-shaper.ts
|
|
4
|
+
var X = 0;
|
|
5
|
+
var C = 1;
|
|
6
|
+
var V = 2;
|
|
7
|
+
var N = 3;
|
|
8
|
+
var H = 4;
|
|
9
|
+
var ZWNJ = 5;
|
|
10
|
+
var ZWJ = 6;
|
|
11
|
+
var M = 7;
|
|
12
|
+
var SM = 8;
|
|
13
|
+
var A = 10;
|
|
14
|
+
var PLACEHOLDER = 11;
|
|
15
|
+
var DOTTEDCIRCLE = 12;
|
|
16
|
+
var RS = 13;
|
|
17
|
+
var MPST = 14;
|
|
18
|
+
var REPHA = 15;
|
|
19
|
+
var RA = 16;
|
|
20
|
+
var CM = 17;
|
|
21
|
+
var SYMBOL = 18;
|
|
22
|
+
var CS = 19;
|
|
23
|
+
var RA_TO_BECOME_REPH = 1;
|
|
24
|
+
var PRE_M = 2;
|
|
25
|
+
var PRE_C = 3;
|
|
26
|
+
var BASE_C = 4;
|
|
27
|
+
var AFTER_MAIN = 5;
|
|
28
|
+
var BEFORE_SUB = 7;
|
|
29
|
+
var BELOW_C = 8;
|
|
30
|
+
var AFTER_SUB = 9;
|
|
31
|
+
var BEFORE_POST = 10;
|
|
32
|
+
var POST_C = 11;
|
|
33
|
+
var AFTER_POST = 12;
|
|
34
|
+
var SMVD = 13;
|
|
35
|
+
var END = 14;
|
|
36
|
+
var flag = (c) => 1 << c;
|
|
37
|
+
var CONSONANTS = flag(C) | flag(CS) | flag(RA) | flag(CM) | flag(V) | flag(PLACEHOLDER) | flag(DOTTEDCIRCLE);
|
|
38
|
+
var JOINERS = flag(ZWJ) | flag(ZWNJ);
|
|
39
|
+
var CONSONANT_SYLLABLE = 0;
|
|
40
|
+
var VOWEL_SYLLABLE = 1;
|
|
41
|
+
var STANDALONE_CLUSTER = 2;
|
|
42
|
+
var SYMBOL_CLUSTER = 3;
|
|
43
|
+
var BROKEN_CLUSTER = 4;
|
|
44
|
+
var NON_INDIC = 5;
|
|
45
|
+
var SCRIPTS = [
|
|
46
|
+
[2304, {
|
|
47
|
+
tags: ["dev2", "deva"],
|
|
48
|
+
virama: 2381,
|
|
49
|
+
rephPos: BEFORE_POST,
|
|
50
|
+
rephMode: "implicit",
|
|
51
|
+
blwfPostOnly: false
|
|
52
|
+
}],
|
|
53
|
+
[2432, {
|
|
54
|
+
tags: ["bng2", "beng"],
|
|
55
|
+
virama: 2509,
|
|
56
|
+
rephPos: AFTER_SUB,
|
|
57
|
+
rephMode: "implicit",
|
|
58
|
+
blwfPostOnly: false
|
|
59
|
+
}],
|
|
60
|
+
[2560, {
|
|
61
|
+
tags: ["gur2", "guru"],
|
|
62
|
+
virama: 2637,
|
|
63
|
+
rephPos: BEFORE_SUB,
|
|
64
|
+
rephMode: "implicit",
|
|
65
|
+
blwfPostOnly: false
|
|
66
|
+
}],
|
|
67
|
+
[2688, {
|
|
68
|
+
tags: ["gjr2", "gujr"],
|
|
69
|
+
virama: 2765,
|
|
70
|
+
rephPos: BEFORE_POST,
|
|
71
|
+
rephMode: "implicit",
|
|
72
|
+
blwfPostOnly: false
|
|
73
|
+
}],
|
|
74
|
+
[2816, {
|
|
75
|
+
tags: ["ory2", "orya"],
|
|
76
|
+
virama: 2893,
|
|
77
|
+
rephPos: AFTER_MAIN,
|
|
78
|
+
rephMode: "implicit",
|
|
79
|
+
blwfPostOnly: false
|
|
80
|
+
}],
|
|
81
|
+
[2944, {
|
|
82
|
+
tags: ["tml2", "taml"],
|
|
83
|
+
virama: 3021,
|
|
84
|
+
rephPos: AFTER_POST,
|
|
85
|
+
rephMode: "implicit",
|
|
86
|
+
blwfPostOnly: false
|
|
87
|
+
}],
|
|
88
|
+
[3072, {
|
|
89
|
+
tags: ["tel2", "telu"],
|
|
90
|
+
virama: 3149,
|
|
91
|
+
rephPos: AFTER_POST,
|
|
92
|
+
rephMode: "explicit",
|
|
93
|
+
blwfPostOnly: true
|
|
94
|
+
}],
|
|
95
|
+
[3200, {
|
|
96
|
+
tags: ["knd2", "knda"],
|
|
97
|
+
virama: 3277,
|
|
98
|
+
rephPos: AFTER_POST,
|
|
99
|
+
rephMode: "implicit",
|
|
100
|
+
blwfPostOnly: true
|
|
101
|
+
}],
|
|
102
|
+
[3328, {
|
|
103
|
+
tags: ["mlm2", "mlym"],
|
|
104
|
+
virama: 3405,
|
|
105
|
+
rephPos: AFTER_MAIN,
|
|
106
|
+
rephMode: "logical",
|
|
107
|
+
blwfPostOnly: false
|
|
108
|
+
}]
|
|
109
|
+
];
|
|
110
|
+
/** The script whose block a code point is in, or `undefined` for one shared by all. */
|
|
111
|
+
function scriptOf(cp) {
|
|
112
|
+
if (cp >= 43232 && cp <= 43263) return SCRIPTS[0][1];
|
|
113
|
+
if (cp < 2304 || cp > 3455) return void 0;
|
|
114
|
+
if (cp === 2404 || cp === 2405 || cp >= 2385 && cp <= 2388) return void 0;
|
|
115
|
+
return SCRIPTS[cp - 2304 >> 7][1];
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Whether a string holds a letter of one of the scripts this shapes.
|
|
119
|
+
*
|
|
120
|
+
* @param text The text.
|
|
121
|
+
* @returns `true` when one of its characters is in their blocks.
|
|
122
|
+
*/
|
|
123
|
+
function hasIndic(text) {
|
|
124
|
+
for (let i = 0; i < text.length; i++) {
|
|
125
|
+
const c = text.charCodeAt(i);
|
|
126
|
+
if (c >= 2304 && c <= 3455 && c !== 2404 && c !== 2405) return true;
|
|
127
|
+
}
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
var DIGITS = "0123456789abcdefghijklmnopqrstuv";
|
|
131
|
+
var SINGLE = new Map(SINGLES.map(([cp, c, p]) => [cp, [c, p]]));
|
|
132
|
+
/** A character's category and position; X and END for one the scripts do not use. */
|
|
133
|
+
function properties(cp) {
|
|
134
|
+
let table;
|
|
135
|
+
let at = 0;
|
|
136
|
+
if (cp >= 2304 && cp <= 3455) {
|
|
137
|
+
table = MAIN;
|
|
138
|
+
at = cp - 2304;
|
|
139
|
+
} else if (cp >= 7376 && cp <= 7423) {
|
|
140
|
+
table = VEDIC;
|
|
141
|
+
at = cp - 7376;
|
|
142
|
+
} else if (cp >= 43232 && cp <= 43263) {
|
|
143
|
+
table = EXTENDED;
|
|
144
|
+
at = cp - 43232;
|
|
145
|
+
}
|
|
146
|
+
if (table === void 0) return SINGLE.get(cp) ?? [X, END];
|
|
147
|
+
return [DIGITS.indexOf(table[2 * at]), DIGITS.indexOf(table[2 * at + 1])];
|
|
148
|
+
}
|
|
149
|
+
var DECOMPOSE = new Map(DECOMPOSITIONS.map(([cp, a, b]) => [cp, [a, b]]));
|
|
150
|
+
var COMPOSE = new Map(DECOMPOSITIONS.filter(([, , , excluded]) => !excluded).map(([cp, a, b]) => [`${a},${b}`, cp]));
|
|
151
|
+
COMPOSE.set(`2479,2492`, 2527);
|
|
152
|
+
var KEEP_WHOLE = /* @__PURE__ */ new Set([
|
|
153
|
+
2353,
|
|
154
|
+
2524,
|
|
155
|
+
2525,
|
|
156
|
+
2964
|
|
157
|
+
]);
|
|
158
|
+
var CCC = new Map([
|
|
159
|
+
...COMBINING,
|
|
160
|
+
[3157, 4],
|
|
161
|
+
[3158, 5]
|
|
162
|
+
]);
|
|
163
|
+
var MARK_RE = /\p{M}/u;
|
|
164
|
+
var isMark = (cp) => MARK_RE.test(String.fromCodePoint(cp));
|
|
165
|
+
var WORDISH_RE = /[\p{L}\p{M}\p{Cf}\p{Co}\p{Cs}\p{Cn}]/u;
|
|
166
|
+
var sym = (...cats) => ({
|
|
167
|
+
kind: "sym",
|
|
168
|
+
set: cats.reduce((m, c) => m | flag(c), 0)
|
|
169
|
+
});
|
|
170
|
+
var seq = (...parts) => ({
|
|
171
|
+
kind: "seq",
|
|
172
|
+
parts
|
|
173
|
+
});
|
|
174
|
+
var alt = (...parts) => ({
|
|
175
|
+
kind: "alt",
|
|
176
|
+
parts
|
|
177
|
+
});
|
|
178
|
+
var opt = (part) => ({
|
|
179
|
+
kind: "opt",
|
|
180
|
+
part
|
|
181
|
+
});
|
|
182
|
+
var star = (part) => ({
|
|
183
|
+
kind: "star",
|
|
184
|
+
part
|
|
185
|
+
});
|
|
186
|
+
function compile(pattern) {
|
|
187
|
+
const on = [];
|
|
188
|
+
const to = [];
|
|
189
|
+
const free = [];
|
|
190
|
+
const state = () => {
|
|
191
|
+
on.push(0);
|
|
192
|
+
to.push(-1);
|
|
193
|
+
free.push([]);
|
|
194
|
+
return on.length - 1;
|
|
195
|
+
};
|
|
196
|
+
const build = (p) => {
|
|
197
|
+
const s = state();
|
|
198
|
+
const e = state();
|
|
199
|
+
switch (p.kind) {
|
|
200
|
+
case "sym":
|
|
201
|
+
on[s] = p.set;
|
|
202
|
+
to[s] = e;
|
|
203
|
+
break;
|
|
204
|
+
case "seq": {
|
|
205
|
+
let at = s;
|
|
206
|
+
for (const part of p.parts) {
|
|
207
|
+
const [ps, pe] = build(part);
|
|
208
|
+
free[at].push(ps);
|
|
209
|
+
at = pe;
|
|
210
|
+
}
|
|
211
|
+
free[at].push(e);
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
case "alt":
|
|
215
|
+
for (const part of p.parts) {
|
|
216
|
+
const [ps, pe] = build(part);
|
|
217
|
+
free[s].push(ps);
|
|
218
|
+
free[pe].push(e);
|
|
219
|
+
}
|
|
220
|
+
break;
|
|
221
|
+
case "opt": {
|
|
222
|
+
const [ps, pe] = build(p.part);
|
|
223
|
+
free[s].push(ps, e);
|
|
224
|
+
free[pe].push(e);
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
case "star": {
|
|
228
|
+
const [ps, pe] = build(p.part);
|
|
229
|
+
free[s].push(ps, e);
|
|
230
|
+
free[pe].push(ps, e);
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return [s, e];
|
|
235
|
+
};
|
|
236
|
+
const [start, accept] = build(pattern);
|
|
237
|
+
return {
|
|
238
|
+
on,
|
|
239
|
+
to,
|
|
240
|
+
free,
|
|
241
|
+
start,
|
|
242
|
+
accept
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
/** How long a match of `nfa` from `from` can be, up to `limit`; 0 for none. */
|
|
246
|
+
function longest(nfa, cats, from, limit) {
|
|
247
|
+
const close = (states) => {
|
|
248
|
+
const out = /* @__PURE__ */ new Set();
|
|
249
|
+
const stack = [...states];
|
|
250
|
+
while (stack.length > 0) {
|
|
251
|
+
const s = stack.pop();
|
|
252
|
+
if (out.has(s)) continue;
|
|
253
|
+
out.add(s);
|
|
254
|
+
for (const t of nfa.free[s]) stack.push(t);
|
|
255
|
+
}
|
|
256
|
+
return out;
|
|
257
|
+
};
|
|
258
|
+
let states = close([nfa.start]);
|
|
259
|
+
let best = 0;
|
|
260
|
+
for (let i = from; i < cats.length && i - from < limit; i++) {
|
|
261
|
+
const bit = flag(cats[i]);
|
|
262
|
+
const next = [];
|
|
263
|
+
for (const s of states) if (nfa.on[s] & bit) next.push(nfa.to[s]);
|
|
264
|
+
if (next.length === 0) break;
|
|
265
|
+
states = close(next);
|
|
266
|
+
if (states.has(nfa.accept)) best = i - from + 1;
|
|
267
|
+
}
|
|
268
|
+
return best;
|
|
269
|
+
}
|
|
270
|
+
var RULES = (() => {
|
|
271
|
+
const c = sym(C, RA);
|
|
272
|
+
const n = seq(opt(seq(opt(sym(ZWNJ)), sym(RS))), opt(seq(sym(N), opt(sym(N)))));
|
|
273
|
+
const z = sym(ZWJ, ZWNJ);
|
|
274
|
+
const reph = alt(seq(sym(RA), sym(H)), sym(REPHA));
|
|
275
|
+
const cn = seq(c, opt(sym(ZWJ)), opt(n));
|
|
276
|
+
const symbol = seq(sym(SYMBOL), opt(sym(N)));
|
|
277
|
+
const matraGroup = seq(star(z), alt(sym(M), seq(opt(sym(SM)), sym(MPST))), opt(sym(N)), opt(sym(H)));
|
|
278
|
+
const syllableTail = seq(opt(seq(opt(z), sym(SM), opt(sym(SM)), opt(sym(ZWNJ)))), star(sym(A)));
|
|
279
|
+
const halantGroup = seq(opt(z), sym(H), opt(seq(sym(ZWJ), opt(sym(N)))));
|
|
280
|
+
const halantOrMatraGroup = alt(alt(halantGroup, seq(sym(H), sym(ZWNJ))), star(matraGroup));
|
|
281
|
+
const tail = seq(star(seq(halantGroup, cn)), opt(sym(CM)), halantOrMatraGroup, syllableTail);
|
|
282
|
+
return [
|
|
283
|
+
[CONSONANT_SYLLABLE, compile(seq(opt(sym(REPHA, CS)), cn, tail))],
|
|
284
|
+
[VOWEL_SYLLABLE, compile(seq(opt(reph), sym(V), opt(n), alt(sym(ZWJ), tail)))],
|
|
285
|
+
[STANDALONE_CLUSTER, compile(seq(alt(seq(opt(sym(REPHA, CS)), sym(PLACEHOLDER)), seq(opt(reph), sym(DOTTEDCIRCLE))), opt(n), tail))],
|
|
286
|
+
[SYMBOL_CLUSTER, compile(seq(symbol, syllableTail))],
|
|
287
|
+
[BROKEN_CLUSTER, compile(seq(opt(reph), opt(n), tail))]
|
|
288
|
+
];
|
|
289
|
+
})();
|
|
290
|
+
var MAX_SYLLABLE = 64;
|
|
291
|
+
/** Cut a run of categories into syllables, longest match first: [start, end, type] each. */
|
|
292
|
+
function syllables(cats) {
|
|
293
|
+
const out = [];
|
|
294
|
+
let i = 0;
|
|
295
|
+
while (i < cats.length) {
|
|
296
|
+
let len = 0;
|
|
297
|
+
let type = NON_INDIC;
|
|
298
|
+
for (const [t, nfa] of RULES) {
|
|
299
|
+
const l = longest(nfa, cats, i, MAX_SYLLABLE);
|
|
300
|
+
if (l > len) {
|
|
301
|
+
len = l;
|
|
302
|
+
type = t;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (len === 0) len = 1;
|
|
306
|
+
out.push([
|
|
307
|
+
i,
|
|
308
|
+
i + len,
|
|
309
|
+
type
|
|
310
|
+
]);
|
|
311
|
+
i += len;
|
|
312
|
+
}
|
|
313
|
+
return out;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Where the syllables of the scripts of India begin in a text, by the grammar
|
|
317
|
+
* the shaper cuts them with: a syllable is the unit a reph, a conjunct or a
|
|
318
|
+
* vowel sign is shaped within, and shaped in two halves it falls apart.
|
|
319
|
+
*
|
|
320
|
+
* @param text The text.
|
|
321
|
+
* @returns Per code point: whether a syllable — or anything that is not one —
|
|
322
|
+
* begins at it.
|
|
323
|
+
*/
|
|
324
|
+
function syllableStarts(text) {
|
|
325
|
+
const cats = [...text].map((ch) => properties(ch.codePointAt(0))[0]);
|
|
326
|
+
const out = cats.map(() => false);
|
|
327
|
+
for (const [start] of syllables(cats)) out[start] = true;
|
|
328
|
+
return out;
|
|
329
|
+
}
|
|
330
|
+
var GLOBAL = 1;
|
|
331
|
+
var RPHF = 2;
|
|
332
|
+
var PREF = 4;
|
|
333
|
+
var BLWF = 8;
|
|
334
|
+
var ABVF = 16;
|
|
335
|
+
var HALF = 32;
|
|
336
|
+
var PSTF = 64;
|
|
337
|
+
var INIT = 128;
|
|
338
|
+
var indic = (tag, mask = GLOBAL) => ({
|
|
339
|
+
tag,
|
|
340
|
+
mask,
|
|
341
|
+
manualZwj: true,
|
|
342
|
+
manualZwnj: true,
|
|
343
|
+
perSyllable: true
|
|
344
|
+
});
|
|
345
|
+
var plain = (tag) => ({
|
|
346
|
+
tag,
|
|
347
|
+
mask: GLOBAL
|
|
348
|
+
});
|
|
349
|
+
var markFeature = (tag) => ({
|
|
350
|
+
tag,
|
|
351
|
+
mask: GLOBAL,
|
|
352
|
+
manualZwj: true,
|
|
353
|
+
manualZwnj: true
|
|
354
|
+
});
|
|
355
|
+
/** The features, stage by stage: each stage runs to the end before the next. */
|
|
356
|
+
var BASIC = [
|
|
357
|
+
indic("nukt"),
|
|
358
|
+
indic("akhn"),
|
|
359
|
+
indic("rphf", RPHF),
|
|
360
|
+
indic("rkrf"),
|
|
361
|
+
indic("pref", PREF),
|
|
362
|
+
indic("blwf", BLWF),
|
|
363
|
+
indic("abvf", ABVF),
|
|
364
|
+
indic("half", HALF),
|
|
365
|
+
indic("pstf", PSTF),
|
|
366
|
+
indic("vatu"),
|
|
367
|
+
indic("cjct")
|
|
368
|
+
];
|
|
369
|
+
var PRESENTATION = [
|
|
370
|
+
indic("init", INIT),
|
|
371
|
+
indic("pres"),
|
|
372
|
+
indic("abvs"),
|
|
373
|
+
indic("blws"),
|
|
374
|
+
indic("psts"),
|
|
375
|
+
indic("haln"),
|
|
376
|
+
...[
|
|
377
|
+
"abvm",
|
|
378
|
+
"blwm",
|
|
379
|
+
"rlig",
|
|
380
|
+
"calt",
|
|
381
|
+
"clig",
|
|
382
|
+
"curs",
|
|
383
|
+
"dist",
|
|
384
|
+
"liga",
|
|
385
|
+
"rclt"
|
|
386
|
+
].map(plain),
|
|
387
|
+
markFeature("mark"),
|
|
388
|
+
markFeature("mkmk")
|
|
389
|
+
];
|
|
390
|
+
var plans = /* @__PURE__ */ new WeakMap();
|
|
391
|
+
function planFor(parsed, config) {
|
|
392
|
+
let byScript = plans.get(parsed);
|
|
393
|
+
if (!byScript) {
|
|
394
|
+
byScript = /* @__PURE__ */ new Map();
|
|
395
|
+
plans.set(parsed, byScript);
|
|
396
|
+
}
|
|
397
|
+
const had = byScript.get(config);
|
|
398
|
+
if (had !== void 0) return had ?? void 0;
|
|
399
|
+
const face = OtlFace.of(parsed);
|
|
400
|
+
const script = pickScript(face.gsub, config.tags);
|
|
401
|
+
if (script === void 0 || !config.tags.includes(script)) {
|
|
402
|
+
byScript.set(config, null);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const tags = config.tags;
|
|
406
|
+
const oldSpec = !script.endsWith("2");
|
|
407
|
+
const has = /* @__PURE__ */ new Set();
|
|
408
|
+
for (const f of [...BASIC, ...PRESENTATION]) if (planLookups(face.gsub, tags, [f]).length > 0) has.add(f.tag);
|
|
409
|
+
const only = (tag) => planLookups(face.gsub, tags, [BASIC.find((f) => f.tag === tag)]);
|
|
410
|
+
const plan = {
|
|
411
|
+
config,
|
|
412
|
+
face,
|
|
413
|
+
oldSpec,
|
|
414
|
+
early: planLookups(face.gsub, tags, [
|
|
415
|
+
plain("rvrn"),
|
|
416
|
+
plain("ltra"),
|
|
417
|
+
plain("ltrm"),
|
|
418
|
+
{
|
|
419
|
+
tag: "locl",
|
|
420
|
+
mask: GLOBAL,
|
|
421
|
+
perSyllable: true
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
tag: "ccmp",
|
|
425
|
+
mask: GLOBAL,
|
|
426
|
+
perSyllable: true
|
|
427
|
+
}
|
|
428
|
+
]),
|
|
429
|
+
basic: BASIC.map((f) => planLookups(face.gsub, tags, [f])),
|
|
430
|
+
late: planLookups(face.gsub, tags, PRESENTATION),
|
|
431
|
+
has,
|
|
432
|
+
rphf: only("rphf"),
|
|
433
|
+
pref: only("pref"),
|
|
434
|
+
blwf: only("blwf"),
|
|
435
|
+
pstf: only("pstf"),
|
|
436
|
+
vatu: only("vatu"),
|
|
437
|
+
zeroContext: !oldSpec && config !== SCRIPTS[8][1],
|
|
438
|
+
virama: parsed.glyphForCodepoint(config.virama),
|
|
439
|
+
consonants: /* @__PURE__ */ new Map()
|
|
440
|
+
};
|
|
441
|
+
byScript.set(config, plan);
|
|
442
|
+
return plan;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Shape a run of text in one face, when the face has OpenType layout for the
|
|
446
|
+
* script of India its text is in.
|
|
447
|
+
*
|
|
448
|
+
* @param parsed The face.
|
|
449
|
+
* @param text The text.
|
|
450
|
+
* @param kern Whether to apply the face's `kern` feature (the layout profile decides).
|
|
451
|
+
* @returns The shaped glyphs, or `undefined` when the text holds no letter of
|
|
452
|
+
* those scripts or the face has no layout for them — the caller's
|
|
453
|
+
* own shaping applies then.
|
|
454
|
+
*/
|
|
455
|
+
function shapeIndic(parsed, text, kern = true) {
|
|
456
|
+
const cps = [...text].map((ch) => ch.codePointAt(0));
|
|
457
|
+
const runs = [];
|
|
458
|
+
cps.forEach((cp, i) => {
|
|
459
|
+
const config = scriptOf(cp);
|
|
460
|
+
const last = runs[runs.length - 1];
|
|
461
|
+
if (last && (!config || config === last.config)) last.to = i + 1;
|
|
462
|
+
else if (config) runs.push({
|
|
463
|
+
config,
|
|
464
|
+
from: last ? i : 0,
|
|
465
|
+
to: i + 1
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
if (runs.length === 0) return void 0;
|
|
469
|
+
runs[runs.length - 1].to = cps.length;
|
|
470
|
+
const out = emptyRun();
|
|
471
|
+
let shapedAny = false;
|
|
472
|
+
for (const run of runs) {
|
|
473
|
+
const plan = planFor(parsed, run.config);
|
|
474
|
+
const shaped = plan ? shapeRun(plan, parsed, cps.slice(run.from, run.to), kern) : unshaped(parsed, cps.slice(run.from, run.to));
|
|
475
|
+
shapedAny ||= plan !== void 0;
|
|
476
|
+
out.gids.push(...shaped.gids);
|
|
477
|
+
out.clusters.push(...shaped.clusters.map((c) => c + run.from));
|
|
478
|
+
out.sources.push(...shaped.sources.map((s) => s.map((c) => c + run.from)));
|
|
479
|
+
out.advances.push(...shaped.advances);
|
|
480
|
+
out.xOffsets.push(...shaped.xOffsets);
|
|
481
|
+
out.yOffsets.push(...shaped.yOffsets);
|
|
482
|
+
}
|
|
483
|
+
return shapedAny ? out : void 0;
|
|
484
|
+
}
|
|
485
|
+
/** A run with no glyphs yet. */
|
|
486
|
+
function emptyRun() {
|
|
487
|
+
return {
|
|
488
|
+
gids: [],
|
|
489
|
+
clusters: [],
|
|
490
|
+
sources: [],
|
|
491
|
+
advances: [],
|
|
492
|
+
xOffsets: [],
|
|
493
|
+
yOffsets: []
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
/** Each character its own glyph, at its own advance. */
|
|
497
|
+
function unshaped(parsed, cps) {
|
|
498
|
+
const gids = cps.map((cp) => parsed.glyphForCodepoint(cp));
|
|
499
|
+
return {
|
|
500
|
+
gids,
|
|
501
|
+
clusters: cps.map((_, i) => i),
|
|
502
|
+
sources: cps.map((_, i) => [i]),
|
|
503
|
+
advances: gids.map((g) => parsed.advanceWidths[g] ?? 0),
|
|
504
|
+
xOffsets: gids.map(() => 0),
|
|
505
|
+
yOffsets: gids.map(() => 0)
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
/** Shape the characters of one script in a face that has its layout. */
|
|
509
|
+
function shapeRun(plan, parsed, cps, kern) {
|
|
510
|
+
const { config } = plan;
|
|
511
|
+
const buf = normalize(parsed, plan.face, cps);
|
|
512
|
+
for (const g of buf) {
|
|
513
|
+
[g.cat, g.pos] = properties(g.cp);
|
|
514
|
+
if (g.cat === ZWJ) g.joiner = "zwj";
|
|
515
|
+
else if (g.cat === ZWNJ) g.joiner = "zwnj";
|
|
516
|
+
}
|
|
517
|
+
let serial = 0;
|
|
518
|
+
for (const [start, end, type] of syllables(buf.map((g) => g.cat))) {
|
|
519
|
+
serial++;
|
|
520
|
+
for (let i = start; i < end; i++) {
|
|
521
|
+
buf[i].syllable = serial;
|
|
522
|
+
buf[i].type = type;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
substitute(plan.face, plan.early, buf);
|
|
526
|
+
initialReordering(plan, parsed, buf);
|
|
527
|
+
for (const steps of plan.basic) substitute(plan.face, steps, buf);
|
|
528
|
+
finalReordering(plan, buf);
|
|
529
|
+
substitute(plan.face, plan.late, buf);
|
|
530
|
+
const gpos = planLookups(plan.face.gpos, config.tags, [
|
|
531
|
+
...[
|
|
532
|
+
"abvm",
|
|
533
|
+
"blwm",
|
|
534
|
+
"curs",
|
|
535
|
+
"dist",
|
|
536
|
+
...kern ? ["kern"] : []
|
|
537
|
+
].map(plain),
|
|
538
|
+
markFeature("mark"),
|
|
539
|
+
markFeature("mkmk")
|
|
540
|
+
]);
|
|
541
|
+
const pos = position(plan.face, gpos, buf);
|
|
542
|
+
removeIgnorables(buf, pos);
|
|
543
|
+
const out = emptyRun();
|
|
544
|
+
buf.forEach((g, i) => {
|
|
545
|
+
const p = pos[i];
|
|
546
|
+
out.gids.push(g.gid);
|
|
547
|
+
out.clusters.push(g.cluster);
|
|
548
|
+
out.sources.push(g.sources);
|
|
549
|
+
out.advances.push(p.xAdvance);
|
|
550
|
+
out.xOffsets.push(p.xOffset);
|
|
551
|
+
out.yOffsets.push(p.yOffset);
|
|
552
|
+
});
|
|
553
|
+
return out;
|
|
554
|
+
}
|
|
555
|
+
var IGNORABLE = /* @__PURE__ */ new Set([
|
|
556
|
+
8203,
|
|
557
|
+
8204,
|
|
558
|
+
8205,
|
|
559
|
+
8206,
|
|
560
|
+
8207,
|
|
561
|
+
8288,
|
|
562
|
+
65279
|
|
563
|
+
]);
|
|
564
|
+
/**
|
|
565
|
+
* Take out what draws nothing, once the lookups are done with it: a join
|
|
566
|
+
* control has decided whether a conjunct forms and whether a mark attaches,
|
|
567
|
+
* and is not drawn. Its cluster goes to a neighbour the way HarfBuzz's
|
|
568
|
+
* `delete_glyph` gives it.
|
|
569
|
+
*/
|
|
570
|
+
function removeIgnorables(buf, pos) {
|
|
571
|
+
for (let i = 0; i < buf.length; i++) {
|
|
572
|
+
const g = buf[i];
|
|
573
|
+
if (!IGNORABLE.has(g.cp) || ligated(g)) continue;
|
|
574
|
+
const prev = buf[i - 1];
|
|
575
|
+
const next = buf[i + 1];
|
|
576
|
+
if (next?.cluster === g.cluster || prev?.cluster === g.cluster) {} else if (prev) {
|
|
577
|
+
if (g.cluster < prev.cluster) {
|
|
578
|
+
const old = prev.cluster;
|
|
579
|
+
for (let k = i; k > 0 && buf[k - 1].cluster === old; k--) buf[k - 1].cluster = g.cluster;
|
|
580
|
+
}
|
|
581
|
+
} else if (next) mergeClusters(buf, i, i + 2);
|
|
582
|
+
buf.splice(i, 1);
|
|
583
|
+
pos.splice(i, 1);
|
|
584
|
+
i--;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
var VOWEL_CONSTRAINTS = new Map([
|
|
588
|
+
[2309, [
|
|
589
|
+
2362,
|
|
590
|
+
2363,
|
|
591
|
+
2366,
|
|
592
|
+
2373,
|
|
593
|
+
2374,
|
|
594
|
+
2377,
|
|
595
|
+
2378,
|
|
596
|
+
2379,
|
|
597
|
+
2380,
|
|
598
|
+
2383,
|
|
599
|
+
2390,
|
|
600
|
+
2391
|
|
601
|
+
]],
|
|
602
|
+
[2310, [
|
|
603
|
+
2362,
|
|
604
|
+
2373,
|
|
605
|
+
2374,
|
|
606
|
+
2375,
|
|
607
|
+
2376
|
|
608
|
+
]],
|
|
609
|
+
[2313, [2369]],
|
|
610
|
+
[2319, [
|
|
611
|
+
2373,
|
|
612
|
+
2374,
|
|
613
|
+
2375
|
|
614
|
+
]],
|
|
615
|
+
[2437, [2494]],
|
|
616
|
+
[2443, [2499]],
|
|
617
|
+
[2444, [2530]],
|
|
618
|
+
[2565, [
|
|
619
|
+
2622,
|
|
620
|
+
2632,
|
|
621
|
+
2636
|
|
622
|
+
]],
|
|
623
|
+
[2674, [
|
|
624
|
+
2623,
|
|
625
|
+
2624,
|
|
626
|
+
2631
|
|
627
|
+
]],
|
|
628
|
+
[2675, [
|
|
629
|
+
2625,
|
|
630
|
+
2626,
|
|
631
|
+
2635
|
|
632
|
+
]],
|
|
633
|
+
[2693, [
|
|
634
|
+
2750,
|
|
635
|
+
2757,
|
|
636
|
+
2759,
|
|
637
|
+
2760,
|
|
638
|
+
2761,
|
|
639
|
+
2763,
|
|
640
|
+
2764
|
|
641
|
+
]],
|
|
642
|
+
[2757, [2750]],
|
|
643
|
+
[2821, [2878]],
|
|
644
|
+
[2831, [2903]],
|
|
645
|
+
[2835, [2903]],
|
|
646
|
+
[2949, [3010]],
|
|
647
|
+
[3090, [3148, 3157]],
|
|
648
|
+
[3135, [3157]],
|
|
649
|
+
[3142, [3157]],
|
|
650
|
+
[3146, [3157]],
|
|
651
|
+
[3209, [3262]],
|
|
652
|
+
[3211, [3262]],
|
|
653
|
+
[3218, [3276]],
|
|
654
|
+
[3335, [3415]],
|
|
655
|
+
[3337, [3415]],
|
|
656
|
+
[3342, [3398]],
|
|
657
|
+
[3346, [3390, 3415]]
|
|
658
|
+
].map(([first, signs]) => [first, new Set(signs)]));
|
|
659
|
+
/**
|
|
660
|
+
* The characters with a dotted circle put into each sequence that spells one
|
|
661
|
+
* vowel as another (see {@link VOWEL_CONSTRAINTS}), each with its cluster and
|
|
662
|
+
* its index in the text: the circle takes the cluster of the sign it carries,
|
|
663
|
+
* and stands for no character.
|
|
664
|
+
*/
|
|
665
|
+
function constrainVowels(cps, clusters) {
|
|
666
|
+
const out = [];
|
|
667
|
+
const take = (i) => {
|
|
668
|
+
out.push([
|
|
669
|
+
cps[i],
|
|
670
|
+
clusters[i],
|
|
671
|
+
[i]
|
|
672
|
+
]);
|
|
673
|
+
};
|
|
674
|
+
let i = 0;
|
|
675
|
+
while (i < cps.length) {
|
|
676
|
+
const cp = cps[i];
|
|
677
|
+
let matched = i + 1 < cps.length && VOWEL_CONSTRAINTS.get(cp)?.has(cps[i + 1]) === true;
|
|
678
|
+
if (cp === 2352 && cps[i + 1] === 2381 && cps[i + 2] === 2311) {
|
|
679
|
+
take(i);
|
|
680
|
+
i++;
|
|
681
|
+
matched = true;
|
|
682
|
+
}
|
|
683
|
+
take(i);
|
|
684
|
+
i++;
|
|
685
|
+
if (matched && i < cps.length) {
|
|
686
|
+
out.push([
|
|
687
|
+
9676,
|
|
688
|
+
clusters[i],
|
|
689
|
+
[]
|
|
690
|
+
]);
|
|
691
|
+
take(i);
|
|
692
|
+
i++;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return out;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Characters to glyphs the way HarfBuzz's normalizer does for these scripts:
|
|
699
|
+
* every grapheme one cluster; a composite taken apart wherever the face has
|
|
700
|
+
* its parts (a nukta letter becomes its letter and the nukta, a two-part vowel
|
|
701
|
+
* sign its two parts); marks in canonical order; and a letter and a mark put
|
|
702
|
+
* back together where the face draws the composite and Unicode allows it.
|
|
703
|
+
* Each glyph stands for its own character, the parts of a composite each for
|
|
704
|
+
* the composite, and a letter put back together for both of its characters.
|
|
705
|
+
*/
|
|
706
|
+
function normalize(parsed, face, cps) {
|
|
707
|
+
const glyph = (cp) => parsed.glyphForCodepoint(cp);
|
|
708
|
+
const make = (cp, cluster, sources) => {
|
|
709
|
+
const gid = glyph(cp);
|
|
710
|
+
return {
|
|
711
|
+
gid,
|
|
712
|
+
cluster,
|
|
713
|
+
mask: GLOBAL,
|
|
714
|
+
cls: face.glyphClass(gid),
|
|
715
|
+
syllable: 0,
|
|
716
|
+
ligId: 0,
|
|
717
|
+
ligComp: 0,
|
|
718
|
+
sources,
|
|
719
|
+
cp,
|
|
720
|
+
cat: X,
|
|
721
|
+
pos: END,
|
|
722
|
+
type: NON_INDIC
|
|
723
|
+
};
|
|
724
|
+
};
|
|
725
|
+
const clusters = [];
|
|
726
|
+
cps.forEach((cp, i) => {
|
|
727
|
+
const continues = i > 0 && (isMark(cp) || cp === 8205);
|
|
728
|
+
clusters.push(continues ? clusters[i - 1] : i);
|
|
729
|
+
});
|
|
730
|
+
const decompose = (cp, out) => {
|
|
731
|
+
if (KEEP_WHOLE.has(cp)) return false;
|
|
732
|
+
const parts = DECOMPOSE.get(cp);
|
|
733
|
+
if (!parts) return false;
|
|
734
|
+
const [a, b] = parts;
|
|
735
|
+
if (glyph(b) === 0) return false;
|
|
736
|
+
const mark = out.length;
|
|
737
|
+
if (decompose(a, out)) {
|
|
738
|
+
out.push(b);
|
|
739
|
+
return true;
|
|
740
|
+
}
|
|
741
|
+
out.length = mark;
|
|
742
|
+
if (glyph(a) === 0) return false;
|
|
743
|
+
out.push(a, b);
|
|
744
|
+
return true;
|
|
745
|
+
};
|
|
746
|
+
const buf = [];
|
|
747
|
+
for (const [cp, cluster, sources] of constrainVowels(cps, clusters)) {
|
|
748
|
+
const parts = [];
|
|
749
|
+
if (decompose(cp, parts)) for (const p of parts) buf.push(make(p, cluster, sources));
|
|
750
|
+
else buf.push(make(cp, cluster, sources));
|
|
751
|
+
}
|
|
752
|
+
const ccc = (g) => CCC.get(g.cp) ?? 0;
|
|
753
|
+
for (let i = 0; i < buf.length; i++) {
|
|
754
|
+
if (ccc(buf[i]) === 0) continue;
|
|
755
|
+
let end = i + 1;
|
|
756
|
+
while (end < buf.length && ccc(buf[end]) !== 0) end++;
|
|
757
|
+
for (let k = i + 1; k < end; k++) {
|
|
758
|
+
let j = k;
|
|
759
|
+
while (j > i && ccc(buf[j - 1]) > ccc(buf[k])) j--;
|
|
760
|
+
if (j === k) continue;
|
|
761
|
+
mergeClusters(buf, j, k + 1);
|
|
762
|
+
const [moved] = buf.splice(k, 1);
|
|
763
|
+
buf.splice(j, 0, moved);
|
|
764
|
+
}
|
|
765
|
+
i = end;
|
|
766
|
+
}
|
|
767
|
+
const out = [];
|
|
768
|
+
let starter = -1;
|
|
769
|
+
for (const g of buf) {
|
|
770
|
+
if (isMark(g.cp) && starter >= 0) {
|
|
771
|
+
const s = out[starter];
|
|
772
|
+
const prev = out[out.length - 1];
|
|
773
|
+
const composed = isMark(s.cp) ? void 0 : COMPOSE.get(`${s.cp},${g.cp}`);
|
|
774
|
+
if (composed !== void 0 && (starter === out.length - 1 || ccc(prev) < ccc(g)) && glyph(composed) !== 0) {
|
|
775
|
+
s.cluster = Math.min(s.cluster, g.cluster);
|
|
776
|
+
s.sources = joinSources([s, g]) ?? [];
|
|
777
|
+
s.cp = composed;
|
|
778
|
+
s.gid = glyph(composed);
|
|
779
|
+
s.cls = face.glyphClass(s.gid);
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
out.push(g);
|
|
784
|
+
if (ccc(g) === 0) starter = out.length - 1;
|
|
785
|
+
}
|
|
786
|
+
return out;
|
|
787
|
+
}
|
|
788
|
+
var ligated = (g) => ((g.props ?? 0) & 2) !== 0;
|
|
789
|
+
var isOneOf = (g, flags) => !ligated(g) && (flag(g.cat) & flags) !== 0;
|
|
790
|
+
var isConsonant = (g) => isOneOf(g, CONSONANTS);
|
|
791
|
+
var isJoiner = (g) => isOneOf(g, JOINERS);
|
|
792
|
+
var isHalant = (g) => isOneOf(g, flag(H));
|
|
793
|
+
/** Each consonant's form by the face: below-base, post-base, or a base. */
|
|
794
|
+
function consonantPosition(plan, gid) {
|
|
795
|
+
const known = plan.consonants.get(gid);
|
|
796
|
+
if (known !== void 0) return known;
|
|
797
|
+
const v = plan.virama;
|
|
798
|
+
const would = (steps) => wouldSubstitute(plan.face, steps, [v, gid], plan.zeroContext) || wouldSubstitute(plan.face, steps, [gid, v], plan.zeroContext);
|
|
799
|
+
const pos = would(plan.blwf) || would(plan.vatu) ? BELOW_C : would(plan.pstf) || would(plan.pref) ? POST_C : BASE_C;
|
|
800
|
+
plan.consonants.set(gid, pos);
|
|
801
|
+
return pos;
|
|
802
|
+
}
|
|
803
|
+
function eachSyllable(buf, visit) {
|
|
804
|
+
let start = 0;
|
|
805
|
+
while (start < buf.length) {
|
|
806
|
+
let end = start + 1;
|
|
807
|
+
while (end < buf.length && buf[end].syllable === buf[start].syllable) end++;
|
|
808
|
+
visit(start, end);
|
|
809
|
+
start = end;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
function initialReordering(plan, parsed, buf) {
|
|
813
|
+
if (plan.virama) {
|
|
814
|
+
for (const g of buf) if (g.pos === BASE_C) g.pos = consonantPosition(plan, g.gid);
|
|
815
|
+
}
|
|
816
|
+
insertDottedCircles(parsed, plan.face, buf);
|
|
817
|
+
eachSyllable(buf, (start, end) => {
|
|
818
|
+
const type = buf[start].type;
|
|
819
|
+
if (type === SYMBOL_CLUSTER || type === NON_INDIC) return;
|
|
820
|
+
reorderSyllable(plan, buf, start, end);
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
/** A broken cluster — a mark with nothing to sit on — is given a dotted circle to sit on. */
|
|
824
|
+
function insertDottedCircles(parsed, face, buf) {
|
|
825
|
+
if (!buf.some((g) => g.type === BROKEN_CLUSTER)) return;
|
|
826
|
+
const gid = parsed.glyphForCodepoint(9676);
|
|
827
|
+
if (!gid) return;
|
|
828
|
+
let last = 0;
|
|
829
|
+
for (let i = 0; i < buf.length; i++) {
|
|
830
|
+
const g = buf[i];
|
|
831
|
+
if (g.type !== BROKEN_CLUSTER || g.syllable === last) continue;
|
|
832
|
+
last = g.syllable;
|
|
833
|
+
let at = i;
|
|
834
|
+
while (at < buf.length && buf[at].syllable === last && buf[at].cat === REPHA) at++;
|
|
835
|
+
buf.splice(at, 0, {
|
|
836
|
+
gid,
|
|
837
|
+
cluster: g.cluster,
|
|
838
|
+
mask: g.mask,
|
|
839
|
+
cls: face.glyphClass(gid),
|
|
840
|
+
syllable: g.syllable,
|
|
841
|
+
ligId: 0,
|
|
842
|
+
ligComp: 0,
|
|
843
|
+
sources: [],
|
|
844
|
+
cp: 9676,
|
|
845
|
+
cat: DOTTEDCIRCLE,
|
|
846
|
+
pos: END,
|
|
847
|
+
type: BROKEN_CLUSTER
|
|
848
|
+
});
|
|
849
|
+
i = at;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
function reorderSyllable(plan, buf, start, end) {
|
|
853
|
+
const { config } = plan;
|
|
854
|
+
if (config === SCRIPTS[7][1] && start + 3 <= end && buf[start].cat === RA && buf[start + 1].cat === H && buf[start + 2].cat === ZWJ) {
|
|
855
|
+
mergeClusters(buf, start + 1, start + 3);
|
|
856
|
+
[buf[start + 1], buf[start + 2]] = [buf[start + 2], buf[start + 1]];
|
|
857
|
+
}
|
|
858
|
+
let base = end;
|
|
859
|
+
let hasReph = false;
|
|
860
|
+
let limit = start;
|
|
861
|
+
if (plan.has.has("rphf") && start + 3 <= end && (config.rephMode === "implicit" && !isJoiner(buf[start + 2]) || config.rephMode === "explicit" && buf[start + 2].cat === ZWJ)) {
|
|
862
|
+
const glyphs = [buf[start].gid, buf[start + 1].gid];
|
|
863
|
+
if (wouldSubstitute(plan.face, plan.rphf, glyphs, plan.zeroContext) || config.rephMode === "explicit" && wouldSubstitute(plan.face, plan.rphf, [...glyphs, buf[start + 2].gid], plan.zeroContext)) {
|
|
864
|
+
limit += 2;
|
|
865
|
+
while (limit < end && isJoiner(buf[limit])) limit++;
|
|
866
|
+
base = start;
|
|
867
|
+
hasReph = true;
|
|
868
|
+
}
|
|
869
|
+
} else if (config.rephMode === "logical" && buf[start].cat === REPHA) {
|
|
870
|
+
limit += 1;
|
|
871
|
+
while (limit < end && isJoiner(buf[limit])) limit++;
|
|
872
|
+
base = start;
|
|
873
|
+
hasReph = true;
|
|
874
|
+
}
|
|
875
|
+
{
|
|
876
|
+
let i = end;
|
|
877
|
+
let seenBelow = false;
|
|
878
|
+
do {
|
|
879
|
+
i--;
|
|
880
|
+
const g = buf[i];
|
|
881
|
+
if (isConsonant(g)) {
|
|
882
|
+
if (g.pos !== BELOW_C && (g.pos !== POST_C || seenBelow)) {
|
|
883
|
+
base = i;
|
|
884
|
+
break;
|
|
885
|
+
}
|
|
886
|
+
if (g.pos === BELOW_C) seenBelow = true;
|
|
887
|
+
base = i;
|
|
888
|
+
} else if (start < i && g.cat === ZWJ && buf[i - 1].cat === H) break;
|
|
889
|
+
} while (i > limit);
|
|
890
|
+
}
|
|
891
|
+
if (hasReph && base === start && limit - base <= 2) hasReph = false;
|
|
892
|
+
for (let i = start; i < base; i++) buf[i].pos = Math.min(PRE_C, buf[i].pos);
|
|
893
|
+
if (base < end) buf[base].pos = BASE_C;
|
|
894
|
+
if (hasReph) buf[start].pos = RA_TO_BECOME_REPH;
|
|
895
|
+
if (plan.oldSpec) {
|
|
896
|
+
const kannada = config === SCRIPTS[7][1];
|
|
897
|
+
for (let i = base + 1; i < end; i++) {
|
|
898
|
+
if (buf[i].cat !== H) continue;
|
|
899
|
+
let j = end - 1;
|
|
900
|
+
for (; j > i; j--) if (isConsonant(buf[j]) || kannada && buf[j].cat === H) break;
|
|
901
|
+
if (buf[j].cat !== H && j > i) {
|
|
902
|
+
const [t] = buf.splice(i, 1);
|
|
903
|
+
buf.splice(j, 0, t);
|
|
904
|
+
}
|
|
905
|
+
break;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
{
|
|
909
|
+
let lastPos = 0;
|
|
910
|
+
for (let i = start; i < end; i++) {
|
|
911
|
+
const g = buf[i];
|
|
912
|
+
if (flag(g.cat) & (JOINERS | flag(N) | flag(RS) | flag(CM) | flag(H))) {
|
|
913
|
+
g.pos = lastPos;
|
|
914
|
+
if (g.cat === H && g.pos === PRE_M) {
|
|
915
|
+
for (let j = i; j > start; j--) if (buf[j - 1].pos !== PRE_M) {
|
|
916
|
+
g.pos = buf[j - 1].pos;
|
|
917
|
+
break;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
} else if (g.pos !== SMVD) {
|
|
921
|
+
if (g.cat === MPST && i > start && buf[i - 1].cat === SM) buf[i - 1].pos = g.pos;
|
|
922
|
+
lastPos = g.pos;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
{
|
|
927
|
+
let last = base;
|
|
928
|
+
for (let i = base + 1; i < end; i++) {
|
|
929
|
+
const g = buf[i];
|
|
930
|
+
if (isConsonant(g)) {
|
|
931
|
+
for (let j = last + 1; j < i; j++) if (buf[j].pos < SMVD) buf[j].pos = g.pos;
|
|
932
|
+
last = i;
|
|
933
|
+
} else if (g.cat === M || g.cat === MPST) last = i;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
{
|
|
937
|
+
const slice = buf.slice(start, end);
|
|
938
|
+
slice.forEach((g, k) => g.ord = k);
|
|
939
|
+
slice.sort((a, b) => a.pos - b.pos || a.ord - b.ord);
|
|
940
|
+
for (let i = start; i < end; i++) buf[i] = slice[i - start];
|
|
941
|
+
base = end;
|
|
942
|
+
let firstLeft = end;
|
|
943
|
+
let lastLeft = end;
|
|
944
|
+
for (let i = start; i < end; i++) {
|
|
945
|
+
if (buf[i].pos === BASE_C) {
|
|
946
|
+
base = i;
|
|
947
|
+
break;
|
|
948
|
+
}
|
|
949
|
+
if (buf[i].pos === PRE_M) {
|
|
950
|
+
if (firstLeft === end) firstLeft = i;
|
|
951
|
+
lastLeft = i;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
if (firstLeft < lastLeft) {
|
|
955
|
+
reverse(buf, firstLeft, lastLeft + 1);
|
|
956
|
+
let i = firstLeft;
|
|
957
|
+
for (let j = i; j <= lastLeft; j++) if (buf[j].cat === M || buf[j].cat === MPST) {
|
|
958
|
+
reverse(buf, i, j + 1);
|
|
959
|
+
i = j + 1;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
if (plan.oldSpec || end - start > 127) mergeClusters(buf, base, end);
|
|
963
|
+
else {
|
|
964
|
+
const seen = /* @__PURE__ */ new Set();
|
|
965
|
+
const at = (i) => start + buf[i].ord;
|
|
966
|
+
for (let i = base; i < end; i++) {
|
|
967
|
+
if (seen.has(i)) continue;
|
|
968
|
+
let min = i;
|
|
969
|
+
let max = i;
|
|
970
|
+
let j = at(i);
|
|
971
|
+
while (j !== i && !seen.has(j)) {
|
|
972
|
+
min = Math.min(min, j);
|
|
973
|
+
max = Math.max(max, j);
|
|
974
|
+
seen.add(j);
|
|
975
|
+
j = at(j);
|
|
976
|
+
}
|
|
977
|
+
mergeClusters(buf, Math.max(base, min), max + 1);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
for (let i = start; i < end && buf[i].pos === RA_TO_BECOME_REPH; i++) buf[i].mask |= RPHF;
|
|
982
|
+
const pre = HALF | (!plan.oldSpec && !config.blwfPostOnly ? BLWF : 0);
|
|
983
|
+
for (let i = start; i < base; i++) buf[i].mask |= pre;
|
|
984
|
+
for (let i = base + 1; i < end; i++) buf[i].mask |= 88;
|
|
985
|
+
if (plan.oldSpec && config === SCRIPTS[0][1]) {
|
|
986
|
+
for (let i = start; i + 1 < base; i++) if (buf[i].cat === RA && buf[i + 1].cat === H && (i + 2 === base || buf[i + 2].cat !== ZWJ)) {
|
|
987
|
+
buf[i].mask |= BLWF;
|
|
988
|
+
buf[i + 1].mask |= BLWF;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
if (plan.has.has("pref") && base + 2 < end) {
|
|
992
|
+
for (let i = base + 1; i + 1 < end; i++) if (wouldSubstitute(plan.face, plan.pref, [buf[i].gid, buf[i + 1].gid], plan.zeroContext)) {
|
|
993
|
+
buf[i].mask |= PREF;
|
|
994
|
+
buf[i + 1].mask |= PREF;
|
|
995
|
+
break;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
for (let i = start + 1; i < end; i++) {
|
|
999
|
+
if (!isJoiner(buf[i]) || buf[i].cat !== ZWNJ) continue;
|
|
1000
|
+
let j = i;
|
|
1001
|
+
do {
|
|
1002
|
+
j--;
|
|
1003
|
+
buf[j].mask &= -33;
|
|
1004
|
+
} while (j > start && !isConsonant(buf[j]));
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
/** Reverse `buf[start..end)` in place. */
|
|
1008
|
+
function reverse(buf, start, end) {
|
|
1009
|
+
for (let a = start, b = end - 1; a < b; a++, b--) [buf[a], buf[b]] = [buf[b], buf[a]];
|
|
1010
|
+
}
|
|
1011
|
+
function finalReordering(plan, buf) {
|
|
1012
|
+
eachSyllable(buf, (start, end) => finalSyllable(plan, buf, start, end));
|
|
1013
|
+
}
|
|
1014
|
+
function finalSyllable(plan, buf, start, end) {
|
|
1015
|
+
const { config } = plan;
|
|
1016
|
+
const malayalamOrTamil = config === SCRIPTS[8][1] || config === SCRIPTS[5][1];
|
|
1017
|
+
if (plan.virama) for (let i = start; i < end; i++) {
|
|
1018
|
+
const g = buf[i];
|
|
1019
|
+
const props = g.props ?? 0;
|
|
1020
|
+
if (g.gid === plan.virama && props & 2 && props & 4) {
|
|
1021
|
+
g.cat = H;
|
|
1022
|
+
g.props = props & -7;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
let tryPref = plan.has.has("pref");
|
|
1026
|
+
const formed = (g) => {
|
|
1027
|
+
const props = g.props ?? 0;
|
|
1028
|
+
return (props & 2) !== 0 && (props & 4) === 0;
|
|
1029
|
+
};
|
|
1030
|
+
let base = start;
|
|
1031
|
+
for (; base < end; base++) {
|
|
1032
|
+
if (buf[base].pos < BASE_C) continue;
|
|
1033
|
+
if (tryPref && base + 1 < end) {
|
|
1034
|
+
for (let i = base + 1; i < end; i++) {
|
|
1035
|
+
if ((buf[i].mask & PREF) === 0) continue;
|
|
1036
|
+
if (!((buf[i].props ?? 0) & 1 && formed(buf[i]))) {
|
|
1037
|
+
base = i;
|
|
1038
|
+
while (base < end && isHalant(buf[base])) base++;
|
|
1039
|
+
if (base < end) buf[base].pos = BASE_C;
|
|
1040
|
+
tryPref = false;
|
|
1041
|
+
}
|
|
1042
|
+
break;
|
|
1043
|
+
}
|
|
1044
|
+
if (base === end) break;
|
|
1045
|
+
}
|
|
1046
|
+
if (config === SCRIPTS[8][1]) for (let i = base + 1; i < end; i++) {
|
|
1047
|
+
while (i < end && isJoiner(buf[i])) i++;
|
|
1048
|
+
if (i === end || !isHalant(buf[i])) break;
|
|
1049
|
+
i++;
|
|
1050
|
+
while (i < end && isJoiner(buf[i])) i++;
|
|
1051
|
+
if (i < end && isConsonant(buf[i]) && buf[i].pos === BELOW_C) {
|
|
1052
|
+
base = i;
|
|
1053
|
+
buf[base].pos = BASE_C;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
if (start < base && buf[base].pos > BASE_C) base--;
|
|
1057
|
+
break;
|
|
1058
|
+
}
|
|
1059
|
+
if (base === end && start < base && isOneOf(buf[base - 1], flag(ZWJ))) base--;
|
|
1060
|
+
if (base < end) while (start < base && isOneOf(buf[base], flag(N) | flag(H))) base--;
|
|
1061
|
+
if (start + 1 < end && start < base) {
|
|
1062
|
+
let newPos = base === end ? base - 2 : base - 1;
|
|
1063
|
+
if (!malayalamOrTamil) for (;;) {
|
|
1064
|
+
while (newPos > start && !isOneOf(buf[newPos], flag(M) | flag(MPST) | flag(H))) newPos--;
|
|
1065
|
+
if (isHalant(buf[newPos]) && buf[newPos].pos !== PRE_M) {
|
|
1066
|
+
if (newPos + 1 < end && buf[newPos + 1].cat === ZWJ && newPos > start) {
|
|
1067
|
+
newPos--;
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1070
|
+
} else newPos = start;
|
|
1071
|
+
break;
|
|
1072
|
+
}
|
|
1073
|
+
if (start < newPos && buf[newPos].pos !== PRE_M) for (let i = newPos; i > start; i--) {
|
|
1074
|
+
if (buf[i - 1].pos !== PRE_M) continue;
|
|
1075
|
+
const old = i - 1;
|
|
1076
|
+
if (old < base && base <= newPos) base--;
|
|
1077
|
+
const [t] = buf.splice(old, 1);
|
|
1078
|
+
buf.splice(newPos, 0, t);
|
|
1079
|
+
mergeClusters(buf, newPos, Math.min(end, base + 1));
|
|
1080
|
+
newPos--;
|
|
1081
|
+
}
|
|
1082
|
+
else for (let i = start; i < base; i++) if (buf[i].pos === PRE_M) {
|
|
1083
|
+
mergeClusters(buf, i, Math.min(end, base + 1));
|
|
1084
|
+
break;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
if (start + 1 < end && buf[start].pos === RA_TO_BECOME_REPH && buf[start].cat === REPHA !== formed(buf[start])) {
|
|
1088
|
+
const afterHalant = () => {
|
|
1089
|
+
let p = start + 1;
|
|
1090
|
+
while (p < base && !isHalant(buf[p])) p++;
|
|
1091
|
+
if (p < base && isHalant(buf[p])) {
|
|
1092
|
+
if (p + 1 < base && isJoiner(buf[p + 1])) p++;
|
|
1093
|
+
return p;
|
|
1094
|
+
}
|
|
1095
|
+
};
|
|
1096
|
+
let target;
|
|
1097
|
+
const rephPos = config.rephPos;
|
|
1098
|
+
if (rephPos !== AFTER_POST) {
|
|
1099
|
+
target = afterHalant();
|
|
1100
|
+
if (target === void 0 && rephPos === AFTER_MAIN) {
|
|
1101
|
+
let p = base;
|
|
1102
|
+
while (p + 1 < end && buf[p + 1].pos <= AFTER_MAIN) p++;
|
|
1103
|
+
if (p < end) target = p;
|
|
1104
|
+
}
|
|
1105
|
+
if (target === void 0 && rephPos === AFTER_SUB) {
|
|
1106
|
+
let p = base;
|
|
1107
|
+
while (p + 1 < end && !(flag(buf[p + 1].pos) & (flag(POST_C) | flag(AFTER_POST) | flag(SMVD)))) p++;
|
|
1108
|
+
if (p < end) target = p;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
target ??= afterHalant();
|
|
1112
|
+
if (target === void 0) {
|
|
1113
|
+
let p = end - 1;
|
|
1114
|
+
while (p > start && buf[p].pos === SMVD) p--;
|
|
1115
|
+
if (isHalant(buf[p])) {
|
|
1116
|
+
for (let i = base + 1; i < p; i++) if (buf[i].cat === M || buf[i].cat === MPST) p--;
|
|
1117
|
+
}
|
|
1118
|
+
target = p;
|
|
1119
|
+
}
|
|
1120
|
+
mergeClusters(buf, start, target + 1);
|
|
1121
|
+
const [reph] = buf.splice(start, 1);
|
|
1122
|
+
buf.splice(target, 0, reph);
|
|
1123
|
+
if (start < base && base <= target) base--;
|
|
1124
|
+
}
|
|
1125
|
+
if (tryPref && base + 1 < end) for (let i = base + 1; i < end; i++) {
|
|
1126
|
+
if ((buf[i].mask & PREF) === 0) continue;
|
|
1127
|
+
if (formed(buf[i])) {
|
|
1128
|
+
let newPos = base;
|
|
1129
|
+
if (!malayalamOrTamil) while (newPos > start && !isOneOf(buf[newPos - 1], flag(M) | flag(MPST) | flag(H))) newPos--;
|
|
1130
|
+
if (newPos > start && isHalant(buf[newPos - 1])) {
|
|
1131
|
+
if (newPos < end && isJoiner(buf[newPos])) newPos++;
|
|
1132
|
+
}
|
|
1133
|
+
mergeClusters(buf, newPos, i + 1);
|
|
1134
|
+
const [t] = buf.splice(i, 1);
|
|
1135
|
+
buf.splice(newPos, 0, t);
|
|
1136
|
+
if (newPos <= base && base < i) base++;
|
|
1137
|
+
}
|
|
1138
|
+
break;
|
|
1139
|
+
}
|
|
1140
|
+
if (buf[start].pos === PRE_M) {
|
|
1141
|
+
const prev = start > 0 ? buf[start - 1] : void 0;
|
|
1142
|
+
if (!prev || !WORDISH_RE.test(String.fromCodePoint(prev.cp))) buf[start].mask |= INIT;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
//#endregion
|
|
1146
|
+
export { hasIndic, shapeIndic, syllableStarts };
|