@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,208 @@
1
+ import OTProcessor from './OTProcessor';
2
+ import GlyphInfo from './GlyphInfo';
3
+
4
+ export default class GSUBProcessor extends OTProcessor {
5
+ lookupDirection(lookup) {
6
+ let type = lookup.lookupType === 7 ? lookup.subTables[0]?.lookupType : lookup.lookupType;
7
+ return type === 8 ? -1 : 1;
8
+ }
9
+
10
+ applyLookup(lookupType, table) {
11
+ switch (lookupType) {
12
+ case 1: { // Single Substitution
13
+ let index = this.coverageIndex(table.coverage);
14
+ if (index === -1) {
15
+ return false;
16
+ }
17
+
18
+ let glyph = this.glyphIterator.cur;
19
+ switch (table.version) {
20
+ case 1:
21
+ glyph.id = (glyph.id + table.deltaGlyphID) & 0xffff;
22
+ break;
23
+
24
+ case 2:
25
+ glyph.id = table.substitute.get(index);
26
+ break;
27
+ }
28
+
29
+ return true;
30
+ }
31
+
32
+ case 2: { // Multiple Substitution
33
+ let index = this.coverageIndex(table.coverage);
34
+ if (index !== -1) {
35
+ let sequence = table.sequences.get(index);
36
+
37
+ if (sequence.length === 0) {
38
+ // If the sequence length is zero, delete the glyph.
39
+ // The OpenType spec disallows this, but seems like Harfbuzz and Uniscribe allow it.
40
+ this.glyphs.splice(this.glyphIterator.index, 1);
41
+ return true;
42
+ }
43
+
44
+ this.glyphIterator.cur.id = sequence[0];
45
+ this.glyphIterator.cur.ligatureComponent = 0;
46
+
47
+ let features = this.glyphIterator.cur.features;
48
+ let curGlyph = this.glyphIterator.cur;
49
+ let replacement = sequence.slice(1).map((gid, i) => {
50
+ let glyph = new GlyphInfo(this.font, gid, undefined, features);
51
+ glyph.shaperInfo = curGlyph.shaperInfo;
52
+ glyph.isLigated = curGlyph.isLigated;
53
+ glyph.ligatureComponent = i + 1;
54
+ glyph.substituted = true;
55
+ glyph.isMultiplied = true;
56
+ return glyph;
57
+ });
58
+
59
+ this.glyphs.splice(this.glyphIterator.index + 1, 0, ...replacement);
60
+ return true;
61
+ }
62
+
63
+ return false;
64
+ }
65
+
66
+ case 3: { // Alternate Substitution
67
+ let index = this.coverageIndex(table.coverage);
68
+ if (index === -1) return false;
69
+
70
+ // 1-based index from user features (e.g. { aalt: 2 }); boolean / missing → first
71
+ let alternates = table.alternateSet.get(index);
72
+ let alt = this.userFeatures?.[this.currentFeature];
73
+ let i = Number.isInteger(alt) && alt > 0 ? alt - 1 : 0;
74
+ this.glyphIterator.cur.id = alternates[i < alternates.length ? i : 0];
75
+ return true;
76
+ }
77
+
78
+ case 4: { // Ligature Substitution
79
+ let index = this.coverageIndex(table.coverage);
80
+ if (index === -1) return false;
81
+
82
+ for (let ligature of table.ligatureSets.get(index)) {
83
+ let matched = this.sequenceMatchIndices(1, ligature.components);
84
+ if (!matched) continue;
85
+
86
+ let curGlyph = this.glyphIterator.cur;
87
+
88
+ // Concatenate all of the characters the new ligature will represent
89
+ let characters = curGlyph.codePoints.slice();
90
+ for (let index of matched) {
91
+ characters.push(...this.glyphs[index].codePoints);
92
+ }
93
+
94
+ // Create the replacement ligature glyph
95
+ let ligatureGlyph = new GlyphInfo(this.font, ligature.glyph, characters, curGlyph.features);
96
+ ligatureGlyph.shaperInfo = curGlyph.shaperInfo;
97
+ ligatureGlyph.isLigated = true;
98
+ ligatureGlyph.substituted = true;
99
+
100
+ // From Harfbuzz:
101
+ // - If it *is* a mark ligature, we don't allocate a new ligature id, and leave
102
+ // the ligature to keep its old ligature id. This will allow it to attach to
103
+ // a base ligature in GPOS. Eg. if the sequence is: LAM,LAM,SHADDA,FATHA,HEH,
104
+ // and LAM,LAM,HEH for a ligature, they will leave SHADDA and FATHA with a
105
+ // ligature id and component value of 2. Then if SHADDA,FATHA form a ligature
106
+ // later, we don't want them to lose their ligature id/component, otherwise
107
+ // GPOS will fail to correctly position the mark ligature on top of the
108
+ // LAM,LAM,HEH ligature. See https://bugzilla.gnome.org/show_bug.cgi?id=676343
109
+ //
110
+ // - If a ligature is formed of components that some of which are also ligatures
111
+ // themselves, and those ligature components had marks attached to *their*
112
+ // components, we have to attach the marks to the new ligature component
113
+ // positions! Now *that*'s tricky! And these marks may be following the
114
+ // last component of the whole sequence, so we should loop forward looking
115
+ // for them and update them.
116
+ //
117
+ // Eg. the sequence is LAM,LAM,SHADDA,FATHA,HEH, and the font first forms a
118
+ // 'calt' ligature of LAM,HEH, leaving the SHADDA and FATHA with a ligature
119
+ // id and component == 1. Now, during 'liga', the LAM and the LAM-HEH ligature
120
+ // form a LAM-LAM-HEH ligature. We need to reassign the SHADDA and FATHA to
121
+ // the new ligature with a component value of 2.
122
+ //
123
+ // This in fact happened to a font... See https://bugzilla.gnome.org/show_bug.cgi?id=437633
124
+ let isMarkLigature = curGlyph.isMark;
125
+ for (let i = 0; i < matched.length && isMarkLigature; i++) {
126
+ isMarkLigature = this.glyphs[matched[i]].isMark;
127
+ }
128
+
129
+ ligatureGlyph.ligatureID = isMarkLigature ? null : this.ligatureID++;
130
+
131
+ let lastLigID = curGlyph.ligatureID;
132
+ let lastNumComps = curGlyph.codePoints.length;
133
+ let curComps = lastNumComps;
134
+ let idx = this.glyphIterator.index + 1;
135
+
136
+ // Set ligatureID and ligatureComponent on glyphs that were skipped in the matched sequence.
137
+ // This allows GPOS to attach marks to the correct ligature components.
138
+ for (let matchIndex of matched) {
139
+ // Don't assign new ligature components for mark ligatures (see above)
140
+ if (isMarkLigature) {
141
+ idx = matchIndex;
142
+ } else {
143
+ while (idx < matchIndex) {
144
+ let ligatureComponent = curComps - lastNumComps + Math.min(this.glyphs[idx].ligatureComponent || 1, lastNumComps);
145
+ this.glyphs[idx].ligatureID = ligatureGlyph.ligatureID;
146
+ this.glyphs[idx].ligatureComponent = ligatureComponent;
147
+ idx++;
148
+ }
149
+ }
150
+
151
+ lastLigID = this.glyphs[idx].ligatureID;
152
+ lastNumComps = this.glyphs[idx].codePoints.length;
153
+ curComps += lastNumComps;
154
+ idx++; // skip base glyph
155
+ }
156
+
157
+ // Adjust ligature components for any marks following
158
+ if (lastLigID && !isMarkLigature) {
159
+ for (let i = idx; i < this.glyphs.length; i++) {
160
+ if (this.glyphs[i].ligatureID === lastLigID) {
161
+ let ligatureComponent = curComps - lastNumComps + Math.min(this.glyphs[i].ligatureComponent || 1, lastNumComps);
162
+ this.glyphs[i].ligatureComponent = ligatureComponent;
163
+ } else {
164
+ break;
165
+ }
166
+ }
167
+ }
168
+
169
+ // Delete the matched glyphs, and replace the current glyph with the ligature glyph
170
+ for (let i = matched.length - 1; i >= 0; i--) {
171
+ this.glyphs.splice(matched[i], 1);
172
+ }
173
+
174
+ this.glyphs[this.glyphIterator.index] = ligatureGlyph;
175
+ return true;
176
+ }
177
+
178
+ return false;
179
+ }
180
+
181
+ case 5: // Contextual Substitution
182
+ return this.applyContext(table);
183
+
184
+ case 6: // Chaining Contextual Substitution
185
+ return this.applyChainingContext(table);
186
+
187
+ case 7: // Extension Substitution
188
+ return this.applyLookup(table.lookupType, table.extension);
189
+
190
+ case 8: { // Reverse Chaining Contextual Single Substitution
191
+ let index = this.coverageIndex(table.coverage);
192
+ if (index === -1) return false;
193
+
194
+ // Backtrack is stored closest-first; coverageSequenceMatches walks furthest-first.
195
+ if (!this.coverageSequenceMatches(-table.backtrackGlyphCount, [...table.backtrackCoverage].reverse())
196
+ || !this.coverageSequenceMatches(1, table.lookaheadCoverage)) {
197
+ return false;
198
+ }
199
+
200
+ this.glyphIterator.cur.id = table.substitute.get(index);
201
+ return true;
202
+ }
203
+
204
+ default:
205
+ throw new Error(`GSUB lookupType ${lookupType} is not supported`);
206
+ }
207
+ }
208
+ }
@@ -0,0 +1,57 @@
1
+ import {isMark} from '../packages/unicode-properties/index.js';
2
+ import OTProcessor from './OTProcessor';
3
+
4
+ export default class GlyphInfo {
5
+ constructor(font, id, codePoints = [], features) {
6
+ this._font = font;
7
+ this.codePoints = codePoints;
8
+ this.id = id;
9
+
10
+ this.features = {};
11
+ if (Array.isArray(features)) {
12
+ for (let i = 0; i < features.length; i++) {
13
+ let feature = features[i];
14
+ this.features[feature] = true;
15
+ }
16
+ } else if (typeof features === 'object') {
17
+ Object.assign(this.features, features);
18
+ }
19
+
20
+ this.ligatureID = null;
21
+ this.ligatureComponent = null;
22
+ this.isLigated = false;
23
+ this.cursiveAttachment = null;
24
+ this.markAttachment = null;
25
+ this.shaperInfo = null;
26
+ this.substituted = false;
27
+ this.isMultiplied = false;
28
+ }
29
+
30
+ get id() {
31
+ return this._id;
32
+ }
33
+
34
+ set id(id) {
35
+ this._id = id;
36
+ this.substituted = true;
37
+
38
+ let GDEF = this._font.GDEF;
39
+ if (GDEF && GDEF.glyphClassDef) {
40
+ // TODO: clean this up
41
+ let classID = OTProcessor.prototype.getClassID(id, GDEF.glyphClassDef);
42
+ this.isBase = classID === 1;
43
+ this.isLigature = classID === 2;
44
+ this.isMark = classID === 3;
45
+ this.markAttachmentType = GDEF.markAttachClassDef ? OTProcessor.prototype.getClassID(id, GDEF.markAttachClassDef) : 0;
46
+ } else {
47
+ this.isMark = this.codePoints.length > 0 && this.codePoints.every(isMark);
48
+ this.isBase = !this.isMark;
49
+ this.isLigature = this.codePoints.length > 1;
50
+ this.markAttachmentType = 0;
51
+ }
52
+ }
53
+
54
+ copy() {
55
+ return new GlyphInfo(this._font, this.id, this.codePoints, this.features);
56
+ }
57
+ }
@@ -0,0 +1,82 @@
1
+ export default class GlyphIterator {
2
+ constructor(glyphs, options) {
3
+ this.glyphs = glyphs;
4
+ this.reset(options);
5
+ }
6
+
7
+ reset(options = {}, index = 0, markFilteringSet = null) {
8
+ this.options = options;
9
+ this.flags = options.flags || {};
10
+ this.markAttachmentType = options.markAttachmentType || 0;
11
+ this.markFilteringSet = markFilteringSet;
12
+ this.index = index;
13
+ }
14
+
15
+ get cur() {
16
+ return this.glyphs[this.index] || null;
17
+ }
18
+
19
+ shouldIgnore(glyph) {
20
+ // Mark skip hierarchy (OT chapter 2): ignoreMarks > mark filtering set > markAttachmentType.
21
+ if (glyph.isMark) {
22
+ if (this.flags.ignoreMarks) {
23
+ return true;
24
+ }
25
+ if (this.flags.useMarkFilteringSet) {
26
+ return !this.markFilteringSet?.has(glyph.id);
27
+ }
28
+ if (this.markAttachmentType) {
29
+ return glyph.markAttachmentType !== this.markAttachmentType;
30
+ }
31
+ }
32
+
33
+ return (this.flags.ignoreBaseGlyphs && glyph.isBase) ||
34
+ (this.flags.ignoreLigatures && glyph.isLigature);
35
+ }
36
+
37
+ move(dir) {
38
+ this.index += dir;
39
+ while (0 <= this.index && this.index < this.glyphs.length && this.shouldIgnore(this.glyphs[this.index])) {
40
+ this.index += dir;
41
+ }
42
+
43
+ if (0 > this.index || this.index >= this.glyphs.length) {
44
+ return null;
45
+ }
46
+
47
+ return this.glyphs[this.index];
48
+ }
49
+
50
+ next() {
51
+ return this.move(+1);
52
+ }
53
+
54
+ prev() {
55
+ return this.move(-1);
56
+ }
57
+
58
+ peek(count = 1) {
59
+ let idx = this.index;
60
+ let res = this.increment(count);
61
+ this.index = idx;
62
+ return res;
63
+ }
64
+
65
+ peekIndex(count = 1) {
66
+ let idx = this.index;
67
+ this.increment(count);
68
+ let res = this.index;
69
+ this.index = idx;
70
+ return res;
71
+ }
72
+
73
+ increment(count = 1) {
74
+ let dir = count < 0 ? -1 : 1;
75
+ count = Math.abs(count);
76
+ while (count--) {
77
+ this.move(dir);
78
+ }
79
+
80
+ return this.glyphs[this.index];
81
+ }
82
+ }
@@ -0,0 +1,117 @@
1
+ import ShapingPlan from './ShapingPlan';
2
+ import * as Shapers from './shapers';
3
+ import GlyphInfo from './GlyphInfo';
4
+ import GSUBProcessor from './GSUBProcessor';
5
+ import GPOSProcessor from './GPOSProcessor';
6
+
7
+ export default class OTLayoutEngine {
8
+ constructor(font) {
9
+ this.font = font;
10
+ this.glyphInfos = null;
11
+ this.plan = null;
12
+ this.GSUBProcessor = null;
13
+ this.GPOSProcessor = null;
14
+ this.fallbackPosition = true;
15
+
16
+ if (font.GSUB) {
17
+ this.GSUBProcessor = new GSUBProcessor(font, font.GSUB);
18
+ }
19
+
20
+ if (font.GPOS) {
21
+ this.GPOSProcessor = new GPOSProcessor(font, font.GPOS);
22
+ }
23
+ }
24
+
25
+ setup(glyphRun) {
26
+ // Map glyphs to GlyphInfo objects so data can be passed between
27
+ // GSUB and GPOS without mutating the real (shared) Glyph objects.
28
+ this.glyphInfos = glyphRun.glyphs.map(glyph => new GlyphInfo(this.font, glyph.id, [...glyph.codePoints]));
29
+
30
+ // Select an OpenType script for GSUB/GPOS features.
31
+ let script = null;
32
+ if (this.GPOSProcessor) {
33
+ script = this.GPOSProcessor.selectScript(glyphRun.script, glyphRun.language, glyphRun.direction);
34
+ }
35
+
36
+ if (this.GSUBProcessor) {
37
+ script = this.GSUBProcessor.selectScript(glyphRun.script, glyphRun.language, glyphRun.direction);
38
+ }
39
+
40
+ // Choose the complex shaper from the buffer Unicode script (HB model). The OT
41
+ // script may fall back to DFLT/latn even for Thai text; that must not select
42
+ // DefaultShaper or Thai SARA AM / PUA preprocessing is skipped.
43
+ this.shaper = Shapers.choose(glyphRun.script || script);
44
+ this.plan = new ShapingPlan(this.font, script || glyphRun.script, glyphRun.direction);
45
+ this.plan.bufferScript = glyphRun.script;
46
+ this.shaper.plan(this.plan, this.glyphInfos, glyphRun.features);
47
+
48
+ // Enabled features as true, then overlay user values (e.g. aalt: 2) without mutating input.
49
+ let features = {};
50
+ for (let key in this.plan.allFeatures) {
51
+ features[key] = true;
52
+ }
53
+ glyphRun.features = Object.assign(features, glyphRun.features);
54
+ }
55
+
56
+ substitute(glyphRun) {
57
+ if (this.GSUBProcessor) {
58
+ this.plan.process(this.GSUBProcessor, this.glyphInfos);
59
+
60
+ // Map glyph infos back to normal Glyph objects
61
+ glyphRun.glyphs = this.glyphInfos.map(glyphInfo => this.font.getGlyph(glyphInfo.id, glyphInfo.codePoints));
62
+ }
63
+ }
64
+
65
+ position(glyphRun) {
66
+ if (this.shaper.zeroMarkWidths === 'BEFORE_GPOS') {
67
+ this.zeroMarkAdvances(glyphRun.positions);
68
+ }
69
+
70
+ if (this.GPOSProcessor) {
71
+ this.plan.process(this.GPOSProcessor, this.glyphInfos, glyphRun.positions);
72
+ }
73
+
74
+ if (this.shaper.zeroMarkWidths === 'AFTER_GPOS') {
75
+ this.zeroMarkAdvances(glyphRun.positions);
76
+ }
77
+
78
+ // Reverse the glyphs and positions if the script is right-to-left
79
+ if (glyphRun.direction === 'rtl') {
80
+ glyphRun.glyphs.reverse();
81
+ glyphRun.positions.reverse();
82
+ }
83
+
84
+ return this.GPOSProcessor && this.GPOSProcessor.features;
85
+ }
86
+
87
+ zeroMarkAdvances(positions) {
88
+ for (let i = 0; i < this.glyphInfos.length; i++) {
89
+ if (this.glyphInfos[i].isMark) {
90
+ positions[i].xAdvance = 0;
91
+ positions[i].yAdvance = 0;
92
+ }
93
+ }
94
+ }
95
+
96
+ cleanup() {
97
+ this.glyphInfos = null;
98
+ this.plan = null;
99
+ this.shaper = null;
100
+ }
101
+
102
+ getAvailableFeatures(script, language) {
103
+ let features = [];
104
+
105
+ if (this.GSUBProcessor) {
106
+ this.GSUBProcessor.selectScript(script, language);
107
+ features.push(...Object.keys(this.GSUBProcessor.features));
108
+ }
109
+
110
+ if (this.GPOSProcessor) {
111
+ this.GPOSProcessor.selectScript(script, language);
112
+ features.push(...Object.keys(this.GPOSProcessor.features));
113
+ }
114
+
115
+ return features;
116
+ }
117
+ }