@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,250 @@
1
+ import {getCombiningClass} from '../packages/unicode-properties/index.js';
2
+
3
+ /**
4
+ * This class is used when GPOS does not define 'mark' or 'mkmk' features
5
+ * for positioning marks relative to base glyphs. It uses the unicode
6
+ * combining class property to position marks.
7
+ *
8
+ * Based on code from Harfbuzz, thanks!
9
+ * https://github.com/behdad/harfbuzz/blob/master/src/hb-ot-shape-fallback.cc
10
+ */
11
+ export default class UnicodeLayoutEngine {
12
+ constructor(font) {
13
+ this.font = font;
14
+ }
15
+
16
+ positionGlyphs(glyphs, positions) {
17
+ // find each base + mark cluster, and position the marks relative to the base
18
+ let clusterStart = 0;
19
+ let clusterEnd = 0;
20
+ for (let index = 0; index < glyphs.length; index++) {
21
+ let glyph = glyphs[index];
22
+ if (glyph.isMark) { // TODO: handle ligatures
23
+ clusterEnd = index;
24
+ } else {
25
+ if (clusterStart !== clusterEnd) {
26
+ this.positionCluster(glyphs, positions, clusterStart, clusterEnd);
27
+ }
28
+
29
+ clusterStart = clusterEnd = index;
30
+ }
31
+ }
32
+
33
+ if (clusterStart !== clusterEnd) {
34
+ this.positionCluster(glyphs, positions, clusterStart, clusterEnd);
35
+ }
36
+
37
+ return positions;
38
+ }
39
+
40
+ positionCluster(glyphs, positions, clusterStart, clusterEnd) {
41
+ let base = glyphs[clusterStart];
42
+ let baseBox = base.cbox.copy();
43
+
44
+ // adjust bounding box for ligature glyphs
45
+ if (base.codePoints.length > 1) {
46
+ // LTR. TODO: RTL support.
47
+ baseBox.minX += ((base.codePoints.length - 1) * baseBox.width) / base.codePoints.length;
48
+ }
49
+
50
+ let xOffset = -positions[clusterStart].xAdvance;
51
+ let yOffset = 0;
52
+ let yGap = this.font.unitsPerEm / 16;
53
+
54
+ // position each of the mark glyphs relative to the base glyph
55
+ for (let index = clusterStart + 1; index <= clusterEnd; index++) {
56
+ let mark = glyphs[index];
57
+ let markBox = mark.cbox;
58
+ let position = positions[index];
59
+
60
+ let combiningClass = this.getCombiningClass(mark.codePoints[0]);
61
+
62
+ if (combiningClass !== 'Not_Reordered') {
63
+ position.xOffset = position.yOffset = 0;
64
+
65
+ // x positioning
66
+ switch (combiningClass) {
67
+ case 'Double_Above':
68
+ case 'Double_Below':
69
+ // LTR. TODO: RTL support.
70
+ position.xOffset += baseBox.minX - markBox.width / 2 - markBox.minX;
71
+ break;
72
+
73
+ case 'Attached_Below_Left':
74
+ case 'Below_Left':
75
+ case 'Above_Left':
76
+ // left align
77
+ position.xOffset += baseBox.minX - markBox.minX;
78
+ break;
79
+
80
+ case 'Attached_Above_Right':
81
+ case 'Below_Right':
82
+ case 'Above_Right':
83
+ // right align
84
+ position.xOffset += baseBox.maxX - markBox.width - markBox.minX;
85
+ break;
86
+
87
+ default: // Attached_Below, Attached_Above, Below, Above, other
88
+ // center align
89
+ position.xOffset += baseBox.minX + (baseBox.width - markBox.width) / 2 - markBox.minX;
90
+ }
91
+
92
+ // y positioning
93
+ switch (combiningClass) {
94
+ case 'Double_Below':
95
+ case 'Below_Left':
96
+ case 'Below':
97
+ case 'Below_Right':
98
+ case 'Attached_Below_Left':
99
+ case 'Attached_Below':
100
+ // add a small gap between the glyphs if they are not attached
101
+ if (combiningClass === 'Attached_Below_Left' || combiningClass === 'Attached_Below') {
102
+ baseBox.minY += yGap;
103
+ }
104
+
105
+ position.yOffset = -baseBox.minY - markBox.maxY;
106
+ baseBox.minY += markBox.height;
107
+ break;
108
+
109
+ case 'Double_Above':
110
+ case 'Above_Left':
111
+ case 'Above':
112
+ case 'Above_Right':
113
+ case 'Attached_Above':
114
+ case 'Attached_Above_Right':
115
+ // add a small gap between the glyphs if they are not attached
116
+ if (combiningClass === 'Attached_Above' || combiningClass === 'Attached_Above_Right') {
117
+ baseBox.maxY += yGap;
118
+ }
119
+
120
+ position.yOffset = baseBox.maxY - markBox.minY;
121
+ baseBox.maxY += markBox.height;
122
+ break;
123
+ }
124
+
125
+ position.xAdvance = position.yAdvance = 0;
126
+ position.xOffset += xOffset;
127
+ position.yOffset += yOffset;
128
+
129
+ } else {
130
+ xOffset -= position.xAdvance;
131
+ yOffset -= position.yAdvance;
132
+ }
133
+ }
134
+
135
+ return;
136
+ }
137
+
138
+ getCombiningClass(codePoint) {
139
+ let combiningClass = getCombiningClass(codePoint);
140
+
141
+ // Thai / Lao need some per-character work
142
+ if ((codePoint & ~0xff) === 0x0e00) {
143
+ if (combiningClass === 'Not_Reordered') {
144
+ switch (codePoint) {
145
+ case 0x0e31:
146
+ case 0x0e34:
147
+ case 0x0e35:
148
+ case 0x0e36:
149
+ case 0x0e37:
150
+ case 0x0e47:
151
+ case 0x0e4c:
152
+ case 0x0e4d:
153
+ case 0x0e4e:
154
+ return 'Above_Right';
155
+
156
+ case 0x0eb1:
157
+ case 0x0eb4:
158
+ case 0x0eb5:
159
+ case 0x0eb6:
160
+ case 0x0eb7:
161
+ case 0x0ebb:
162
+ case 0x0ecc:
163
+ case 0x0ecd:
164
+ return 'Above';
165
+
166
+ case 0x0ebc:
167
+ return 'Below';
168
+ }
169
+ } else if (codePoint === 0x0e3a) { // virama
170
+ return 'Below_Right';
171
+ }
172
+ }
173
+
174
+ switch (combiningClass) {
175
+ // Hebrew
176
+
177
+ case 'CCC10': // sheva
178
+ case 'CCC11': // hataf segol
179
+ case 'CCC12': // hataf patah
180
+ case 'CCC13': // hataf qamats
181
+ case 'CCC14': // hiriq
182
+ case 'CCC15': // tsere
183
+ case 'CCC16': // segol
184
+ case 'CCC17': // patah
185
+ case 'CCC18': // qamats
186
+ case 'CCC20': // qubuts
187
+ case 'CCC22': // meteg
188
+ return 'Below';
189
+
190
+ case 'CCC23': // rafe
191
+ return 'Attached_Above';
192
+
193
+ case 'CCC24': // shin dot
194
+ return 'Above_Right';
195
+
196
+ case 'CCC25': // sin dot
197
+ case 'CCC19': // holam
198
+ return 'Above_Left';
199
+
200
+ case 'CCC26': // point varika
201
+ return 'Above';
202
+
203
+ case 'CCC21': // dagesh
204
+ break;
205
+
206
+ // Arabic and Syriac
207
+
208
+ case 'CCC27': // fathatan
209
+ case 'CCC28': // dammatan
210
+ case 'CCC30': // fatha
211
+ case 'CCC31': // damma
212
+ case 'CCC33': // shadda
213
+ case 'CCC34': // sukun
214
+ case 'CCC35': // superscript alef
215
+ case 'CCC36': // superscript alaph
216
+ return 'Above';
217
+
218
+ case 'CCC29': // kasratan
219
+ case 'CCC32': // kasra
220
+ return 'Below';
221
+
222
+ // Thai
223
+
224
+ case 'CCC103': // sara u / sara uu
225
+ return 'Below_Right';
226
+
227
+ case 'CCC107': // mai
228
+ return 'Above_Right';
229
+
230
+ // Lao
231
+
232
+ case 'CCC118': // sign u / sign uu
233
+ return 'Below';
234
+
235
+ case 'CCC122': // mai
236
+ return 'Above';
237
+
238
+ // Tibetan
239
+
240
+ case 'CCC129': // sign aa
241
+ case 'CCC132': // sign u
242
+ return 'Below';
243
+
244
+ case 'CCC130': // sign i
245
+ return 'Above';
246
+ }
247
+
248
+ return combiningClass;
249
+ }
250
+ }
package/src/node.js ADDED
@@ -0,0 +1,16 @@
1
+ import { registerFormat } from './base';
2
+ import TTFFont from './TTFFont';
3
+ import WOFFFont from './WOFFFont';
4
+ import WOFF2Font from './WOFF2Font';
5
+ import TrueTypeCollection from './TrueTypeCollection';
6
+ import DFont from './DFont';
7
+
8
+ // Register font formats
9
+ registerFormat(TTFFont);
10
+ registerFormat(WOFFFont);
11
+ registerFormat(WOFF2Font);
12
+ registerFormat(TrueTypeCollection);
13
+ registerFormat(DFont);
14
+
15
+ export * from './base';
16
+ export * from './fs';
@@ -0,0 +1,311 @@
1
+ import OTProcessor from './OTProcessor';
2
+
3
+ export default class GPOSProcessor extends OTProcessor {
4
+ applyPositionValue(sequenceIndex, value) {
5
+ let position = this.positions[this.glyphIterator.peekIndex(sequenceIndex)];
6
+ if (value.xAdvance != null) position.xAdvance += value.xAdvance;
7
+ if (value.yAdvance != null) position.yAdvance += value.yAdvance;
8
+ if (value.xPlacement != null) position.xOffset += value.xPlacement;
9
+ if (value.yPlacement != null) position.yOffset += value.yPlacement;
10
+
11
+ // Adjustments for font variations
12
+ let variationProcessor = this.font._variationProcessor;
13
+ let variationStore = this.font.GDEF && this.font.GDEF.itemVariationStore;
14
+ if (variationProcessor && variationStore) {
15
+ if (value.xPlaDevice) {
16
+ position.xOffset += variationProcessor.getDelta(variationStore, value.xPlaDevice.a, value.xPlaDevice.b);
17
+ }
18
+
19
+ if (value.yPlaDevice) {
20
+ position.yOffset += variationProcessor.getDelta(variationStore, value.yPlaDevice.a, value.yPlaDevice.b);
21
+ }
22
+
23
+ if (value.xAdvDevice) {
24
+ position.xAdvance += variationProcessor.getDelta(variationStore, value.xAdvDevice.a, value.xAdvDevice.b);
25
+ }
26
+
27
+ if (value.yAdvDevice) {
28
+ position.yAdvance += variationProcessor.getDelta(variationStore, value.yAdvDevice.a, value.yAdvDevice.b);
29
+ }
30
+ }
31
+
32
+ // TODO: device tables
33
+ }
34
+
35
+ applyLookup(lookupType, table) {
36
+ switch (lookupType) {
37
+ case 1: { // Single positioning value
38
+ let index = this.coverageIndex(table.coverage);
39
+ if (index === -1) return false;
40
+
41
+ switch (table.version) {
42
+ case 1:
43
+ this.applyPositionValue(0, table.value);
44
+ break;
45
+
46
+ case 2:
47
+ this.applyPositionValue(0, table.values.get(index));
48
+ break;
49
+ }
50
+
51
+ return true;
52
+ }
53
+
54
+ case 2: { // Pair Adjustment Positioning
55
+ let nextGlyph = this.glyphIterator.peek();
56
+ if (!nextGlyph) return false;
57
+
58
+ let index = this.coverageIndex(table.coverage);
59
+ if (index === -1) return false;
60
+
61
+ switch (table.version) {
62
+ case 1: { // Adjustments for glyph pairs
63
+ let set = table.pairSets.get(index);
64
+
65
+ for (let pair of set) {
66
+ if (pair.secondGlyph === nextGlyph.id) {
67
+ this.applyPositionValue(0, pair.value1);
68
+ this.applyPositionValue(1, pair.value2);
69
+ return true;
70
+ }
71
+ }
72
+
73
+ return false;
74
+ }
75
+
76
+ case 2: { // Class pair adjustment
77
+ let class1 = this.getClassID(this.glyphIterator.cur.id, table.classDef1);
78
+ let class2 = this.getClassID(nextGlyph.id, table.classDef2);
79
+ if (class1 === -1 || class2 === -1) return false;
80
+
81
+ let pair = table.classRecords.get(class1).get(class2);
82
+ this.applyPositionValue(0, pair.value1);
83
+ this.applyPositionValue(1, pair.value2);
84
+ return true;
85
+ }
86
+ }
87
+
88
+ return false;
89
+ }
90
+
91
+ case 3: { // Cursive Attachment Positioning
92
+ let nextIndex = this.glyphIterator.peekIndex();
93
+ let nextGlyph = this.glyphs[nextIndex];
94
+ if (!nextGlyph) return false;
95
+
96
+ let curRecord = table.entryExitRecords[this.coverageIndex(table.coverage)];
97
+ if (!curRecord || !curRecord.exitAnchor) return false;
98
+
99
+ let nextRecord = table.entryExitRecords[this.coverageIndex(table.coverage, nextGlyph.id)];
100
+ if (!nextRecord || !nextRecord.entryAnchor) return false;
101
+
102
+ let entry = this.getAnchor(nextRecord.entryAnchor);
103
+ let exit = this.getAnchor(curRecord.exitAnchor);
104
+
105
+ let cur = this.positions[this.glyphIterator.index];
106
+ let next = this.positions[nextIndex];
107
+ let d;
108
+
109
+ switch (this.direction) {
110
+ case 'ltr':
111
+ cur.xAdvance = exit.x + cur.xOffset;
112
+
113
+ d = entry.x + next.xOffset;
114
+ next.xAdvance -= d;
115
+ next.xOffset -= d;
116
+ break;
117
+
118
+ case 'rtl':
119
+ d = exit.x + cur.xOffset;
120
+ cur.xAdvance -= d;
121
+ cur.xOffset -= d;
122
+ next.xAdvance = entry.x + next.xOffset;
123
+ break;
124
+ }
125
+
126
+ if (this.glyphIterator.flags.rightToLeft) {
127
+ this.glyphIterator.cur.cursiveAttachment = nextIndex;
128
+ cur.yOffset = entry.y - exit.y;
129
+ } else {
130
+ nextGlyph.cursiveAttachment = this.glyphIterator.index;
131
+ cur.yOffset = exit.y - entry.y;
132
+ }
133
+
134
+ return true;
135
+ }
136
+
137
+ case 4: { // Mark to base positioning
138
+ let markIndex = this.coverageIndex(table.markCoverage);
139
+ if (markIndex === -1) return false;
140
+
141
+ // search backward for a base glyph
142
+ let baseGlyphIndex = this.glyphIterator.index;
143
+ while (--baseGlyphIndex >= 0 && (this.glyphs[baseGlyphIndex].isMark || this.glyphs[baseGlyphIndex].ligatureComponent > 0));
144
+
145
+ if (baseGlyphIndex < 0) return false;
146
+
147
+ let baseIndex = this.coverageIndex(table.baseCoverage, this.glyphs[baseGlyphIndex].id);
148
+ if (baseIndex === -1) return false;
149
+
150
+ let markRecord = table.markArray[markIndex];
151
+ let baseAnchor = table.baseArray[baseIndex][markRecord.class];
152
+ return this.applyAnchor(markRecord, baseAnchor, baseGlyphIndex);
153
+ }
154
+
155
+ case 5: { // Mark to ligature positioning
156
+ let markIndex = this.coverageIndex(table.markCoverage);
157
+ if (markIndex === -1) return false;
158
+
159
+ // search backward for a base glyph
160
+ let baseGlyphIndex = this.glyphIterator.index;
161
+ while (--baseGlyphIndex >= 0 && this.glyphs[baseGlyphIndex].isMark);
162
+
163
+ if (baseGlyphIndex < 0) return false;
164
+
165
+ let ligIndex = this.coverageIndex(table.ligatureCoverage, this.glyphs[baseGlyphIndex].id);
166
+ if (ligIndex === -1) return false;
167
+
168
+ let ligAttach = table.ligatureArray[ligIndex];
169
+ let markGlyph = this.glyphIterator.cur;
170
+ let ligGlyph = this.glyphs[baseGlyphIndex];
171
+ let compIndex = ligGlyph.ligatureID && ligGlyph.ligatureID === markGlyph.ligatureID && (markGlyph.ligatureComponent > 0)
172
+ ? Math.min(markGlyph.ligatureComponent, ligGlyph.codePoints.length) - 1
173
+ : ligGlyph.codePoints.length - 1;
174
+
175
+ let markRecord = table.markArray[markIndex];
176
+ let baseAnchor = ligAttach[compIndex][markRecord.class];
177
+ return this.applyAnchor(markRecord, baseAnchor, baseGlyphIndex);
178
+ }
179
+
180
+ case 6: { // Mark to mark positioning
181
+ let mark1Index = this.coverageIndex(table.mark1Coverage);
182
+ if (mark1Index === -1) return false;
183
+
184
+ // get the previous mark to attach to
185
+ let prevIndex = this.glyphIterator.peekIndex(-1);
186
+ let prev = this.glyphs[prevIndex];
187
+ if (!prev || !prev.isMark) return false;
188
+
189
+ let cur = this.glyphIterator.cur;
190
+
191
+ // The following logic was borrowed from Harfbuzz
192
+ let good = false;
193
+ if (cur.ligatureID === prev.ligatureID) {
194
+ if (!cur.ligatureID) { // Marks belonging to the same base
195
+ good = true;
196
+ } else if (cur.ligatureComponent === prev.ligatureComponent) { // Marks belonging to the same ligature component
197
+ good = true;
198
+ }
199
+ } else {
200
+ // If ligature ids don't match, it may be the case that one of the marks
201
+ // itself is a ligature, in which case match.
202
+ if ((cur.ligatureID && !cur.ligatureComponent) || (prev.ligatureID && !prev.ligatureComponent)) {
203
+ good = true;
204
+ }
205
+ }
206
+
207
+ if (!good) return false;
208
+
209
+ let mark2Index = this.coverageIndex(table.mark2Coverage, prev.id);
210
+ if (mark2Index === -1) return false;
211
+
212
+ let markRecord = table.mark1Array[mark1Index];
213
+ let baseAnchor = table.mark2Array[mark2Index][markRecord.class];
214
+ return this.applyAnchor(markRecord, baseAnchor, prevIndex);
215
+ }
216
+
217
+ case 7: // Contextual positioning
218
+ return this.applyContext(table);
219
+
220
+ case 8: // Chaining contextual positioning
221
+ return this.applyChainingContext(table);
222
+
223
+ case 9: // Extension positioning
224
+ return this.applyLookup(table.lookupType, table.extension);
225
+
226
+ default:
227
+ throw new Error(`Unsupported GPOS table: ${lookupType}`);
228
+ }
229
+ }
230
+
231
+ applyAnchor(markRecord, baseAnchor, baseGlyphIndex) {
232
+ // NULL offset = no anchor for this class; false so later subtables can try (HarfBuzz).
233
+ if (!baseAnchor || !markRecord.markAnchor) return false;
234
+
235
+ let baseCoords = this.getAnchor(baseAnchor);
236
+ let markCoords = this.getAnchor(markRecord.markAnchor);
237
+ let markPos = this.positions[this.glyphIterator.index];
238
+
239
+ markPos.xOffset = baseCoords.x - markCoords.x;
240
+ markPos.yOffset = baseCoords.y - markCoords.y;
241
+ this.glyphIterator.cur.markAttachment = baseGlyphIndex;
242
+ return true;
243
+ }
244
+
245
+ getAnchor(anchor) {
246
+ // TODO: contour point, device tables
247
+ let x = anchor.xCoordinate;
248
+ let y = anchor.yCoordinate;
249
+
250
+ // Adjustments for font variations
251
+ let variationProcessor = this.font._variationProcessor;
252
+ let variationStore = this.font.GDEF && this.font.GDEF.itemVariationStore;
253
+ if (variationProcessor && variationStore) {
254
+ if (anchor.xDeviceTable) {
255
+ x += variationProcessor.getDelta(variationStore, anchor.xDeviceTable.a, anchor.xDeviceTable.b);
256
+ }
257
+
258
+ if (anchor.yDeviceTable) {
259
+ y += variationProcessor.getDelta(variationStore, anchor.yDeviceTable.a, anchor.yDeviceTable.b);
260
+ }
261
+ }
262
+
263
+ return { x, y };
264
+ }
265
+
266
+ applyFeatures(userFeatures, glyphs, advances) {
267
+ super.applyFeatures(userFeatures, glyphs, advances);
268
+
269
+ for (var i = 0; i < this.glyphs.length; i++) {
270
+ this.fixCursiveAttachment(i);
271
+ }
272
+
273
+ this.fixMarkAttachment();
274
+ }
275
+
276
+ fixCursiveAttachment(i) {
277
+ let glyph = this.glyphs[i];
278
+ if (glyph.cursiveAttachment != null) {
279
+ let j = glyph.cursiveAttachment;
280
+
281
+ glyph.cursiveAttachment = null;
282
+ this.fixCursiveAttachment(j);
283
+
284
+ this.positions[i].yOffset += this.positions[j].yOffset;
285
+ }
286
+ }
287
+
288
+ fixMarkAttachment() {
289
+ for (let i = 0; i < this.glyphs.length; i++) {
290
+ let glyph = this.glyphs[i];
291
+ if (glyph.markAttachment != null) {
292
+ let j = glyph.markAttachment;
293
+
294
+ this.positions[i].xOffset += this.positions[j].xOffset;
295
+ this.positions[i].yOffset += this.positions[j].yOffset;
296
+
297
+ if (this.direction === 'ltr') {
298
+ for (let k = j; k < i; k++) {
299
+ this.positions[i].xOffset -= this.positions[k].xAdvance;
300
+ this.positions[i].yOffset -= this.positions[k].yAdvance;
301
+ }
302
+ } else {
303
+ for (let k = j + 1; k < i + 1; k++) {
304
+ this.positions[i].xOffset += this.positions[k].xAdvance;
305
+ this.positions[i].yOffset += this.positions[k].yAdvance;
306
+ }
307
+ }
308
+ }
309
+ }
310
+ }
311
+ }