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.
Files changed (71) hide show
  1. package/README.md +13 -10
  2. package/dist/esm/core/converter/facade.js +1 -0
  3. package/dist/esm/core/converter/ream.d.ts +6 -6
  4. package/dist/esm/core/converter/ream.js +11 -5
  5. package/dist/esm/core/document-model/index.d.ts +1 -1
  6. package/dist/esm/core/document-model/types.d.ts +15 -0
  7. package/dist/esm/core/drawingml/text-warp.d.ts +34 -0
  8. package/dist/esm/core/drawingml/text-warp.js +49 -1
  9. package/dist/esm/core/font/index.d.ts +1 -1
  10. package/dist/esm/core/font/index.js +8 -0
  11. package/dist/esm/core/font/indic-shaper.d.ts +47 -0
  12. package/dist/esm/core/font/indic-shaper.js +1146 -0
  13. package/dist/esm/core/font/indic-table.d.ts +12 -0
  14. package/dist/esm/core/font/indic-table.js +463 -0
  15. package/dist/esm/core/font/measure.d.ts +45 -4
  16. package/dist/esm/core/font/measure.js +253 -14
  17. package/dist/esm/core/font/opentype-lookups.d.ts +214 -0
  18. package/dist/esm/core/font/opentype-lookups.js +1112 -0
  19. package/dist/esm/core/fonts/families.d.ts +4 -0
  20. package/dist/esm/core/fonts/families.js +43 -12
  21. package/dist/esm/core/fonts/provider.js +1 -0
  22. package/dist/esm/core/fonts/remote-fonts.d.ts +4 -2
  23. package/dist/esm/core/fonts/remote-fonts.js +36 -0
  24. package/dist/esm/core/fonts/scripts.d.ts +19 -0
  25. package/dist/esm/core/fonts/scripts.js +96 -1
  26. package/dist/esm/core/numbering/apply.js +12 -8
  27. package/dist/esm/core/style-cascade/index.d.ts +1 -1
  28. package/dist/esm/core/style-cascade/index.js +2 -2
  29. package/dist/esm/core/style-cascade/resolver.d.ts +9 -0
  30. package/dist/esm/core/style-cascade/resolver.js +31 -3
  31. package/dist/esm/core/style-cascade/table.js +3 -8
  32. package/dist/esm/excel/xlsx-to-pdf.js +1 -0
  33. package/dist/esm/index.js +1 -0
  34. package/dist/esm/layout/line-paint.d.ts +5 -4
  35. package/dist/esm/layout/line-paint.js +6 -5
  36. package/dist/esm/layout/page-doc.d.ts +26 -0
  37. package/dist/esm/layout/styled-layout.d.ts +8 -1
  38. package/dist/esm/layout/styled-layout.js +459 -112
  39. package/dist/esm/layout/undrawn.d.ts +18 -0
  40. package/dist/esm/layout/undrawn.js +59 -0
  41. package/dist/esm/pdf/cid-font.d.ts +5 -0
  42. package/dist/esm/pdf/cid-font.js +8 -2
  43. package/dist/esm/pdf/styled-page-emitter.js +117 -28
  44. package/dist/esm/pdf/text-page-renderer.js +1 -0
  45. package/dist/esm/pdf-reader/content.d.ts +1 -1
  46. package/dist/esm/pdf-reader/content.js +48 -13
  47. package/dist/esm/pdf-reader/embedded-fonts.js +1 -0
  48. package/dist/esm/pdf-reader/encodings.d.ts +9 -0
  49. package/dist/esm/pdf-reader/encodings.js +66 -1
  50. package/dist/esm/pdf-reader/face-outlines.js +1 -0
  51. package/dist/esm/pdf-reader/text.d.ts +10 -0
  52. package/dist/esm/pdf-reader/text.js +25 -3
  53. package/dist/esm/pdf-reader/vector.js +2 -2
  54. package/dist/esm/pptx/embedded-fonts.js +1 -0
  55. package/dist/esm/pptx/sp-helpers.js +4 -1
  56. package/dist/esm/svg/svg-text.js +30 -7
  57. package/dist/esm/word/doc/doc-reader.js +148 -57
  58. package/dist/esm/word/doc/doc-text.d.ts +80 -11
  59. package/dist/esm/word/doc/doc-text.js +438 -75
  60. package/dist/esm/word/document-parser.js +48 -28
  61. package/dist/esm/word/docx-to-pdf.d.ts +5 -5
  62. package/dist/esm/word/docx-to-pdf.js +9 -7
  63. package/dist/esm/word/docx-writer.js +3 -5
  64. package/dist/esm/word/font-embed.js +1 -0
  65. package/dist/esm/word/font-table.js +1 -0
  66. package/dist/esm/word/numbering-parser.js +3 -0
  67. package/dist/esm/word/paragraph-properties.js +2 -2
  68. package/dist/esm/word/run-properties.js +2 -2
  69. package/dist/esm/word/xml-helpers.d.ts +18 -0
  70. package/dist/esm/word/xml-helpers.js +48 -1
  71. package/package.json +2 -2
@@ -1,6 +1,155 @@
1
1
  import { shapeText } from "./opentype-layout.js";
2
+ import { hasIndic, shapeIndic } from "./indic-shaper.js";
2
3
  //#region src/core/font/measure.ts
3
4
  var EMPTY_KERNING = /* @__PURE__ */ new Map();
5
+ /** The most shaped words a face keeps; past it the cache starts over. */
6
+ var SHAPED_CACHE = 4096;
7
+ /**
8
+ * The glyphs a face draws a string with, and for each glyph the Indic shaper
9
+ * made out of several characters (a conjunct, a reph, a vowel sign's
10
+ * contextual form), the text it stands for — which a PDF's /ToUnicode has to
11
+ * name, since no character maps to it.
12
+ *
13
+ * @param parsed The face.
14
+ * @param text The text.
15
+ * @returns The glyph ids, and the text of each glyph no character reaches.
16
+ */
17
+ function shapeGlyphs(parsed, text) {
18
+ const run = hasIndic(text) ? shapeIndic(parsed, text) : void 0;
19
+ if (!run) return { gids: shapeText(text, parsed.glyphForCodepoint, parsed.advanceWidths, parsed.ligatures, parsed.kerning, parsed.joiningForms).gids };
20
+ return {
21
+ gids: run.gids,
22
+ glyphText: shapedGlyphText(parsed, text, run)
23
+ };
24
+ }
25
+ /** The characters a face's cmap gives each glyph, scanned as /ToUnicode scans them: U+0009 to U+FFFF. */
26
+ var cmapNames = /* @__PURE__ */ new WeakMap();
27
+ function namesOf(parsed) {
28
+ let names = cmapNames.get(parsed);
29
+ if (!names) {
30
+ const map = /* @__PURE__ */ new Map();
31
+ for (let cp = 9; cp <= 65535; cp++) {
32
+ if (cp >= 55296 && cp <= 57343) continue;
33
+ const gid = parsed.glyphForCodepoint(cp);
34
+ if (gid === 0) continue;
35
+ const had = map.get(gid);
36
+ if (had) had.push(cp);
37
+ else map.set(gid, [cp]);
38
+ }
39
+ names = map;
40
+ cmapNames.set(parsed, names);
41
+ }
42
+ return names;
43
+ }
44
+ /** A character's canonical parts: क़ is क and the nukta, ো is ে and া. */
45
+ var partsOf = (cp) => [...String.fromCodePoint(cp).normalize("NFD")].map((ch) => ch.codePointAt(0));
46
+ var IGNORABLE_RE = /\p{Default_Ignorable_Code_Point}/u;
47
+ /**
48
+ * Each glyph the shaper made that no character maps to, with the characters
49
+ * it was made from, in the order they were written: the reph of पार्श्व
50
+ * stands for र्, the conjunct it rides on for श्व, the short i of हि for ि.
51
+ * Each pair is one cluster, and a glyph given the cluster's text less what
52
+ * its other glyphs are named after read the tail of पार्श्व twice: once for
53
+ * the conjunct and once for the reph.
54
+ *
55
+ * A cluster is read glyph by glyph:
56
+ * - A glyph a character maps to is named after it by /ToUnicode, and reads
57
+ * that much of what it was made from — or, where a face moved a glyph by
58
+ * swapping two (Noto Sans Bengali sets a reph after a vowel sign so), of
59
+ * the rest of its cluster.
60
+ * - A made glyph reads what is left of the characters it was made from. Of a
61
+ * character drawn in several, each takes a part: a two-part vowel sign
62
+ * whose first part joined the consonant stands there for its first part,
63
+ * and the glyph of the second part for the second.
64
+ * - What is left of the cluster goes to a made glyph that read nothing, or
65
+ * else to the one after the glyph it was drawn as: the o of कों is set as
66
+ * the glyph of the aa, its top in the anusvara's, and the anusvara stands
67
+ * for both. Left out, a reader lost the o of a plural.
68
+ * - A made glyph that still reads nothing — one of the pieces a face draws a
69
+ * letter in — stands for its letter, so that every glyph drawn reads.
70
+ */
71
+ function shapedGlyphText(parsed, text, run) {
72
+ const cps = [...text].map((ch) => ch.codePointAt(0));
73
+ const names = namesOf(parsed);
74
+ const made = run.gids.map((gid) => gid !== 0 && !names.has(gid));
75
+ const starts = [...new Set(run.clusters)].sort((a, b) => a - b);
76
+ const members = /* @__PURE__ */ new Map();
77
+ const clusterOf = [];
78
+ starts.forEach((c, k) => {
79
+ const all = [];
80
+ for (let s = k === 0 ? 0 : c; s < (starts[k + 1] ?? cps.length); s++) {
81
+ all.push(s);
82
+ clusterOf[s] = c;
83
+ }
84
+ members.set(c, all);
85
+ });
86
+ const carriers = cps.map(() => []);
87
+ const madeIn = /* @__PURE__ */ new Map();
88
+ run.sources.forEach((own, i) => {
89
+ for (const s of own) carriers[s]?.push(i);
90
+ if (!made[i]) return;
91
+ const had = madeIn.get(run.clusters[i]);
92
+ if (had) had.push(i);
93
+ else madeIn.set(run.clusters[i], [i]);
94
+ });
95
+ const unread = cps.map(partsOf);
96
+ const take = (parts, from, late) => {
97
+ const pool = from.map((s) => [...unread[s]]);
98
+ for (const part of parts) {
99
+ let q = -1;
100
+ pool.forEach((have, at) => {
101
+ if (have.includes(part) && (q < 0 || late)) q = at;
102
+ });
103
+ if (q < 0) return false;
104
+ pool[q].splice(pool[q].indexOf(part), 1);
105
+ }
106
+ from.forEach((s, at) => unread[s] = pool[at]);
107
+ return true;
108
+ };
109
+ const madeFrom = /* @__PURE__ */ new Set();
110
+ run.gids.forEach((gid, j) => {
111
+ const own = run.sources[j];
112
+ if (made[j]) for (const s of own) madeFrom.add(s);
113
+ const said = names.get(gid);
114
+ if (!said) return;
115
+ const late = own.some((s) => madeFrom.has(s));
116
+ if (said.some((name) => take(partsOf(name), own, late))) return;
117
+ const others = (members.get(run.clusters[j]) ?? []).filter((s) => !own.includes(s));
118
+ if (said.some((name) => take(partsOf(name), others, late))) return;
119
+ if (own.length > 1 && !said.some((name) => IGNORABLE_RE.test(String.fromCodePoint(name)))) for (const s of own) unread[s] = [];
120
+ });
121
+ const pieces = run.gids.map(() => []);
122
+ const rest = (s) => {
123
+ const left = unread[s];
124
+ if (left.length === partsOf(cps[s]).length) return String.fromCodePoint(cps[s]);
125
+ return String.fromCodePoint(...left).normalize("NFC");
126
+ };
127
+ cps.forEach((_, s) => {
128
+ const left = unread[s];
129
+ const mine = carriers[s].filter((i) => made[i]);
130
+ if (left.length === 0 || mine.length === 0) return;
131
+ if (mine.length === 1) pieces[mine[0]].push([s, rest(s)]);
132
+ else mine.forEach((i, k) => {
133
+ const part = k === mine.length - 1 ? left.slice(k) : left.slice(k, k + 1);
134
+ if (part.length > 0) pieces[i].push([s, String.fromCodePoint(...part).normalize("NFC")]);
135
+ });
136
+ unread[s] = [];
137
+ });
138
+ cps.forEach((cp, s) => {
139
+ if (unread[s].length === 0 || IGNORABLE_RE.test(String.fromCodePoint(cp))) return;
140
+ const near = madeIn.get(clusterOf[s]) ?? [];
141
+ const after = carriers[s][0] ?? -1;
142
+ const to = near.find((i) => pieces[i].length === 0) ?? near.find((i) => i > after) ?? near.at(-1);
143
+ if (to !== void 0) pieces[to].push([s, rest(s)]);
144
+ });
145
+ const out = /* @__PURE__ */ new Map();
146
+ run.gids.forEach((gid, i) => {
147
+ if (!made[i] || out.has(gid)) return;
148
+ const said = [...pieces[i].length > 0 ? pieces[i] : run.sources[i].map((s) => [s, String.fromCodePoint(cps[s])])].sort((a, b) => a[0] - b[0]).map(([, t]) => t).join("");
149
+ if (said !== "") out.set(gid, said);
150
+ });
151
+ return out;
152
+ }
4
153
  /**
5
154
  * Build a {@link FontMeasure} over a parsed font.
6
155
  *
@@ -21,20 +170,38 @@ function createFontMeasure(parsed, kern = true) {
21
170
  if (gid < 0 || gid >= parsed.numGlyphs) return 1e3;
22
171
  return widths[gid];
23
172
  };
173
+ const indicRuns = /* @__PURE__ */ new Map();
174
+ const shape = (text) => {
175
+ if (hasIndic(text)) {
176
+ let run = indicRuns.get(text);
177
+ if (run === void 0) {
178
+ if (indicRuns.size >= SHAPED_CACHE) indicRuns.clear();
179
+ run = shapeIndic(parsed, text, kern) ?? null;
180
+ indicRuns.set(text, run);
181
+ }
182
+ if (run) return {
183
+ gids: run.gids,
184
+ advances: run.advances,
185
+ indic: run
186
+ };
187
+ }
188
+ return shapeText(text, parsed.glyphForCodepoint, parsed.advanceWidths, parsed.ligatures, kerning, parsed.joiningForms);
189
+ };
24
190
  const textWidthPt = (text, fontSize) => {
25
- const shaped = shapeText(text, parsed.glyphForCodepoint, parsed.advanceWidths, parsed.ligatures, kerning, parsed.joiningForms);
191
+ const shaped = shape(text);
26
192
  let totalEm = 0;
27
193
  for (const a of shaped.advances) totalEm += a;
28
194
  return totalEm * fontSize / parsed.unitsPerEm;
29
195
  };
30
196
  const encodeTextAsCidHex = (text) => {
31
- const shaped = shapeText(text, parsed.glyphForCodepoint, parsed.advanceWidths, parsed.ligatures, kerning, parsed.joiningForms);
197
+ const shaped = shape(text);
32
198
  let out = "";
33
199
  for (const gid of shaped.gids) out += gid.toString(16).padStart(4, "0").toUpperCase();
34
200
  return out;
35
201
  };
36
- const showText = (text) => {
37
- const shaped = shapeText(text, parsed.glyphForCodepoint, parsed.advanceWidths, parsed.ligatures, kerning, parsed.joiningForms);
202
+ const showText = (text, fontSizePt, clusterSpacingPt, before) => {
203
+ const shaped = shape(text);
204
+ if (shaped.indic) return showPlaced(shaped.indic, fontSizePt, clusterSpacingPt ?? 0, before);
38
205
  const parts = [];
39
206
  let hex = "";
40
207
  let kerned = false;
@@ -52,12 +219,80 @@ function createFontMeasure(parsed, kern = true) {
52
219
  if (hex !== "") parts.push(`<${hex}>`);
53
220
  return `[${parts.join(" ")}] TJ`;
54
221
  };
222
+ const showPlaced = (run, fontSizePt, spacingPt, before) => {
223
+ const upem = parsed.unitsPerEm;
224
+ const ops = [];
225
+ let parts = [];
226
+ let hex = "";
227
+ let shift = 0;
228
+ let rise = 0;
229
+ const flushShift = () => {
230
+ if (shift === 0) return;
231
+ if (hex !== "") parts.push(`<${hex}>`);
232
+ hex = "";
233
+ parts.push(String(Number(shift.toFixed(2))));
234
+ shift = 0;
235
+ };
236
+ const flushShow = () => {
237
+ if (hex !== "") parts.push(`<${hex}>`);
238
+ hex = "";
239
+ if (parts.length === 1 && parts[0].startsWith("<")) ops.push(`${parts[0]} Tj`);
240
+ else if (parts.length > 0) ops.push(`[${parts.join(" ")}] TJ`);
241
+ parts = [];
242
+ };
243
+ const move = (units) => {
244
+ shift -= units * 1e3 / upem;
245
+ };
246
+ for (let i = 0; i < run.gids.length; i++) {
247
+ const gid = run.gids[i];
248
+ const dx = run.xOffsets[i] ?? 0;
249
+ const dy = fontSizePt === void 0 ? 0 : (run.yOffsets[i] ?? 0) * fontSizePt / upem;
250
+ const op = before?.get(i);
251
+ if (op !== void 0) {
252
+ flushShift();
253
+ flushShow();
254
+ ops.push(op);
255
+ }
256
+ if (dy !== rise) {
257
+ flushShift();
258
+ flushShow();
259
+ ops.push(`${String(Number(dy.toFixed(3)))} Ts`);
260
+ rise = dy;
261
+ }
262
+ move(dx);
263
+ flushShift();
264
+ hex += gid.toString(16).padStart(4, "0").toUpperCase();
265
+ move((run.advances[i] ?? 0) - (parsed.advanceWidths[gid] ?? 0) - dx);
266
+ if (run.clusters[i + 1] !== run.clusters[i] && spacingPt !== 0 && fontSizePt) shift -= spacingPt * 1e3 / fontSizePt;
267
+ }
268
+ flushShift();
269
+ flushShow();
270
+ if (rise !== 0) ops.push("0 Ts");
271
+ return ops.join(" ");
272
+ };
55
273
  const glyphRun = (text) => {
56
- const shaped = shapeText(text, parsed.glyphForCodepoint, parsed.advanceWidths, parsed.ligatures, kerning, parsed.joiningForms);
57
- return {
274
+ const shaped = shape(text);
275
+ const run = shaped.indic;
276
+ if (!run) return {
58
277
  gids: shaped.gids,
59
278
  advances: shaped.advances
60
279
  };
280
+ return {
281
+ gids: run.gids,
282
+ advances: run.advances,
283
+ xOffsets: run.xOffsets,
284
+ yOffsets: run.yOffsets,
285
+ clusterEnds: run.clusters.map((c, i) => run.clusters[i + 1] !== c)
286
+ };
287
+ };
288
+ const clustered = (text) => shape(text).indic !== void 0;
289
+ const glyphSources = (text) => {
290
+ const run = shape(text).indic;
291
+ return run?.sources.map((s, i) => s[0] ?? run.clusters[i]);
292
+ };
293
+ const spacingSlots = (text) => {
294
+ const run = shape(text).indic;
295
+ return run ? new Set(run.clusters).size : [...text].length;
61
296
  };
62
297
  const glyf = parsed.tables.get("glyf");
63
298
  const glyphInk = (gid) => {
@@ -75,15 +310,16 @@ function createFontMeasure(parsed, kern = true) {
75
310
  };
76
311
  };
77
312
  const textInkPt = (text, fontSize) => {
78
- const shaped = shapeText(text, parsed.glyphForCodepoint, parsed.advanceWidths, parsed.ligatures, kerning, parsed.joiningForms);
313
+ const shaped = shape(text);
79
314
  let yMax = -Infinity;
80
315
  let yMin = Infinity;
81
- for (const gid of shaped.gids) {
316
+ shaped.gids.forEach((gid, i) => {
82
317
  const ink = glyphInk(gid);
83
- if (!ink) continue;
84
- if (ink.yMax > yMax) yMax = ink.yMax;
85
- if (ink.yMin < yMin) yMin = ink.yMin;
86
- }
318
+ if (!ink) return;
319
+ const dy = shaped.indic?.yOffsets[i] ?? 0;
320
+ if (ink.yMax + dy > yMax) yMax = ink.yMax + dy;
321
+ if (ink.yMin + dy < yMin) yMin = ink.yMin + dy;
322
+ });
87
323
  if (!Number.isFinite(yMax) || !Number.isFinite(yMin)) {
88
324
  yMax = parsed.capHeight || parsed.ascender;
89
325
  yMin = 0;
@@ -100,8 +336,11 @@ function createFontMeasure(parsed, kern = true) {
100
336
  textInkPt,
101
337
  encodeTextAsCidHex,
102
338
  showText,
103
- glyphRun
339
+ glyphRun,
340
+ clustered,
341
+ glyphSources,
342
+ spacingSlots
104
343
  };
105
344
  }
106
345
  //#endregion
107
- export { createFontMeasure };
346
+ export { createFontMeasure, shapeGlyphs };
@@ -0,0 +1,214 @@
1
+ import { ParsedTtf } from './ttf-parser.js';
2
+ /** One glyph in the buffer the lookups run over. */
3
+ export interface OtlGlyph {
4
+ /** The glyph id. */
5
+ gid: number;
6
+ /** Index of the first character it was made from; a ligature takes the least. */
7
+ cluster: number;
8
+ /** The features that may act on it, one bit each (see {@link OtlStep.mask}). */
9
+ mask: number;
10
+ /** Its GDEF class: 0 unclassified, 1 base, 2 ligature, 3 mark, 4 component. */
11
+ cls: number;
12
+ /** The syllable it belongs to, or 0; a per-syllable lookup matches within one. */
13
+ syllable: number;
14
+ /** A join control, which a match may step over (see {@link OtlStep}). */
15
+ joiner?: 'zwj' | 'zwnj';
16
+ /** The ligature it is, or the ligature a mark inside it was skipped by; 0 none. */
17
+ ligId: number;
18
+ /** For a mark skipped inside a ligature: the component it followed, from 1. */
19
+ ligComp: number;
20
+ /**
21
+ * What substitution made it, as {@link SUBSTITUTED}, {@link LIGATED} and
22
+ * {@link MULTIPLIED} bits: a shaper asks whether a reph or a pre-base form
23
+ * actually formed. Absent ⇒ none.
24
+ */
25
+ props?: number;
26
+ /**
27
+ * The characters it stands for, as indices into the text, least first. A
28
+ * ligature stands for the characters of its components, and each glyph of
29
+ * a multiple substitution for those of the glyph it replaced; a reader of
30
+ * the page is told the text of a glyph no character maps to from this.
31
+ * Absent where the shaper does not keep it.
32
+ */
33
+ sources?: ReadonlyArray<number>;
34
+ }
35
+ /** {@link OtlGlyph.props}: a GSUB lookup replaced the glyph. */
36
+ export declare const SUBSTITUTED = 1;
37
+ /** {@link OtlGlyph.props}: …by a ligature, last of all. */
38
+ export declare const LIGATED = 2;
39
+ /** {@link OtlGlyph.props}: …by a multiple substitution, last of all. */
40
+ export declare const MULTIPLIED = 4;
41
+ /** Where a glyph is drawn, in font units: its advance, and its offset off the pen. */
42
+ export interface OtlPosition {
43
+ xAdvance: number;
44
+ yAdvance: number;
45
+ xOffset: number;
46
+ yOffset: number;
47
+ /** The glyph it hangs on, as the distance to it in the buffer; 0 none. */
48
+ attachChain: number;
49
+ /** How it hangs there: 1 a mark on its anchor, 2 a cursive join. */
50
+ attachType: 0 | 1 | 2;
51
+ }
52
+ /** One lookup to run, with the glyphs it may act on and how it matches. */
53
+ export interface OtlStep {
54
+ /** Its index in the table's LookupList. */
55
+ readonly lookup: number;
56
+ /** It acts on a glyph whose mask shares a bit with this one. */
57
+ readonly mask: number;
58
+ /** A ZWJ between the glyphs of a match is stepped over. */
59
+ readonly autoZwj: boolean;
60
+ /** A ZWNJ between the glyphs of a context is stepped over. */
61
+ readonly autoZwnj: boolean;
62
+ /** A match keeps to the syllable of the glyph it starts on. */
63
+ readonly perSyllable: boolean;
64
+ }
65
+ /** A feature a shaper asks for: its tag, and how its lookups are to run. */
66
+ export interface OtlFeature {
67
+ readonly tag: string;
68
+ readonly mask: number;
69
+ /** The font's own rules decide what a ZWJ does, so a match does not skip it. */
70
+ readonly manualZwj?: boolean;
71
+ /** …and what a ZWNJ does. */
72
+ readonly manualZwnj?: boolean;
73
+ readonly perSyllable?: boolean;
74
+ }
75
+ interface LangSys {
76
+ readonly required: number;
77
+ readonly features: ReadonlyArray<number>;
78
+ }
79
+ interface Script {
80
+ readonly defaultLangSys?: LangSys;
81
+ readonly langSys: ReadonlyMap<string, LangSys>;
82
+ }
83
+ interface Lookup {
84
+ /** The type of its subtables, with an Extension unwrapped. */
85
+ readonly type: number;
86
+ readonly flag: number;
87
+ readonly markSet: number;
88
+ /** Absolute offsets of its subtables, an Extension's target in its place. */
89
+ readonly subtables: ReadonlyArray<number>;
90
+ }
91
+ /** A GSUB or GPOS table, read as far as its lists. */
92
+ export interface LayoutTable {
93
+ readonly kind: 'GSUB' | 'GPOS';
94
+ readonly scripts: ReadonlyMap<string, Script>;
95
+ readonly features: ReadonlyArray<{
96
+ readonly tag: string;
97
+ readonly lookups: ReadonlyArray<number>;
98
+ }>;
99
+ readonly lookups: ReadonlyArray<Lookup>;
100
+ }
101
+ /**
102
+ * A face's layout tables, read once: GDEF's classes and the lists of GSUB and
103
+ * GPOS, with each coverage table and class definition read the first time a
104
+ * lookup asks for it.
105
+ */
106
+ export declare class OtlFace {
107
+ readonly parsed: ParsedTtf;
108
+ private static readonly cache;
109
+ /** The face's GSUB, when it has one that reads. */
110
+ readonly gsub?: LayoutTable;
111
+ /** …and its GPOS. */
112
+ readonly gpos?: LayoutTable;
113
+ private readonly raw;
114
+ private readonly glyphClasses;
115
+ private readonly attachClasses;
116
+ private readonly markSets;
117
+ private readonly coverages;
118
+ private readonly classDefs;
119
+ private constructor();
120
+ /**
121
+ * The layout tables of a face, read on first use and kept with it.
122
+ *
123
+ * @param parsed The face.
124
+ * @returns Its {@link OtlFace}.
125
+ */
126
+ static of(parsed: ParsedTtf): OtlFace;
127
+ /** A glyph's GDEF class: 1 base, 2 ligature, 3 mark, 4 component, 0 none. */
128
+ glyphClass(gid: number): number;
129
+ /** A mark's attachment class, which a lookup's flag may select. */
130
+ attachClass(gid: number): number;
131
+ /** Whether a glyph is in one of GDEF's mark glyph sets. */
132
+ inMarkSet(set: number, gid: number): boolean;
133
+ /** @internal The coverage table at an absolute offset: glyph → coverage index. */
134
+ coverage(off: number): ReadonlyMap<number, number>;
135
+ /** @internal The class definition at an absolute offset: glyph → class, 0 unlisted. */
136
+ classDef(off: number): ReadonlyMap<number, number>;
137
+ /** @internal The raw bytes of the face. */
138
+ get bytes(): Uint8Array;
139
+ private readTable;
140
+ }
141
+ /**
142
+ * The script a table answers for, of the tags a shaper prefers: the first the
143
+ * table has, else its default script, else Latin.
144
+ *
145
+ * @param table The GSUB or GPOS table.
146
+ * @param scriptTags The tags, most preferred first (`dev2`, then `deva`).
147
+ * @returns The tag found, or `undefined` when the table has none of them.
148
+ */
149
+ export declare function pickScript(table: LayoutTable | undefined, scriptTags: ReadonlyArray<string>): string | undefined;
150
+ /**
151
+ * The lookups a set of features runs, merged and in LookupList order — the
152
+ * order they apply in, whichever feature named them.
153
+ *
154
+ * A lookup two of the features name runs once, on the glyphs either feature
155
+ * may act on. The language system's required feature, if it has one, runs on
156
+ * every glyph.
157
+ *
158
+ * @param table The GSUB or GPOS table.
159
+ * @param scriptTags The script tags to look for, most preferred first.
160
+ * @param features The features, with the glyphs each acts on.
161
+ * @param language A language system tag; the script's default otherwise.
162
+ * @returns The steps to run, in order.
163
+ */
164
+ export declare function planLookups(table: LayoutTable | undefined, scriptTags: ReadonlyArray<string>, features: ReadonlyArray<OtlFeature>, language?: string): Array<OtlStep>;
165
+ /**
166
+ * Run a stage of GSUB lookups over the buffer, each over the whole of it
167
+ * before the next begins.
168
+ *
169
+ * @param face The face.
170
+ * @param steps The lookups, from {@link planLookups} over its GSUB.
171
+ * @param buf The glyphs; substituted in place, and may grow or shrink.
172
+ */
173
+ export declare function substitute<TGlyph extends OtlGlyph>(face: OtlFace, steps: ReadonlyArray<OtlStep>, buf: Array<TGlyph>): void;
174
+ /**
175
+ * Position the buffer: each glyph's own advance, then a stage of GPOS lookups,
176
+ * then every mark and cursive join carried along with the glyph it hangs on.
177
+ *
178
+ * @param face The face.
179
+ * @param steps The lookups, from {@link planLookups} over its GPOS.
180
+ * @param buf The glyphs, after substitution.
181
+ * @returns A position per glyph, in font units, y up.
182
+ */
183
+ export declare function position<TGlyph extends OtlGlyph>(face: OtlFace, steps: ReadonlyArray<OtlStep>, buf: Array<TGlyph>): Array<OtlPosition>;
184
+ /**
185
+ * Whether a feature's lookups would substitute this exact sequence of glyphs
186
+ * — the question a shaper asks before it decides that a consonant takes its
187
+ * below-base form, or that a Ra starts a reph.
188
+ *
189
+ * @param face The face.
190
+ * @param steps The feature's lookups, from {@link planLookups}.
191
+ * @param gids The glyphs, in order.
192
+ * @param zeroContext Only a rule with no backtrack and no lookahead counts.
193
+ * @returns `true` when one of them would.
194
+ */
195
+ export declare function wouldSubstitute(face: OtlFace, steps: ReadonlyArray<OtlStep>, gids: ReadonlyArray<number>, zeroContext: boolean): boolean;
196
+ /**
197
+ * Make `buf[start..end)` one cluster, the least of theirs. A cluster cut at
198
+ * either edge comes in whole, as HarfBuzz merges them: a vowel sign that was
199
+ * one cluster with the last letter of a conjunct is one with the conjunct.
200
+ *
201
+ * @param buf The glyphs.
202
+ * @param start The first glyph of the stretch.
203
+ * @param end The glyph after its last.
204
+ */
205
+ export declare function mergeClusters(buf: Array<OtlGlyph>, start: number, end: number): void;
206
+ /**
207
+ * The characters a run of glyphs stands for together, each once, least first;
208
+ * `undefined` when none of them keeps its characters.
209
+ *
210
+ * @param glyphs The glyphs.
211
+ * @returns Their sources, merged.
212
+ */
213
+ export declare function joinSources(glyphs: ReadonlyArray<OtlGlyph>): ReadonlyArray<number> | undefined;
214
+ export {};