@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,238 @@
1
+ import DefaultShaper from './DefaultShaper';
2
+ import GlyphInfo from '../GlyphInfo';
3
+
4
+ /**
5
+ * Thai / Lao shaper (HarfBuzz hb-ot-shaper-thai.cc):
6
+ * 1. Decompose SARA AM → NIKHAHIT + SARA AA and reorder NIKHAHIT past above marks
7
+ * 2. PUA tone/vowel shift fallback for legacy fonts without Thai GSUB
8
+ */
9
+ export default class ThaiShaper extends DefaultShaper {
10
+ static assignFeatures(plan, glyphs) {
11
+ super.assignFeatures(plan, glyphs);
12
+ preprocessThai(glyphs, plan.font);
13
+ // HB: PUA only when buffer script is Thai and the font has no Thai GSUB.
14
+ if (isThaiBufferScript(plan.bufferScript || plan.script) && !hasThaiGsub(plan.font)) {
15
+ applyThaiPuaShaping(glyphs, plan.font);
16
+ }
17
+ }
18
+ }
19
+
20
+ // Thai/Lao SARA AM differ only by the 0x80 bit (U+0E33 / U+0EB3).
21
+ function isSaraAm(u) {
22
+ return (u & ~0x0080) === 0x0E33;
23
+ }
24
+
25
+ function nikhahitFromSaraAm(u) {
26
+ return u - 0x0E33 + 0x0E4D;
27
+ }
28
+
29
+ function saraAaFromSaraAm(u) {
30
+ return u - 1;
31
+ }
32
+
33
+ // Above-base marks (Thai; Lao is the same set with +0x80).
34
+ function isAboveBaseMark(u) {
35
+ const c = u & ~0x0080;
36
+ return c === 0x0E31
37
+ || (c >= 0x0E34 && c <= 0x0E37)
38
+ || (c >= 0x0E47 && c <= 0x0E4E)
39
+ || c === 0x0E3B;
40
+ }
41
+
42
+ function preprocessThai(glyphs, font) {
43
+ let i = 0;
44
+ while (i < glyphs.length) {
45
+ const u = glyphs[i].codePoints[0];
46
+ if (!isSaraAm(u)) {
47
+ i++;
48
+ continue;
49
+ }
50
+
51
+ const features = glyphs[i].features;
52
+ const nikhahit = makeGlyph(font, nikhahitFromSaraAm(u), features);
53
+ const saraAa = makeGlyph(font, saraAaFromSaraAm(u), features);
54
+ glyphs.splice(i, 1, nikhahit, saraAa);
55
+
56
+ // Walk NIKHAHIT backward over above-base marks toward the base.
57
+ let nikhahitIndex = i;
58
+ let target = nikhahitIndex;
59
+ while (target > 0 && isAboveBaseMark(glyphs[target - 1].codePoints[0])) {
60
+ target--;
61
+ }
62
+ if (target !== nikhahitIndex) {
63
+ const moved = glyphs.splice(nikhahitIndex, 1)[0];
64
+ glyphs.splice(target, 0, moved);
65
+ }
66
+
67
+ i += 2;
68
+ }
69
+ }
70
+
71
+ function makeGlyph(font, codePoint, features) {
72
+ const id = font.glyphForCodePoint(codePoint).id;
73
+ return new GlyphInfo(font, id, [codePoint], features);
74
+ }
75
+
76
+ // PUA fallback: above/below state machines remap marks (and some bases) to
77
+ // Windows/Mac private-use codepoints when the font ships those glyphs.
78
+ const NOP = 0;
79
+ const SD = 1;
80
+ const SL = 2;
81
+ const SDL = 3;
82
+ const RD = 4;
83
+
84
+ const NC = 0; // normal consonant
85
+ const AC = 1; // ascender (1B/1D/1F)
86
+ const RC = 2; // removable descender (0D/10)
87
+ const DC = 3; // strict descender (0E/0F)
88
+ const NOT_CONSONANT = 4;
89
+
90
+ const AV = 0; // above-base vowel/mark
91
+ const BV = 1; // below-base vowel/mark
92
+ const T = 2; // tone mark
93
+ const NOT_MARK = 3;
94
+
95
+ function getConsonantType(u) {
96
+ if (u === 0x0E1B || u === 0x0E1D || u === 0x0E1F) return AC;
97
+ if (u === 0x0E0D || u === 0x0E10) return RC;
98
+ if (u === 0x0E0E || u === 0x0E0F) return DC;
99
+ if (u >= 0x0E01 && u <= 0x0E2E) return NC;
100
+ return NOT_CONSONANT;
101
+ }
102
+
103
+ function getMarkType(u) {
104
+ if (
105
+ u === 0x0E31 ||
106
+ (u >= 0x0E34 && u <= 0x0E37) ||
107
+ u === 0x0E47 ||
108
+ (u >= 0x0E4D && u <= 0x0E4E)
109
+ ) {
110
+ return AV;
111
+ }
112
+ if (u >= 0x0E38 && u <= 0x0E3A) return BV;
113
+ if (u >= 0x0E48 && u <= 0x0E4C) return T;
114
+ return NOT_MARK;
115
+ }
116
+
117
+ const T0 = 0, T1 = 1, T2 = 2, T3 = 3;
118
+ const ABOVE_START_STATE = [T0, T1, T0, T0, T3]; // NC AC RC DC NOT_CONSONANT
119
+ const ABOVE_STATE_MACHINE = [
120
+ // AV BV T
121
+ [[NOP, T3], [NOP, T0], [SD, T3]], // T0
122
+ [[SL, T2], [NOP, T1], [SDL, T2]], // T1
123
+ [[NOP, T3], [NOP, T2], [SL, T3]], // T2
124
+ [[NOP, T3], [NOP, T3], [NOP, T3]] // T3
125
+ ];
126
+
127
+ const B0 = 0, B1 = 1, B2 = 2;
128
+ const BELOW_START_STATE = [B0, B0, B1, B2, B2];
129
+ const BELOW_STATE_MACHINE = [
130
+ // AV BV T
131
+ [[NOP, B0], [NOP, B2], [NOP, B0]], // B0
132
+ [[NOP, B1], [RD, B2], [NOP, B1]], // B1
133
+ [[NOP, B2], [SD, B2], [NOP, B2]] // B2
134
+ ];
135
+
136
+ // [original, Windows PUA, Mac PUA] per action
137
+ const PUA_MAPPINGS = {
138
+ [SD]: [
139
+ [0x0E48, 0xF70A, 0xF88B], // MAI EK
140
+ [0x0E49, 0xF70B, 0xF88E], // MAI THO
141
+ [0x0E4A, 0xF70C, 0xF891], // MAI TRI
142
+ [0x0E4B, 0xF70D, 0xF894], // MAI CHATTAWA
143
+ [0x0E4C, 0xF70E, 0xF897], // THANTHAKHAT
144
+ [0x0E38, 0xF718, 0xF89B], // SARA U
145
+ [0x0E39, 0xF719, 0xF89C], // SARA UU
146
+ [0x0E3A, 0xF71A, 0xF89D] // PHINTHU
147
+ ],
148
+ [SDL]: [
149
+ [0x0E48, 0xF705, 0xF88C],
150
+ [0x0E49, 0xF706, 0xF88F],
151
+ [0x0E4A, 0xF707, 0xF892],
152
+ [0x0E4B, 0xF708, 0xF895],
153
+ [0x0E4C, 0xF709, 0xF898]
154
+ ],
155
+ [SL]: [
156
+ [0x0E48, 0xF713, 0xF88A],
157
+ [0x0E49, 0xF714, 0xF88D],
158
+ [0x0E4A, 0xF715, 0xF890],
159
+ [0x0E4B, 0xF716, 0xF893],
160
+ [0x0E4C, 0xF717, 0xF896],
161
+ [0x0E31, 0xF710, 0xF884], // MAI HAN-AKAT
162
+ [0x0E34, 0xF701, 0xF885], // SARA I
163
+ [0x0E35, 0xF702, 0xF886], // SARA II
164
+ [0x0E36, 0xF703, 0xF887], // SARA UE
165
+ [0x0E37, 0xF704, 0xF888], // SARA UEE
166
+ [0x0E47, 0xF712, 0xF889], // MAITAIKHU
167
+ [0x0E4D, 0xF711, 0xF899] // NIKHAHIT
168
+ ],
169
+ [RD]: [
170
+ [0x0E0D, 0xF70F, 0xF89A], // YO YING
171
+ [0x0E10, 0xF700, 0xF89E] // THO THAN
172
+ ]
173
+ };
174
+
175
+ function thaiPuaShape(u, action, font) {
176
+ if (action === NOP) return u;
177
+ const mappings = PUA_MAPPINGS[action];
178
+ if (!mappings) return u;
179
+ for (const [orig, winPua, macPua] of mappings) {
180
+ if (orig !== u) continue;
181
+ if (font.hasGlyphForCodePoint(winPua)) return winPua;
182
+ if (font.hasGlyphForCodePoint(macPua)) return macPua;
183
+ break;
184
+ }
185
+ return u;
186
+ }
187
+
188
+ function replaceGlyphCodePoint(glyphs, index, newCp, font) {
189
+ const prev = glyphs[index];
190
+ if (prev.codePoints[0] === newCp) return;
191
+ glyphs[index] = new GlyphInfo(font, font.glyphForCodePoint(newCp).id, [newCp], prev.features);
192
+ }
193
+
194
+ function applyThaiPuaShaping(glyphs, font) {
195
+ let aboveState = ABOVE_START_STATE[NOT_CONSONANT];
196
+ let belowState = BELOW_START_STATE[NOT_CONSONANT];
197
+ let baseIndex = 0;
198
+
199
+ for (let i = 0; i < glyphs.length; i++) {
200
+ const u = glyphs[i].codePoints[0];
201
+ const mt = getMarkType(u);
202
+
203
+ if (mt === NOT_MARK) {
204
+ const ct = getConsonantType(u);
205
+ aboveState = ABOVE_START_STATE[ct];
206
+ belowState = BELOW_START_STATE[ct];
207
+ baseIndex = i;
208
+ continue;
209
+ }
210
+
211
+ const [aboveAction, aboveNext] = ABOVE_STATE_MACHINE[aboveState][mt];
212
+ const [belowAction, belowNext] = BELOW_STATE_MACHINE[belowState][mt];
213
+ aboveState = aboveNext;
214
+ belowState = belowNext;
215
+
216
+ // At most one action is non-NOP.
217
+ const action = aboveAction !== NOP ? aboveAction : belowAction;
218
+ if (action === NOP) continue;
219
+
220
+ if (action === RD) {
221
+ replaceGlyphCodePoint(glyphs, baseIndex, thaiPuaShape(glyphs[baseIndex].codePoints[0], action, font), font);
222
+ } else {
223
+ replaceGlyphCodePoint(glyphs, i, thaiPuaShape(u, action, font), font);
224
+ }
225
+ }
226
+ }
227
+
228
+ // Gate PUA shaping on absence of Thai GSUB (HB plan->map.found_script[0]).
229
+ function hasThaiGsub(font) {
230
+ const gsub = font.GSUB;
231
+ if (!gsub || !gsub.scriptList) return false;
232
+ return gsub.scriptList.some(entry => entry.tag === 'thai' || entry.tag === 'tha2');
233
+ }
234
+
235
+ function isThaiBufferScript(script) {
236
+ if (Array.isArray(script)) return script.includes('thai');
237
+ return script === 'thai';
238
+ }
@@ -0,0 +1,186 @@
1
+ import DefaultShaper from './DefaultShaper';
2
+ import StateMachine from 'dfa';
3
+ import UnicodeTrie from '../../packages/unicode-trie/index.js';
4
+ import GlyphInfo from '../GlyphInfo';
5
+ import useData from './use.json';
6
+ import { decodeBase64 } from '../../utils';
7
+ import useTrie from './use.trie';
8
+
9
+ const {categories, decompositions} = useData;
10
+ const trie = new UnicodeTrie(decodeBase64(useTrie));
11
+ const stateMachine = new StateMachine(useData);
12
+
13
+ /**
14
+ * This shaper is an implementation of the Universal Shaping Engine, which
15
+ * uses Unicode data to shape a number of scripts without a dedicated shaping engine.
16
+ * See https://www.microsoft.com/typography/OpenTypeDev/USE/intro.htm.
17
+ */
18
+ export default class UniversalShaper extends DefaultShaper {
19
+ static zeroMarkWidths = 'BEFORE_GPOS';
20
+ static planFeatures(plan) {
21
+ plan.addStage(setupSyllables);
22
+
23
+ // Default glyph pre-processing group
24
+ plan.addStage(['locl', 'ccmp', 'nukt', 'akhn']);
25
+
26
+ // Reordering group
27
+ plan.addStage(clearSubstitutionFlags);
28
+ plan.addStage(['rphf'], false);
29
+ plan.addStage(recordRphf);
30
+ plan.addStage(clearSubstitutionFlags);
31
+ plan.addStage(['pref']);
32
+ plan.addStage(recordPref);
33
+
34
+ // Orthographic unit shaping group
35
+ plan.addStage(['rkrf', 'abvf', 'blwf', 'half', 'pstf', 'vatu', 'cjct']);
36
+ plan.addStage(reorder);
37
+
38
+ // Topographical features
39
+ // Scripts that need this are handled by the Arabic shaper, not implemented here for now.
40
+ // plan.addStage(['isol', 'init', 'medi', 'fina', 'med2', 'fin2', 'fin3'], false);
41
+
42
+ // Standard topographic presentation and positional feature application
43
+ plan.addStage(['abvs', 'blws', 'pres', 'psts', 'dist', 'abvm', 'blwm']);
44
+ }
45
+
46
+ static assignFeatures(plan, glyphs) {
47
+ // Decompose split vowels
48
+ // TODO: do this in a more general unicode normalizer
49
+ for (let i = glyphs.length - 1; i >= 0; i--) {
50
+ let codepoint = glyphs[i].codePoints[0];
51
+ if (decompositions[codepoint]) {
52
+ let decomposed = decompositions[codepoint].map(c => {
53
+ let g = plan.font.glyphForCodePoint(c);
54
+ return new GlyphInfo(plan.font, g.id, [c], glyphs[i].features);
55
+ });
56
+
57
+ glyphs.splice(i, 1, ...decomposed);
58
+ }
59
+ }
60
+ }
61
+ }
62
+
63
+ function useCategory(glyph) {
64
+ return trie.get(glyph.codePoints[0]);
65
+ }
66
+
67
+ class USEInfo {
68
+ constructor(category, syllableType, syllable) {
69
+ this.category = category;
70
+ this.syllableType = syllableType;
71
+ this.syllable = syllable;
72
+ }
73
+ }
74
+
75
+ function setupSyllables(font, glyphs) {
76
+ let syllable = 0;
77
+ for (let [start, end, tags] of stateMachine.match(glyphs.map(useCategory))) {
78
+ ++syllable;
79
+
80
+ // Create shaper info
81
+ for (let i = start; i <= end; i++) {
82
+ glyphs[i].shaperInfo = new USEInfo(categories[useCategory(glyphs[i])], tags[0], syllable);
83
+ }
84
+
85
+ // Assign rphf feature
86
+ let limit = glyphs[start].shaperInfo.category === 'R' ? 1 : Math.min(3, end - start);
87
+ for (let i = start; i < start + limit; i++) {
88
+ glyphs[i].features.rphf = true;
89
+ }
90
+ }
91
+ }
92
+
93
+ function clearSubstitutionFlags(font, glyphs) {
94
+ for (let glyph of glyphs) {
95
+ glyph.substituted = false;
96
+ }
97
+ }
98
+
99
+ function recordRphf(font, glyphs) {
100
+ for (let glyph of glyphs) {
101
+ if (glyph.substituted && glyph.features.rphf) {
102
+ // Mark a substituted repha.
103
+ glyph.shaperInfo.category = 'R';
104
+ }
105
+ }
106
+ }
107
+
108
+ function recordPref(font, glyphs) {
109
+ for (let glyph of glyphs) {
110
+ if (glyph.substituted) {
111
+ // Mark a substituted pref as VPre, as they behave the same way.
112
+ glyph.shaperInfo.category = 'VPre';
113
+ }
114
+ }
115
+ }
116
+
117
+ function reorder(font, glyphs) {
118
+ let dottedCircle = font.glyphForCodePoint(0x25cc).id;
119
+
120
+ for (let start = 0, end = nextSyllable(glyphs, 0); start < glyphs.length; start = end, end = nextSyllable(glyphs, start)) {
121
+ let i, j;
122
+ let info = glyphs[start].shaperInfo;
123
+ let type = info.syllableType;
124
+
125
+ // Only a few syllable types need reordering.
126
+ if (type !== 'virama_terminated_cluster' && type !== 'standard_cluster' && type !== 'broken_cluster') {
127
+ continue;
128
+ }
129
+
130
+ // Insert a dotted circle glyph in broken clusters.
131
+ if (type === 'broken_cluster' && dottedCircle) {
132
+ let g = new GlyphInfo(font, dottedCircle, [0x25cc]);
133
+ g.shaperInfo = info;
134
+
135
+ // Insert after possible Repha.
136
+ for (i = start; i < end && glyphs[i].shaperInfo.category === 'R'; i++);
137
+ glyphs.splice(i + 1, 0, g);
138
+ end++;
139
+ }
140
+
141
+ // Move things forward.
142
+ if (info.category === 'R' && end - start > 1) {
143
+ // Got a repha. Reorder it to after first base, before first halant.
144
+ for (i = start + 1; i < end; i++) {
145
+ info = glyphs[i].shaperInfo;
146
+ if (isBase(info) || isHalant(glyphs[i])) {
147
+ // If we hit a halant, move before it; otherwise it's a base: move to it's
148
+ // place, and shift things in between backward.
149
+ if (isHalant(glyphs[i])) {
150
+ i--;
151
+ }
152
+
153
+ glyphs.splice(start, 0, ...glyphs.splice(start + 1, i - start), glyphs[i]);
154
+ break;
155
+ }
156
+ }
157
+ }
158
+
159
+ // Move things back.
160
+ for (i = start, j = end; i < end; i++) {
161
+ info = glyphs[i].shaperInfo;
162
+ if (isBase(info) || isHalant(glyphs[i])) {
163
+ // If we hit a halant, move after it; otherwise it's a base: move to it's
164
+ // place, and shift things in between backward.
165
+ j = isHalant(glyphs[i]) ? i + 1 : i;
166
+ } else if ((info.category === 'VPre' || info.category === 'VMPre') && j < i) {
167
+ glyphs.splice(j, 1, glyphs[i], ...glyphs.splice(j, i - j));
168
+ }
169
+ }
170
+ }
171
+ }
172
+
173
+ function nextSyllable(glyphs, start) {
174
+ if (start >= glyphs.length) return start;
175
+ let syllable = glyphs[start].shaperInfo.syllable;
176
+ while (++start < glyphs.length && glyphs[start].shaperInfo.syllable === syllable);
177
+ return start;
178
+ }
179
+
180
+ function isHalant(glyph) {
181
+ return glyph.shaperInfo.category === 'H' && !glyph.isLigated;
182
+ }
183
+
184
+ function isBase(info) {
185
+ return info.category === 'B' || info.category === 'GB';
186
+ }
Binary file
@@ -0,0 +1,208 @@
1
+ import codepoints from 'codepoints';
2
+ import fs from 'fs';
3
+ import UnicodeTrieBuilder from '../../packages/unicode-trie/builder.js';
4
+ import dfa from 'dfa/compile.js';
5
+ import { CATEGORIES, POSITIONS, CONSONANT_FLAGS } from './indic-data.js';
6
+
7
+ const compile = dfa.default;
8
+
9
+ const CATEGORY_MAP = {
10
+ Avagraha: 'Symbol',
11
+ Bindu: 'SM',
12
+ Brahmi_Joining_Number: 'Placeholder',
13
+ Cantillation_Mark: 'A',
14
+ Consonant: 'C',
15
+ Consonant_Dead: 'C',
16
+ Consonant_Final: 'CM',
17
+ Consonant_Head_Letter: 'C',
18
+ Consonant_Killer: 'M',
19
+ Consonant_Medial: 'CM',
20
+ Consonant_Placeholder: 'Placeholder',
21
+ Consonant_Preceding_Repha: 'Repha',
22
+ Consonant_Prefixed: 'X',
23
+ Consonant_Subjoined: 'CM',
24
+ Consonant_Succeeding_Repha: 'N',
25
+ Consonant_With_Stacker: 'Repha',
26
+ Gemination_Mark: 'SM',
27
+ Invisible_Stacker: 'Coeng',
28
+ Joiner: 'ZWJ',
29
+ Modifying_Letter: 'X',
30
+ Non_Joiner: 'ZWNJ',
31
+ Nukta: 'N',
32
+ Number: 'Placeholder',
33
+ Number_Joiner: 'Placeholder',
34
+ Pure_Killer: 'M',
35
+ Register_Shifter: 'RS',
36
+ Syllable_Modifier: 'M',
37
+ Tone_Letter: 'X',
38
+ Tone_Mark: 'N',
39
+ Virama: 'H',
40
+ Visarga: 'SM',
41
+ Vowel: 'V',
42
+ Vowel_Dependent: 'M',
43
+ Vowel_Independent: 'V'
44
+ };
45
+
46
+ const OVERRIDES = {
47
+ 0x0953: 'SM',
48
+ 0x0954: 'SM',
49
+ 0x0A72: 'C',
50
+ 0x0A73: 'C',
51
+ 0x1CF5: 'C',
52
+ 0x1CF6: 'C',
53
+ 0x1CE2: 'A',
54
+ 0x1CE3: 'A',
55
+ 0x1CE4: 'A',
56
+ 0x1CE5: 'A',
57
+ 0x1CE6: 'A',
58
+ 0x1CE7: 'A',
59
+ 0x1CE8: 'A',
60
+ 0x1CED: 'A',
61
+ 0xA8F2: 'Symbol',
62
+ 0xA8F3: 'Symbol',
63
+ 0xA8F4: 'Symbol',
64
+ 0xA8F5: 'Symbol',
65
+ 0xA8F6: 'Symbol',
66
+ 0xA8F7: 'Symbol',
67
+ 0x1CE9: 'Symbol',
68
+ 0x1CEA: 'Symbol',
69
+ 0x1CEB: 'Symbol',
70
+ 0x1CEC: 'Symbol',
71
+ 0x1CEE: 'Symbol',
72
+ 0x1CEF: 'Symbol',
73
+ 0x1CF0: 'Symbol',
74
+ 0x1CF1: 'Symbol',
75
+ 0x17C6: 'N',
76
+ 0x2010: 'Placeholder',
77
+ 0x2011: 'Placeholder',
78
+ 0x25CC: 'Dotted_Circle',
79
+
80
+ // Ra
81
+ 0x0930: 'Ra', // Devanagari
82
+ 0x09B0: 'Ra', // Bengali
83
+ 0x09F0: 'Ra', // Bengali
84
+ 0x0A30: 'Ra', // Gurmukhi - No Reph
85
+ 0x0AB0: 'Ra', // Gujarati
86
+ 0x0B30: 'Ra', // Oriya
87
+ 0x0BB0: 'Ra', // Tamil - No Reph
88
+ 0x0C30: 'Ra', // Telugu - Reph formed only with ZWJ
89
+ 0x0CB0: 'Ra', // Kannada
90
+ 0x0D30: 'Ra', // Malayalam - No Reph, Logical Repha
91
+ 0x0DBB: 'Ra', // Sinhala - Reph formed only with ZWJ
92
+ 0x179A: 'Ra', // Khmer - No Reph, Visual Repha
93
+ };
94
+
95
+ const POSITION_MAP = {
96
+ Left: 'Pre_C',
97
+ Top: 'Above_C',
98
+ Bottom: 'Below_C',
99
+ Right: 'Post_C',
100
+
101
+ // These should resolve to the position of the last part of the split sequence.
102
+ Bottom_And_Right: 'Post_C',
103
+ Left_And_Right: 'Post_C',
104
+ Top_And_Bottom: 'Below_C',
105
+ Top_And_Bottom_And_Right: 'Post_C',
106
+ Top_And_Left: 'Above_C',
107
+ Top_And_Left_And_Right: 'Post_C',
108
+ Top_And_Right: 'Post_C',
109
+
110
+ Overstruck: 'After_Main',
111
+ Visual_Order_Left: 'Pre_M'
112
+ };
113
+
114
+ function matraPosition(c, pos) {
115
+ switch (pos) {
116
+ case 'Pre_C':
117
+ return 'Pre_M';
118
+
119
+ case 'Post_C':
120
+ switch (c.block) {
121
+ case 'Devanagari': return 'After_Sub';
122
+ case 'Bengali': return 'After_Post';
123
+ case 'Gurmukhi': return 'After_Post';
124
+ case 'Gujarati': return 'After_Post';
125
+ case 'Oriya': return 'After_Post';
126
+ case 'Tamil': return 'After_Post';
127
+ case 'Telugu': return c.code <= 0x0C42 ? 'Before_Sub' : 'After_Sub';
128
+ case 'Kannada': return c.code < 0x0CC3 || c.code > 0xCD6 ? 'Before_Sub' : 'After_Sub';
129
+ case 'Malayalam': return 'After_Post';
130
+ case 'Sinhala': return 'After_Sub';
131
+ case 'Khmer': return 'After_Post';
132
+ default: return 'After_Sub';
133
+ }
134
+
135
+ case 'Above_C':
136
+ switch (c.block) {
137
+ case 'Devanagari': return 'After_Sub';
138
+ case 'Gurmukhi': return 'After_Post'; // Deviate from spec
139
+ case 'Gujarati': return 'After_Sub';
140
+ case 'Oriya': return 'After_Main';
141
+ case 'Tamil': return 'After_Sub';
142
+ case 'Telugu': return 'Before_Sub';
143
+ case 'Kannada': return 'Before_Sub';
144
+ case 'Sinhala': return 'After_Sub';
145
+ case 'Khmer': return 'After_Post';
146
+ default: return 'After_Sub';
147
+ }
148
+
149
+ case 'Below_C':
150
+ switch (c.block) {
151
+ case 'Devanagari': return 'After_Sub';
152
+ case 'Bengali': return 'After_Sub';
153
+ case 'Gurmukhi': return 'After_Post';
154
+ case 'Gujarati': return 'After_Post';
155
+ case 'Oriya': return 'After_Sub';
156
+ case 'Tamil': return 'After_Post';
157
+ case 'Telugu': return 'Before_Sub';
158
+ case 'Kannada': return 'Before_Sub';
159
+ case 'Malayalam': return 'After_Post';
160
+ case 'Sinhala': return 'After_Sub';
161
+ case 'Khmer': return 'After_Post';
162
+ default: return 'After_Sub';
163
+ }
164
+
165
+ default:
166
+ return pos;
167
+ }
168
+ }
169
+
170
+ function getPosition(codepoint, category) {
171
+ let position = POSITION_MAP[codepoint.indicPositionalCategory] || 'End';
172
+
173
+ if (CATEGORIES[category] & CONSONANT_FLAGS) {
174
+ position = 'Base_C';
175
+ } else if (category === 'M') {
176
+ position = matraPosition(codepoint, position);
177
+ } else if (category === 'SM' || category === 'VD' || category === 'A' || category === 'Symbol') {
178
+ position = 'SMVD';
179
+ }
180
+
181
+ // Oriya Bindu is Before_Sub in the spec.
182
+ if (codepoint.code === 0x0B01) {
183
+ position = 'Before_Sub';
184
+ }
185
+
186
+ return Math.log2(POSITIONS[position]);
187
+ }
188
+
189
+ let symbols = {};
190
+ for (let c in CATEGORIES) {
191
+ symbols[c] = Math.log2(CATEGORIES[c]);
192
+ }
193
+
194
+ let trie = new UnicodeTrieBuilder;
195
+ for (let i = 0; i < codepoints.length; i++) {
196
+ let codepoint = codepoints[i];
197
+ if (codepoint) {
198
+ let category = OVERRIDES[codepoint.code] || CATEGORY_MAP[codepoint.indicSyllabicCategory] || 'X';
199
+ let position = getPosition(codepoint, category);
200
+
201
+ trie.set(codepoint.code, (symbols[category] << 8) | position);
202
+ }
203
+ }
204
+
205
+ fs.writeFileSync(new URL('indic.trie', import.meta.url), trie.toBuffer());
206
+
207
+ let stateMachine = compile(fs.readFileSync(new URL('indic.machine', import.meta.url), 'utf8'), symbols);
208
+ fs.writeFileSync(new URL('indic.json', import.meta.url), JSON.stringify(stateMachine));