@cantoo/fontkit 2.0.4 → 2.0.6
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.mjs +4448 -1142
- package/dist/browser-module.mjs.map +1 -1
- package/dist/browser.cjs +4449 -1142
- package/dist/browser.cjs.map +1 -1
- package/dist/main.cjs +4449 -1142
- package/dist/main.cjs.map +1 -1
- package/dist/module.mjs +4448 -1142
- package/dist/module.mjs.map +1 -1
- package/package.json +4 -1
- package/src/CmapProcessor.js +60 -21
- package/src/DFont.js +114 -14
- package/src/TTFFont.js +260 -62
- package/src/TrueTypeCollection.js +59 -15
- package/src/WOFF2Font.js +128 -22
- package/src/WOFFFont.js +45 -8
- package/src/aat/AATFeatureMap.js +83 -40
- package/src/aat/AATLayoutEngine.js +34 -2
- package/src/aat/AATLookupTable.js +24 -6
- package/src/aat/AATMorxProcessor.js +202 -65
- package/src/aat/AATStateMachine.js +35 -3
- package/src/base.js +20 -1
- package/src/cff/CFFCharsets.js +3 -0
- package/src/cff/CFFDict.js +81 -11
- package/src/cff/CFFEncodings.js +2 -0
- package/src/cff/CFFFont.js +120 -24
- package/src/cff/CFFIndex.js +66 -9
- package/src/cff/CFFOperand.js +53 -28
- package/src/cff/CFFPointer.js +41 -2
- package/src/cff/CFFPrivateDict.js +34 -24
- package/src/cff/CFFStandardStrings.js +68 -67
- package/src/cff/CFFTop.js +161 -68
- package/src/decorators.js +34 -15
- package/src/encodings.js +133 -116
- package/src/fs.js +10 -0
- package/src/glyph/BBox.js +14 -0
- package/src/glyph/CFFGlyph.js +293 -152
- package/src/glyph/COLRGlyph.js +40 -5
- package/src/glyph/Glyph.js +72 -13
- package/src/glyph/GlyphVariationProcessor.js +158 -62
- package/src/glyph/Path.js +119 -32
- package/src/glyph/SBIXGlyph.js +57 -8
- package/src/glyph/StandardNames.js +1 -0
- package/src/glyph/TTFGlyph.js +203 -63
- package/src/glyph/TTFGlyphEncoder.js +58 -26
- package/src/glyph/WOFF2Glyph.js +25 -6
- package/src/layout/GlyphPosition.js +6 -0
- package/src/layout/GlyphRun.js +28 -10
- package/src/layout/KernProcessor.js +63 -4
- package/src/layout/LayoutEngine.js +99 -26
- package/src/layout/Script.js +52 -26
- package/src/layout/UnicodeLayoutEngine.js +34 -10
- package/src/opentype/GPOSProcessor.js +156 -45
- package/src/opentype/GSUBProcessor.js +92 -33
- package/src/opentype/GlyphInfo.js +53 -5
- package/src/opentype/GlyphIterator.js +51 -2
- package/src/opentype/OTLayoutEngine.js +66 -8
- package/src/opentype/OTProcessor.js +284 -71
- package/src/opentype/ShapingPlan.js +52 -5
- package/src/opentype/shapers/ArabicShaper.js +27 -8
- package/src/opentype/shapers/DefaultShaper.js +54 -10
- package/src/opentype/shapers/HangulShaper.js +120 -42
- package/src/opentype/shapers/HebrewShaper.js +50 -16
- package/src/opentype/shapers/IndicShaper.js +159 -44
- package/src/opentype/shapers/ThaiShaper.js +50 -19
- package/src/opentype/shapers/UniversalShaper.js +83 -17
- package/src/opentype/shapers/index.js +48 -35
- package/src/opentype/shapers/indic-data.js +2 -2
- package/src/packages/unicode-properties/data-trie.js +2 -1
- package/src/packages/unicode-properties/data.js +13 -1
- package/src/packages/unicode-properties/dist/index.cjs +23 -10
- package/src/packages/unicode-properties/dist/index.cjs.map +2 -2
- package/src/packages/unicode-properties/dist/index.mjs +22 -10
- package/src/packages/unicode-properties/dist/index.mjs.map +2 -2
- package/src/packages/unicode-properties/generate.js +3 -4
- package/src/packages/unicode-properties/index.js +95 -30
- package/src/packages/unicode-properties/package.json +1 -1
- package/src/packages/unicode-trie/builder.js +146 -29
- package/src/packages/unicode-trie/dist/builder.cjs +135 -25
- package/src/packages/unicode-trie/dist/builder.cjs.map +2 -2
- package/src/packages/unicode-trie/dist/builder.mjs +134 -25
- 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 +63 -12
- package/src/packages/unicode-trie/package.json +1 -1
- package/src/packages/unicode-trie/swap.js +15 -2
- package/src/subset/CFFSubset.js +112 -22
- package/src/subset/Subset.js +23 -3
- package/src/subset/TTFSubset.js +84 -41
- package/src/tables/BASE.js +28 -27
- package/src/tables/COLR.js +10 -9
- package/src/tables/CPAL.js +2 -1
- package/src/tables/DSIG.js +8 -7
- package/src/tables/EBDT.js +10 -4
- package/src/tables/EBLC.js +70 -10
- package/src/tables/GDEF.js +18 -17
- package/src/tables/GPOS.js +153 -90
- package/src/tables/GSUB.js +50 -39
- package/src/tables/HVAR.js +51 -5
- package/src/tables/JSTF.js +17 -16
- package/src/tables/LTSH.js +4 -3
- package/src/tables/OS2.js +50 -49
- package/src/tables/PCLT.js +16 -15
- package/src/tables/STAT.js +46 -6
- package/src/tables/VDMX.js +18 -17
- package/src/tables/VORG.js +8 -7
- package/src/tables/WOFF2Directory.js +57 -9
- package/src/tables/WOFFDirectory.js +32 -20
- 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 +83 -63
- package/src/tables/cvt.js +1 -0
- package/src/tables/directory.js +56 -28
- 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 +6 -5
- package/src/tables/glyf.js +2 -1
- package/src/tables/gvar.js +29 -2
- package/src/tables/hdmx.js +18 -7
- package/src/tables/head.js +17 -16
- package/src/tables/hhea.js +15 -14
- package/src/tables/hmtx.js +18 -2
- package/src/tables/index.js +1 -5
- package/src/tables/just.js +15 -4
- package/src/tables/kern.js +84 -47
- package/src/tables/loca.js +23 -10
- package/src/tables/maxp.js +16 -15
- package/src/tables/morx.js +27 -20
- package/src/tables/name.js +120 -39
- package/src/tables/opbd.js +2 -1
- package/src/tables/opentype.js +126 -91
- package/src/tables/post.js +18 -11
- package/src/tables/prep.js +1 -0
- package/src/tables/sbix.js +12 -1
- package/src/tables/variations.js +29 -9
- package/src/tables/vhea.js +15 -14
- package/src/tables/vmtx.js +20 -4
- package/src/utils.js +15 -0
|
@@ -1,13 +1,24 @@
|
|
|
1
|
-
import {getCombiningClass} from '../../packages/unicode-properties/index.js';
|
|
1
|
+
import { getCombiningClass } from '../../packages/unicode-properties/index.js';
|
|
2
2
|
import DefaultShaper from './DefaultShaper';
|
|
3
3
|
import GlyphInfo from '../GlyphInfo';
|
|
4
4
|
|
|
5
|
+
/** @typedef {import('../../../types/fontkit').ShapingPlanLike} ShapingPlanLike */
|
|
6
|
+
/** @typedef {import('../../../types/fontkit').GlyphInfoLike} GlyphInfoLike */
|
|
7
|
+
/** @typedef {import('../../../types/fontkit').LayoutFont} LayoutFont */
|
|
8
|
+
/** @typedef {import('../../../types/fontkit').OTScriptRecord} OTScriptRecord */
|
|
9
|
+
/** @typedef {import('../../../types/fontkit').OTLangSys} OTLangSys */
|
|
10
|
+
/** @typedef {import('../../../types/fontkit').OTLangSysRecord} OTLangSysRecord */
|
|
11
|
+
|
|
5
12
|
/**
|
|
6
13
|
* Hebrew shaper (HarfBuzz hb-ot-shaper-hebrew.cc):
|
|
7
14
|
* - Compose presentation forms (FBxx) when the font lacks GPOS mark for `hebr`
|
|
8
15
|
* - Reorder patah/qamats + sheva/hiriq + meteg/below so meteg sits next to the base
|
|
9
16
|
*/
|
|
10
17
|
export default class HebrewShaper extends DefaultShaper {
|
|
18
|
+
/**
|
|
19
|
+
* @param {ShapingPlanLike} plan
|
|
20
|
+
* @param {GlyphInfoLike[]} glyphs
|
|
21
|
+
*/
|
|
11
22
|
static assignFeatures(plan, glyphs) {
|
|
12
23
|
super.assignFeatures(plan, glyphs);
|
|
13
24
|
|
|
@@ -26,7 +37,12 @@ const DAGESH_FORMS = [
|
|
|
26
37
|
0xFB48, 0xFB49, 0xFB4A
|
|
27
38
|
];
|
|
28
39
|
|
|
29
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Precomposed Hebrew presentation form for `a + b`, or null (HB compose_hebrew).
|
|
42
|
+
* @param {number} a
|
|
43
|
+
* @param {number} b
|
|
44
|
+
* @returns {number | null}
|
|
45
|
+
*/
|
|
30
46
|
function composeHebrewPair(a, b) {
|
|
31
47
|
switch (b) {
|
|
32
48
|
case 0x05B4: // HIRIQ
|
|
@@ -53,10 +69,14 @@ function composeHebrewPair(a, b) {
|
|
|
53
69
|
}
|
|
54
70
|
}
|
|
55
71
|
|
|
56
|
-
|
|
57
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Greedily compose adjacent pairs when the font has the glyph
|
|
74
|
+
* (e.g. SHIN + SHIN_DOT → FB2A, then + DAGESH → FB2C).
|
|
75
|
+
* @param {GlyphInfoLike[]} glyphs
|
|
76
|
+
* @param {LayoutFont} font
|
|
77
|
+
*/
|
|
58
78
|
function composeHebrew(glyphs, font) {
|
|
59
|
-
for (let i = 0; i + 1 < glyphs.length;
|
|
79
|
+
for (let i = 0; i + 1 < glyphs.length;) {
|
|
60
80
|
let composed = glyphs[i].codePoints[0];
|
|
61
81
|
let consumed = 1;
|
|
62
82
|
while (i + consumed < glyphs.length) {
|
|
@@ -66,26 +86,35 @@ function composeHebrew(glyphs, font) {
|
|
|
66
86
|
consumed++;
|
|
67
87
|
}
|
|
68
88
|
if (consumed > 1) {
|
|
89
|
+
let composedGlyph = font.glyphForCodePoint(composed);
|
|
90
|
+
if (!composedGlyph) {
|
|
91
|
+
i++;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
/** @type {number[]} */
|
|
69
95
|
let cps = [];
|
|
70
96
|
for (let j = 0; j < consumed; j++) cps.push(...glyphs[i + j].codePoints);
|
|
71
|
-
glyphs[i] = new GlyphInfo(font,
|
|
97
|
+
glyphs[i] = new GlyphInfo(font, composedGlyph.id, cps, glyphs[i].features);
|
|
72
98
|
glyphs.splice(i + 1, consumed - 1);
|
|
73
99
|
}
|
|
74
100
|
i++;
|
|
75
101
|
}
|
|
76
102
|
}
|
|
77
103
|
|
|
78
|
-
|
|
79
|
-
|
|
104
|
+
/**
|
|
105
|
+
* HB reorder_marks_hebrew: [patah/qamats, sheva/hiriq, meteg/below] → swap last two.
|
|
106
|
+
* unicode-properties already exposes Hebrew modified CCCs (CCC10/14/17/18/22).
|
|
107
|
+
* @param {GlyphInfoLike[]} glyphs
|
|
108
|
+
*/
|
|
80
109
|
function reorderMarksHebrew(glyphs) {
|
|
81
110
|
for (let i = 2; i < glyphs.length; i++) {
|
|
82
111
|
let c0 = getCombiningClass(glyphs[i - 2].codePoints[0]);
|
|
83
112
|
let c1 = getCombiningClass(glyphs[i - 1].codePoints[0]);
|
|
84
113
|
let c2 = getCombiningClass(glyphs[i].codePoints[0]);
|
|
85
114
|
if (
|
|
86
|
-
(c0 === 'CCC17' || c0 === 'CCC18')
|
|
87
|
-
(c1 === 'CCC10' || c1 === 'CCC14')
|
|
88
|
-
(c2 === 'CCC22' || c2 === 'Below')
|
|
115
|
+
(c0 === 'CCC17' || c0 === 'CCC18')
|
|
116
|
+
&& (c1 === 'CCC10' || c1 === 'CCC14')
|
|
117
|
+
&& (c2 === 'CCC22' || c2 === 'Below')
|
|
89
118
|
) {
|
|
90
119
|
[glyphs[i - 1], glyphs[i]] = [glyphs[i], glyphs[i - 1]];
|
|
91
120
|
break;
|
|
@@ -93,13 +122,18 @@ function reorderMarksHebrew(glyphs) {
|
|
|
93
122
|
}
|
|
94
123
|
}
|
|
95
124
|
|
|
96
|
-
|
|
125
|
+
/**
|
|
126
|
+
* Gate fallback composition on Hebrew-script GPOS `mark` (not the global featureList).
|
|
127
|
+
* @param {LayoutFont} font
|
|
128
|
+
* @returns {boolean}
|
|
129
|
+
*/
|
|
97
130
|
function hasGposMark(font) {
|
|
98
|
-
let hebr = font.GPOS?.scriptList?.find(e => e.tag === 'hebr')?.script;
|
|
131
|
+
let hebr = font.GPOS?.scriptList?.find(/** @param {OTScriptRecord} e */ e => e.tag === 'hebr')?.script;
|
|
99
132
|
if (!hebr) return false;
|
|
100
133
|
|
|
101
|
-
let langs = [hebr.defaultLangSys, ...(hebr.langSysRecords || []).map(l => l.langSys)]
|
|
102
|
-
|
|
103
|
-
|
|
134
|
+
let langs = [hebr.defaultLangSys, ...(hebr.langSysRecords || []).map(/** @param {OTLangSysRecord} l */ l => l.langSys)]
|
|
135
|
+
.filter(/** @returns {value is OTLangSys} */ (/** @type {OTLangSys | null} */ value) => value != null);
|
|
136
|
+
return langs.some(/** @param {OTLangSys} ls */ ls =>
|
|
137
|
+
(ls.featureIndexes || []).some(/** @param {number} i */ i => font.GPOS?.featureList[i]?.tag === 'mark')
|
|
104
138
|
);
|
|
105
139
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import DefaultShaper from './DefaultShaper';
|
|
2
2
|
import StateMachine from 'dfa';
|
|
3
3
|
import UnicodeTrie from '../../packages/unicode-trie/index.js';
|
|
4
|
-
import {getCategory} from '../../packages/unicode-properties/index.js';
|
|
4
|
+
import { getCategory } from '../../packages/unicode-properties/index.js';
|
|
5
5
|
import * as Script from '../../layout/Script';
|
|
6
6
|
import GlyphInfo from '../GlyphInfo';
|
|
7
7
|
import indicMachine from './indic.json';
|
|
@@ -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
|
-
glyph.features = {[feature]: true};
|
|
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++) {
|
|
@@ -183,14 +289,14 @@ function initialReordering(font, glyphs, plan) {
|
|
|
183
289
|
}
|
|
184
290
|
|
|
185
291
|
for (let start = 0, end = nextSyllable(glyphs, 0); start < glyphs.length; start = end, end = nextSyllable(glyphs, start)) {
|
|
186
|
-
let {syllableType} = glyphs[start].shaperInfo;
|
|
292
|
+
let { syllableType } = glyphs[start].shaperInfo;
|
|
187
293
|
|
|
188
294
|
if (syllableType === 'symbol_cluster' || syllableType === 'non_indic_cluster') {
|
|
189
295
|
continue;
|
|
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),
|
|
@@ -224,12 +330,12 @@ function initialReordering(font, glyphs, plan) {
|
|
|
224
330
|
// If the syllable starts with Ra + Halant (in a script that has Reph)
|
|
225
331
|
// and has more than one consonant, Ra is excluded from candidates for
|
|
226
332
|
// base consonants.
|
|
227
|
-
if (indicConfig.rephPos !== POSITIONS.Ra_To_Become_Reph
|
|
228
|
-
features.rphf
|
|
229
|
-
start + 3 <= end && (
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
333
|
+
if (indicConfig.rephPos !== POSITIONS.Ra_To_Become_Reph
|
|
334
|
+
&& features.rphf
|
|
335
|
+
&& start + 3 <= end && (
|
|
336
|
+
(indicConfig.rephMode === 'Implicit' && !isJoiner(glyphs[start + 2]))
|
|
337
|
+
|| (indicConfig.rephMode === 'Explicit' && glyphs[start + 2].shaperInfo.category === CATEGORIES.ZWJ)
|
|
338
|
+
)
|
|
233
339
|
) {
|
|
234
340
|
// See if it matches the 'rphf' feature.
|
|
235
341
|
let g = [glyphs[start].copy(), glyphs[start + 1].copy(), glyphs[start + 2].copy()];
|
|
@@ -510,9 +616,9 @@ function initialReordering(font, glyphs, plan) {
|
|
|
510
616
|
//
|
|
511
617
|
// Test case: U+0924,U+094D,U+0930,U+094d,U+200D,U+0915
|
|
512
618
|
for (let i = start; i + 1 < base; i++) {
|
|
513
|
-
if (glyphs[i].shaperInfo.category === CATEGORIES.Ra
|
|
514
|
-
glyphs[i + 1].shaperInfo.category === CATEGORIES.H
|
|
515
|
-
(i + 1 === base || glyphs[i + 2].shaperInfo.category === CATEGORIES.ZWJ)
|
|
619
|
+
if (glyphs[i].shaperInfo.category === CATEGORIES.Ra
|
|
620
|
+
&& glyphs[i + 1].shaperInfo.category === CATEGORIES.H
|
|
621
|
+
&& (i + 1 === base || glyphs[i + 2].shaperInfo.category === CATEGORIES.ZWJ)
|
|
516
622
|
) {
|
|
517
623
|
glyphs[i].features.blwf = true;
|
|
518
624
|
glyphs[i + 1].features.blwf = true;
|
|
@@ -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;
|
|
@@ -715,11 +825,11 @@ function finalReordering(font, glyphs, plan) {
|
|
|
715
825
|
// - If repha is encoded separately and in the logical position, we should only
|
|
716
826
|
// move it if it did NOT ligate. If it ligated, it's probably the font trying
|
|
717
827
|
// to make it work without the reordering.
|
|
718
|
-
if (start + 1 < end
|
|
719
|
-
glyphs[start].shaperInfo.position === POSITIONS.Ra_To_Become_Reph
|
|
720
|
-
(glyphs[start].shaperInfo.category === CATEGORIES.Repha) !== (glyphs[start].isLigated && !glyphs[start].isMultiplied)
|
|
828
|
+
if (start + 1 < end
|
|
829
|
+
&& glyphs[start].shaperInfo.position === POSITIONS.Ra_To_Become_Reph
|
|
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
|
|
|
@@ -838,9 +948,9 @@ function finalReordering(font, glyphs, plan) {
|
|
|
838
948
|
if (tryPref && base + 1 < end) {
|
|
839
949
|
for (let i = base + 1; i < end; i++) {
|
|
840
950
|
if (glyphs[i].features.pref) {
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
951
|
+
// 1. Only reorder a glyph produced by substitution during application
|
|
952
|
+
// of the <pref> feature. (Note that a font may shape a Ra consonant with
|
|
953
|
+
// the feature generally but block it in certain contexts.)
|
|
844
954
|
|
|
845
955
|
// Note: We just check that something got substituted. We don't check that
|
|
846
956
|
// the <pref> feature actually did it...
|
|
@@ -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
|
|
@@ -89,9 +108,10 @@ const NOT_CONSONANT = 4;
|
|
|
89
108
|
|
|
90
109
|
const AV = 0; // above-base vowel/mark
|
|
91
110
|
const BV = 1; // below-base vowel/mark
|
|
92
|
-
const T = 2;
|
|
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,12 +120,13 @@ 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
|
-
u === 0x0E31
|
|
106
|
-
(u >= 0x0E34 && u <= 0x0E37)
|
|
107
|
-
u === 0x0E47
|
|
108
|
-
(u >= 0x0E4D && u <= 0x0E4E)
|
|
126
|
+
u === 0x0E31
|
|
127
|
+
|| (u >= 0x0E34 && u <= 0x0E37)
|
|
128
|
+
|| u === 0x0E47
|
|
129
|
+
|| (u >= 0x0E4D && u <= 0x0E4E)
|
|
109
130
|
) {
|
|
110
131
|
return AV;
|
|
111
132
|
}
|
|
@@ -116,24 +137,27 @@ 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
|
-
[[NOP, T3], [NOP, T0], [SD, T3]],
|
|
122
|
-
[[SL, T2],
|
|
123
|
-
[[NOP, T3], [NOP, T2], [SL, T3]],
|
|
124
|
-
[[NOP, T3], [NOP, T3], [NOP, T3]]
|
|
143
|
+
[[NOP, T3], [NOP, T0], [SD, T3]], // T0
|
|
144
|
+
[[SL, T2], [NOP, T1], [SDL, T2]], // T1
|
|
145
|
+
[[NOP, T3], [NOP, T2], [SL, T3]], // T2
|
|
146
|
+
[[NOP, T3], [NOP, T3], [NOP, T3]] // T3
|
|
125
147
|
];
|
|
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
|
-
[[NOP, B0], [NOP, B2], [NOP, B0]],
|
|
132
|
-
[[NOP, B1], [RD, B2],
|
|
133
|
-
[[NOP, B2], [SD, B2],
|
|
154
|
+
[[NOP, B0], [NOP, B2], [NOP, B0]], // B0
|
|
155
|
+
[[NOP, B1], [RD, B2], [NOP, B1]], // B1
|
|
156
|
+
[[NOP, B2], [SD, B2], [NOP, B2]] // B2
|
|
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
|
|
@@ -143,7 +167,7 @@ const PUA_MAPPINGS = {
|
|
|
143
167
|
[0x0E4C, 0xF70E, 0xF897], // THANTHAKHAT
|
|
144
168
|
[0x0E38, 0xF718, 0xF89B], // SARA U
|
|
145
169
|
[0x0E39, 0xF719, 0xF89C], // SARA UU
|
|
146
|
-
[0x0E3A, 0xF71A, 0xF89D]
|
|
170
|
+
[0x0E3A, 0xF71A, 0xF89D] // PHINTHU
|
|
147
171
|
],
|
|
148
172
|
[SDL]: [
|
|
149
173
|
[0x0E48, 0xF705, 0xF88C],
|
|
@@ -164,14 +188,15 @@ const PUA_MAPPINGS = {
|
|
|
164
188
|
[0x0E36, 0xF703, 0xF887], // SARA UE
|
|
165
189
|
[0x0E37, 0xF704, 0xF888], // SARA UEE
|
|
166
190
|
[0x0E47, 0xF712, 0xF889], // MAITAIKHU
|
|
167
|
-
[0x0E4D, 0xF711, 0xF899]
|
|
191
|
+
[0x0E4D, 0xF711, 0xF899] // NIKHAHIT
|
|
168
192
|
],
|
|
169
193
|
[RD]: [
|
|
170
194
|
[0x0E0D, 0xF70F, 0xF89A], // YO YING
|
|
171
|
-
[0x0E10, 0xF700, 0xF89E]
|
|
195
|
+
[0x0E10, 0xF700, 0xF89E] // THO THAN
|
|
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';
|