@cantoo/fontkit 2.0.5 → 2.0.7
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.
- package/dist/browser-module.d.ts +239 -0
- package/dist/browser-module.mjs +4423 -1117
- package/dist/browser-module.mjs.map +1 -1
- package/dist/browser.cjs +4424 -1117
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts +239 -0
- package/dist/fontkit.umd.d.ts +239 -0
- package/dist/fontkit.umd.js +22076 -0
- package/dist/fontkit.umd.js.map +7 -0
- package/dist/index.d.ts +239 -0
- package/dist/main.cjs +4424 -1117
- package/dist/main.cjs.map +1 -1
- package/dist/main.d.ts +239 -0
- package/dist/module.d.ts +239 -0
- package/dist/module.mjs +4423 -1117
- package/dist/module.mjs.map +1 -1
- package/package.json +22 -5
- package/src/CmapProcessor.js +59 -20
- package/src/DFont.js +114 -14
- package/src/TTFFont.js +259 -57
- package/src/TrueTypeCollection.js +54 -10
- package/src/WOFF2Font.js +128 -16
- package/src/WOFFFont.js +45 -8
- package/src/aat/AATFeatureMap.js +62 -18
- package/src/aat/AATLayoutEngine.js +33 -1
- package/src/aat/AATLookupTable.js +22 -4
- package/src/aat/AATMorxProcessor.js +189 -52
- package/src/aat/AATStateMachine.js +32 -0
- package/src/base.js +19 -0
- package/src/cff/CFFCharsets.js +3 -0
- package/src/cff/CFFDict.js +79 -9
- package/src/cff/CFFEncodings.js +2 -0
- package/src/cff/CFFFont.js +119 -23
- package/src/cff/CFFIndex.js +62 -5
- package/src/cff/CFFOperand.js +52 -17
- package/src/cff/CFFPointer.js +41 -2
- package/src/cff/CFFPrivateDict.js +14 -4
- package/src/cff/CFFStandardStrings.js +1 -0
- package/src/cff/CFFTop.js +103 -10
- package/src/decorators.js +32 -13
- package/src/encodings.js +22 -5
- package/src/fs.js +10 -0
- package/src/glyph/BBox.js +14 -0
- package/src/glyph/CFFGlyph.js +290 -148
- package/src/glyph/COLRGlyph.js +38 -3
- package/src/glyph/Glyph.js +68 -9
- package/src/glyph/GlyphVariationProcessor.js +135 -34
- package/src/glyph/Path.js +107 -20
- package/src/glyph/SBIXGlyph.js +57 -8
- package/src/glyph/StandardNames.js +1 -0
- package/src/glyph/TTFGlyph.js +176 -29
- package/src/glyph/TTFGlyphEncoder.js +32 -0
- package/src/glyph/WOFF2Glyph.js +23 -4
- package/src/layout/GlyphPosition.js +6 -0
- package/src/layout/GlyphRun.js +25 -7
- package/src/layout/KernProcessor.js +62 -3
- package/src/layout/LayoutEngine.js +86 -12
- package/src/layout/Script.js +27 -1
- package/src/layout/UnicodeLayoutEngine.js +29 -4
- package/src/opentype/GPOSProcessor.js +156 -45
- package/src/opentype/GSUBProcessor.js +92 -33
- package/src/opentype/GlyphInfo.js +52 -4
- package/src/opentype/GlyphIterator.js +51 -2
- package/src/opentype/OTLayoutEngine.js +66 -8
- package/src/opentype/OTProcessor.js +283 -70
- package/src/opentype/ShapingPlan.js +51 -3
- package/src/opentype/shapers/ArabicShaper.js +19 -0
- package/src/opentype/shapers/DefaultShaper.js +51 -7
- package/src/opentype/shapers/HangulShaper.js +85 -7
- package/src/opentype/shapers/HebrewShaper.js +45 -11
- package/src/opentype/shapers/IndicShaper.js +141 -26
- package/src/opentype/shapers/ThaiShaper.js +35 -4
- package/src/opentype/shapers/UniversalShaper.js +83 -17
- package/src/opentype/shapers/index.js +15 -2
- package/src/packages/unicode-properties/data-trie.js +1 -0
- package/src/packages/unicode-properties/data.js +12 -0
- package/src/packages/unicode-properties/dist/index.cjs +22 -9
- package/src/packages/unicode-properties/dist/index.cjs.map +2 -2
- package/src/packages/unicode-properties/dist/index.mjs +21 -9
- package/src/packages/unicode-properties/dist/index.mjs.map +2 -2
- package/src/packages/unicode-properties/index.js +65 -0
- package/src/packages/unicode-properties/package.json +1 -1
- package/src/packages/unicode-trie/builder.js +128 -7
- package/src/packages/unicode-trie/dist/builder.cjs +129 -16
- package/src/packages/unicode-trie/dist/builder.cjs.map +2 -2
- package/src/packages/unicode-trie/dist/builder.mjs +128 -16
- package/src/packages/unicode-trie/dist/builder.mjs.map +2 -2
- package/src/packages/unicode-trie/dist/index.cjs +22 -9
- package/src/packages/unicode-trie/dist/index.cjs.map +2 -2
- package/src/packages/unicode-trie/dist/index.mjs +21 -9
- package/src/packages/unicode-trie/dist/index.mjs.map +2 -2
- package/src/packages/unicode-trie/index.js +62 -10
- package/src/packages/unicode-trie/package.json +1 -1
- package/src/packages/unicode-trie/swap.js +13 -0
- package/src/subset/CFFSubset.js +112 -22
- package/src/subset/Subset.js +21 -1
- package/src/subset/TTFSubset.js +79 -36
- package/src/tables/BASE.js +1 -0
- package/src/tables/COLR.js +1 -0
- package/src/tables/CPAL.js +1 -0
- package/src/tables/DSIG.js +1 -0
- package/src/tables/EBDT.js +10 -3
- package/src/tables/EBLC.js +67 -7
- package/src/tables/GDEF.js +1 -0
- package/src/tables/GPOS.js +79 -16
- package/src/tables/GSUB.js +14 -3
- package/src/tables/HVAR.js +51 -5
- package/src/tables/JSTF.js +3 -2
- package/src/tables/LTSH.js +1 -0
- package/src/tables/OS2.js +1 -0
- package/src/tables/PCLT.js +1 -0
- package/src/tables/STAT.js +46 -6
- package/src/tables/VDMX.js +1 -0
- package/src/tables/VORG.js +1 -0
- package/src/tables/WOFF2Directory.js +56 -8
- package/src/tables/WOFFDirectory.js +14 -2
- package/src/tables/aat.js +160 -35
- package/src/tables/avar.js +1 -0
- package/src/tables/bsln.js +3 -2
- package/src/tables/cmap.js +26 -6
- package/src/tables/cvt.js +1 -0
- package/src/tables/directory.js +44 -16
- package/src/tables/feat.js +24 -2
- package/src/tables/fpgm.js +1 -0
- package/src/tables/fvar.js +32 -4
- package/src/tables/gasp.js +1 -0
- package/src/tables/glyf.js +1 -0
- package/src/tables/gvar.js +29 -2
- package/src/tables/hdmx.js +12 -1
- package/src/tables/head.js +1 -0
- package/src/tables/hhea.js +1 -0
- package/src/tables/hmtx.js +18 -2
- package/src/tables/index.js +1 -0
- package/src/tables/just.js +15 -4
- package/src/tables/kern.js +43 -6
- package/src/tables/loca.js +21 -8
- package/src/tables/maxp.js +1 -0
- package/src/tables/morx.js +14 -7
- package/src/tables/name.js +104 -23
- package/src/tables/opbd.js +2 -1
- package/src/tables/opentype.js +39 -4
- package/src/tables/post.js +8 -1
- package/src/tables/prep.js +1 -0
- package/src/tables/sbix.js +12 -1
- package/src/tables/variations.js +23 -3
- package/src/tables/vhea.js +1 -0
- package/src/tables/vmtx.js +18 -2
- package/src/utils.js +15 -0
- package/types/assets.d.ts +13 -0
- package/types/deps.d.ts +83 -0
- package/types/fontkit.d.ts +1324 -0
- package/types/index.d.ts +239 -0
- package/types/restructure.d.ts +354 -0
|
@@ -17,9 +17,24 @@ import {
|
|
|
17
17
|
import { decodeBase64 } from '../../utils';
|
|
18
18
|
import indicTrie from './indic.trie';
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
/** @typedef {import('../../../types/fontkit').ShapingPlanLike} ShapingPlanLike */
|
|
21
|
+
/** @typedef {import('../../../types/fontkit').GlyphInfoLike} GlyphInfoLike */
|
|
22
|
+
/** @typedef {import('../../../types/fontkit').IndicGlyphInfo} IndicGlyphInfo */
|
|
23
|
+
/** @typedef {import('../../../types/fontkit').LayoutFont} LayoutFont */
|
|
24
|
+
/** @typedef {import('../../../types/fontkit').IndicConfig} IndicConfig */
|
|
25
|
+
/** @typedef {import('../../../types/fontkit').IndicShaperInfo} IndicShaperInfo */
|
|
26
|
+
/** @typedef {import('../../../types/fontkit').ShapingStageFn} ShapingStageFn */
|
|
27
|
+
/** @typedef {import('dfa').StateMachineDefinition} StateMachineDefinition */
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {{ decompositions: Record<string, number[]> }} UseDataPartial
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/** @type {UseDataPartial} */
|
|
34
|
+
const useDataTyped = /** @type {UseDataPartial} */ (useData);
|
|
35
|
+
const { decompositions } = useDataTyped;
|
|
21
36
|
const trie = new UnicodeTrie(decodeBase64(indicTrie));
|
|
22
|
-
const stateMachine = new StateMachine(indicMachine);
|
|
37
|
+
const stateMachine = new StateMachine(/** @type {StateMachineDefinition} */ (indicMachine));
|
|
23
38
|
|
|
24
39
|
/**
|
|
25
40
|
* The IndicShaper supports indic scripts e.g. Devanagari, Kannada, etc.
|
|
@@ -27,6 +42,10 @@ const stateMachine = new StateMachine(indicMachine);
|
|
|
27
42
|
*/
|
|
28
43
|
export default class IndicShaper extends DefaultShaper {
|
|
29
44
|
static zeroMarkWidths = 'NONE';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param {ShapingPlanLike} plan
|
|
48
|
+
*/
|
|
30
49
|
static planFeatures(plan) {
|
|
31
50
|
plan.addStage(setupSyllables);
|
|
32
51
|
|
|
@@ -55,24 +74,39 @@ export default class IndicShaper extends DefaultShaper {
|
|
|
55
74
|
});
|
|
56
75
|
|
|
57
76
|
// Setup the indic config for the selected script
|
|
58
|
-
|
|
59
|
-
plan.
|
|
60
|
-
|
|
77
|
+
let scriptTag = Array.isArray(plan.script) ? plan.script[0] : plan.script;
|
|
78
|
+
plan.unicodeScript = scriptTag != null ? Script.fromOpenType(scriptTag) : undefined;
|
|
79
|
+
/** @type {Record<string, IndicConfig>} */
|
|
80
|
+
let configs = INDIC_CONFIGS;
|
|
81
|
+
plan.indicConfig = (plan.unicodeScript && configs[plan.unicodeScript]) || configs.Default;
|
|
82
|
+
let scriptKey = Array.isArray(plan.script)
|
|
83
|
+
? plan.script[plan.script.length - 1]
|
|
84
|
+
: plan.script;
|
|
85
|
+
plan.isOldSpec = !!(plan.indicConfig.hasOldSpec && scriptKey != null && scriptKey[scriptKey.length - 1] !== '2');
|
|
61
86
|
|
|
62
87
|
// TODO: turn off kern (Khmer) and liga features.
|
|
63
88
|
}
|
|
64
89
|
|
|
90
|
+
/**
|
|
91
|
+
* @param {ShapingPlanLike} plan
|
|
92
|
+
* @param {GlyphInfoLike[]} glyphs
|
|
93
|
+
*/
|
|
65
94
|
static assignFeatures(plan, glyphs) {
|
|
66
95
|
// Decompose split matras
|
|
67
96
|
// TODO: do this in a more general unicode normalizer
|
|
68
97
|
for (let i = glyphs.length - 1; i >= 0; i--) {
|
|
69
98
|
let codepoint = glyphs[i].codePoints[0];
|
|
70
|
-
let d =
|
|
99
|
+
let d = /** @type {Record<number, number[]>} */ (INDIC_DECOMPOSITIONS)[codepoint]
|
|
100
|
+
|| decompositions[String(codepoint)];
|
|
71
101
|
if (d) {
|
|
72
|
-
|
|
102
|
+
/** @type {GlyphInfo[]} */
|
|
103
|
+
let decomposed = [];
|
|
104
|
+
for (let c of d) {
|
|
73
105
|
let g = plan.font.glyphForCodePoint(c);
|
|
74
|
-
|
|
75
|
-
|
|
106
|
+
if (g) {
|
|
107
|
+
decomposed.push(new GlyphInfo(plan.font, g.id, [c], glyphs[i].features));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
76
110
|
|
|
77
111
|
glyphs.splice(i, 1, ...decomposed);
|
|
78
112
|
}
|
|
@@ -80,23 +114,63 @@ export default class IndicShaper extends DefaultShaper {
|
|
|
80
114
|
}
|
|
81
115
|
}
|
|
82
116
|
|
|
117
|
+
/**
|
|
118
|
+
* @param {GlyphInfoLike} glyph
|
|
119
|
+
* @returns {number}
|
|
120
|
+
*/
|
|
83
121
|
function indicCategory(glyph) {
|
|
84
122
|
return trie.get(glyph.codePoints[0]) >> 8;
|
|
85
123
|
}
|
|
86
124
|
|
|
125
|
+
/**
|
|
126
|
+
* @param {GlyphInfoLike} glyph
|
|
127
|
+
* @returns {number}
|
|
128
|
+
*/
|
|
87
129
|
function indicPosition(glyph) {
|
|
88
130
|
return 1 << (trie.get(glyph.codePoints[0]) & 0xff);
|
|
89
131
|
}
|
|
90
132
|
|
|
91
133
|
class IndicInfo {
|
|
134
|
+
/**
|
|
135
|
+
* @param {number} category
|
|
136
|
+
* @param {number} position
|
|
137
|
+
* @param {string} syllableType
|
|
138
|
+
* @param {number} syllable
|
|
139
|
+
*/
|
|
92
140
|
constructor(category, position, syllableType, syllable) {
|
|
141
|
+
/** @type {number} */
|
|
93
142
|
this.category = category;
|
|
143
|
+
/** @type {number} */
|
|
94
144
|
this.position = position;
|
|
145
|
+
/** @type {string} */
|
|
95
146
|
this.syllableType = syllableType;
|
|
147
|
+
/** @type {number} */
|
|
96
148
|
this.syllable = syllable;
|
|
97
149
|
}
|
|
98
150
|
}
|
|
99
151
|
|
|
152
|
+
/**
|
|
153
|
+
* @param {import('../../../types/fontkit').IndicShaperInfo | import('../../../types/fontkit').USEShaperInfo | null} info
|
|
154
|
+
* @returns {info is IndicShaperInfo}
|
|
155
|
+
*/
|
|
156
|
+
function isIndicShaperInfo(info) {
|
|
157
|
+
// Discriminate on category kind: Indic uses numeric bitflags, USE uses strings.
|
|
158
|
+
return info != null && typeof info.category === 'number';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* @param {GlyphInfoLike} glyph
|
|
163
|
+
* @returns {IndicShaperInfo}
|
|
164
|
+
*/
|
|
165
|
+
function indicInfo(glyph) {
|
|
166
|
+
let info = glyph.shaperInfo;
|
|
167
|
+
if (!isIndicShaperInfo(info)) {
|
|
168
|
+
throw new Error('Expected Indic shaperInfo');
|
|
169
|
+
}
|
|
170
|
+
return info;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** @type {ShapingStageFn} */
|
|
100
174
|
function setupSyllables(font, glyphs) {
|
|
101
175
|
let syllable = 0;
|
|
102
176
|
let last = 0;
|
|
@@ -131,29 +205,55 @@ function setupSyllables(font, glyphs) {
|
|
|
131
205
|
}
|
|
132
206
|
}
|
|
133
207
|
|
|
208
|
+
/**
|
|
209
|
+
* @param {GlyphInfoLike} glyph
|
|
210
|
+
* @returns {number}
|
|
211
|
+
*/
|
|
134
212
|
function isConsonant(glyph) {
|
|
135
|
-
return glyph.
|
|
213
|
+
return indicInfo(glyph).category & CONSONANT_FLAGS;
|
|
136
214
|
}
|
|
137
215
|
|
|
216
|
+
/**
|
|
217
|
+
* @param {GlyphInfoLike} glyph
|
|
218
|
+
* @returns {number}
|
|
219
|
+
*/
|
|
138
220
|
function isJoiner(glyph) {
|
|
139
|
-
return glyph.
|
|
221
|
+
return indicInfo(glyph).category & JOINER_FLAGS;
|
|
140
222
|
}
|
|
141
223
|
|
|
224
|
+
/**
|
|
225
|
+
* @param {GlyphInfoLike} glyph
|
|
226
|
+
* @returns {number}
|
|
227
|
+
*/
|
|
142
228
|
function isHalantOrCoeng(glyph) {
|
|
143
|
-
return glyph.
|
|
229
|
+
return indicInfo(glyph).category & HALANT_OR_COENG_FLAGS;
|
|
144
230
|
}
|
|
145
231
|
|
|
232
|
+
/**
|
|
233
|
+
* @param {GlyphInfoLike[]} glyphs
|
|
234
|
+
* @param {string} feature
|
|
235
|
+
* @returns {boolean}
|
|
236
|
+
*/
|
|
146
237
|
function wouldSubstitute(glyphs, feature) {
|
|
147
238
|
for (let glyph of glyphs) {
|
|
148
239
|
glyph.features = { [feature]: true };
|
|
149
240
|
}
|
|
150
241
|
|
|
151
|
-
let
|
|
152
|
-
|
|
242
|
+
let engine = glyphs[0]._font._layoutEngine.engine;
|
|
243
|
+
if (!engine?.GSUBProcessor) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
engine.GSUBProcessor.applyFeatures([feature], glyphs);
|
|
153
247
|
|
|
154
248
|
return glyphs.length === 1;
|
|
155
249
|
}
|
|
156
250
|
|
|
251
|
+
/**
|
|
252
|
+
* @param {LayoutFont} font
|
|
253
|
+
* @param {GlyphInfoLike} consonant
|
|
254
|
+
* @param {GlyphInfoLike} virama
|
|
255
|
+
* @returns {number}
|
|
256
|
+
*/
|
|
157
257
|
function consonantPosition(font, consonant, virama) {
|
|
158
258
|
let glyphs = [virama, consonant, virama];
|
|
159
259
|
if (wouldSubstitute(glyphs.slice(0, 2), 'blwf') || wouldSubstitute(glyphs.slice(1, 3), 'blwf')) {
|
|
@@ -167,12 +267,18 @@ function consonantPosition(font, consonant, virama) {
|
|
|
167
267
|
return POSITIONS.Base_C;
|
|
168
268
|
}
|
|
169
269
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
let
|
|
175
|
-
let
|
|
270
|
+
/** @type {ShapingStageFn} */
|
|
271
|
+
function initialReordering(font, glyphsArg, plan) {
|
|
272
|
+
// setupSyllables always attaches IndicShaperInfo before this stage.
|
|
273
|
+
const glyphs = /** @type {IndicGlyphInfo[]} */ (glyphsArg);
|
|
274
|
+
let indicConfig = /** @type {IndicConfig} */ (plan.indicConfig);
|
|
275
|
+
let engine = font._layoutEngine.engine;
|
|
276
|
+
let features = engine?.GSUBProcessor ? engine.GSUBProcessor.features : {};
|
|
277
|
+
|
|
278
|
+
let dottedCircleGlyph = font.glyphForCodePoint(0x25cc);
|
|
279
|
+
let dottedCircle = dottedCircleGlyph ? dottedCircleGlyph.id : 0;
|
|
280
|
+
let viramaGlyph = font.glyphForCodePoint(indicConfig.virama);
|
|
281
|
+
let virama = viramaGlyph ? viramaGlyph.id : 0;
|
|
176
282
|
if (virama) {
|
|
177
283
|
let info = new GlyphInfo(font, virama, [indicConfig.virama]);
|
|
178
284
|
for (let i = 0; i < glyphs.length; i++) {
|
|
@@ -190,7 +296,7 @@ function initialReordering(font, glyphs, plan) {
|
|
|
190
296
|
}
|
|
191
297
|
|
|
192
298
|
if (syllableType === 'broken_cluster' && dottedCircle) {
|
|
193
|
-
let g = new GlyphInfo(font, dottedCircle, [0x25cc]);
|
|
299
|
+
let g = /** @type {IndicGlyphInfo} */ (new GlyphInfo(font, dottedCircle, [0x25cc]));
|
|
194
300
|
g.shaperInfo = new IndicInfo(
|
|
195
301
|
1 << indicCategory(g),
|
|
196
302
|
indicPosition(g),
|
|
@@ -569,9 +675,13 @@ function initialReordering(font, glyphs, plan) {
|
|
|
569
675
|
}
|
|
570
676
|
}
|
|
571
677
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
678
|
+
/** @type {ShapingStageFn} */
|
|
679
|
+
function finalReordering(font, glyphsArg, plan) {
|
|
680
|
+
// setupSyllables / initialReordering always attach IndicShaperInfo before this stage.
|
|
681
|
+
const glyphs = /** @type {IndicGlyphInfo[]} */ (glyphsArg);
|
|
682
|
+
let indicConfig = /** @type {IndicConfig} */ (plan.indicConfig);
|
|
683
|
+
let engine = font._layoutEngine.engine;
|
|
684
|
+
let features = engine?.GSUBProcessor ? engine.GSUBProcessor.features : {};
|
|
575
685
|
|
|
576
686
|
for (let start = 0, end = nextSyllable(glyphs, 0); start < glyphs.length; start = end, end = nextSyllable(glyphs, start)) {
|
|
577
687
|
// 4. Final reordering:
|
|
@@ -597,7 +707,7 @@ function finalReordering(font, glyphs, plan) {
|
|
|
597
707
|
while (base < end && isHalantOrCoeng(glyphs[base])) {
|
|
598
708
|
base++;
|
|
599
709
|
}
|
|
600
|
-
glyphs[base].shaperInfo.position = POSITIONS.
|
|
710
|
+
glyphs[base].shaperInfo.position = POSITIONS.Base_C;
|
|
601
711
|
tryPref = false;
|
|
602
712
|
}
|
|
603
713
|
break;
|
|
@@ -719,7 +829,7 @@ function finalReordering(font, glyphs, plan) {
|
|
|
719
829
|
&& glyphs[start].shaperInfo.position === POSITIONS.Ra_To_Become_Reph
|
|
720
830
|
&& (glyphs[start].shaperInfo.category === CATEGORIES.Repha) !== (glyphs[start].isLigated && !glyphs[start].isMultiplied)
|
|
721
831
|
) {
|
|
722
|
-
let newRephPos;
|
|
832
|
+
let newRephPos = end - 1;
|
|
723
833
|
let rephPos = indicConfig.rephPos;
|
|
724
834
|
let found = false;
|
|
725
835
|
|
|
@@ -906,6 +1016,11 @@ function finalReordering(font, glyphs, plan) {
|
|
|
906
1016
|
}
|
|
907
1017
|
}
|
|
908
1018
|
|
|
1019
|
+
/**
|
|
1020
|
+
* @param {IndicGlyphInfo[]} glyphs
|
|
1021
|
+
* @param {number} start
|
|
1022
|
+
* @returns {number}
|
|
1023
|
+
*/
|
|
909
1024
|
function nextSyllable(glyphs, start) {
|
|
910
1025
|
if (start >= glyphs.length) return start;
|
|
911
1026
|
let syllable = glyphs[start].shaperInfo.syllable;
|
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
import DefaultShaper from './DefaultShaper';
|
|
2
2
|
import GlyphInfo from '../GlyphInfo';
|
|
3
3
|
|
|
4
|
+
/** @typedef {import('../../../types/fontkit').ShapingPlanLike} ShapingPlanLike */
|
|
5
|
+
/** @typedef {import('../../../types/fontkit').GlyphInfoLike} GlyphInfoLike */
|
|
6
|
+
/** @typedef {import('../../../types/fontkit').LayoutFont} LayoutFont */
|
|
7
|
+
/** @typedef {import('../../../types/fontkit').FeatureMap} FeatureMap */
|
|
8
|
+
/** @typedef {import('../../../types/fontkit').FeatureInput} FeatureInput */
|
|
9
|
+
/** @typedef {import('../../../types/fontkit').ScriptTag} ScriptTag */
|
|
10
|
+
/** @typedef {import('../../../types/fontkit').OTScriptRecord} OTScriptRecord */
|
|
11
|
+
|
|
4
12
|
/**
|
|
5
13
|
* Thai / Lao shaper (HarfBuzz hb-ot-shaper-thai.cc):
|
|
6
14
|
* 1. Decompose SARA AM → NIKHAHIT + SARA AA and reorder NIKHAHIT past above marks
|
|
7
15
|
* 2. PUA tone/vowel shift fallback for legacy fonts without Thai GSUB
|
|
8
16
|
*/
|
|
9
17
|
export default class ThaiShaper extends DefaultShaper {
|
|
18
|
+
/**
|
|
19
|
+
* @param {ShapingPlanLike} plan
|
|
20
|
+
* @param {GlyphInfoLike[]} glyphs
|
|
21
|
+
*/
|
|
10
22
|
static assignFeatures(plan, glyphs) {
|
|
11
23
|
super.assignFeatures(plan, glyphs);
|
|
12
24
|
preprocessThai(glyphs, plan.font);
|
|
@@ -18,19 +30,23 @@ export default class ThaiShaper extends DefaultShaper {
|
|
|
18
30
|
}
|
|
19
31
|
|
|
20
32
|
// Thai/Lao SARA AM differ only by the 0x80 bit (U+0E33 / U+0EB3).
|
|
33
|
+
/** @param {number} u @returns {boolean} */
|
|
21
34
|
function isSaraAm(u) {
|
|
22
35
|
return (u & ~0x0080) === 0x0E33;
|
|
23
36
|
}
|
|
24
37
|
|
|
38
|
+
/** @param {number} u @returns {number} */
|
|
25
39
|
function nikhahitFromSaraAm(u) {
|
|
26
40
|
return u - 0x0E33 + 0x0E4D;
|
|
27
41
|
}
|
|
28
42
|
|
|
43
|
+
/** @param {number} u @returns {number} */
|
|
29
44
|
function saraAaFromSaraAm(u) {
|
|
30
45
|
return u - 1;
|
|
31
46
|
}
|
|
32
47
|
|
|
33
48
|
// Above-base marks (Thai; Lao is the same set with +0x80).
|
|
49
|
+
/** @param {number} u @returns {boolean} */
|
|
34
50
|
function isAboveBaseMark(u) {
|
|
35
51
|
const c = u & ~0x0080;
|
|
36
52
|
return c === 0x0E31
|
|
@@ -39,6 +55,7 @@ function isAboveBaseMark(u) {
|
|
|
39
55
|
|| c === 0x0E3B;
|
|
40
56
|
}
|
|
41
57
|
|
|
58
|
+
/** @param {GlyphInfoLike[]} glyphs @param {LayoutFont} font */
|
|
42
59
|
function preprocessThai(glyphs, font) {
|
|
43
60
|
let i = 0;
|
|
44
61
|
while (i < glyphs.length) {
|
|
@@ -68,9 +85,11 @@ function preprocessThai(glyphs, font) {
|
|
|
68
85
|
}
|
|
69
86
|
}
|
|
70
87
|
|
|
88
|
+
/** @param {LayoutFont} font @param {number} codePoint @param {FeatureMap | FeatureInput | null | undefined} features @returns {GlyphInfo} */
|
|
71
89
|
function makeGlyph(font, codePoint, features) {
|
|
72
|
-
const
|
|
73
|
-
|
|
90
|
+
const g = font.glyphForCodePoint(codePoint);
|
|
91
|
+
// .notdef when the mapped code point is somehow missing from the font.
|
|
92
|
+
return new GlyphInfo(font, g ? g.id : 0, [codePoint], features);
|
|
74
93
|
}
|
|
75
94
|
|
|
76
95
|
// PUA fallback: above/below state machines remap marks (and some bases) to
|
|
@@ -92,6 +111,7 @@ const BV = 1; // below-base vowel/mark
|
|
|
92
111
|
const T = 2; // tone mark
|
|
93
112
|
const NOT_MARK = 3;
|
|
94
113
|
|
|
114
|
+
/** @param {number} u @returns {number} */
|
|
95
115
|
function getConsonantType(u) {
|
|
96
116
|
if (u === 0x0E1B || u === 0x0E1D || u === 0x0E1F) return AC;
|
|
97
117
|
if (u === 0x0E0D || u === 0x0E10) return RC;
|
|
@@ -100,6 +120,7 @@ function getConsonantType(u) {
|
|
|
100
120
|
return NOT_CONSONANT;
|
|
101
121
|
}
|
|
102
122
|
|
|
123
|
+
/** @param {number} u @returns {number} */
|
|
103
124
|
function getMarkType(u) {
|
|
104
125
|
if (
|
|
105
126
|
u === 0x0E31
|
|
@@ -116,6 +137,7 @@ function getMarkType(u) {
|
|
|
116
137
|
|
|
117
138
|
const T0 = 0, T1 = 1, T2 = 2, T3 = 3;
|
|
118
139
|
const ABOVE_START_STATE = [T0, T1, T0, T0, T3]; // NC AC RC DC NOT_CONSONANT
|
|
140
|
+
/** @type {[number, number][][]} */
|
|
119
141
|
const ABOVE_STATE_MACHINE = [
|
|
120
142
|
// AV BV T
|
|
121
143
|
[[NOP, T3], [NOP, T0], [SD, T3]], // T0
|
|
@@ -126,6 +148,7 @@ const ABOVE_STATE_MACHINE = [
|
|
|
126
148
|
|
|
127
149
|
const B0 = 0, B1 = 1, B2 = 2;
|
|
128
150
|
const BELOW_START_STATE = [B0, B0, B1, B2, B2];
|
|
151
|
+
/** @type {[number, number][][]} */
|
|
129
152
|
const BELOW_STATE_MACHINE = [
|
|
130
153
|
// AV BV T
|
|
131
154
|
[[NOP, B0], [NOP, B2], [NOP, B0]], // B0
|
|
@@ -134,6 +157,7 @@ const BELOW_STATE_MACHINE = [
|
|
|
134
157
|
];
|
|
135
158
|
|
|
136
159
|
// [original, Windows PUA, Mac PUA] per action
|
|
160
|
+
/** @type {Record<number, number[][]>} */
|
|
137
161
|
const PUA_MAPPINGS = {
|
|
138
162
|
[SD]: [
|
|
139
163
|
[0x0E48, 0xF70A, 0xF88B], // MAI EK
|
|
@@ -172,6 +196,7 @@ const PUA_MAPPINGS = {
|
|
|
172
196
|
]
|
|
173
197
|
};
|
|
174
198
|
|
|
199
|
+
/** @param {number} u @param {number} action @param {LayoutFont} font @returns {number} */
|
|
175
200
|
function thaiPuaShape(u, action, font) {
|
|
176
201
|
if (action === NOP) return u;
|
|
177
202
|
const mappings = PUA_MAPPINGS[action];
|
|
@@ -185,12 +210,16 @@ function thaiPuaShape(u, action, font) {
|
|
|
185
210
|
return u;
|
|
186
211
|
}
|
|
187
212
|
|
|
213
|
+
/** @param {GlyphInfoLike[]} glyphs @param {number} index @param {number} newCp @param {LayoutFont} font */
|
|
188
214
|
function replaceGlyphCodePoint(glyphs, index, newCp, font) {
|
|
189
215
|
const prev = glyphs[index];
|
|
190
216
|
if (prev.codePoints[0] === newCp) return;
|
|
191
|
-
|
|
217
|
+
const g = font.glyphForCodePoint(newCp);
|
|
218
|
+
if (!g) return;
|
|
219
|
+
glyphs[index] = new GlyphInfo(font, g.id, [newCp], prev.features);
|
|
192
220
|
}
|
|
193
221
|
|
|
222
|
+
/** @param {GlyphInfoLike[]} glyphs @param {LayoutFont} font */
|
|
194
223
|
function applyThaiPuaShaping(glyphs, font) {
|
|
195
224
|
let aboveState = ABOVE_START_STATE[NOT_CONSONANT];
|
|
196
225
|
let belowState = BELOW_START_STATE[NOT_CONSONANT];
|
|
@@ -226,12 +255,14 @@ function applyThaiPuaShaping(glyphs, font) {
|
|
|
226
255
|
}
|
|
227
256
|
|
|
228
257
|
// Gate PUA shaping on absence of Thai GSUB (HB plan->map.found_script[0]).
|
|
258
|
+
/** @param {LayoutFont} font @returns {boolean} */
|
|
229
259
|
function hasThaiGsub(font) {
|
|
230
260
|
const gsub = font.GSUB;
|
|
231
261
|
if (!gsub || !gsub.scriptList) return false;
|
|
232
|
-
return gsub.scriptList.some(entry => entry.tag === 'thai' || entry.tag === 'tha2');
|
|
262
|
+
return gsub.scriptList.some(/** @param {OTScriptRecord} entry */ entry => entry.tag === 'thai' || entry.tag === 'tha2');
|
|
233
263
|
}
|
|
234
264
|
|
|
265
|
+
/** @param {ScriptTag | string[] | null | undefined} script @returns {boolean} */
|
|
235
266
|
function isThaiBufferScript(script) {
|
|
236
267
|
if (Array.isArray(script)) return script.includes('thai');
|
|
237
268
|
return script === 'thai';
|
|
@@ -6,9 +6,28 @@ import useData from './use.json';
|
|
|
6
6
|
import { decodeBase64 } from '../../utils';
|
|
7
7
|
import useTrie from './use.trie';
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
/** @typedef {import('../../../types/fontkit').ShapingPlanLike} ShapingPlanLike */
|
|
10
|
+
/** @typedef {import('../../../types/fontkit').GlyphInfoLike} GlyphInfoLike */
|
|
11
|
+
/** @typedef {import('../../../types/fontkit').LayoutFont} LayoutFont */
|
|
12
|
+
/** @typedef {import('../../../types/fontkit').USEShaperInfo} USEShaperInfo */
|
|
13
|
+
/** @typedef {import('dfa').StateMachineDefinition} StateMachineDefinition */
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generated USE payload: category tables plus a compiled `dfa` machine
|
|
17
|
+
* (`stateTable` / `accepting` / `tags`). Extra keys are fine — StateMachine
|
|
18
|
+
* only reads the three DFA fields.
|
|
19
|
+
*
|
|
20
|
+
* @typedef {StateMachineDefinition & {
|
|
21
|
+
* categories: string[],
|
|
22
|
+
* decompositions: Record<string, number[]>
|
|
23
|
+
* }} UseData
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** @type {UseData} */
|
|
27
|
+
const useDataTyped = /** @type {UseData} */ (useData);
|
|
28
|
+
const { categories, decompositions } = useDataTyped;
|
|
10
29
|
const trie = new UnicodeTrie(decodeBase64(useTrie));
|
|
11
|
-
const stateMachine = new StateMachine(
|
|
30
|
+
const stateMachine = new StateMachine(useDataTyped);
|
|
12
31
|
|
|
13
32
|
/**
|
|
14
33
|
* This shaper is an implementation of the Universal Shaping Engine, which
|
|
@@ -17,6 +36,9 @@ const stateMachine = new StateMachine(useData);
|
|
|
17
36
|
*/
|
|
18
37
|
export default class UniversalShaper extends DefaultShaper {
|
|
19
38
|
static zeroMarkWidths = 'BEFORE_GPOS';
|
|
39
|
+
/**
|
|
40
|
+
* @param {ShapingPlanLike} plan
|
|
41
|
+
*/
|
|
20
42
|
static planFeatures(plan) {
|
|
21
43
|
plan.addStage(setupSyllables);
|
|
22
44
|
|
|
@@ -43,16 +65,25 @@ export default class UniversalShaper extends DefaultShaper {
|
|
|
43
65
|
plan.addStage(['abvs', 'blws', 'pres', 'psts', 'dist', 'abvm', 'blwm']);
|
|
44
66
|
}
|
|
45
67
|
|
|
68
|
+
/**
|
|
69
|
+
* @param {ShapingPlanLike} plan
|
|
70
|
+
* @param {GlyphInfoLike[]} glyphs
|
|
71
|
+
*/
|
|
46
72
|
static assignFeatures(plan, glyphs) {
|
|
47
73
|
// Decompose split vowels
|
|
48
74
|
// TODO: do this in a more general unicode normalizer
|
|
49
75
|
for (let i = glyphs.length - 1; i >= 0; i--) {
|
|
50
76
|
let codepoint = glyphs[i].codePoints[0];
|
|
51
|
-
|
|
52
|
-
|
|
77
|
+
let decomp = decompositions[String(codepoint)] || decompositions[/** @type {keyof typeof decompositions} */ (/** @type {unknown} */ (codepoint))];
|
|
78
|
+
if (decomp) {
|
|
79
|
+
/** @type {GlyphInfo[]} */
|
|
80
|
+
let decomposed = [];
|
|
81
|
+
for (let c of decomp) {
|
|
53
82
|
let g = plan.font.glyphForCodePoint(c);
|
|
54
|
-
|
|
55
|
-
|
|
83
|
+
if (g) {
|
|
84
|
+
decomposed.push(new GlyphInfo(plan.font, g.id, [c], glyphs[i].features));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
56
87
|
|
|
57
88
|
glyphs.splice(i, 1, ...decomposed);
|
|
58
89
|
}
|
|
@@ -60,11 +91,17 @@ export default class UniversalShaper extends DefaultShaper {
|
|
|
60
91
|
}
|
|
61
92
|
}
|
|
62
93
|
|
|
94
|
+
/** @param {GlyphInfoLike} glyph @returns {number} */
|
|
63
95
|
function useCategory(glyph) {
|
|
64
96
|
return trie.get(glyph.codePoints[0]);
|
|
65
97
|
}
|
|
66
98
|
|
|
67
99
|
class USEInfo {
|
|
100
|
+
/**
|
|
101
|
+
* @param {string} category
|
|
102
|
+
* @param {string} syllableType
|
|
103
|
+
* @param {number} syllable
|
|
104
|
+
*/
|
|
68
105
|
constructor(category, syllableType, syllable) {
|
|
69
106
|
this.category = category;
|
|
70
107
|
this.syllableType = syllableType;
|
|
@@ -72,6 +109,27 @@ class USEInfo {
|
|
|
72
109
|
}
|
|
73
110
|
}
|
|
74
111
|
|
|
112
|
+
/**
|
|
113
|
+
* @param {import('../../../types/fontkit').IndicShaperInfo | import('../../../types/fontkit').USEShaperInfo | null} info
|
|
114
|
+
* @returns {info is USEShaperInfo}
|
|
115
|
+
*/
|
|
116
|
+
function isUSEShaperInfo(info) {
|
|
117
|
+
return info != null && typeof info.category === 'string';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @param {GlyphInfoLike} glyph
|
|
122
|
+
* @returns {USEShaperInfo}
|
|
123
|
+
*/
|
|
124
|
+
function useInfo(glyph) {
|
|
125
|
+
let info = glyph.shaperInfo;
|
|
126
|
+
if (!isUSEShaperInfo(info)) {
|
|
127
|
+
throw new Error('Expected USE shaperInfo');
|
|
128
|
+
}
|
|
129
|
+
return info;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** @type {import('../../../types/fontkit').ShapingStageFn} */
|
|
75
133
|
function setupSyllables(font, glyphs) {
|
|
76
134
|
let syllable = 0;
|
|
77
135
|
for (let [start, end, tags] of stateMachine.match(glyphs.map(useCategory))) {
|
|
@@ -83,43 +141,48 @@ function setupSyllables(font, glyphs) {
|
|
|
83
141
|
}
|
|
84
142
|
|
|
85
143
|
// Assign rphf feature
|
|
86
|
-
let limit = glyphs[start].
|
|
144
|
+
let limit = useInfo(glyphs[start]).category === 'R' ? 1 : Math.min(3, end - start);
|
|
87
145
|
for (let i = start; i < start + limit; i++) {
|
|
88
146
|
glyphs[i].features.rphf = true;
|
|
89
147
|
}
|
|
90
148
|
}
|
|
91
149
|
}
|
|
92
150
|
|
|
151
|
+
/** @type {import('../../../types/fontkit').ShapingStageFn} */
|
|
93
152
|
function clearSubstitutionFlags(font, glyphs) {
|
|
94
153
|
for (let glyph of glyphs) {
|
|
95
154
|
glyph.substituted = false;
|
|
96
155
|
}
|
|
97
156
|
}
|
|
98
157
|
|
|
158
|
+
/** @type {import('../../../types/fontkit').ShapingStageFn} */
|
|
99
159
|
function recordRphf(font, glyphs) {
|
|
100
160
|
for (let glyph of glyphs) {
|
|
101
161
|
if (glyph.substituted && glyph.features.rphf) {
|
|
102
162
|
// Mark a substituted repha.
|
|
103
|
-
glyph.
|
|
163
|
+
useInfo(glyph).category = 'R';
|
|
104
164
|
}
|
|
105
165
|
}
|
|
106
166
|
}
|
|
107
167
|
|
|
168
|
+
/** @type {import('../../../types/fontkit').ShapingStageFn} */
|
|
108
169
|
function recordPref(font, glyphs) {
|
|
109
170
|
for (let glyph of glyphs) {
|
|
110
171
|
if (glyph.substituted) {
|
|
111
172
|
// Mark a substituted pref as VPre, as they behave the same way.
|
|
112
|
-
glyph.
|
|
173
|
+
useInfo(glyph).category = 'VPre';
|
|
113
174
|
}
|
|
114
175
|
}
|
|
115
176
|
}
|
|
116
177
|
|
|
178
|
+
/** @type {import('../../../types/fontkit').ShapingStageFn} */
|
|
117
179
|
function reorder(font, glyphs) {
|
|
118
|
-
let
|
|
180
|
+
let dottedCircleGlyph = font.glyphForCodePoint(0x25cc);
|
|
181
|
+
let dottedCircle = dottedCircleGlyph ? dottedCircleGlyph.id : 0;
|
|
119
182
|
|
|
120
183
|
for (let start = 0, end = nextSyllable(glyphs, 0); start < glyphs.length; start = end, end = nextSyllable(glyphs, start)) {
|
|
121
184
|
let i, j;
|
|
122
|
-
let info = glyphs[start]
|
|
185
|
+
let info = useInfo(glyphs[start]);
|
|
123
186
|
let type = info.syllableType;
|
|
124
187
|
|
|
125
188
|
// Only a few syllable types need reordering.
|
|
@@ -133,7 +196,7 @@ function reorder(font, glyphs) {
|
|
|
133
196
|
g.shaperInfo = info;
|
|
134
197
|
|
|
135
198
|
// Insert after possible Repha.
|
|
136
|
-
for (i = start; i < end && glyphs[i].
|
|
199
|
+
for (i = start; i < end && useInfo(glyphs[i]).category === 'R'; i++);
|
|
137
200
|
glyphs.splice(i + 1, 0, g);
|
|
138
201
|
end++;
|
|
139
202
|
}
|
|
@@ -142,7 +205,7 @@ function reorder(font, glyphs) {
|
|
|
142
205
|
if (info.category === 'R' && end - start > 1) {
|
|
143
206
|
// Got a repha. Reorder it to after first base, before first halant.
|
|
144
207
|
for (i = start + 1; i < end; i++) {
|
|
145
|
-
info = glyphs[i]
|
|
208
|
+
info = useInfo(glyphs[i]);
|
|
146
209
|
if (isBase(info) || isHalant(glyphs[i])) {
|
|
147
210
|
// If we hit a halant, move before it; otherwise it's a base: move to it's
|
|
148
211
|
// place, and shift things in between backward.
|
|
@@ -158,7 +221,7 @@ function reorder(font, glyphs) {
|
|
|
158
221
|
|
|
159
222
|
// Move things back.
|
|
160
223
|
for (i = start, j = end; i < end; i++) {
|
|
161
|
-
info = glyphs[i]
|
|
224
|
+
info = useInfo(glyphs[i]);
|
|
162
225
|
if (isBase(info) || isHalant(glyphs[i])) {
|
|
163
226
|
// If we hit a halant, move after it; otherwise it's a base: move to it's
|
|
164
227
|
// place, and shift things in between backward.
|
|
@@ -170,17 +233,20 @@ function reorder(font, glyphs) {
|
|
|
170
233
|
}
|
|
171
234
|
}
|
|
172
235
|
|
|
236
|
+
/** @param {GlyphInfoLike[]} glyphs @param {number} start @returns {number} */
|
|
173
237
|
function nextSyllable(glyphs, start) {
|
|
174
238
|
if (start >= glyphs.length) return start;
|
|
175
|
-
let syllable = glyphs[start].
|
|
176
|
-
while (++start < glyphs.length && glyphs[start].
|
|
239
|
+
let syllable = useInfo(glyphs[start]).syllable;
|
|
240
|
+
while (++start < glyphs.length && useInfo(glyphs[start]).syllable === syllable);
|
|
177
241
|
return start;
|
|
178
242
|
}
|
|
179
243
|
|
|
244
|
+
/** @param {GlyphInfoLike} glyph @returns {boolean} */
|
|
180
245
|
function isHalant(glyph) {
|
|
181
|
-
return glyph.
|
|
246
|
+
return useInfo(glyph).category === 'H' && !glyph.isLigated;
|
|
182
247
|
}
|
|
183
248
|
|
|
249
|
+
/** @param {USEShaperInfo} info @returns {boolean} */
|
|
184
250
|
function isBase(info) {
|
|
185
251
|
return info.category === 'B' || info.category === 'GB';
|
|
186
252
|
}
|