@mark1russell7/typeset 0.1.2 → 0.1.3

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.
@@ -22,6 +22,15 @@ interface GlyphInfo {
22
22
  /** The code point of the character of the glyph, after the normalization. */
23
23
  readonly codePoint: number;
24
24
  }
25
+ /** One substitution that a lookup can do, with no context. */
26
+ interface SubstitutionRule {
27
+ /** The glyph that the rule replaces. The first glyph of a ligature is this glyph. */
28
+ readonly from: number;
29
+ /** The glyphs that replace it. A multiple substitution can give no glyph. */
30
+ readonly to: readonly number[];
31
+ /** The other glyphs of a ligature, in order. Another rule has no such glyphs. */
32
+ readonly components?: readonly number[];
33
+ }
25
34
  /** A lookup of GSUB for one script and one set of features. */
26
35
  interface SubstitutionLookup {
27
36
  /** The index of the lookup in the lookup list of GSUB. */
@@ -49,6 +58,12 @@ interface SubstitutionLookup {
49
58
  * `hb_ot_layout_lookup_would_substitute` with zero context tells it. A context rule with backtrack or lookahead does not apply.
50
59
  */
51
60
  wouldApply(glyphs: readonly number[]): boolean;
61
+ /**
62
+ * This function gives the rules of the lookup, and the rules of each lookup that it nests in a context rule. Each lookup
63
+ * has one list, and the list of this lookup is the first. The lists do not tell a context, so each nested rule counts.
64
+ * An alternate substitution gives one rule for each alternate.
65
+ */
66
+ rules(): readonly (readonly SubstitutionRule[])[];
52
67
  }
53
68
  /** One axis of `fvar`, in the user units of the axis. */
54
69
  interface Axis {
@@ -80,6 +95,16 @@ interface PairLookup {
80
95
  readonly skip?: (glyph: number) => boolean;
81
96
  /** This function gives the value for a pair, or `undefined` when no subtable applies. */
82
97
  pair(first: number, second: number): PairValue | undefined;
98
+ /**
99
+ * This function gives the largest positive value of each glyph of a pair, over the pairs with two glyphs of `glyphs`.
100
+ * A value is at least 0. It uses the values of the instance, so it includes each variation delta.
101
+ */
102
+ largest(glyphs: ReadonlySet<number>): LargestKerning;
103
+ }
104
+ /** The largest positive kerning of the first glyph and of the second glyph of a pair, in font units. */
105
+ interface LargestKerning {
106
+ readonly first: number;
107
+ readonly second: number;
83
108
  }
84
109
  /** The options of `readFont`. */
85
110
  interface ReadOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"font.d.ts","names":[],"sources":["../../src/fonts/substitution.ts","../../src/fonts/variations.ts","../../src/fonts/kerning.ts","../../src/fonts/font.ts"],"mappings":";;;;;cAQa;;;;;UA6CI;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;WAES;;;UAIM;;WAEN;;WAEA;;WAEA;;WAEA;;;;;;EAMT,MAAM,kBAAkB,kBAAkB;;;;;;EAM1C,WAAW,kBAAkB,OAAO,aAAa,cAAc;;;;;EAK/D,WAAW;;;UC3FI;WACN;WACA;WACA;WACA;;;KAIC,WAAW,SAAS;;UCLf;WACN;WACA;;WAEA;;WAEA;;WAEA;;;UAIM;WACN;;;;;WAKA,QAAQ;;EAEjB,KAAK,eAAe,iBAAiB;;;UCbtB;;WAEN,aAAa;;;;;WAKb,WAAW;;;;;WAKX;;;;;WAKA;;;;;WAKA;;;KAIC;;UAGK;;;;;WAKN;WACA;;WAEA,eAAe;;WAEf,UAAU;;WAEV;;WAEA,eAAe;WACf;;EAET,MAAM;;EAEN,QAAQ;;EAER,WAAW;;EAEX,QAAQ,0BAA0B;;EAElC,cAAc,gBAAgB,uCAAuC;;EAErE,GAAG,UAAU,WAAW;;;iBAIV,SAAS,OAAO,YAAY,UAAS,cAAmB;;UA8JvD;;WAEN;;;;;WAKA;;WAEA;;;;;;WAMA;;;;;;WAMA;;;;;WAKA;;;;;WAKA;;;;;WAKA;;;;;;iBAYK,aAAa;;;;;;;iBAgDb,cAAc,MAAM,MAAM,cAAc,UAAS;EAAwB;EAAkB;;;;;;iBAkG3F,aAAa,MAAM,MAAM,cAAc,cAAc,UAAS"}
1
+ {"version":3,"file":"font.d.ts","names":[],"sources":["../../src/fonts/substitution.ts","../../src/fonts/variations.ts","../../src/fonts/kerning.ts","../../src/fonts/font.ts"],"mappings":";;;;;cAQa;;;;;UA6CI;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;WAES;;;UAIM;;WAEN;;WAEA;;WAEA;;;UAIM;;WAEN;;WAEA;;WAEA;;WAEA;;;;;;EAMT,MAAM,kBAAkB,kBAAkB;;;;;;EAM1C,WAAW,kBAAkB,OAAO,aAAa,cAAc;;;;;EAK/D,WAAW;;;;;;EAMX,4BAA4B;;;UC3Gb;WACN;WACA;WACA;WACA;;;KAIC,WAAW,SAAS;;UCLf;WACN;WACA;;WAEA;;WAEA;;WAEA;;;UAIM;WACN;;;;;WAKA,QAAQ;;EAEjB,KAAK,eAAe,iBAAiB;;;;;EAKrC,QAAQ,QAAQ,sBAAsB;;;UAIvB;WACN;WACA;;;UCxBM;;WAEN,aAAa;;;;;WAKb,WAAW;;;;;WAKX;;;;;WAKA;;;;;WAKA;;;KAIC;;UAGK;;;;;WAKN;WACA;;WAEA,eAAe;;WAEf,UAAU;;WAEV;;WAEA,eAAe;WACf;;EAET,MAAM;;EAEN,QAAQ;;EAER,WAAW;;EAEX,QAAQ,0BAA0B;;EAElC,cAAc,gBAAgB,uCAAuC;;EAErE,GAAG,UAAU,WAAW;;;iBAIV,SAAS,OAAO,YAAY,UAAS,cAAmB;;UA8JvD;;WAEN;;;;;WAKA;;WAEA;;;;;;WAMA;;;;;;WAMA;;;;;WAKA;;;;;WAKA;;;;;WAKA;;;;;;iBAYK,aAAa;;;;;;;iBAgEb,cAAc,MAAM,MAAM,cAAc,UAAS;EAAwB;EAAkB;;;;;;iBAkG3F,aAAa,MAAM,MAAM,cAAc,cAAc,UAAS"}
@@ -123,6 +123,27 @@ function coverageIndex(table, glyph) {
123
123
  }
124
124
  throw new Error(`the Coverage format ${format} is unknown`);
125
125
  }
126
+ /** This function gives each glyph of a Coverage table with its coverage index, in the order of the table. */
127
+ function coverageGlyphs(table) {
128
+ const format = table.u16(0);
129
+ const count = table.u16(2);
130
+ const glyphs = [];
131
+ if (format === 1) {
132
+ for (let k = 0; k < count; k++) glyphs.push([table.u16(4 + k * 2), k]);
133
+ return glyphs;
134
+ }
135
+ if (format === 2) {
136
+ for (let k = 0; k < count; k++) {
137
+ const at = 4 + k * 6;
138
+ const start = table.u16(at);
139
+ const end = table.u16(at + 2);
140
+ const index = table.u16(at + 4);
141
+ for (let glyph = start; glyph <= end; glyph++) glyphs.push([glyph, index + glyph - start]);
142
+ }
143
+ return glyphs;
144
+ }
145
+ throw new Error(`the Coverage format ${format} is unknown`);
146
+ }
126
147
  /** The GDEF glyph classes: 1 base, 2 ligature, 3 mark, 4 component. */
127
148
  function readGlyphClasses(gdef) {
128
149
  if (gdef === void 0) return () => 0;
@@ -168,7 +189,7 @@ function pairValue(first, second, skipSecond) {
168
189
  skipSecond
169
190
  };
170
191
  }
171
- /** This function makes the pair function of one PairPos subtable. */
192
+ /** This function makes the functions of one PairPos subtable. */
172
193
  function pairSubtable(table, store) {
173
194
  const format = table.u16(0);
174
195
  const coverage = table.part(table.u16(2));
@@ -177,28 +198,53 @@ function pairSubtable(table, store) {
177
198
  const size1 = valueSize(format1);
178
199
  const size2 = valueSize(format2);
179
200
  const skipSecond = format2 !== 0;
180
- if (format === 1) return (first, second) => {
181
- const index = coverageIndex(coverage, first);
182
- if (index < 0) return void 0;
183
- const set = table.part(table.u16(10 + index * 2));
201
+ if (format === 1) {
184
202
  const recordSize = 2 + size1 + size2;
185
- let low = 0;
186
- let high = set.u16(0) - 1;
187
- while (low <= high) {
188
- const middle = low + high >> 1;
189
- const at = 2 + middle * recordSize;
190
- const glyph = set.u16(at);
191
- if (second < glyph) high = middle - 1;
192
- else if (second > glyph) low = middle + 1;
193
- else return pairValue(xAdvance(set, at + 2, format1, set, store), xAdvance(set, at + 2 + size1, format2, set, store), skipSecond);
194
- }
195
- };
203
+ const pair = (first, second) => {
204
+ const index = coverageIndex(coverage, first);
205
+ if (index < 0) return void 0;
206
+ const set = table.part(table.u16(10 + index * 2));
207
+ let low = 0;
208
+ let high = set.u16(0) - 1;
209
+ while (low <= high) {
210
+ const middle = low + high >> 1;
211
+ const at = 2 + middle * recordSize;
212
+ const glyph = set.u16(at);
213
+ if (second < glyph) high = middle - 1;
214
+ else if (second > glyph) low = middle + 1;
215
+ else return pairValue(xAdvance(set, at + 2, format1, set, store), xAdvance(set, at + 2 + size1, format2, set, store), skipSecond);
216
+ }
217
+ };
218
+ const largest = (glyphs) => {
219
+ let first = 0;
220
+ let second = 0;
221
+ for (const glyph of glyphs) {
222
+ const index = coverageIndex(coverage, glyph);
223
+ if (index < 0) continue;
224
+ const set = table.part(table.u16(10 + index * 2));
225
+ for (let record = 0; record < set.u16(0); record++) {
226
+ const at = 2 + record * recordSize;
227
+ if (!glyphs.has(set.u16(at))) continue;
228
+ first = Math.max(first, xAdvance(set, at + 2, format1, set, store).value);
229
+ second = Math.max(second, xAdvance(set, at + 2 + size1, format2, set, store).value);
230
+ }
231
+ }
232
+ return {
233
+ first,
234
+ second
235
+ };
236
+ };
237
+ return {
238
+ pair,
239
+ largest
240
+ };
241
+ }
196
242
  if (format === 2) {
197
243
  const classes1 = table.part(table.u16(8));
198
244
  const classes2 = table.part(table.u16(10));
199
245
  const count1 = table.u16(12);
200
246
  const count2 = table.u16(14);
201
- return (first, second) => {
247
+ const pair = (first, second) => {
202
248
  if (coverageIndex(coverage, first) < 0) return void 0;
203
249
  const class1 = classOf(classes1, first);
204
250
  const class2 = classOf(classes2, second);
@@ -206,6 +252,30 @@ function pairSubtable(table, store) {
206
252
  const at = 16 + (class1 * count2 + class2) * (size1 + size2);
207
253
  return pairValue(xAdvance(table, at, format1, table, store), xAdvance(table, at + size1, format2, table, store), skipSecond);
208
254
  };
255
+ const largest = (glyphs) => {
256
+ const firsts = /* @__PURE__ */ new Set();
257
+ const seconds = /* @__PURE__ */ new Set();
258
+ for (const glyph of glyphs) {
259
+ if (coverageIndex(coverage, glyph) >= 0) firsts.add(classOf(classes1, glyph));
260
+ seconds.add(classOf(classes2, glyph));
261
+ }
262
+ let first = 0;
263
+ let second = 0;
264
+ for (const class1 of firsts) for (const class2 of seconds) {
265
+ if (class1 >= count1 || class2 >= count2) continue;
266
+ const at = 16 + (class1 * count2 + class2) * (size1 + size2);
267
+ first = Math.max(first, xAdvance(table, at, format1, table, store).value);
268
+ second = Math.max(second, xAdvance(table, at + size1, format2, table, store).value);
269
+ }
270
+ return {
271
+ first,
272
+ second
273
+ };
274
+ };
275
+ return {
276
+ pair,
277
+ largest
278
+ };
209
279
  }
210
280
  throw new Error(`the PairPos format ${format} is unknown`);
211
281
  }
@@ -278,9 +348,16 @@ function readGposKerning(gpos, script, store, filter) {
278
348
  ...skip === void 0 ? {} : { skip },
279
349
  pair(first, second) {
280
350
  for (const subtable of subtables) {
281
- const value = subtable(first, second);
351
+ const value = subtable.pair(first, second);
282
352
  if (value !== void 0) return value;
283
353
  }
354
+ },
355
+ largest(glyphs) {
356
+ const values = subtables.map((subtable) => subtable.largest(glyphs));
357
+ return {
358
+ first: Math.max(...values.map((value) => value.first)),
359
+ second: Math.max(...values.map((value) => value.second))
360
+ };
284
361
  }
285
362
  }];
286
363
  });
@@ -313,6 +390,14 @@ function readKernTable(kern) {
313
390
  second: 0,
314
391
  skipSecond: false
315
392
  };
393
+ },
394
+ largest(glyphs) {
395
+ let first = 0;
396
+ for (const [key, value] of pairs) if (glyphs.has(Math.floor(key / 65536)) && glyphs.has(key % 65536)) first = Math.max(first, value);
397
+ return {
398
+ first,
399
+ second: 0
400
+ };
316
401
  }
317
402
  }];
318
403
  }
@@ -540,6 +625,81 @@ function records(table, at, count) {
540
625
  lookupIndex: table.u16(at + k * 4 + 2)
541
626
  }));
542
627
  }
628
+ /** This function gives each rule table of the rule sets at `at` of a context subtable of format 1 or 2. An offset 0 is an empty set. */
629
+ function ruleTables(t, at) {
630
+ const tables = [];
631
+ for (let s = 0; s < t.u16(at); s++) {
632
+ const offset = t.u16(at + 2 + s * 2);
633
+ if (offset === 0) continue;
634
+ const set = t.part(offset);
635
+ for (let r = 0; r < set.u16(0); r++) tables.push(set.part(set.u16(2 + r * 2)));
636
+ }
637
+ return tables;
638
+ }
639
+ /** This function gives the records of each rule of a context subtable (lookup type 5). */
640
+ function contextRecords(t) {
641
+ const format = t.u16(0);
642
+ if (format === 3) return records(t, 6 + t.u16(2) * 2, t.u16(4));
643
+ return ruleTables(t, format === 2 ? 6 : 4).flatMap((rule) => records(rule, 4 + (rule.u16(0) - 1) * 2, rule.u16(2)));
644
+ }
645
+ /** This function gives the records of each rule of a chained context subtable (lookup type 6). */
646
+ function chainRecords(t) {
647
+ const format = t.u16(0);
648
+ if (format === 3) {
649
+ const inputAt = 4 + t.u16(2) * 2;
650
+ const aheadAt = inputAt + 2 + t.u16(inputAt) * 2;
651
+ const recordsAt = aheadAt + 2 + t.u16(aheadAt) * 2;
652
+ return records(t, recordsAt + 2, t.u16(recordsAt));
653
+ }
654
+ return ruleTables(t, format === 2 ? 10 : 4).flatMap((rule) => {
655
+ const inputAt = 2 + rule.u16(0) * 2;
656
+ const aheadAt = inputAt + 2 + Math.max(0, rule.u16(inputAt) - 1) * 2;
657
+ const recordsAt = aheadAt + 2 + rule.u16(aheadAt) * 2;
658
+ return records(rule, recordsAt + 2, rule.u16(recordsAt));
659
+ });
660
+ }
661
+ /** This function adds the rules of one subtable of a lookup type to `rules`, and each nested lookup to `nested`. */
662
+ function readRules(type, t, rules, nested) {
663
+ if (type === 5 || type === 6) {
664
+ for (const record of type === 5 ? contextRecords(t) : chainRecords(t)) nested.push(record.lookupIndex);
665
+ return;
666
+ }
667
+ if (type < 1 || type > 8 || type === 7) return;
668
+ const format = t.u16(0);
669
+ for (const [from, index] of coverageGlyphs(t.part(t.u16(2)))) if (type === 1) rules.push({
670
+ from,
671
+ to: [format === 1 ? from + t.i16(4) & 65535 : t.u16(6 + index * 2)]
672
+ });
673
+ else if (type === 2) {
674
+ const sequence = t.part(t.u16(6 + index * 2));
675
+ rules.push({
676
+ from,
677
+ to: Array.from({ length: sequence.u16(0) }, (_, k) => sequence.u16(2 + k * 2))
678
+ });
679
+ } else if (type === 3) {
680
+ const set = t.part(t.u16(6 + index * 2));
681
+ for (let k = 0; k < set.u16(0); k++) rules.push({
682
+ from,
683
+ to: [set.u16(2 + k * 2)]
684
+ });
685
+ } else if (type === 4) {
686
+ const set = t.part(t.u16(6 + index * 2));
687
+ for (let l = 0; l < set.u16(0); l++) {
688
+ const ligature = set.part(set.u16(2 + l * 2));
689
+ rules.push({
690
+ from,
691
+ to: [ligature.u16(0)],
692
+ components: Array.from({ length: ligature.u16(2) - 1 }, (_, k) => ligature.u16(4 + k * 2))
693
+ });
694
+ }
695
+ } else {
696
+ const aheadAt = 6 + t.u16(4) * 2;
697
+ rules.push({
698
+ from,
699
+ to: [t.u16(aheadAt + 2 + t.u16(aheadAt) * 2 + 2 + index * 2)]
700
+ });
701
+ }
702
+ }
543
703
  /** The lookups of one GSUB table. It reads each lookup at its first use. */
544
704
  var Gsub = class {
545
705
  lookups;
@@ -800,6 +960,23 @@ var Gsub = class {
800
960
  return false;
801
961
  });
802
962
  }
963
+ /** This function gives the rules of a lookup and of each lookup that it nests, as `rules` of `SubstitutionLookup` tells. */
964
+ rulesOf(index) {
965
+ const lists = [];
966
+ const seen = /* @__PURE__ */ new Set();
967
+ const visit = (at) => {
968
+ const lookup = this.lookup(at);
969
+ if (seen.has(at) || lookup === void 0) return;
970
+ seen.add(at);
971
+ const rules = [];
972
+ const nested = [];
973
+ for (const subtable of lookup.subtables) readRules(lookup.type, subtable, rules, nested);
974
+ lists.push(rules);
975
+ nested.forEach(visit);
976
+ };
977
+ visit(index);
978
+ return lists;
979
+ }
803
980
  /** This function applies one subtable at `i`. It gives the next position, or -1 when the subtable does not match. */
804
981
  subtable(lookup, t, glyphs, i, depth) {
805
982
  const glyph = glyphs[i];
@@ -977,7 +1154,8 @@ function readSubstitutions(gsub, script, features, filter) {
977
1154
  features: tags,
978
1155
  apply: (glyphs, masks, mask) => table.applyAll(index, glyphs, masks, mask),
979
1156
  applyInfos: (glyphs, infos, mask, perSyllable) => table.applyRun(index, glyphs, infos, mask, perSyllable),
980
- wouldApply: (glyphs) => table.wouldApply(index, glyphs)
1157
+ wouldApply: (glyphs) => table.wouldApply(index, glyphs),
1158
+ rules: () => table.rulesOf(index)
981
1159
  });
982
1160
  }
983
1161
  return result;
@@ -1717,17 +1895,7 @@ function vowelConstraints(codePoints) {
1717
1895
  */
1718
1896
  function normalize(codePoints, hasGlyph) {
1719
1897
  const allSimple = !codePoints.some((codePoint, i) => i > 0 && isMark(codePoint));
1720
- const decomposed = [];
1721
- for (const codePoint of codePoints) {
1722
- const parts = codePoint === 2353 ? [codePoint] : [...String.fromCodePoint(codePoint).normalize("NFD")].map((part) => part.codePointAt(0));
1723
- if (parts.length > 1 || parts[0] !== codePoint) {
1724
- if (parts.every(hasGlyph)) {
1725
- decomposed.push(...parts);
1726
- continue;
1727
- }
1728
- }
1729
- decomposed.push(codePoint);
1730
- }
1898
+ const decomposed = codePoints.flatMap((codePoint) => decomposition(codePoint, hasGlyph));
1731
1899
  if (allSimple) return decomposed;
1732
1900
  for (let i = 1; i < decomposed.length; i++) for (let j = i; j > 0; j--) {
1733
1901
  const before = combiningClass(decomposed[j - 1]);
@@ -1751,6 +1919,38 @@ function normalize(codePoints, hasGlyph) {
1751
1919
  }
1752
1920
  return out;
1753
1921
  }
1922
+ /** This function gives the code points of a character after the decomposition of `normalize`. */
1923
+ function decomposition(codePoint, hasGlyph) {
1924
+ const parts = codePoint === 2353 ? [codePoint] : [...String.fromCodePoint(codePoint).normalize("NFD")].map((part) => part.codePointAt(0));
1925
+ return (parts.length > 1 || parts[0] !== codePoint) && parts.every(hasGlyph) ? parts : [codePoint];
1926
+ }
1927
+ /**
1928
+ * This function gives the code points that the normalization can give for the characters `characters`. A composite is
1929
+ * not always in the set: a nukta that follows U+0928 gives U+0929. The function repeats until no new composite appears.
1930
+ */
1931
+ function indicForms(characters, hasGlyph) {
1932
+ const parts = new Map([...characters].map((codePoint) => [codePoint, decomposition(codePoint, hasGlyph)]));
1933
+ const codePoints = new Set([...parts.values()].flat());
1934
+ for (const codePoint of characters) codePoints.add(codePoint);
1935
+ codePoints.add(9676);
1936
+ const composites = /* @__PURE__ */ new Map();
1937
+ for (let size = 0; size !== codePoints.size;) {
1938
+ size = codePoints.size;
1939
+ const marks = [...codePoints].filter(isMark);
1940
+ for (const starter of codePoints) for (const mark of marks) {
1941
+ const composed = compose(starter, mark);
1942
+ if (composed === void 0 || !hasGlyph(composed)) continue;
1943
+ const known = composites.get(starter) ?? [];
1944
+ if (!known.includes(composed)) composites.set(starter, [...known, composed]);
1945
+ codePoints.add(composed);
1946
+ }
1947
+ }
1948
+ return {
1949
+ parts,
1950
+ composites,
1951
+ codePoints
1952
+ };
1953
+ }
1754
1954
  /** This function gives the canonical composite of two code points, or `undefined`. A mark does not compose with a mark. */
1755
1955
  function compose(first, second) {
1756
1956
  if (isMark(first)) return void 0;
@@ -1869,6 +2069,24 @@ const finalFeatures = [
1869
2069
  "psts",
1870
2070
  "haln"
1871
2071
  ];
2072
+ /** This function gives the features of the page that the last stage applies. */
2073
+ function pageFeatures(features) {
2074
+ return features.filter((tag) => ![
2075
+ "ccmp",
2076
+ "locl",
2077
+ "liga"
2078
+ ].includes(tag) && !finalFeatures.includes(tag));
2079
+ }
2080
+ /** This function gives all the features that `indicShaper` applies for the features `features` of the page. */
2081
+ function indicFeatures(features) {
2082
+ return [
2083
+ "locl",
2084
+ "ccmp",
2085
+ ...basicFeatures.map(([tag]) => tag),
2086
+ ...finalFeatures,
2087
+ ...pageFeatures(features)
2088
+ ];
2089
+ }
1872
2090
  /** The mask bit of the global features. Each glyph has it. */
1873
2091
  const GLOBAL = 1;
1874
2092
  /** The mask bits of the features that apply only to some glyphs of a syllable. */
@@ -1940,12 +2158,7 @@ function indicShaper(font, script, features) {
1940
2158
  lookups: lookupsOf([tag]),
1941
2159
  mask: global ? GLOBAL : featureBits.get(tag)
1942
2160
  }));
1943
- const extra = features.filter((tag) => ![
1944
- "ccmp",
1945
- "locl",
1946
- "liga"
1947
- ].includes(tag) && !finalFeatures.includes(tag));
1948
- const last = lookupsOf([...finalFeatures, ...extra]).map((lookup) => ({
2161
+ const last = lookupsOf([...finalFeatures, ...pageFeatures(features)]).map((lookup) => ({
1949
2162
  lookup,
1950
2163
  mask: lookup.features.reduce((mask, tag) => mask | (tag === "init" ? initMask : GLOBAL), 0),
1951
2164
  perSyllable: lookup.features.some((tag) => finalFeatures.includes(tag))
@@ -3401,16 +3614,32 @@ function shapingWords(text) {
3401
3614
  if (word !== "") words.push(word);
3402
3615
  return words;
3403
3616
  }
3404
- /** The GSUB pass of one set of options: a function that gives the glyphs of one word, and the kerning lookups. */
3405
- function shaperOf(font, options) {
3617
+ /** This function gives the choices of a set of options: the script, the features, the shaper and the kerning lookups. */
3618
+ function shapingOf(font, options) {
3406
3619
  const script = options.script ?? "DFLT";
3407
3620
  const features = options.features ?? defaultFeatures;
3408
3621
  const kerning = options.kerning === false ? [] : font.kerning(script);
3409
3622
  if (options.indic !== false && indicScripts.includes(script)) return {
3623
+ script,
3624
+ features,
3625
+ shaper: "indic",
3626
+ kerning
3627
+ };
3628
+ return {
3629
+ script,
3630
+ features,
3631
+ shaper: options.joining !== false && joiningScripts.includes(script) ? "joining" : "plain",
3632
+ kerning
3633
+ };
3634
+ }
3635
+ /** The GSUB pass of one set of options: a function that gives the glyphs of one word, and the kerning lookups. */
3636
+ function shaperOf(font, options) {
3637
+ const { script, features, shaper, kerning } = shapingOf(font, options);
3638
+ if (shaper === "indic") return {
3410
3639
  substitute: indicShaper(font, script, features),
3411
3640
  kerning
3412
3641
  };
3413
- const joins = options.joining !== false && joiningScripts.includes(script);
3642
+ const joins = shaper === "joining";
3414
3643
  const lookups = joins ? [] : font.substitutions(script, features);
3415
3644
  const stages = joins ? joiningStages(features, features.includes("rclt") && font.substitutions(script, ["rclt"]).length > 0).map((stage) => ({
3416
3645
  stage,
@@ -3545,6 +3774,6 @@ function measureWidth(font, text, size, options = {}) {
3545
3774
  for (const advance of advances) units += advance;
3546
3775
  return units * size / font.unitsPerEm;
3547
3776
  }
3548
- export { canvasWords as a, shapingWords as i, measureWidth as n, defaultFeatures as o, readFont as r, readTables as s, glyphAdvances as t };
3777
+ export { shapingWords as a, indicFeatures as c, readTables as d, shapingOf as i, indicForms as l, measureWidth as n, canvasWords as o, readFont as r, joiningStages as s, glyphAdvances as t, defaultFeatures as u };
3549
3778
 
3550
3779
  //# sourceMappingURL=font.js.map