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,1112 @@
|
|
|
1
|
+
var RIGHT_TO_LEFT = 1;
|
|
2
|
+
var IGNORE_FLAGS = 14;
|
|
3
|
+
var IGNORE_MARKS = 8;
|
|
4
|
+
var USE_MARK_FILTERING_SET = 16;
|
|
5
|
+
var MARK_ATTACHMENT_TYPE = 65280;
|
|
6
|
+
/** GDEF glyph classes. */
|
|
7
|
+
var MARK = 3;
|
|
8
|
+
var MAX_NESTING = 6;
|
|
9
|
+
var MAX_GROWTH = 8;
|
|
10
|
+
var MAX_OPS_PER_GLYPH = 1024;
|
|
11
|
+
var u16 = (b, o) => b[o] << 8 | b[o + 1];
|
|
12
|
+
var i16 = (b, o) => {
|
|
13
|
+
const v = u16(b, o);
|
|
14
|
+
return v >= 32768 ? v - 65536 : v;
|
|
15
|
+
};
|
|
16
|
+
var u32 = (b, o) => b[o] * 16777216 + (b[o + 1] << 16 | b[o + 2] << 8 | b[o + 3]);
|
|
17
|
+
var tag = (b, o) => String.fromCharCode(b[o], b[o + 1], b[o + 2], b[o + 3]);
|
|
18
|
+
/**
|
|
19
|
+
* A face's layout tables, read once: GDEF's classes and the lists of GSUB and
|
|
20
|
+
* GPOS, with each coverage table and class definition read the first time a
|
|
21
|
+
* lookup asks for it.
|
|
22
|
+
*/
|
|
23
|
+
var OtlFace = class OtlFace {
|
|
24
|
+
parsed;
|
|
25
|
+
static cache = /* @__PURE__ */ new WeakMap();
|
|
26
|
+
/** The face's GSUB, when it has one that reads. */
|
|
27
|
+
gsub;
|
|
28
|
+
/** …and its GPOS. */
|
|
29
|
+
gpos;
|
|
30
|
+
raw;
|
|
31
|
+
glyphClasses;
|
|
32
|
+
attachClasses;
|
|
33
|
+
markSets;
|
|
34
|
+
coverages = /* @__PURE__ */ new Map();
|
|
35
|
+
classDefs = /* @__PURE__ */ new Map();
|
|
36
|
+
constructor(parsed) {
|
|
37
|
+
this.parsed = parsed;
|
|
38
|
+
this.raw = parsed.raw;
|
|
39
|
+
const gdef = parsed.tables.get("GDEF");
|
|
40
|
+
let glyphClasses = /* @__PURE__ */ new Map();
|
|
41
|
+
let attachClasses = /* @__PURE__ */ new Map();
|
|
42
|
+
let markSets = [];
|
|
43
|
+
if (gdef) try {
|
|
44
|
+
const b = gdef.offset;
|
|
45
|
+
const minor = u16(this.raw, b + 2);
|
|
46
|
+
const classOff = u16(this.raw, b + 4);
|
|
47
|
+
const attachOff = u16(this.raw, b + 10);
|
|
48
|
+
if (classOff) glyphClasses = this.classDef(b + classOff);
|
|
49
|
+
if (attachOff) attachClasses = this.classDef(b + attachOff);
|
|
50
|
+
const setsOff = minor >= 2 ? u16(this.raw, b + 12) : 0;
|
|
51
|
+
if (setsOff) {
|
|
52
|
+
const s = b + setsOff;
|
|
53
|
+
const count = u16(this.raw, s + 2);
|
|
54
|
+
for (let i = 0; i < count; i++) markSets.push(s + u32(this.raw, s + 4 + 4 * i));
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
glyphClasses = /* @__PURE__ */ new Map();
|
|
58
|
+
attachClasses = /* @__PURE__ */ new Map();
|
|
59
|
+
markSets = [];
|
|
60
|
+
}
|
|
61
|
+
this.glyphClasses = glyphClasses;
|
|
62
|
+
this.attachClasses = attachClasses;
|
|
63
|
+
this.markSets = markSets;
|
|
64
|
+
const gsub = this.readTable("GSUB");
|
|
65
|
+
const gpos = this.readTable("GPOS");
|
|
66
|
+
if (gsub) this.gsub = gsub;
|
|
67
|
+
if (gpos) this.gpos = gpos;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The layout tables of a face, read on first use and kept with it.
|
|
71
|
+
*
|
|
72
|
+
* @param parsed The face.
|
|
73
|
+
* @returns Its {@link OtlFace}.
|
|
74
|
+
*/
|
|
75
|
+
static of(parsed) {
|
|
76
|
+
let face = OtlFace.cache.get(parsed);
|
|
77
|
+
if (!face) {
|
|
78
|
+
face = new OtlFace(parsed);
|
|
79
|
+
OtlFace.cache.set(parsed, face);
|
|
80
|
+
}
|
|
81
|
+
return face;
|
|
82
|
+
}
|
|
83
|
+
/** A glyph's GDEF class: 1 base, 2 ligature, 3 mark, 4 component, 0 none. */
|
|
84
|
+
glyphClass(gid) {
|
|
85
|
+
return this.glyphClasses.get(gid) ?? 0;
|
|
86
|
+
}
|
|
87
|
+
/** A mark's attachment class, which a lookup's flag may select. */
|
|
88
|
+
attachClass(gid) {
|
|
89
|
+
return this.attachClasses.get(gid) ?? 0;
|
|
90
|
+
}
|
|
91
|
+
/** Whether a glyph is in one of GDEF's mark glyph sets. */
|
|
92
|
+
inMarkSet(set, gid) {
|
|
93
|
+
const off = this.markSets[set];
|
|
94
|
+
return off !== void 0 && this.coverage(off).has(gid);
|
|
95
|
+
}
|
|
96
|
+
/** @internal The coverage table at an absolute offset: glyph → coverage index. */
|
|
97
|
+
coverage(off) {
|
|
98
|
+
let cov = this.coverages.get(off);
|
|
99
|
+
if (cov) return cov;
|
|
100
|
+
const b = this.raw;
|
|
101
|
+
const map = /* @__PURE__ */ new Map();
|
|
102
|
+
const format = u16(b, off);
|
|
103
|
+
if (format === 1) {
|
|
104
|
+
const n = u16(b, off + 2);
|
|
105
|
+
for (let i = 0; i < n; i++) map.set(u16(b, off + 4 + 2 * i), i);
|
|
106
|
+
} else if (format === 2) {
|
|
107
|
+
const n = u16(b, off + 2);
|
|
108
|
+
for (let i = 0; i < n; i++) {
|
|
109
|
+
const r = off + 4 + 6 * i;
|
|
110
|
+
const start = u16(b, r);
|
|
111
|
+
const end = u16(b, r + 2);
|
|
112
|
+
const index = u16(b, r + 4);
|
|
113
|
+
for (let g = start; g <= end; g++) map.set(g, index + g - start);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
cov = map;
|
|
117
|
+
this.coverages.set(off, cov);
|
|
118
|
+
return cov;
|
|
119
|
+
}
|
|
120
|
+
/** @internal The class definition at an absolute offset: glyph → class, 0 unlisted. */
|
|
121
|
+
classDef(off) {
|
|
122
|
+
let def = this.classDefs.get(off);
|
|
123
|
+
if (def) return def;
|
|
124
|
+
const b = this.raw;
|
|
125
|
+
const map = /* @__PURE__ */ new Map();
|
|
126
|
+
const format = u16(b, off);
|
|
127
|
+
if (format === 1) {
|
|
128
|
+
const start = u16(b, off + 2);
|
|
129
|
+
const n = u16(b, off + 4);
|
|
130
|
+
for (let i = 0; i < n; i++) {
|
|
131
|
+
const c = u16(b, off + 6 + 2 * i);
|
|
132
|
+
if (c) map.set(start + i, c);
|
|
133
|
+
}
|
|
134
|
+
} else if (format === 2) {
|
|
135
|
+
const n = u16(b, off + 2);
|
|
136
|
+
for (let i = 0; i < n; i++) {
|
|
137
|
+
const r = off + 4 + 6 * i;
|
|
138
|
+
const c = u16(b, r + 4);
|
|
139
|
+
if (!c) continue;
|
|
140
|
+
for (let g = u16(b, r); g <= u16(b, r + 2); g++) map.set(g, c);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
def = map;
|
|
144
|
+
this.classDefs.set(off, def);
|
|
145
|
+
return def;
|
|
146
|
+
}
|
|
147
|
+
/** @internal The raw bytes of the face. */
|
|
148
|
+
get bytes() {
|
|
149
|
+
return this.raw;
|
|
150
|
+
}
|
|
151
|
+
readTable(kind) {
|
|
152
|
+
const info = this.parsed.tables.get(kind);
|
|
153
|
+
if (!info) return void 0;
|
|
154
|
+
try {
|
|
155
|
+
const b = this.raw;
|
|
156
|
+
const base = info.offset;
|
|
157
|
+
const scriptList = base + u16(b, base + 4);
|
|
158
|
+
const featureList = base + u16(b, base + 6);
|
|
159
|
+
const lookupList = base + u16(b, base + 8);
|
|
160
|
+
const langSys = (off) => {
|
|
161
|
+
const n = u16(b, off + 4);
|
|
162
|
+
const features = [];
|
|
163
|
+
for (let i = 0; i < n; i++) features.push(u16(b, off + 6 + 2 * i));
|
|
164
|
+
return {
|
|
165
|
+
required: u16(b, off + 2),
|
|
166
|
+
features
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
const scripts = /* @__PURE__ */ new Map();
|
|
170
|
+
for (let i = 0, n = u16(b, scriptList); i < n; i++) {
|
|
171
|
+
const r = scriptList + 2 + 6 * i;
|
|
172
|
+
const s = scriptList + u16(b, r + 4);
|
|
173
|
+
const defOff = u16(b, s);
|
|
174
|
+
const langs = /* @__PURE__ */ new Map();
|
|
175
|
+
for (let k = 0, m = u16(b, s + 2); k < m; k++) {
|
|
176
|
+
const lr = s + 4 + 6 * k;
|
|
177
|
+
langs.set(tag(b, lr), langSys(s + u16(b, lr + 4)));
|
|
178
|
+
}
|
|
179
|
+
scripts.set(tag(b, r), {
|
|
180
|
+
...defOff ? { defaultLangSys: langSys(s + defOff) } : {},
|
|
181
|
+
langSys: langs
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
const features = [];
|
|
185
|
+
for (let i = 0, n = u16(b, featureList); i < n; i++) {
|
|
186
|
+
const r = featureList + 2 + 6 * i;
|
|
187
|
+
const f = featureList + u16(b, r + 4);
|
|
188
|
+
const lookups = [];
|
|
189
|
+
for (let k = 0, m = u16(b, f + 2); k < m; k++) lookups.push(u16(b, f + 4 + 2 * k));
|
|
190
|
+
features.push({
|
|
191
|
+
tag: tag(b, r),
|
|
192
|
+
lookups
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
const extension = kind === "GSUB" ? 7 : 9;
|
|
196
|
+
const lookups = [];
|
|
197
|
+
for (let i = 0, n = u16(b, lookupList); i < n; i++) {
|
|
198
|
+
const l = lookupList + u16(b, lookupList + 2 + 2 * i);
|
|
199
|
+
const declared = u16(b, l);
|
|
200
|
+
let type = declared;
|
|
201
|
+
const flag = u16(b, l + 2);
|
|
202
|
+
const count = u16(b, l + 4);
|
|
203
|
+
const subtables = [];
|
|
204
|
+
for (let k = 0; k < count; k++) {
|
|
205
|
+
let st = l + u16(b, l + 6 + 2 * k);
|
|
206
|
+
if (declared === extension) {
|
|
207
|
+
type = u16(b, st + 2);
|
|
208
|
+
st += u32(b, st + 4);
|
|
209
|
+
}
|
|
210
|
+
subtables.push(st);
|
|
211
|
+
}
|
|
212
|
+
const markSet = flag & USE_MARK_FILTERING_SET ? u16(b, l + 6 + 2 * count) : 0;
|
|
213
|
+
lookups.push({
|
|
214
|
+
type,
|
|
215
|
+
flag,
|
|
216
|
+
markSet,
|
|
217
|
+
subtables
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
kind,
|
|
222
|
+
scripts,
|
|
223
|
+
features,
|
|
224
|
+
lookups
|
|
225
|
+
};
|
|
226
|
+
} catch {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
/**
|
|
232
|
+
* The script a table answers for, of the tags a shaper prefers: the first the
|
|
233
|
+
* table has, else its default script, else Latin.
|
|
234
|
+
*
|
|
235
|
+
* @param table The GSUB or GPOS table.
|
|
236
|
+
* @param scriptTags The tags, most preferred first (`dev2`, then `deva`).
|
|
237
|
+
* @returns The tag found, or `undefined` when the table has none of them.
|
|
238
|
+
*/
|
|
239
|
+
function pickScript(table, scriptTags) {
|
|
240
|
+
if (!table) return void 0;
|
|
241
|
+
for (const t of [
|
|
242
|
+
...scriptTags,
|
|
243
|
+
"DFLT",
|
|
244
|
+
"dflt",
|
|
245
|
+
"latn"
|
|
246
|
+
]) if (table.scripts.has(t)) return t;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* The lookups a set of features runs, merged and in LookupList order — the
|
|
250
|
+
* order they apply in, whichever feature named them.
|
|
251
|
+
*
|
|
252
|
+
* A lookup two of the features name runs once, on the glyphs either feature
|
|
253
|
+
* may act on. The language system's required feature, if it has one, runs on
|
|
254
|
+
* every glyph.
|
|
255
|
+
*
|
|
256
|
+
* @param table The GSUB or GPOS table.
|
|
257
|
+
* @param scriptTags The script tags to look for, most preferred first.
|
|
258
|
+
* @param features The features, with the glyphs each acts on.
|
|
259
|
+
* @param language A language system tag; the script's default otherwise.
|
|
260
|
+
* @returns The steps to run, in order.
|
|
261
|
+
*/
|
|
262
|
+
function planLookups(table, scriptTags, features, language) {
|
|
263
|
+
if (!table) return [];
|
|
264
|
+
const scriptTag = pickScript(table, scriptTags);
|
|
265
|
+
const script = scriptTag === void 0 ? void 0 : table.scripts.get(scriptTag);
|
|
266
|
+
const lang = (language !== void 0 ? script?.langSys.get(language) : void 0) ?? script?.defaultLangSys;
|
|
267
|
+
if (!lang) return [];
|
|
268
|
+
const steps = /* @__PURE__ */ new Map();
|
|
269
|
+
const add = (lookup, f) => {
|
|
270
|
+
if (lookup >= table.lookups.length) return;
|
|
271
|
+
const had = steps.get(lookup);
|
|
272
|
+
steps.set(lookup, {
|
|
273
|
+
lookup,
|
|
274
|
+
mask: (had?.mask ?? 0) | f.mask,
|
|
275
|
+
autoZwj: (had?.autoZwj ?? true) && f.manualZwj !== true,
|
|
276
|
+
autoZwnj: (had?.autoZwnj ?? true) && f.manualZwnj !== true,
|
|
277
|
+
perSyllable: (had?.perSyllable ?? true) && f.perSyllable === true
|
|
278
|
+
});
|
|
279
|
+
};
|
|
280
|
+
const required = table.features[lang.required];
|
|
281
|
+
if (lang.required !== 65535 && required) for (const l of required.lookups) add(l, {
|
|
282
|
+
tag: required.tag,
|
|
283
|
+
mask: -1
|
|
284
|
+
});
|
|
285
|
+
for (const f of features) for (const index of lang.features) {
|
|
286
|
+
const feature = table.features[index];
|
|
287
|
+
if (feature?.tag !== f.tag) continue;
|
|
288
|
+
for (const l of feature.lookups) add(l, f);
|
|
289
|
+
}
|
|
290
|
+
return [...steps.values()].sort((a, b) => a.lookup - b.lookup);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Whether a lookup sees a glyph at all, by its flags: the classes it ignores,
|
|
294
|
+
* and of the marks the one class, or the one set, it keeps.
|
|
295
|
+
*/
|
|
296
|
+
function visible(face, g, flag, markSet) {
|
|
297
|
+
const cls = g.cls;
|
|
298
|
+
if (cls >= 1 && cls <= 3 && flag & IGNORE_FLAGS & 1 << cls) return false;
|
|
299
|
+
if (cls !== MARK) return true;
|
|
300
|
+
if (flag & USE_MARK_FILTERING_SET) return face.inMarkSet(markSet, g.gid);
|
|
301
|
+
if (flag & MARK_ATTACHMENT_TYPE) return face.attachClass(g.gid) === flag >> 8;
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Step from `from` towards `dir`, `count` times, onto the glyphs a match takes:
|
|
306
|
+
* HarfBuzz's skipping iterator. A glyph the lookup's flags hide is stepped
|
|
307
|
+
* over. A join control is stepped over unless it is what the rule asks for,
|
|
308
|
+
* and only where the lookup leaves it to the engine: a ZWJ in a context
|
|
309
|
+
* always, in the input when the feature does; a ZWNJ in GPOS, and in a GSUB
|
|
310
|
+
* context when the feature does. In the input, every glyph must carry the
|
|
311
|
+
* step's mask, and with a per-syllable step every glyph keeps to the syllable
|
|
312
|
+
* of the glyph the lookup is applied at.
|
|
313
|
+
*
|
|
314
|
+
* @returns The positions it landed on, or `undefined` where the match fails.
|
|
315
|
+
*/
|
|
316
|
+
function walk(r, from, dir, count, want, context, flag = r.lookup.flag) {
|
|
317
|
+
const out = [];
|
|
318
|
+
const gpos = r.table.kind === "GPOS";
|
|
319
|
+
const ignoreZwnj = gpos || context && r.step.autoZwnj;
|
|
320
|
+
const ignoreZwj = context || r.step.autoZwj;
|
|
321
|
+
const syllable = r.step.perSyllable && !gpos && from === r.idx ? r.buf[r.idx]?.syllable ?? 0 : 0;
|
|
322
|
+
let i = from;
|
|
323
|
+
while (out.length < count) {
|
|
324
|
+
i += dir;
|
|
325
|
+
const g = r.buf[i];
|
|
326
|
+
if (!g) return void 0;
|
|
327
|
+
if (!visible(r.face, g, flag, r.lookup.markSet)) continue;
|
|
328
|
+
const maybeSkip = g.joiner !== void 0 && (ignoreZwnj || g.joiner !== "zwnj") && (ignoreZwj || g.joiner !== "zwj");
|
|
329
|
+
if ((context || (g.mask & r.step.mask) !== 0) && (syllable === 0 || g.syllable === syllable) && (want ? want(g, out.length) : !maybeSkip)) {
|
|
330
|
+
out.push(i);
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
if (!maybeSkip) return void 0;
|
|
334
|
+
}
|
|
335
|
+
return out;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Run a stage of GSUB lookups over the buffer, each over the whole of it
|
|
339
|
+
* before the next begins.
|
|
340
|
+
*
|
|
341
|
+
* @param face The face.
|
|
342
|
+
* @param steps The lookups, from {@link planLookups} over its GSUB.
|
|
343
|
+
* @param buf The glyphs; substituted in place, and may grow or shrink.
|
|
344
|
+
*/
|
|
345
|
+
function substitute(face, steps, buf) {
|
|
346
|
+
const table = face.gsub;
|
|
347
|
+
if (!table) return;
|
|
348
|
+
const maxLength = Math.max(64, buf.length * MAX_GROWTH);
|
|
349
|
+
for (const step of steps) {
|
|
350
|
+
const lookup = table.lookups[step.lookup];
|
|
351
|
+
if (!lookup) continue;
|
|
352
|
+
const starts = firstGlyphs(face, table, step.lookup);
|
|
353
|
+
const r = {
|
|
354
|
+
face,
|
|
355
|
+
table,
|
|
356
|
+
buf,
|
|
357
|
+
pos: void 0,
|
|
358
|
+
step,
|
|
359
|
+
lookup,
|
|
360
|
+
idx: 0,
|
|
361
|
+
depth: 0,
|
|
362
|
+
ops: 0,
|
|
363
|
+
maxLength
|
|
364
|
+
};
|
|
365
|
+
if (lookup.type === 8) {
|
|
366
|
+
for (let i = buf.length - 1; i >= 0; i--) {
|
|
367
|
+
const g = buf[i];
|
|
368
|
+
if (!(g.mask & step.mask) || !starts.has(g.gid)) continue;
|
|
369
|
+
if (!visible(face, g, lookup.flag, lookup.markSet)) continue;
|
|
370
|
+
r.idx = i;
|
|
371
|
+
applyLookup(r);
|
|
372
|
+
}
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
let i = 0;
|
|
376
|
+
while (i < buf.length) {
|
|
377
|
+
const g = buf[i];
|
|
378
|
+
if (g.mask & step.mask && starts.has(g.gid) && visible(face, g, lookup.flag, lookup.markSet)) {
|
|
379
|
+
r.idx = i;
|
|
380
|
+
r.lookup = lookup;
|
|
381
|
+
const next = applyLookup(r);
|
|
382
|
+
if (next >= 0) {
|
|
383
|
+
i = next;
|
|
384
|
+
if (++r.ops > MAX_OPS_PER_GLYPH * Math.max(1, buf.length)) break;
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
i++;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Position the buffer: each glyph's own advance, then a stage of GPOS lookups,
|
|
394
|
+
* then every mark and cursive join carried along with the glyph it hangs on.
|
|
395
|
+
*
|
|
396
|
+
* @param face The face.
|
|
397
|
+
* @param steps The lookups, from {@link planLookups} over its GPOS.
|
|
398
|
+
* @param buf The glyphs, after substitution.
|
|
399
|
+
* @returns A position per glyph, in font units, y up.
|
|
400
|
+
*/
|
|
401
|
+
function position(face, steps, buf) {
|
|
402
|
+
const advances = face.parsed.advanceWidths;
|
|
403
|
+
const pos = buf.map((g) => ({
|
|
404
|
+
xAdvance: advances[g.gid] ?? 0,
|
|
405
|
+
yAdvance: 0,
|
|
406
|
+
xOffset: 0,
|
|
407
|
+
yOffset: 0,
|
|
408
|
+
attachChain: 0,
|
|
409
|
+
attachType: 0
|
|
410
|
+
}));
|
|
411
|
+
const table = face.gpos;
|
|
412
|
+
if (table) for (const step of steps) {
|
|
413
|
+
const lookup = table.lookups[step.lookup];
|
|
414
|
+
if (!lookup) continue;
|
|
415
|
+
const starts = firstGlyphs(face, table, step.lookup);
|
|
416
|
+
const r = {
|
|
417
|
+
face,
|
|
418
|
+
table,
|
|
419
|
+
buf,
|
|
420
|
+
pos,
|
|
421
|
+
step,
|
|
422
|
+
lookup,
|
|
423
|
+
idx: 0,
|
|
424
|
+
depth: 0,
|
|
425
|
+
ops: 0,
|
|
426
|
+
maxLength: buf.length
|
|
427
|
+
};
|
|
428
|
+
let i = 0;
|
|
429
|
+
while (i < buf.length) {
|
|
430
|
+
const g = buf[i];
|
|
431
|
+
if (g.mask & step.mask && starts.has(g.gid) && visible(face, g, lookup.flag, lookup.markSet)) {
|
|
432
|
+
r.idx = i;
|
|
433
|
+
r.lookup = lookup;
|
|
434
|
+
const next = applyLookup(r);
|
|
435
|
+
if (next >= 0) {
|
|
436
|
+
i = Math.max(next, i + 1);
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
i++;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
for (let i = 0; i < pos.length; i++) settle(pos, i, 24);
|
|
444
|
+
return pos;
|
|
445
|
+
}
|
|
446
|
+
var firstGlyphCache = /* @__PURE__ */ new WeakMap();
|
|
447
|
+
/**
|
|
448
|
+
* Every glyph a lookup can start at: the union of its subtables' first
|
|
449
|
+
* coverage. A glyph outside it is passed over without reading a subtable, as
|
|
450
|
+
* HarfBuzz's digests pass it over; most of a few hundred lookups touch none of
|
|
451
|
+
* a word's glyphs.
|
|
452
|
+
*/
|
|
453
|
+
function firstGlyphs(face, table, index) {
|
|
454
|
+
let byLookup = firstGlyphCache.get(table);
|
|
455
|
+
if (!byLookup) {
|
|
456
|
+
byLookup = /* @__PURE__ */ new Map();
|
|
457
|
+
firstGlyphCache.set(table, byLookup);
|
|
458
|
+
}
|
|
459
|
+
const had = byLookup.get(index);
|
|
460
|
+
if (had) return had;
|
|
461
|
+
const b = face.bytes;
|
|
462
|
+
const lookup = table.lookups[index];
|
|
463
|
+
const out = /* @__PURE__ */ new Set();
|
|
464
|
+
const contextual = table.kind === "GSUB" ? lookup.type === 5 || lookup.type === 6 : lookup.type >= 7;
|
|
465
|
+
for (const st of lookup.subtables) {
|
|
466
|
+
let cov = st + u16(b, st + 2);
|
|
467
|
+
if (contextual && u16(b, st) === 3) {
|
|
468
|
+
if (lookup.type === 6 || lookup.type === 8) {
|
|
469
|
+
const back = u16(b, st + 2);
|
|
470
|
+
cov = st + u16(b, st + 6 + 2 * back);
|
|
471
|
+
} else cov = st + u16(b, st + 6);
|
|
472
|
+
}
|
|
473
|
+
for (const g of face.coverage(cov).keys()) out.add(g);
|
|
474
|
+
}
|
|
475
|
+
byLookup.set(index, out);
|
|
476
|
+
return out;
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Whether a feature's lookups would substitute this exact sequence of glyphs
|
|
480
|
+
* — the question a shaper asks before it decides that a consonant takes its
|
|
481
|
+
* below-base form, or that a Ra starts a reph.
|
|
482
|
+
*
|
|
483
|
+
* @param face The face.
|
|
484
|
+
* @param steps The feature's lookups, from {@link planLookups}.
|
|
485
|
+
* @param gids The glyphs, in order.
|
|
486
|
+
* @param zeroContext Only a rule with no backtrack and no lookahead counts.
|
|
487
|
+
* @returns `true` when one of them would.
|
|
488
|
+
*/
|
|
489
|
+
function wouldSubstitute(face, steps, gids, zeroContext) {
|
|
490
|
+
const table = face.gsub;
|
|
491
|
+
if (!table || gids.length === 0) return false;
|
|
492
|
+
const b = face.bytes;
|
|
493
|
+
const n = gids.length;
|
|
494
|
+
const first = gids[0];
|
|
495
|
+
for (const step of steps) {
|
|
496
|
+
const lookup = table.lookups[step.lookup];
|
|
497
|
+
if (!lookup) continue;
|
|
498
|
+
for (const st of lookup.subtables) {
|
|
499
|
+
const format = u16(b, st);
|
|
500
|
+
switch (lookup.type) {
|
|
501
|
+
case 1:
|
|
502
|
+
case 2:
|
|
503
|
+
case 3:
|
|
504
|
+
if (n === 1 && face.coverage(st + u16(b, st + 2)).has(first)) return true;
|
|
505
|
+
break;
|
|
506
|
+
case 4: {
|
|
507
|
+
const index = face.coverage(st + u16(b, st + 2)).get(first);
|
|
508
|
+
if (index === void 0) break;
|
|
509
|
+
const set = st + u16(b, st + 6 + 2 * index);
|
|
510
|
+
for (let k = 0, m = u16(b, set); k < m; k++) {
|
|
511
|
+
const lig = set + u16(b, set + 2 + 2 * k);
|
|
512
|
+
if (u16(b, lig + 2) !== n) continue;
|
|
513
|
+
let ok = true;
|
|
514
|
+
for (let c = 1; c < n && ok; c++) ok = u16(b, lig + 2 + 2 * c) === gids[c];
|
|
515
|
+
if (ok) return true;
|
|
516
|
+
}
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
519
|
+
case 5:
|
|
520
|
+
case 6: {
|
|
521
|
+
const rules = contextRules(face, lookup.type, format, st, first);
|
|
522
|
+
for (const rule of rules) {
|
|
523
|
+
if (rule.input.length + 1 !== n) continue;
|
|
524
|
+
if (zeroContext && (rule.backtrack.length > 0 || rule.lookahead.length > 0)) continue;
|
|
525
|
+
let ok = true;
|
|
526
|
+
for (let c = 1; c < n && ok; c++) ok = rule.input[c - 1](gids[c]);
|
|
527
|
+
if (ok) return true;
|
|
528
|
+
}
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
536
|
+
/** Try the lookup's subtables at `r.idx`; the index to go on from, or -1. */
|
|
537
|
+
function applyLookup(r) {
|
|
538
|
+
for (const st of r.lookup.subtables) {
|
|
539
|
+
const next = r.table.kind === "GSUB" ? applySubstitution(r, st) : applyPositioning(r, st);
|
|
540
|
+
if (next >= 0) return next;
|
|
541
|
+
}
|
|
542
|
+
return -1;
|
|
543
|
+
}
|
|
544
|
+
function applySubstitution(r, st) {
|
|
545
|
+
const b = r.face.bytes;
|
|
546
|
+
const g = r.buf[r.idx];
|
|
547
|
+
const format = u16(b, st);
|
|
548
|
+
switch (r.lookup.type) {
|
|
549
|
+
case 1: {
|
|
550
|
+
if (format !== 1 && format !== 2) return -1;
|
|
551
|
+
const index = r.face.coverage(st + u16(b, st + 2)).get(g.gid);
|
|
552
|
+
if (index === void 0) return -1;
|
|
553
|
+
const gid = format === 1 ? g.gid + i16(b, st + 4) & 65535 : u16(b, st + 6 + 2 * index);
|
|
554
|
+
replace(r, r.idx, gid);
|
|
555
|
+
return r.idx + 1;
|
|
556
|
+
}
|
|
557
|
+
case 2: {
|
|
558
|
+
if (format !== 1) return -1;
|
|
559
|
+
const index = r.face.coverage(st + u16(b, st + 2)).get(g.gid);
|
|
560
|
+
if (index === void 0) return -1;
|
|
561
|
+
const seq = st + u16(b, st + 6 + 2 * index);
|
|
562
|
+
const count = u16(b, seq);
|
|
563
|
+
if (r.buf.length + count - 1 > r.maxLength) return -1;
|
|
564
|
+
if (count === 1) {
|
|
565
|
+
replace(r, r.idx, u16(b, seq + 2));
|
|
566
|
+
return r.idx + 1;
|
|
567
|
+
}
|
|
568
|
+
const made = [];
|
|
569
|
+
const props = (g.props ?? 0) | 5;
|
|
570
|
+
for (let k = 0; k < count; k++) {
|
|
571
|
+
const gid = u16(b, seq + 2 + 2 * k);
|
|
572
|
+
made.push({
|
|
573
|
+
...g,
|
|
574
|
+
gid,
|
|
575
|
+
cls: r.face.glyphClass(gid),
|
|
576
|
+
props
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
r.buf.splice(r.idx, 1, ...made);
|
|
580
|
+
return r.idx + count;
|
|
581
|
+
}
|
|
582
|
+
case 3: {
|
|
583
|
+
if (format !== 1) return -1;
|
|
584
|
+
const index = r.face.coverage(st + u16(b, st + 2)).get(g.gid);
|
|
585
|
+
if (index === void 0) return -1;
|
|
586
|
+
const set = st + u16(b, st + 6 + 2 * index);
|
|
587
|
+
if (u16(b, set) === 0) return -1;
|
|
588
|
+
replace(r, r.idx, u16(b, set + 2));
|
|
589
|
+
return r.idx + 1;
|
|
590
|
+
}
|
|
591
|
+
case 4: return format === 1 ? ligate(r, st) : -1;
|
|
592
|
+
case 5:
|
|
593
|
+
case 6: return applyContext(r, st);
|
|
594
|
+
case 8: return format === 1 ? reverseChain(r, st) : -1;
|
|
595
|
+
default: return -1;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
/** Put another glyph in a slot, with the class GDEF gives the new one. */
|
|
599
|
+
function replace(r, at, gid) {
|
|
600
|
+
const g = r.buf[at];
|
|
601
|
+
g.gid = gid;
|
|
602
|
+
g.cls = r.face.glyphClass(gid);
|
|
603
|
+
g.props = (g.props ?? 0) | 1;
|
|
604
|
+
}
|
|
605
|
+
var nextLigId = 1;
|
|
606
|
+
/**
|
|
607
|
+
* Make `buf[start..end)` one cluster, the least of theirs. A cluster cut at
|
|
608
|
+
* either edge comes in whole, as HarfBuzz merges them: a vowel sign that was
|
|
609
|
+
* one cluster with the last letter of a conjunct is one with the conjunct.
|
|
610
|
+
*
|
|
611
|
+
* @param buf The glyphs.
|
|
612
|
+
* @param start The first glyph of the stretch.
|
|
613
|
+
* @param end The glyph after its last.
|
|
614
|
+
*/
|
|
615
|
+
function mergeClusters(buf, start, end) {
|
|
616
|
+
if (end - start < 2) return;
|
|
617
|
+
let cluster = buf[start].cluster;
|
|
618
|
+
for (let i = start + 1; i < end; i++) cluster = Math.min(cluster, buf[i].cluster);
|
|
619
|
+
if (cluster !== buf[end - 1].cluster) while (end < buf.length && buf[end - 1].cluster === buf[end].cluster) end++;
|
|
620
|
+
if (cluster !== buf[start].cluster) while (start > 0 && buf[start - 1].cluster === buf[start].cluster) start--;
|
|
621
|
+
for (let i = start; i < end; i++) buf[i].cluster = cluster;
|
|
622
|
+
}
|
|
623
|
+
/** GSUB LookupType 4: the first ligature of the set whose components follow. */
|
|
624
|
+
function ligate(r, st) {
|
|
625
|
+
const b = r.face.bytes;
|
|
626
|
+
const index = r.face.coverage(st + u16(b, st + 2)).get(r.buf[r.idx].gid);
|
|
627
|
+
if (index === void 0) return -1;
|
|
628
|
+
const set = st + u16(b, st + 6 + 2 * index);
|
|
629
|
+
for (let k = 0, m = u16(b, set); k < m; k++) {
|
|
630
|
+
const lig = set + u16(b, set + 2 + 2 * k);
|
|
631
|
+
const count = u16(b, lig + 2);
|
|
632
|
+
const at = count <= 1 ? [] : walk(r, r.idx, 1, count - 1, (g, nth) => g.gid === u16(b, lig + 4 + 2 * nth), false);
|
|
633
|
+
if (!at) continue;
|
|
634
|
+
const last = at.length > 0 ? at[at.length - 1] : r.idx;
|
|
635
|
+
const sources = joinSources([r.buf[r.idx], ...at.map((i) => r.buf[i])]);
|
|
636
|
+
mergeClusters(r.buf, r.idx, last + 1);
|
|
637
|
+
const ligId = nextLigId++;
|
|
638
|
+
let comp = 1;
|
|
639
|
+
for (let i = r.idx + 1; i <= last; i++) {
|
|
640
|
+
const g = r.buf[i];
|
|
641
|
+
if (at.includes(i)) comp++;
|
|
642
|
+
else {
|
|
643
|
+
g.ligId = ligId;
|
|
644
|
+
g.ligComp = comp;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
const first = r.buf[r.idx];
|
|
648
|
+
first.ligId = ligId;
|
|
649
|
+
first.ligComp = 0;
|
|
650
|
+
replace(r, r.idx, u16(b, lig));
|
|
651
|
+
first.props = ((first.props ?? 0) | 2) & -5;
|
|
652
|
+
if (sources) first.sources = sources;
|
|
653
|
+
for (let c = at.length - 1; c >= 0; c--) r.buf.splice(at[c], 1);
|
|
654
|
+
return last + 1 - at.length;
|
|
655
|
+
}
|
|
656
|
+
return -1;
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* The characters a run of glyphs stands for together, each once, least first;
|
|
660
|
+
* `undefined` when none of them keeps its characters.
|
|
661
|
+
*
|
|
662
|
+
* @param glyphs The glyphs.
|
|
663
|
+
* @returns Their sources, merged.
|
|
664
|
+
*/
|
|
665
|
+
function joinSources(glyphs) {
|
|
666
|
+
if (glyphs.every((g) => g.sources === void 0)) return void 0;
|
|
667
|
+
return [...new Set(glyphs.flatMap((g) => g.sources ?? []))].sort((a, b) => a - b);
|
|
668
|
+
}
|
|
669
|
+
/** GSUB LookupType 8: one substitution, by context, from the end backwards. */
|
|
670
|
+
function reverseChain(r, st) {
|
|
671
|
+
const b = r.face.bytes;
|
|
672
|
+
const index = r.face.coverage(st + u16(b, st + 2)).get(r.buf[r.idx].gid);
|
|
673
|
+
if (index === void 0) return -1;
|
|
674
|
+
let o = st + 4;
|
|
675
|
+
const back = u16(b, o);
|
|
676
|
+
const backCov = Array.from({ length: back }, (_, k) => st + u16(b, o + 2 + 2 * k));
|
|
677
|
+
o += 2 + 2 * back;
|
|
678
|
+
const ahead = u16(b, o);
|
|
679
|
+
const aheadCov = Array.from({ length: ahead }, (_, k) => st + u16(b, o + 2 + 2 * k));
|
|
680
|
+
o += 2 + 2 * ahead;
|
|
681
|
+
const covers = (offs) => (g, nth) => r.face.coverage(offs[nth]).has(g.gid);
|
|
682
|
+
if (!walk(r, r.idx, -1, back, covers(backCov), true)) return -1;
|
|
683
|
+
if (!walk(r, r.idx, 1, ahead, covers(aheadCov), true)) return -1;
|
|
684
|
+
replace(r, r.idx, u16(b, o + 2 + 2 * index));
|
|
685
|
+
return r.idx + 1;
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* The rules of a context subtable (LookupType 5/6 of GSUB, 7/8 of GPOS) that
|
|
689
|
+
* may start on `first`, in the order they are tried.
|
|
690
|
+
*/
|
|
691
|
+
function contextRules(face, type, format, st, first) {
|
|
692
|
+
let bySubtable = ruleCache.get(face);
|
|
693
|
+
if (!bySubtable) {
|
|
694
|
+
bySubtable = /* @__PURE__ */ new Map();
|
|
695
|
+
ruleCache.set(face, bySubtable);
|
|
696
|
+
}
|
|
697
|
+
let byGlyph = bySubtable.get(st);
|
|
698
|
+
if (!byGlyph) {
|
|
699
|
+
byGlyph = /* @__PURE__ */ new Map();
|
|
700
|
+
bySubtable.set(st, byGlyph);
|
|
701
|
+
}
|
|
702
|
+
let rules = byGlyph.get(first);
|
|
703
|
+
if (!rules) {
|
|
704
|
+
rules = readContextRules(face, type, format, st, first);
|
|
705
|
+
byGlyph.set(first, rules);
|
|
706
|
+
}
|
|
707
|
+
return rules;
|
|
708
|
+
}
|
|
709
|
+
var ruleCache = /* @__PURE__ */ new WeakMap();
|
|
710
|
+
function readContextRules(face, type, format, st, first) {
|
|
711
|
+
const b = face.bytes;
|
|
712
|
+
const chained = type === 6 || type === 8;
|
|
713
|
+
const records = (o, n) => Array.from({ length: n }, (_, k) => [u16(b, o + 4 * k), u16(b, o + 4 * k + 2)]);
|
|
714
|
+
const glyphIs = (gid) => (g) => g === gid;
|
|
715
|
+
const out = [];
|
|
716
|
+
if (format === 1 || format === 2) {
|
|
717
|
+
let index;
|
|
718
|
+
let ruleSets;
|
|
719
|
+
let classes = [];
|
|
720
|
+
if (format === 1) {
|
|
721
|
+
index = face.coverage(st + u16(b, st + 2)).get(first);
|
|
722
|
+
ruleSets = st + 6;
|
|
723
|
+
} else if (!chained) {
|
|
724
|
+
if (!face.coverage(st + u16(b, st + 2)).has(first)) return out;
|
|
725
|
+
const input = face.classDef(st + u16(b, st + 4));
|
|
726
|
+
classes = [
|
|
727
|
+
input,
|
|
728
|
+
input,
|
|
729
|
+
input
|
|
730
|
+
];
|
|
731
|
+
index = input.get(first) ?? 0;
|
|
732
|
+
ruleSets = st + 8;
|
|
733
|
+
} else {
|
|
734
|
+
if (!face.coverage(st + u16(b, st + 2)).has(first)) return out;
|
|
735
|
+
const def = (o) => u16(b, st + o) ? face.classDef(st + u16(b, st + o)) : /* @__PURE__ */ new Map();
|
|
736
|
+
classes = [
|
|
737
|
+
def(4),
|
|
738
|
+
def(6),
|
|
739
|
+
def(8)
|
|
740
|
+
];
|
|
741
|
+
index = classes[1].get(first) ?? 0;
|
|
742
|
+
ruleSets = st + 12;
|
|
743
|
+
}
|
|
744
|
+
if (index === void 0) return out;
|
|
745
|
+
const setCount = u16(b, ruleSets - 2);
|
|
746
|
+
if (index >= setCount) return out;
|
|
747
|
+
const setOff = u16(b, ruleSets + 2 * index);
|
|
748
|
+
if (!setOff) return out;
|
|
749
|
+
const set = st + setOff;
|
|
750
|
+
const test = format === 1 ? (_which) => glyphIs : (which) => (cls) => (g) => (classes[which].get(g) ?? 0) === cls;
|
|
751
|
+
for (let k = 0, m = u16(b, set); k < m; k++) {
|
|
752
|
+
let o = set + u16(b, set + 2 + 2 * k);
|
|
753
|
+
if (!chained) {
|
|
754
|
+
const glyphs = u16(b, o);
|
|
755
|
+
const count = u16(b, o + 2);
|
|
756
|
+
const input = Array.from({ length: Math.max(0, glyphs - 1) }, (_, c) => test(1)(u16(b, o + 4 + 2 * c)));
|
|
757
|
+
out.push({
|
|
758
|
+
backtrack: [],
|
|
759
|
+
input,
|
|
760
|
+
lookahead: [],
|
|
761
|
+
records: records(o + 4 + 2 * input.length, count)
|
|
762
|
+
});
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
const back = u16(b, o);
|
|
766
|
+
const backtrack = Array.from({ length: back }, (_, c) => test(0)(u16(b, o + 2 + 2 * c)));
|
|
767
|
+
o += 2 + 2 * back;
|
|
768
|
+
const glyphs = u16(b, o);
|
|
769
|
+
const input = Array.from({ length: Math.max(0, glyphs - 1) }, (_, c) => test(1)(u16(b, o + 2 + 2 * c)));
|
|
770
|
+
o += 2 + 2 * input.length;
|
|
771
|
+
const ahead = u16(b, o);
|
|
772
|
+
const lookahead = Array.from({ length: ahead }, (_, c) => test(2)(u16(b, o + 2 + 2 * c)));
|
|
773
|
+
o += 2 + 2 * ahead;
|
|
774
|
+
out.push({
|
|
775
|
+
backtrack,
|
|
776
|
+
input,
|
|
777
|
+
lookahead,
|
|
778
|
+
records: records(o + 2, u16(b, o))
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
return out;
|
|
782
|
+
}
|
|
783
|
+
if (format !== 3) return out;
|
|
784
|
+
const covers = (off) => (g) => face.coverage(off).has(g);
|
|
785
|
+
if (!chained) {
|
|
786
|
+
const glyphs = u16(b, st + 2);
|
|
787
|
+
const count = u16(b, st + 4);
|
|
788
|
+
const covs = Array.from({ length: glyphs }, (_, c) => st + u16(b, st + 6 + 2 * c));
|
|
789
|
+
if (covs.length === 0 || !face.coverage(covs[0]).has(first)) return out;
|
|
790
|
+
out.push({
|
|
791
|
+
backtrack: [],
|
|
792
|
+
input: covs.slice(1).map(covers),
|
|
793
|
+
lookahead: [],
|
|
794
|
+
records: records(st + 6 + 2 * glyphs, count)
|
|
795
|
+
});
|
|
796
|
+
return out;
|
|
797
|
+
}
|
|
798
|
+
let o = st + 2;
|
|
799
|
+
const back = u16(b, o);
|
|
800
|
+
const backtrack = Array.from({ length: back }, (_, c) => covers(st + u16(b, o + 2 + 2 * c)));
|
|
801
|
+
o += 2 + 2 * back;
|
|
802
|
+
const glyphs = u16(b, o);
|
|
803
|
+
const covs = Array.from({ length: glyphs }, (_, c) => st + u16(b, o + 2 + 2 * c));
|
|
804
|
+
o += 2 + 2 * glyphs;
|
|
805
|
+
const ahead = u16(b, o);
|
|
806
|
+
const lookahead = Array.from({ length: ahead }, (_, c) => covers(st + u16(b, o + 2 + 2 * c)));
|
|
807
|
+
o += 2 + 2 * ahead;
|
|
808
|
+
if (covs.length === 0 || !face.coverage(covs[0]).has(first)) return out;
|
|
809
|
+
out.push({
|
|
810
|
+
backtrack,
|
|
811
|
+
input: covs.slice(1).map(covers),
|
|
812
|
+
lookahead,
|
|
813
|
+
records: records(o + 2, u16(b, o))
|
|
814
|
+
});
|
|
815
|
+
return out;
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* A context lookup (GSUB 5/6, GPOS 7/8): the first rule whose glyphs are all
|
|
819
|
+
* there runs its lookups at the places it names, and the run goes on after
|
|
820
|
+
* the matched input.
|
|
821
|
+
*/
|
|
822
|
+
function applyContext(r, st) {
|
|
823
|
+
const b = r.face.bytes;
|
|
824
|
+
const rules = contextRules(r.face, r.lookup.type, u16(b, st), st, r.buf[r.idx].gid);
|
|
825
|
+
for (const rule of rules) {
|
|
826
|
+
const input = walk(r, r.idx, 1, rule.input.length, (g, nth) => rule.input[nth](g.gid), false);
|
|
827
|
+
if (!input) continue;
|
|
828
|
+
const end = input.length > 0 ? input[input.length - 1] : r.idx;
|
|
829
|
+
if (!walk(r, r.idx, -1, rule.backtrack.length, (g, nth) => rule.backtrack[nth](g.gid), true)) continue;
|
|
830
|
+
if (!walk(r, end, 1, rule.lookahead.length, (g, nth) => rule.lookahead[nth](g.gid), true)) continue;
|
|
831
|
+
return runRecords(r, [r.idx, ...input], rule.records);
|
|
832
|
+
}
|
|
833
|
+
return -1;
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Run a rule's SequenceLookupRecords at the places of its input, and return
|
|
837
|
+
* the index after the input. A lookup that grows or shrinks the buffer moves
|
|
838
|
+
* the places after it, by HarfBuzz's reckoning: glyphs it added came right
|
|
839
|
+
* after the place it ran at, and glyphs it removed were the places after it.
|
|
840
|
+
*/
|
|
841
|
+
function runRecords(r, places, records) {
|
|
842
|
+
const match = [...places];
|
|
843
|
+
let end = match[match.length - 1] + 1;
|
|
844
|
+
if (r.depth >= MAX_NESTING) return end;
|
|
845
|
+
const outer = {
|
|
846
|
+
lookup: r.lookup,
|
|
847
|
+
idx: r.idx
|
|
848
|
+
};
|
|
849
|
+
for (const [seqIndex, lookupIndex] of records) {
|
|
850
|
+
if (seqIndex >= match.length) continue;
|
|
851
|
+
const lookup = r.table.lookups[lookupIndex];
|
|
852
|
+
if (!lookup) continue;
|
|
853
|
+
const at = match[seqIndex];
|
|
854
|
+
if (at >= r.buf.length) continue;
|
|
855
|
+
const before = r.buf.length;
|
|
856
|
+
r.lookup = lookup;
|
|
857
|
+
r.idx = at;
|
|
858
|
+
r.depth++;
|
|
859
|
+
const applied = applyLookup(r) >= 0;
|
|
860
|
+
r.depth--;
|
|
861
|
+
r.lookup = outer.lookup;
|
|
862
|
+
r.idx = outer.idx;
|
|
863
|
+
if (!applied) continue;
|
|
864
|
+
let delta = r.buf.length - before;
|
|
865
|
+
if (delta === 0) continue;
|
|
866
|
+
end += delta;
|
|
867
|
+
if (end < at) {
|
|
868
|
+
delta += at - end;
|
|
869
|
+
end = at;
|
|
870
|
+
}
|
|
871
|
+
let next = seqIndex + 1;
|
|
872
|
+
if (delta < 0) {
|
|
873
|
+
delta = Math.max(delta, next - match.length);
|
|
874
|
+
next -= delta;
|
|
875
|
+
}
|
|
876
|
+
const tail = match.slice(next);
|
|
877
|
+
match.length = next + delta;
|
|
878
|
+
for (let j = seqIndex + 1; j < next + delta; j++) match[j] = match[j - 1] + 1;
|
|
879
|
+
for (const t of tail) match.push(t + delta);
|
|
880
|
+
}
|
|
881
|
+
return end;
|
|
882
|
+
}
|
|
883
|
+
/** A ValueRecord's size in bytes, by its format. */
|
|
884
|
+
function valueSize(format) {
|
|
885
|
+
let n = 0;
|
|
886
|
+
for (let f = format & 255; f; f >>= 1) n += f & 1;
|
|
887
|
+
return 2 * n;
|
|
888
|
+
}
|
|
889
|
+
/** Add a ValueRecord to a glyph's position; device tables are not read. */
|
|
890
|
+
function addValue(b, o, format, p) {
|
|
891
|
+
if (format & 1) {
|
|
892
|
+
p.xOffset += i16(b, o);
|
|
893
|
+
o += 2;
|
|
894
|
+
}
|
|
895
|
+
if (format & 2) {
|
|
896
|
+
p.yOffset += i16(b, o);
|
|
897
|
+
o += 2;
|
|
898
|
+
}
|
|
899
|
+
if (format & 4) {
|
|
900
|
+
p.xAdvance += i16(b, o);
|
|
901
|
+
o += 2;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
/** An anchor's point; formats 2 and 3 add a contour point or a device table, not read. */
|
|
905
|
+
function anchor(b, o) {
|
|
906
|
+
return [i16(b, o + 2), i16(b, o + 4)];
|
|
907
|
+
}
|
|
908
|
+
function applyPositioning(r, st) {
|
|
909
|
+
const b = r.face.bytes;
|
|
910
|
+
const pos = r.pos;
|
|
911
|
+
const g = r.buf[r.idx];
|
|
912
|
+
const format = u16(b, st);
|
|
913
|
+
switch (r.lookup.type) {
|
|
914
|
+
case 1: {
|
|
915
|
+
const index = r.face.coverage(st + u16(b, st + 2)).get(g.gid);
|
|
916
|
+
if (index === void 0) return -1;
|
|
917
|
+
const vf = u16(b, st + 4);
|
|
918
|
+
if (format === 1) addValue(b, st + 6, vf, pos[r.idx]);
|
|
919
|
+
else if (format === 2) addValue(b, st + 8 + index * valueSize(vf), vf, pos[r.idx]);
|
|
920
|
+
else return -1;
|
|
921
|
+
return r.idx + 1;
|
|
922
|
+
}
|
|
923
|
+
case 2: return pairPos(r, st, format);
|
|
924
|
+
case 3: return format === 1 ? cursivePos(r, st) : -1;
|
|
925
|
+
case 4:
|
|
926
|
+
case 5:
|
|
927
|
+
case 6: return format === 1 ? markPos(r, st) : -1;
|
|
928
|
+
case 7:
|
|
929
|
+
case 8: return applyContext(r, st);
|
|
930
|
+
default: return -1;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
/** GPOS LookupType 2: the current glyph and the next one the lookup sees. */
|
|
934
|
+
function pairPos(r, st, format) {
|
|
935
|
+
const b = r.face.bytes;
|
|
936
|
+
const pos = r.pos;
|
|
937
|
+
const first = r.buf[r.idx];
|
|
938
|
+
const index = r.face.coverage(st + u16(b, st + 2)).get(first.gid);
|
|
939
|
+
if (index === void 0) return -1;
|
|
940
|
+
const next = walk(r, r.idx, 1, 1, void 0, false);
|
|
941
|
+
if (!next) return -1;
|
|
942
|
+
const j = next[0];
|
|
943
|
+
const second = r.buf[j].gid;
|
|
944
|
+
const vf1 = u16(b, st + 4);
|
|
945
|
+
const vf2 = u16(b, st + 6);
|
|
946
|
+
const s1 = valueSize(vf1);
|
|
947
|
+
const s2 = valueSize(vf2);
|
|
948
|
+
let record;
|
|
949
|
+
if (format === 1) {
|
|
950
|
+
const set = st + u16(b, st + 10 + 2 * index);
|
|
951
|
+
const size = 2 + s1 + s2;
|
|
952
|
+
let lo = 0;
|
|
953
|
+
let hi = u16(b, set) - 1;
|
|
954
|
+
while (lo <= hi) {
|
|
955
|
+
const mid = lo + hi >> 1;
|
|
956
|
+
const at = set + 2 + mid * size;
|
|
957
|
+
const gid = u16(b, at);
|
|
958
|
+
if (gid === second) {
|
|
959
|
+
record = at + 2;
|
|
960
|
+
break;
|
|
961
|
+
}
|
|
962
|
+
if (gid < second) lo = mid + 1;
|
|
963
|
+
else hi = mid - 1;
|
|
964
|
+
}
|
|
965
|
+
} else if (format === 2) {
|
|
966
|
+
const c1 = r.face.classDef(st + u16(b, st + 8)).get(first.gid) ?? 0;
|
|
967
|
+
const c2 = r.face.classDef(st + u16(b, st + 10)).get(second) ?? 0;
|
|
968
|
+
const n1 = u16(b, st + 12);
|
|
969
|
+
const n2 = u16(b, st + 14);
|
|
970
|
+
if (c1 >= n1 || c2 >= n2) return -1;
|
|
971
|
+
record = st + 16 + (c1 * n2 + c2) * (s1 + s2);
|
|
972
|
+
} else return -1;
|
|
973
|
+
if (record === void 0) return -1;
|
|
974
|
+
addValue(b, record, vf1, pos[r.idx]);
|
|
975
|
+
addValue(b, record + s1, vf2, pos[j]);
|
|
976
|
+
return vf2 ? j + 1 : j;
|
|
977
|
+
}
|
|
978
|
+
/** GPOS LookupType 3: the current glyph's entry joined to the exit of the one before. */
|
|
979
|
+
function cursivePos(r, st) {
|
|
980
|
+
const b = r.face.bytes;
|
|
981
|
+
const pos = r.pos;
|
|
982
|
+
const cov = r.face.coverage(st + u16(b, st + 2));
|
|
983
|
+
const record = (gid) => {
|
|
984
|
+
const index = cov.get(gid);
|
|
985
|
+
return index === void 0 ? void 0 : st + 6 + 4 * index;
|
|
986
|
+
};
|
|
987
|
+
const here = record(r.buf[r.idx].gid);
|
|
988
|
+
if (here === void 0 || !u16(b, here)) return -1;
|
|
989
|
+
const prev = walk(r, r.idx, -1, 1, void 0, false);
|
|
990
|
+
if (!prev) return -1;
|
|
991
|
+
const i = prev[0];
|
|
992
|
+
const there = record(r.buf[i].gid);
|
|
993
|
+
if (there === void 0 || !u16(b, there + 2)) return -1;
|
|
994
|
+
const j = r.idx;
|
|
995
|
+
const [exitX, exitY] = anchor(b, st + u16(b, there + 2));
|
|
996
|
+
const [entryX, entryY] = anchor(b, st + u16(b, here));
|
|
997
|
+
pos[i].xAdvance = exitX + pos[i].xOffset;
|
|
998
|
+
const d = entryX + pos[j].xOffset;
|
|
999
|
+
pos[j].xAdvance -= d;
|
|
1000
|
+
pos[j].xOffset -= d;
|
|
1001
|
+
let child = i;
|
|
1002
|
+
let parent = j;
|
|
1003
|
+
let y = entryY - exitY;
|
|
1004
|
+
if (!(r.lookup.flag & RIGHT_TO_LEFT)) {
|
|
1005
|
+
child = j;
|
|
1006
|
+
parent = i;
|
|
1007
|
+
y = -y;
|
|
1008
|
+
}
|
|
1009
|
+
reverseCursive(pos, child, parent);
|
|
1010
|
+
pos[child].attachType = 2;
|
|
1011
|
+
pos[child].attachChain = parent - child;
|
|
1012
|
+
pos[child].yOffset = y;
|
|
1013
|
+
if (pos[parent].attachChain === -pos[child].attachChain) {
|
|
1014
|
+
pos[parent].attachChain = 0;
|
|
1015
|
+
pos[parent].yOffset = 0;
|
|
1016
|
+
}
|
|
1017
|
+
return r.idx + 1;
|
|
1018
|
+
}
|
|
1019
|
+
/** Turn a cursive chain round, so the whole of it now hangs off a new parent. */
|
|
1020
|
+
function reverseCursive(pos, i, newParent) {
|
|
1021
|
+
const p = pos[i];
|
|
1022
|
+
const chain = p.attachChain;
|
|
1023
|
+
if (!chain || p.attachType !== 2) return;
|
|
1024
|
+
p.attachChain = 0;
|
|
1025
|
+
const j = i + chain;
|
|
1026
|
+
const q = pos[j];
|
|
1027
|
+
if (j === newParent || !q) return;
|
|
1028
|
+
reverseCursive(pos, j, newParent);
|
|
1029
|
+
q.yOffset = -p.yOffset;
|
|
1030
|
+
q.attachChain = -chain;
|
|
1031
|
+
q.attachType = 2;
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
1034
|
+
* GPOS LookupType 4, 5 and 6: a mark on the anchor of the base, the ligature
|
|
1035
|
+
* component or the mark it belongs to.
|
|
1036
|
+
*/
|
|
1037
|
+
function markPos(r, st) {
|
|
1038
|
+
const b = r.face.bytes;
|
|
1039
|
+
const pos = r.pos;
|
|
1040
|
+
const mark = r.buf[r.idx];
|
|
1041
|
+
const markIndex = r.face.coverage(st + u16(b, st + 2)).get(mark.gid);
|
|
1042
|
+
if (markIndex === void 0) return -1;
|
|
1043
|
+
const classCount = u16(b, st + 6);
|
|
1044
|
+
const markArray = st + u16(b, st + 8);
|
|
1045
|
+
const targetArray = st + u16(b, st + 10);
|
|
1046
|
+
const targetCov = r.face.coverage(st + u16(b, st + 4));
|
|
1047
|
+
let j = -1;
|
|
1048
|
+
if (r.lookup.type === 6) {
|
|
1049
|
+
const prev = walk(r, r.idx, -1, 1, void 0, false, r.lookup.flag & -15);
|
|
1050
|
+
if (!prev) return -1;
|
|
1051
|
+
j = prev[0];
|
|
1052
|
+
if (r.buf[j].cls !== MARK) return -1;
|
|
1053
|
+
} else {
|
|
1054
|
+
const prev = walk(r, r.idx, -1, 1, void 0, false, IGNORE_MARKS);
|
|
1055
|
+
if (!prev) return -1;
|
|
1056
|
+
j = prev[0];
|
|
1057
|
+
}
|
|
1058
|
+
const targetIndex = targetCov.get(r.buf[j].gid);
|
|
1059
|
+
if (targetIndex === void 0) return -1;
|
|
1060
|
+
const markRecord = markArray + 2 + 4 * markIndex;
|
|
1061
|
+
const markClass = u16(b, markRecord);
|
|
1062
|
+
const markAnchor = anchor(b, markArray + u16(b, markRecord + 2));
|
|
1063
|
+
if (markClass >= classCount) return -1;
|
|
1064
|
+
let anchorOff;
|
|
1065
|
+
if (r.lookup.type === 5) {
|
|
1066
|
+
const attach = targetArray + u16(b, targetArray + 2 + 2 * targetIndex);
|
|
1067
|
+
const components = u16(b, attach);
|
|
1068
|
+
if (components === 0) return -1;
|
|
1069
|
+
const target = r.buf[j];
|
|
1070
|
+
const comp = mark.ligId !== 0 && mark.ligId === target.ligId && mark.ligComp > 0 ? Math.min(mark.ligComp, components) : components;
|
|
1071
|
+
anchorOff = u16(b, attach + 2 + 2 * ((comp - 1) * classCount + markClass));
|
|
1072
|
+
if (!anchorOff) return -1;
|
|
1073
|
+
anchorOff += attach;
|
|
1074
|
+
} else {
|
|
1075
|
+
anchorOff = u16(b, targetArray + 2 + 2 * (targetIndex * classCount + markClass));
|
|
1076
|
+
if (!anchorOff) return -1;
|
|
1077
|
+
anchorOff += targetArray;
|
|
1078
|
+
}
|
|
1079
|
+
const [bx, by] = anchor(b, anchorOff);
|
|
1080
|
+
const p = pos[r.idx];
|
|
1081
|
+
p.xOffset = bx - markAnchor[0];
|
|
1082
|
+
p.yOffset = by - markAnchor[1];
|
|
1083
|
+
p.attachType = 1;
|
|
1084
|
+
p.attachChain = j - r.idx;
|
|
1085
|
+
return r.idx + 1;
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Carry a glyph along with the one it hangs on: a cursive join takes its
|
|
1089
|
+
* parent's rise, a mark its base's offset less the advances between them.
|
|
1090
|
+
*/
|
|
1091
|
+
function settle(pos, i, depth) {
|
|
1092
|
+
const p = pos[i];
|
|
1093
|
+
const chain = p.attachChain;
|
|
1094
|
+
if (!chain) return;
|
|
1095
|
+
p.attachChain = 0;
|
|
1096
|
+
const j = i + chain;
|
|
1097
|
+
const q = pos[j];
|
|
1098
|
+
if (!q || depth === 0) return;
|
|
1099
|
+
settle(pos, j, depth - 1);
|
|
1100
|
+
if (p.attachType === 2) {
|
|
1101
|
+
p.yOffset += q.yOffset;
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
p.xOffset += q.xOffset;
|
|
1105
|
+
p.yOffset += q.yOffset;
|
|
1106
|
+
for (let k = j; k < i; k++) {
|
|
1107
|
+
p.xOffset -= pos[k].xAdvance;
|
|
1108
|
+
p.yOffset -= pos[k].yAdvance;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
//#endregion
|
|
1112
|
+
export { OtlFace, joinSources, mergeClusters, pickScript, planLookups, position, substitute, wouldSubstitute };
|