@cantoo/fontkit 2.0.4

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 (163) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +274 -0
  3. package/dist/browser-module.mjs +13660 -0
  4. package/dist/browser-module.mjs.map +1 -0
  5. package/dist/browser.cjs +13689 -0
  6. package/dist/browser.cjs.map +1 -0
  7. package/dist/main.cjs +13712 -0
  8. package/dist/main.cjs.map +1 -0
  9. package/dist/module.mjs +13673 -0
  10. package/dist/module.mjs.map +1 -0
  11. package/package.json +99 -0
  12. package/src/CmapProcessor.js +285 -0
  13. package/src/DFont.js +113 -0
  14. package/src/TTFFont.js +621 -0
  15. package/src/TrueTypeCollection.js +65 -0
  16. package/src/WOFF2Font.js +222 -0
  17. package/src/WOFFFont.js +56 -0
  18. package/src/aat/AATFeatureMap.js +545 -0
  19. package/src/aat/AATLayoutEngine.js +48 -0
  20. package/src/aat/AATLookupTable.js +125 -0
  21. package/src/aat/AATMorxProcessor.js +429 -0
  22. package/src/aat/AATStateMachine.js +96 -0
  23. package/src/base.js +29 -0
  24. package/src/cff/CFFCharsets.js +99 -0
  25. package/src/cff/CFFDict.js +166 -0
  26. package/src/cff/CFFEncodings.js +48 -0
  27. package/src/cff/CFFFont.js +158 -0
  28. package/src/cff/CFFIndex.js +150 -0
  29. package/src/cff/CFFOperand.js +135 -0
  30. package/src/cff/CFFPointer.js +50 -0
  31. package/src/cff/CFFPrivateDict.js +39 -0
  32. package/src/cff/CFFStandardStrings.js +71 -0
  33. package/src/cff/CFFTop.js +236 -0
  34. package/src/decorators.js +36 -0
  35. package/src/encodings.js +219 -0
  36. package/src/fs.js +12 -0
  37. package/src/glyph/BBox.js +72 -0
  38. package/src/glyph/CBDTGlyph.js +9 -0
  39. package/src/glyph/CFFGlyph.js +635 -0
  40. package/src/glyph/COLRGlyph.js +108 -0
  41. package/src/glyph/Glyph.js +270 -0
  42. package/src/glyph/GlyphVariationProcessor.js +487 -0
  43. package/src/glyph/Path.js +257 -0
  44. package/src/glyph/SBIXGlyph.js +54 -0
  45. package/src/glyph/StandardNames.js +27 -0
  46. package/src/glyph/TTFGlyph.js +408 -0
  47. package/src/glyph/TTFGlyphEncoder.js +158 -0
  48. package/src/glyph/WOFF2Glyph.js +70 -0
  49. package/src/index.js +15 -0
  50. package/src/layout/GlyphPosition.js +30 -0
  51. package/src/layout/GlyphRun.js +108 -0
  52. package/src/layout/KernProcessor.js +97 -0
  53. package/src/layout/LayoutEngine.js +190 -0
  54. package/src/layout/Script.js +231 -0
  55. package/src/layout/UnicodeLayoutEngine.js +250 -0
  56. package/src/node.js +16 -0
  57. package/src/opentype/GPOSProcessor.js +311 -0
  58. package/src/opentype/GSUBProcessor.js +208 -0
  59. package/src/opentype/GlyphInfo.js +57 -0
  60. package/src/opentype/GlyphIterator.js +82 -0
  61. package/src/opentype/OTLayoutEngine.js +117 -0
  62. package/src/opentype/OTProcessor.js +472 -0
  63. package/src/opentype/ShapingPlan.js +121 -0
  64. package/src/opentype/shapers/ArabicShaper.js +124 -0
  65. package/src/opentype/shapers/DefaultShaper.js +116 -0
  66. package/src/opentype/shapers/HangulShaper.js +285 -0
  67. package/src/opentype/shapers/HebrewShaper.js +105 -0
  68. package/src/opentype/shapers/IndicShaper.js +914 -0
  69. package/src/opentype/shapers/ThaiShaper.js +238 -0
  70. package/src/opentype/shapers/UniversalShaper.js +186 -0
  71. package/src/opentype/shapers/data.trie +0 -0
  72. package/src/opentype/shapers/gen-indic.js +208 -0
  73. package/src/opentype/shapers/gen-use.js +287 -0
  74. package/src/opentype/shapers/generate-data.js +33 -0
  75. package/src/opentype/shapers/index.js +109 -0
  76. package/src/opentype/shapers/indic-data.js +175 -0
  77. package/src/opentype/shapers/indic.json +1 -0
  78. package/src/opentype/shapers/indic.machine +31 -0
  79. package/src/opentype/shapers/indic.trie +0 -0
  80. package/src/opentype/shapers/use.json +1 -0
  81. package/src/opentype/shapers/use.machine +44 -0
  82. package/src/opentype/shapers/use.trie +0 -0
  83. package/src/packages/unicode-properties/LICENSE +8 -0
  84. package/src/packages/unicode-properties/README.md +12 -0
  85. package/src/packages/unicode-properties/data-trie.js +1 -0
  86. package/src/packages/unicode-properties/data.js +1 -0
  87. package/src/packages/unicode-properties/data.json +1 -0
  88. package/src/packages/unicode-properties/data.trie +0 -0
  89. package/src/packages/unicode-properties/dist/index.cjs +257 -0
  90. package/src/packages/unicode-properties/dist/index.cjs.map +7 -0
  91. package/src/packages/unicode-properties/dist/index.mjs +234 -0
  92. package/src/packages/unicode-properties/dist/index.mjs.map +7 -0
  93. package/src/packages/unicode-properties/generate.js +107 -0
  94. package/src/packages/unicode-properties/index.js +184 -0
  95. package/src/packages/unicode-properties/package.json +50 -0
  96. package/src/packages/unicode-trie/LICENSE +8 -0
  97. package/src/packages/unicode-trie/README.md +13 -0
  98. package/src/packages/unicode-trie/builder.js +965 -0
  99. package/src/packages/unicode-trie/dist/builder.cjs +711 -0
  100. package/src/packages/unicode-trie/dist/builder.cjs.map +7 -0
  101. package/src/packages/unicode-trie/dist/builder.mjs +690 -0
  102. package/src/packages/unicode-trie/dist/builder.mjs.map +7 -0
  103. package/src/packages/unicode-trie/dist/index.cjs +112 -0
  104. package/src/packages/unicode-trie/dist/index.cjs.map +7 -0
  105. package/src/packages/unicode-trie/dist/index.mjs +91 -0
  106. package/src/packages/unicode-trie/dist/index.mjs.map +7 -0
  107. package/src/packages/unicode-trie/index.js +135 -0
  108. package/src/packages/unicode-trie/package.json +49 -0
  109. package/src/packages/unicode-trie/swap.js +21 -0
  110. package/src/subset/CFFSubset.js +176 -0
  111. package/src/subset/Subset.js +63 -0
  112. package/src/subset/TTFSubset.js +130 -0
  113. package/src/tables/BASE.js +78 -0
  114. package/src/tables/COLR.js +25 -0
  115. package/src/tables/CPAL.js +24 -0
  116. package/src/tables/DSIG.js +21 -0
  117. package/src/tables/EBDT.js +91 -0
  118. package/src/tables/EBLC.js +80 -0
  119. package/src/tables/GDEF.js +57 -0
  120. package/src/tables/GPOS.js +209 -0
  121. package/src/tables/GSUB.js +85 -0
  122. package/src/tables/HVAR.js +44 -0
  123. package/src/tables/JSTF.js +43 -0
  124. package/src/tables/LTSH.js +10 -0
  125. package/src/tables/OS2.js +84 -0
  126. package/src/tables/PCLT.js +21 -0
  127. package/src/tables/STAT.js +99 -0
  128. package/src/tables/VDMX.js +33 -0
  129. package/src/tables/VORG.js +14 -0
  130. package/src/tables/WOFF2Directory.js +73 -0
  131. package/src/tables/WOFFDirectory.js +37 -0
  132. package/src/tables/aat.js +157 -0
  133. package/src/tables/avar.js +19 -0
  134. package/src/tables/bsln.js +31 -0
  135. package/src/tables/cmap.js +127 -0
  136. package/src/tables/cvt.js +6 -0
  137. package/src/tables/directory.js +58 -0
  138. package/src/tables/feat.js +28 -0
  139. package/src/tables/fpgm.js +8 -0
  140. package/src/tables/fvar.js +31 -0
  141. package/src/tables/gasp.js +15 -0
  142. package/src/tables/glyf.js +4 -0
  143. package/src/tables/gvar.js +27 -0
  144. package/src/tables/hdmx.js +15 -0
  145. package/src/tables/head.js +25 -0
  146. package/src/tables/hhea.js +19 -0
  147. package/src/tables/hmtx.js +11 -0
  148. package/src/tables/index.js +119 -0
  149. package/src/tables/just.js +81 -0
  150. package/src/tables/kern.js +91 -0
  151. package/src/tables/loca.js +30 -0
  152. package/src/tables/maxp.js +20 -0
  153. package/src/tables/morx.js +79 -0
  154. package/src/tables/name.js +157 -0
  155. package/src/tables/opbd.js +15 -0
  156. package/src/tables/opentype.js +217 -0
  157. package/src/tables/post.js +34 -0
  158. package/src/tables/prep.js +6 -0
  159. package/src/tables/sbix.js +17 -0
  160. package/src/tables/variations.js +80 -0
  161. package/src/tables/vhea.js +19 -0
  162. package/src/tables/vmtx.js +12 -0
  163. package/src/utils.js +62 -0
@@ -0,0 +1,472 @@
1
+ import GlyphIterator from './GlyphIterator';
2
+ import * as Script from '../layout/Script';
3
+
4
+ const DEFAULT_SCRIPTS = ['DFLT', 'dflt', 'latn'];
5
+
6
+ export default class OTProcessor {
7
+ constructor(font, table) {
8
+ this.font = font;
9
+ this.table = table;
10
+
11
+ this.script = null;
12
+ this.scriptTag = null;
13
+
14
+ this.language = null;
15
+ this.languageTag = null;
16
+
17
+ this.features = {};
18
+ this.lookups = {};
19
+
20
+ // FeatureVariations apply at the current location; default to normalized
21
+ // origin when no variation is set (HarfBuzz behavior). Without this,
22
+ // variable fonts skip FeatureVariations until getVariation() is called.
23
+ let coords = font._variationProcessor?.normalizedCoords
24
+ ?? (font.fvar ? new Array(font.fvar.axis.length).fill(0) : null);
25
+ this.variationsIndex = coords ? this.findVariationsIndex(coords) : -1;
26
+
27
+ // initialize to default script + language
28
+ this.selectScript();
29
+
30
+ // current context (set by applyFeatures)
31
+ this.glyphs = [];
32
+ this.positions = []; // only used by GPOS
33
+ this.ligatureID = 1;
34
+ this.currentFeature = null;
35
+ }
36
+
37
+ findScript(script) {
38
+ if (this.table.scriptList == null) {
39
+ return null;
40
+ }
41
+
42
+ if (!Array.isArray(script)) {
43
+ script = [script];
44
+ }
45
+
46
+ for (let s of script) {
47
+ for (let entry of this.table.scriptList) {
48
+ if (entry.tag === s) {
49
+ return entry;
50
+ }
51
+ }
52
+ }
53
+
54
+ return null;
55
+ }
56
+
57
+ selectScript(script, language, direction) {
58
+ let changed = false;
59
+ let entry;
60
+ if (!this.script || script !== this.scriptTag) {
61
+ entry = this.findScript(script);
62
+ if (!entry) {
63
+ entry = this.findScript(DEFAULT_SCRIPTS);
64
+ }
65
+
66
+ if (!entry) {
67
+ return this.scriptTag;
68
+ }
69
+
70
+ this.scriptTag = entry.tag;
71
+ this.script = entry.script;
72
+ this.language = null;
73
+ this.languageTag = null;
74
+ changed = true;
75
+ }
76
+
77
+ if (!direction || direction !== this.direction) {
78
+ this.direction = direction || Script.direction(script);
79
+ }
80
+
81
+ if (language && language.length < 4) {
82
+ language += ' '.repeat(4 - language.length);
83
+ }
84
+
85
+ if (!language || language !== this.languageTag) {
86
+ this.language = null;
87
+
88
+ for (let lang of this.script.langSysRecords) {
89
+ if (lang.tag === language) {
90
+ this.language = lang.langSys;
91
+ this.languageTag = lang.tag;
92
+ break;
93
+ }
94
+ }
95
+
96
+ if (!this.language) {
97
+ this.language = this.script.defaultLangSys;
98
+ this.languageTag = null;
99
+ }
100
+
101
+ changed = true;
102
+ }
103
+
104
+ // Build a feature lookup table
105
+ if (changed) {
106
+ this.features = {};
107
+ if (this.language) {
108
+ for (let featureIndex of this.language.featureIndexes) {
109
+ let record = this.table.featureList[featureIndex];
110
+ let substituteFeature = this.substituteFeatureForVariations(featureIndex);
111
+ this.features[record.tag] = substituteFeature || record.feature;
112
+ }
113
+ }
114
+ }
115
+
116
+ return this.scriptTag;
117
+ }
118
+
119
+ lookupsForFeatures(userFeatures = [], exclude) {
120
+ let lookups = [];
121
+ for (let tag of userFeatures) {
122
+ let feature = this.features[tag];
123
+ if (!feature) {
124
+ continue;
125
+ }
126
+
127
+ for (let lookupIndex of feature.lookupListIndexes) {
128
+ if (exclude && exclude.indexOf(lookupIndex) !== -1) {
129
+ continue;
130
+ }
131
+
132
+ lookups.push({
133
+ feature: tag,
134
+ index: lookupIndex,
135
+ lookup: this.table.lookupList.get(lookupIndex)
136
+ });
137
+ }
138
+ }
139
+
140
+ lookups.sort((a, b) => a.index - b.index);
141
+ return lookups;
142
+ }
143
+
144
+ substituteFeatureForVariations(featureIndex) {
145
+ if (this.variationsIndex === -1) {
146
+ return null;
147
+ }
148
+
149
+ let record = this.table.featureVariations.featureVariationRecords[this.variationsIndex];
150
+ let substitutions = record.featureTableSubstitution.substitutions;
151
+ for (let substitution of substitutions) {
152
+ if (substitution.featureIndex === featureIndex) {
153
+ return substitution.alternateFeatureTable;
154
+ }
155
+ }
156
+
157
+ return null;
158
+ }
159
+
160
+ findVariationsIndex(coords) {
161
+ let variations = this.table.featureVariations;
162
+ if (!variations) {
163
+ return -1;
164
+ }
165
+
166
+ let records = variations.featureVariationRecords;
167
+ for (let i = 0; i < records.length; i++) {
168
+ let conditions = records[i].conditionSet.conditionTable;
169
+ if (this.variationConditionsMatch(conditions, coords)) {
170
+ return i;
171
+ }
172
+ }
173
+
174
+ return -1;
175
+ }
176
+
177
+ variationConditionsMatch(conditions, coords) {
178
+ return conditions.every(condition => {
179
+ let coord = condition.axisIndex < coords.length ? coords[condition.axisIndex] : 0;
180
+ return condition.filterRangeMinValue <= coord && coord <= condition.filterRangeMaxValue;
181
+ });
182
+ }
183
+
184
+ applyFeatures(userFeatures, glyphs, advances) {
185
+ let lookups = this.lookupsForFeatures(userFeatures);
186
+ this.applyLookups(lookups, glyphs, advances);
187
+ }
188
+
189
+ applyLookups(lookups, glyphs, positions) {
190
+ this.glyphs = glyphs;
191
+ this.positions = positions;
192
+ this.glyphIterator = new GlyphIterator(glyphs);
193
+
194
+ for (let { feature, lookup } of lookups) {
195
+ this.currentFeature = feature;
196
+
197
+ // Direction may be RTL for some lookups (see lookupDirection).
198
+ let direction = this.lookupDirection(lookup);
199
+ this.glyphIterator.reset(
200
+ lookup.flags,
201
+ direction < 0 ? glyphs.length - 1 : 0,
202
+ this.getMarkFilteringSet(lookup)
203
+ );
204
+
205
+ while (this.glyphIterator.index >= 0 && this.glyphIterator.index < glyphs.length) {
206
+ if (feature in this.glyphIterator.cur.features) {
207
+ for (let table of lookup.subTables) {
208
+ if (this.applyLookup(lookup.lookupType, table)) {
209
+ break;
210
+ }
211
+ }
212
+ }
213
+ this.glyphIterator.move(direction);
214
+ }
215
+ }
216
+ }
217
+
218
+ // Subclasses may reverse iteration for specific lookup types (e.g. GSUB Type 8).
219
+ lookupDirection(_lookup) {
220
+ return 1;
221
+ }
222
+
223
+ applyLookup(_lookup, _table) {
224
+ throw new Error("applyLookup must be implemented by subclasses");
225
+ }
226
+
227
+ applyLookupList(lookupRecords) {
228
+ let options = this.glyphIterator.options;
229
+ let markFilteringSet = this.glyphIterator.markFilteringSet;
230
+ let glyphIndex = this.glyphIterator.index;
231
+
232
+ for (let lookupRecord of lookupRecords) {
233
+ // Reset flags and find glyph index for this lookup record
234
+ this.glyphIterator.reset(options, glyphIndex, markFilteringSet);
235
+ this.glyphIterator.increment(lookupRecord.sequenceIndex);
236
+
237
+ // Get the lookup and setup flags for subtables
238
+ let lookup = this.table.lookupList.get(lookupRecord.lookupListIndex);
239
+ this.glyphIterator.reset(lookup.flags, this.glyphIterator.index, this.getMarkFilteringSet(lookup));
240
+
241
+ // Apply lookup subtables until one matches
242
+ for (let table of lookup.subTables) {
243
+ if (this.applyLookup(lookup.lookupType, table)) {
244
+ break;
245
+ }
246
+ }
247
+ }
248
+
249
+ this.glyphIterator.reset(options, glyphIndex, markFilteringSet);
250
+ return true;
251
+ }
252
+
253
+ getMarkFilteringSet(lookup) {
254
+ if (!lookup.flags.flags.useMarkFilteringSet) {
255
+ return null;
256
+ }
257
+
258
+ let coverage = this.font.GDEF?.markGlyphSetsDef?.coverage?.[lookup.markFilteringSet];
259
+ if (!coverage) {
260
+ return null;
261
+ }
262
+
263
+ // Cache a Set-like {has} that reuses coverageIndex (avoids expanding large ranges).
264
+ let cache = (this._markFilteringCache ??= new Map());
265
+ let filter = cache.get(coverage);
266
+ if (!filter) {
267
+ filter = { has: id => this.coverageIndex(coverage, id) >= 0 };
268
+ cache.set(coverage, filter);
269
+ }
270
+ return filter;
271
+ }
272
+
273
+ coverageIndex(coverage, glyph) {
274
+ if (glyph == null) {
275
+ glyph = this.glyphIterator.cur.id;
276
+ }
277
+
278
+ switch (coverage.version) {
279
+ case 1:
280
+ return coverage.glyphs.indexOf(glyph);
281
+
282
+ case 2:
283
+ for (let range of coverage.rangeRecords) {
284
+ if (range.start <= glyph && glyph <= range.end) {
285
+ return range.startCoverageIndex + glyph - range.start;
286
+ }
287
+ }
288
+
289
+ break;
290
+ }
291
+
292
+ return -1;
293
+ }
294
+
295
+ match(sequenceIndex, sequence, fn, matched) {
296
+ let pos = this.glyphIterator.index;
297
+ let glyph = this.glyphIterator.increment(sequenceIndex);
298
+ let idx = 0;
299
+
300
+ while (idx < sequence.length && glyph && fn(sequence[idx], glyph)) {
301
+ if (matched) {
302
+ matched.push(this.glyphIterator.index);
303
+ }
304
+
305
+ idx++;
306
+ glyph = this.glyphIterator.next();
307
+ }
308
+
309
+ this.glyphIterator.index = pos;
310
+ if (idx < sequence.length) {
311
+ return false;
312
+ }
313
+
314
+ return matched || true;
315
+ }
316
+
317
+ sequenceMatches(sequenceIndex, sequence) {
318
+ return this.match(sequenceIndex, sequence, (component, glyph) => component === glyph.id);
319
+ }
320
+
321
+ sequenceMatchIndices(sequenceIndex, sequence) {
322
+ return this.match(sequenceIndex, sequence, (component, glyph) => {
323
+ // If the current feature doesn't apply to this glyph,
324
+ if (!(this.currentFeature in glyph.features)) {
325
+ return false;
326
+ }
327
+
328
+ return component === glyph.id;
329
+ }, []);
330
+ }
331
+
332
+ coverageSequenceMatches(sequenceIndex, sequence) {
333
+ return this.match(sequenceIndex, sequence, (coverage, glyph) =>
334
+ this.coverageIndex(coverage, glyph.id) >= 0
335
+ );
336
+ }
337
+
338
+ getClassID(glyph, classDef) {
339
+ switch (classDef.version) {
340
+ case 1: { // Class array
341
+ let i = glyph - classDef.startGlyph;
342
+ if (i >= 0 && i < classDef.classValueArray.length) {
343
+ return classDef.classValueArray[i];
344
+ }
345
+
346
+ break;
347
+ }
348
+
349
+ case 2:
350
+ for (let range of classDef.classRangeRecord) {
351
+ if (range.start <= glyph && glyph <= range.end) {
352
+ return range.class;
353
+ }
354
+ }
355
+
356
+ break;
357
+ }
358
+
359
+ return 0;
360
+ }
361
+
362
+ classSequenceMatches(sequenceIndex, sequence, classDef) {
363
+ return this.match(sequenceIndex, sequence, (classID, glyph) =>
364
+ classID === this.getClassID(glyph.id, classDef)
365
+ );
366
+ }
367
+
368
+ applyContext(table) {
369
+ let index, set;
370
+ switch (table.version) {
371
+ case 1:
372
+ index = this.coverageIndex(table.coverage);
373
+ if (index === -1) {
374
+ return false;
375
+ }
376
+
377
+ set = table.ruleSets[index];
378
+ for (let rule of set) {
379
+ if (this.sequenceMatches(1, rule.input)) {
380
+ return this.applyLookupList(rule.lookupRecords);
381
+ }
382
+ }
383
+
384
+ break;
385
+
386
+ case 2:
387
+ if (this.coverageIndex(table.coverage) === -1) {
388
+ return false;
389
+ }
390
+
391
+ index = this.getClassID(this.glyphIterator.cur.id, table.classDef);
392
+ if (index === -1) {
393
+ return false;
394
+ }
395
+
396
+ set = table.classSet[index];
397
+ for (let rule of set) {
398
+ if (this.classSequenceMatches(1, rule.classes, table.classDef)) {
399
+ return this.applyLookupList(rule.lookupRecords);
400
+ }
401
+ }
402
+
403
+ break;
404
+
405
+ case 3:
406
+ if (this.coverageSequenceMatches(0, table.coverages)) {
407
+ return this.applyLookupList(table.lookupRecords);
408
+ }
409
+
410
+ break;
411
+ }
412
+
413
+ return false;
414
+ }
415
+
416
+ applyChainingContext(table) {
417
+ let index;
418
+ switch (table.version) {
419
+ case 1: {
420
+ index = this.coverageIndex(table.coverage);
421
+ if (index === -1) {
422
+ return false;
423
+ }
424
+
425
+ let set = table.chainRuleSets[index];
426
+ for (let rule of set) {
427
+ // Backtrack is stored closest-first; sequenceMatches walks furthest-first.
428
+ if (this.sequenceMatches(-rule.backtrack.length, [...rule.backtrack].reverse())
429
+ && this.sequenceMatches(1, rule.input)
430
+ && this.sequenceMatches(1 + rule.input.length, rule.lookahead)) {
431
+ return this.applyLookupList(rule.lookupRecords);
432
+ }
433
+ }
434
+
435
+ break;
436
+ }
437
+
438
+ case 2: {
439
+ if (this.coverageIndex(table.coverage) === -1) {
440
+ return false;
441
+ }
442
+
443
+ index = this.getClassID(this.glyphIterator.cur.id, table.inputClassDef);
444
+ let rules = table.chainClassSet[index];
445
+ if (!rules) {
446
+ return false;
447
+ }
448
+
449
+ for (let rule of rules) {
450
+ if (this.classSequenceMatches(-rule.backtrack.length, [...rule.backtrack].reverse(), table.backtrackClassDef) &&
451
+ this.classSequenceMatches(1, rule.input, table.inputClassDef) &&
452
+ this.classSequenceMatches(1 + rule.input.length, rule.lookahead, table.lookaheadClassDef)) {
453
+ return this.applyLookupList(rule.lookupRecords);
454
+ }
455
+ }
456
+
457
+ break;
458
+ }
459
+
460
+ case 3:
461
+ if (this.coverageSequenceMatches(-table.backtrackGlyphCount, [...table.backtrackCoverage].reverse()) &&
462
+ this.coverageSequenceMatches(0, table.inputCoverage) &&
463
+ this.coverageSequenceMatches(table.inputGlyphCount, table.lookaheadCoverage)) {
464
+ return this.applyLookupList(table.lookupRecords);
465
+ }
466
+
467
+ break;
468
+ }
469
+
470
+ return false;
471
+ }
472
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * ShapingPlans are used by the OpenType shapers to store which
3
+ * features should by applied, and in what order to apply them.
4
+ * The features are applied in groups called stages. A feature
5
+ * can be applied globally to all glyphs, or locally to only
6
+ * specific glyphs.
7
+ *
8
+ * @private
9
+ */
10
+ export default class ShapingPlan {
11
+ constructor(font, script, direction) {
12
+ this.font = font;
13
+ this.script = script;
14
+ this.direction = direction;
15
+ this.stages = [];
16
+ this.globalFeatures = {};
17
+ this.allFeatures = {};
18
+ this.userFeatures = null; // original user feature values (for Type 3 alternate index)
19
+ }
20
+
21
+ /**
22
+ * Adds the given features to the last stage.
23
+ * Already-planned tags are not duplicated, but a global add still promotes them.
24
+ */
25
+ _addFeatures(features, global) {
26
+ let stageIndex = this.stages.length - 1;
27
+ let stage = this.stages[stageIndex];
28
+ for (let feature of features) {
29
+ if (this.allFeatures[feature] == null) {
30
+ stage.push(feature);
31
+ this.allFeatures[feature] = stageIndex;
32
+ }
33
+
34
+ if (global) {
35
+ this.globalFeatures[feature] = true;
36
+ }
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Add features to the last stage
42
+ */
43
+ add(arg, global = true) {
44
+ if (this.stages.length === 0) {
45
+ this.stages.push([]);
46
+ }
47
+
48
+ if (typeof arg === 'string') {
49
+ arg = [arg];
50
+ }
51
+
52
+ if (Array.isArray(arg)) {
53
+ this._addFeatures(arg, global);
54
+ } else if (typeof arg === 'object') {
55
+ this._addFeatures(arg.global || [], true);
56
+ this._addFeatures(arg.local || [], false);
57
+ } else {
58
+ throw new Error("Unsupported argument to ShapingPlan#add");
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Add a new stage
64
+ */
65
+ addStage(arg, global) {
66
+ if (typeof arg === 'function') {
67
+ this.stages.push(arg, []);
68
+ } else {
69
+ this.stages.push([]);
70
+ this.add(arg, global);
71
+ }
72
+ }
73
+
74
+ setFeatureOverrides(features) {
75
+ if (Array.isArray(features)) {
76
+ this.add(features);
77
+ } else if (typeof features === 'object') {
78
+ this.userFeatures = features;
79
+ for (let tag in features) {
80
+ if (features[tag]) {
81
+ this.add(tag);
82
+ } else if (this.allFeatures[tag] != null) {
83
+ let stage = this.stages[this.allFeatures[tag]];
84
+ stage.splice(stage.indexOf(tag), 1);
85
+ delete this.allFeatures[tag];
86
+ delete this.globalFeatures[tag];
87
+ }
88
+ }
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Assigns the global features to the given glyphs
94
+ */
95
+ assignGlobalFeatures(glyphs) {
96
+ for (let glyph of glyphs) {
97
+ for (let feature in this.globalFeatures) {
98
+ glyph.features[feature] = true;
99
+ }
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Executes the planned stages using the given OTProcessor
105
+ */
106
+ process(processor, glyphs, positions) {
107
+ // Preserve original user values (e.g. { aalt: 2 }) for GSUB Type 3.
108
+ processor.userFeatures = this.userFeatures;
109
+
110
+ for (let stage of this.stages) {
111
+ if (typeof stage === 'function') {
112
+ if (!positions) {
113
+ stage(this.font, glyphs, this);
114
+ }
115
+
116
+ } else if (stage.length > 0) {
117
+ processor.applyFeatures(stage, glyphs, positions);
118
+ }
119
+ }
120
+ }
121
+ }
@@ -0,0 +1,124 @@
1
+ import DefaultShaper from './DefaultShaper';
2
+ import {getCategory} from '../../packages/unicode-properties/index.js';
3
+ import UnicodeTrie from '../../packages/unicode-trie/index.js';
4
+ import { decodeBase64 } from '../../utils';
5
+ import dataTrie from './data.trie';
6
+
7
+ const trie = new UnicodeTrie(decodeBase64(dataTrie));
8
+ const FEATURES = ['isol', 'fina', 'fin2', 'fin3', 'medi', 'med2', 'init'];
9
+
10
+ const ShapingClasses = {
11
+ Non_Joining: 0,
12
+ Left_Joining: 1,
13
+ Right_Joining: 2,
14
+ Dual_Joining: 3,
15
+ Join_Causing: 3,
16
+ ALAPH: 4,
17
+ 'DALATH RISH': 5,
18
+ Transparent: 6
19
+ };
20
+
21
+ const ISOL = 'isol';
22
+ const FINA = 'fina';
23
+ const FIN2 = 'fin2';
24
+ const FIN3 = 'fin3';
25
+ const MEDI = 'medi';
26
+ const MED2 = 'med2';
27
+ const INIT = 'init';
28
+ const NONE = null;
29
+
30
+ // Each entry is [prevAction, curAction, nextState]
31
+ const STATE_TABLE = [
32
+ // Non_Joining, Left_Joining, Right_Joining, Dual_Joining, ALAPH, DALATH RISH
33
+ // State 0: prev was U, not willing to join.
34
+ [ [ NONE, NONE, 0 ], [ NONE, ISOL, 2 ], [ NONE, ISOL, 1 ], [ NONE, ISOL, 2 ], [ NONE, ISOL, 1 ], [ NONE, ISOL, 6 ] ],
35
+
36
+ // State 1: prev was R or ISOL/ALAPH, not willing to join.
37
+ [ [ NONE, NONE, 0 ], [ NONE, ISOL, 2 ], [ NONE, ISOL, 1 ], [ NONE, ISOL, 2 ], [ NONE, FIN2, 5 ], [ NONE, ISOL, 6 ] ],
38
+
39
+ // State 2: prev was D/L in ISOL form, willing to join.
40
+ [ [ NONE, NONE, 0 ], [ NONE, ISOL, 2 ], [ INIT, FINA, 1 ], [ INIT, FINA, 3 ], [ INIT, FINA, 4 ], [ INIT, FINA, 6 ] ],
41
+
42
+ // State 3: prev was D in FINA form, willing to join.
43
+ [ [ NONE, NONE, 0 ], [ NONE, ISOL, 2 ], [ MEDI, FINA, 1 ], [ MEDI, FINA, 3 ], [ MEDI, FINA, 4 ], [ MEDI, FINA, 6 ] ],
44
+
45
+ // State 4: prev was FINA ALAPH, not willing to join.
46
+ [ [ NONE, NONE, 0 ], [ NONE, ISOL, 2 ], [ MED2, ISOL, 1 ], [ MED2, ISOL, 2 ], [ MED2, FIN2, 5 ], [ MED2, ISOL, 6 ] ],
47
+
48
+ // State 5: prev was FIN2/FIN3 ALAPH, not willing to join.
49
+ [ [ NONE, NONE, 0 ], [ NONE, ISOL, 2 ], [ ISOL, ISOL, 1 ], [ ISOL, ISOL, 2 ], [ ISOL, FIN2, 5 ], [ ISOL, ISOL, 6 ] ],
50
+
51
+ // State 6: prev was DALATH/RISH, not willing to join.
52
+ [ [ NONE, NONE, 0 ], [ NONE, ISOL, 2 ], [ NONE, ISOL, 1 ], [ NONE, ISOL, 2 ], [ NONE, FIN3, 5 ], [ NONE, ISOL, 6 ] ]
53
+ ];
54
+
55
+ /**
56
+ * This is a shaper for Arabic, and other cursive scripts.
57
+ * It uses data from ArabicShaping.txt in the Unicode database,
58
+ * compiled to a UnicodeTrie by generate-data.coffee.
59
+ *
60
+ * The shaping state machine was ported from Harfbuzz.
61
+ * https://github.com/behdad/harfbuzz/blob/master/src/hb-ot-shape-complex-arabic.cc
62
+ */
63
+ export default class ArabicShaper extends DefaultShaper {
64
+ static planFeatures(plan) {
65
+ plan.add(['ccmp', 'locl']);
66
+ for (let i = 0; i < FEATURES.length; i++) {
67
+ let feature = FEATURES[i];
68
+ plan.addStage(feature, false);
69
+ }
70
+
71
+ plan.addStage('mset');
72
+ }
73
+
74
+ static assignFeatures(plan, glyphs) {
75
+ super.assignFeatures(plan, glyphs);
76
+
77
+ let prev = -1;
78
+ let state = 0;
79
+ let actions = [];
80
+
81
+ // Apply the state machine to map glyphs to features
82
+ for (let i = 0; i < glyphs.length; i++) {
83
+ let curAction, prevAction;
84
+ let glyph = glyphs[i];
85
+ let type = getShapingClass(glyph.codePoints[0]);
86
+ if (type === ShapingClasses.Transparent) {
87
+ actions[i] = NONE;
88
+ continue;
89
+ }
90
+
91
+ [prevAction, curAction, state] = STATE_TABLE[state][type];
92
+
93
+ if (prevAction !== NONE && prev !== -1) {
94
+ actions[prev] = prevAction;
95
+ }
96
+
97
+ actions[i] = curAction;
98
+ prev = i;
99
+ }
100
+
101
+ // Apply the chosen features to their respective glyphs
102
+ for (let index = 0; index < glyphs.length; index++) {
103
+ let feature;
104
+ let glyph = glyphs[index];
105
+ if ((feature = actions[index])) {
106
+ glyph.features[feature] = true;
107
+ }
108
+ }
109
+ }
110
+ }
111
+
112
+ function getShapingClass(codePoint) {
113
+ let res = trie.get(codePoint);
114
+ if (res) {
115
+ return res - 1;
116
+ }
117
+
118
+ let category = getCategory(codePoint);
119
+ if (category === 'Mn' || category === 'Me' || category === 'Cf') {
120
+ return ShapingClasses.Transparent;
121
+ }
122
+
123
+ return ShapingClasses.Non_Joining;
124
+ }