@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
|
@@ -7,24 +7,59 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @private
|
|
9
9
|
*/
|
|
10
|
+
|
|
11
|
+
/** @typedef {import('../../types/fontkit').LayoutFont} LayoutFont */
|
|
12
|
+
/** @typedef {import('../../types/fontkit').ScriptTag} ScriptTag */
|
|
13
|
+
/** @typedef {import('../../types/fontkit').TextDirection} TextDirection */
|
|
14
|
+
/** @typedef {import('../../types/fontkit').FeatureMap} FeatureMap */
|
|
15
|
+
/** @typedef {import('../../types/fontkit').FeatureInput} FeatureInput */
|
|
16
|
+
/** @typedef {import('../../types/fontkit').GlyphInfoLike} GlyphInfoLike */
|
|
17
|
+
/** @typedef {import('../../types/fontkit').GlyphPositionLike} GlyphPositionLike */
|
|
18
|
+
/** @typedef {import('../../types/fontkit').ShapingStage} ShapingStage */
|
|
19
|
+
/** @typedef {import('../../types/fontkit').ShapingStageFn} ShapingStageFn */
|
|
20
|
+
/** @typedef {import('./OTProcessor').default} OTProcessor */
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {{ global?: string[], local?: string[] }} FeatureStagesArg
|
|
24
|
+
*/
|
|
25
|
+
|
|
10
26
|
export default class ShapingPlan {
|
|
27
|
+
/**
|
|
28
|
+
* @param {LayoutFont} font
|
|
29
|
+
* @param {ScriptTag | string[] | null | undefined} script
|
|
30
|
+
* @param {TextDirection} direction
|
|
31
|
+
*/
|
|
11
32
|
constructor(font, script, direction) {
|
|
33
|
+
/** @type {LayoutFont} */
|
|
12
34
|
this.font = font;
|
|
35
|
+
/** @type {ScriptTag | string[] | null | undefined} */
|
|
13
36
|
this.script = script;
|
|
37
|
+
/** @type {TextDirection} */
|
|
14
38
|
this.direction = direction;
|
|
39
|
+
/** @type {ShapingStage[]} */
|
|
15
40
|
this.stages = [];
|
|
41
|
+
/** @type {FeatureMap} */
|
|
16
42
|
this.globalFeatures = {};
|
|
43
|
+
/** @type {Record<string, number>} */
|
|
17
44
|
this.allFeatures = {};
|
|
45
|
+
/** @type {FeatureMap | null} */
|
|
18
46
|
this.userFeatures = null; // original user feature values (for Type 3 alternate index)
|
|
47
|
+
/** @type {ScriptTag | string[] | null | undefined} */
|
|
48
|
+
this.bufferScript = undefined;
|
|
19
49
|
}
|
|
20
50
|
|
|
21
51
|
/**
|
|
22
52
|
* Adds the given features to the last stage.
|
|
23
53
|
* Already-planned tags are not duplicated, but a global add still promotes them.
|
|
54
|
+
* @param {string[]} features
|
|
55
|
+
* @param {boolean} global
|
|
24
56
|
*/
|
|
25
57
|
_addFeatures(features, global) {
|
|
26
58
|
let stageIndex = this.stages.length - 1;
|
|
27
59
|
let stage = this.stages[stageIndex];
|
|
60
|
+
if (!Array.isArray(stage)) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
28
63
|
for (let feature of features) {
|
|
29
64
|
if (this.allFeatures[feature] == null) {
|
|
30
65
|
stage.push(feature);
|
|
@@ -39,6 +74,8 @@ export default class ShapingPlan {
|
|
|
39
74
|
|
|
40
75
|
/**
|
|
41
76
|
* Add features to the last stage
|
|
77
|
+
* @param {string | string[] | FeatureStagesArg} arg
|
|
78
|
+
* @param {boolean} [global]
|
|
42
79
|
*/
|
|
43
80
|
add(arg, global = true) {
|
|
44
81
|
if (this.stages.length === 0) {
|
|
@@ -51,16 +88,18 @@ export default class ShapingPlan {
|
|
|
51
88
|
|
|
52
89
|
if (Array.isArray(arg)) {
|
|
53
90
|
this._addFeatures(arg, global);
|
|
54
|
-
} else if (typeof arg === 'object') {
|
|
91
|
+
} else if (typeof arg === 'object' && arg !== null) {
|
|
55
92
|
this._addFeatures(arg.global || [], true);
|
|
56
93
|
this._addFeatures(arg.local || [], false);
|
|
57
94
|
} else {
|
|
58
|
-
throw new Error(
|
|
95
|
+
throw new Error('Unsupported argument to ShapingPlan#add');
|
|
59
96
|
}
|
|
60
97
|
}
|
|
61
98
|
|
|
62
99
|
/**
|
|
63
100
|
* Add a new stage
|
|
101
|
+
* @param {string | string[] | FeatureStagesArg | ShapingStageFn} arg
|
|
102
|
+
* @param {boolean} [global]
|
|
64
103
|
*/
|
|
65
104
|
addStage(arg, global) {
|
|
66
105
|
if (typeof arg === 'function') {
|
|
@@ -71,17 +110,22 @@ export default class ShapingPlan {
|
|
|
71
110
|
}
|
|
72
111
|
}
|
|
73
112
|
|
|
113
|
+
/**
|
|
114
|
+
* @param {FeatureInput | null | undefined} features
|
|
115
|
+
*/
|
|
74
116
|
setFeatureOverrides(features) {
|
|
75
117
|
if (Array.isArray(features)) {
|
|
76
118
|
this.add(features);
|
|
77
|
-
} else if (typeof features === 'object') {
|
|
119
|
+
} else if (typeof features === 'object' && features !== null) {
|
|
78
120
|
this.userFeatures = features;
|
|
79
121
|
for (let tag in features) {
|
|
80
122
|
if (features[tag]) {
|
|
81
123
|
this.add(tag);
|
|
82
124
|
} else if (this.allFeatures[tag] != null) {
|
|
83
125
|
let stage = this.stages[this.allFeatures[tag]];
|
|
84
|
-
|
|
126
|
+
if (Array.isArray(stage)) {
|
|
127
|
+
stage.splice(stage.indexOf(tag), 1);
|
|
128
|
+
}
|
|
85
129
|
delete this.allFeatures[tag];
|
|
86
130
|
delete this.globalFeatures[tag];
|
|
87
131
|
}
|
|
@@ -91,6 +135,7 @@ export default class ShapingPlan {
|
|
|
91
135
|
|
|
92
136
|
/**
|
|
93
137
|
* Assigns the global features to the given glyphs
|
|
138
|
+
* @param {GlyphInfoLike[]} glyphs
|
|
94
139
|
*/
|
|
95
140
|
assignGlobalFeatures(glyphs) {
|
|
96
141
|
for (let glyph of glyphs) {
|
|
@@ -102,6 +147,9 @@ export default class ShapingPlan {
|
|
|
102
147
|
|
|
103
148
|
/**
|
|
104
149
|
* Executes the planned stages using the given OTProcessor
|
|
150
|
+
* @param {OTProcessor} processor
|
|
151
|
+
* @param {GlyphInfoLike[]} glyphs
|
|
152
|
+
* @param {GlyphPositionLike[] | null | undefined} [positions]
|
|
105
153
|
*/
|
|
106
154
|
process(processor, glyphs, positions) {
|
|
107
155
|
// Preserve original user values (e.g. { aalt: 2 }) for GSUB Type 3.
|
|
@@ -112,7 +160,6 @@ export default class ShapingPlan {
|
|
|
112
160
|
if (!positions) {
|
|
113
161
|
stage(this.font, glyphs, this);
|
|
114
162
|
}
|
|
115
|
-
|
|
116
163
|
} else if (stage.length > 0) {
|
|
117
164
|
processor.applyFeatures(stage, glyphs, positions);
|
|
118
165
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import DefaultShaper from './DefaultShaper';
|
|
2
|
-
import {getCategory} from '../../packages/unicode-properties/index.js';
|
|
2
|
+
import { getCategory } from '../../packages/unicode-properties/index.js';
|
|
3
3
|
import UnicodeTrie from '../../packages/unicode-trie/index.js';
|
|
4
4
|
import { decodeBase64 } from '../../utils';
|
|
5
5
|
import dataTrie from './data.trie';
|
|
6
6
|
|
|
7
|
+
/** @typedef {import('../../../types/fontkit').ShapingPlanLike} ShapingPlanLike */
|
|
8
|
+
/** @typedef {import('../../../types/fontkit').GlyphInfoLike} GlyphInfoLike */
|
|
9
|
+
|
|
7
10
|
const trie = new UnicodeTrie(decodeBase64(dataTrie));
|
|
8
11
|
const FEATURES = ['isol', 'fina', 'fin2', 'fin3', 'medi', 'med2', 'init'];
|
|
9
12
|
|
|
@@ -25,31 +28,35 @@ const FIN3 = 'fin3';
|
|
|
25
28
|
const MEDI = 'medi';
|
|
26
29
|
const MED2 = 'med2';
|
|
27
30
|
const INIT = 'init';
|
|
31
|
+
/** @type {null} */
|
|
28
32
|
const NONE = null;
|
|
29
33
|
|
|
34
|
+
/** @typedef {[string | null, string | null, number]} ArabicStateTransition */
|
|
35
|
+
|
|
30
36
|
// Each entry is [prevAction, curAction, nextState]
|
|
37
|
+
/** @type {ArabicStateTransition[][]} */
|
|
31
38
|
const STATE_TABLE = [
|
|
32
39
|
// Non_Joining, Left_Joining, Right_Joining, Dual_Joining, ALAPH, DALATH RISH
|
|
33
40
|
// State 0: prev was U, not willing to join.
|
|
34
|
-
[
|
|
41
|
+
[[NONE, NONE, 0], [NONE, ISOL, 2], [NONE, ISOL, 1], [NONE, ISOL, 2], [NONE, ISOL, 1], [NONE, ISOL, 6]],
|
|
35
42
|
|
|
36
43
|
// State 1: prev was R or ISOL/ALAPH, not willing to join.
|
|
37
|
-
[
|
|
44
|
+
[[NONE, NONE, 0], [NONE, ISOL, 2], [NONE, ISOL, 1], [NONE, ISOL, 2], [NONE, FIN2, 5], [NONE, ISOL, 6]],
|
|
38
45
|
|
|
39
46
|
// State 2: prev was D/L in ISOL form, willing to join.
|
|
40
|
-
[
|
|
47
|
+
[[NONE, NONE, 0], [NONE, ISOL, 2], [INIT, FINA, 1], [INIT, FINA, 3], [INIT, FINA, 4], [INIT, FINA, 6]],
|
|
41
48
|
|
|
42
49
|
// State 3: prev was D in FINA form, willing to join.
|
|
43
|
-
[
|
|
50
|
+
[[NONE, NONE, 0], [NONE, ISOL, 2], [MEDI, FINA, 1], [MEDI, FINA, 3], [MEDI, FINA, 4], [MEDI, FINA, 6]],
|
|
44
51
|
|
|
45
52
|
// State 4: prev was FINA ALAPH, not willing to join.
|
|
46
|
-
[
|
|
53
|
+
[[NONE, NONE, 0], [NONE, ISOL, 2], [MED2, ISOL, 1], [MED2, ISOL, 2], [MED2, FIN2, 5], [MED2, ISOL, 6]],
|
|
47
54
|
|
|
48
55
|
// State 5: prev was FIN2/FIN3 ALAPH, not willing to join.
|
|
49
|
-
[
|
|
56
|
+
[[NONE, NONE, 0], [NONE, ISOL, 2], [ISOL, ISOL, 1], [ISOL, ISOL, 2], [ISOL, FIN2, 5], [ISOL, ISOL, 6]],
|
|
50
57
|
|
|
51
58
|
// State 6: prev was DALATH/RISH, not willing to join.
|
|
52
|
-
[
|
|
59
|
+
[[NONE, NONE, 0], [NONE, ISOL, 2], [NONE, ISOL, 1], [NONE, ISOL, 2], [NONE, FIN3, 5], [NONE, ISOL, 6]]
|
|
53
60
|
];
|
|
54
61
|
|
|
55
62
|
/**
|
|
@@ -61,6 +68,9 @@ const STATE_TABLE = [
|
|
|
61
68
|
* https://github.com/behdad/harfbuzz/blob/master/src/hb-ot-shape-complex-arabic.cc
|
|
62
69
|
*/
|
|
63
70
|
export default class ArabicShaper extends DefaultShaper {
|
|
71
|
+
/**
|
|
72
|
+
* @param {ShapingPlanLike} plan
|
|
73
|
+
*/
|
|
64
74
|
static planFeatures(plan) {
|
|
65
75
|
plan.add(['ccmp', 'locl']);
|
|
66
76
|
for (let i = 0; i < FEATURES.length; i++) {
|
|
@@ -71,11 +81,16 @@ export default class ArabicShaper extends DefaultShaper {
|
|
|
71
81
|
plan.addStage('mset');
|
|
72
82
|
}
|
|
73
83
|
|
|
84
|
+
/**
|
|
85
|
+
* @param {ShapingPlanLike} plan
|
|
86
|
+
* @param {GlyphInfoLike[]} glyphs
|
|
87
|
+
*/
|
|
74
88
|
static assignFeatures(plan, glyphs) {
|
|
75
89
|
super.assignFeatures(plan, glyphs);
|
|
76
90
|
|
|
77
91
|
let prev = -1;
|
|
78
92
|
let state = 0;
|
|
93
|
+
/** @type {Array<string | null | undefined>} */
|
|
79
94
|
let actions = [];
|
|
80
95
|
|
|
81
96
|
// Apply the state machine to map glyphs to features
|
|
@@ -109,6 +124,10 @@ export default class ArabicShaper extends DefaultShaper {
|
|
|
109
124
|
}
|
|
110
125
|
}
|
|
111
126
|
|
|
127
|
+
/**
|
|
128
|
+
* @param {number} codePoint
|
|
129
|
+
* @returns {number}
|
|
130
|
+
*/
|
|
112
131
|
function getShapingClass(codePoint) {
|
|
113
132
|
let res = trie.get(codePoint);
|
|
114
133
|
if (res) {
|
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
import {isDigit, isMark} from '../../packages/unicode-properties/index.js';
|
|
1
|
+
import { isDigit, isMark } from '../../packages/unicode-properties/index.js';
|
|
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').FeatureInput} FeatureInput */
|
|
7
|
+
/** @typedef {import('../../../types/fontkit').LayoutFont} LayoutFont */
|
|
8
|
+
/** @typedef {import('../../../types/fontkit').FeatureMap} FeatureMap */
|
|
9
|
+
/** @typedef {import('../../../types/fontkit').TextDirection} TextDirection */
|
|
10
|
+
|
|
4
11
|
const VARIATION_FEATURES = ['rvrn'];
|
|
5
12
|
const COMMON_FEATURES = ['ccmp', 'locl', 'rlig', 'mark', 'mkmk'];
|
|
6
13
|
const FRACTIONAL_FEATURES = ['frac', 'numr', 'dnom'];
|
|
7
14
|
const HORIZONTAL_FEATURES = ['calt', 'clig', 'liga', 'rclt', 'curs', 'kern'];
|
|
8
15
|
const _VERTICAL_FEATURES = ['vert'];
|
|
16
|
+
/** @type {Record<TextDirection, string[]>} */
|
|
9
17
|
const DIRECTIONAL_FEATURES = {
|
|
10
18
|
ltr: ['ltra', 'ltrm'],
|
|
11
19
|
rtl: ['rtla', 'rtlm']
|
|
@@ -13,6 +21,12 @@ const DIRECTIONAL_FEATURES = {
|
|
|
13
21
|
|
|
14
22
|
export default class DefaultShaper {
|
|
15
23
|
static zeroMarkWidths = 'AFTER_GPOS';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {ShapingPlanLike} plan
|
|
27
|
+
* @param {GlyphInfoLike[]} glyphs
|
|
28
|
+
* @param {FeatureInput | null | undefined} features
|
|
29
|
+
*/
|
|
16
30
|
static plan(plan, glyphs, features) {
|
|
17
31
|
// Plan the features we want to apply
|
|
18
32
|
this.planPreprocessing(plan);
|
|
@@ -26,6 +40,9 @@ export default class DefaultShaper {
|
|
|
26
40
|
this.assignFeatures(plan, glyphs);
|
|
27
41
|
}
|
|
28
42
|
|
|
43
|
+
/**
|
|
44
|
+
* @param {ShapingPlanLike} plan
|
|
45
|
+
*/
|
|
29
46
|
static planPreprocessing(plan) {
|
|
30
47
|
plan.add({
|
|
31
48
|
global: [...VARIATION_FEATURES, ...DIRECTIONAL_FEATURES[plan.direction]],
|
|
@@ -33,15 +50,26 @@ export default class DefaultShaper {
|
|
|
33
50
|
});
|
|
34
51
|
}
|
|
35
52
|
|
|
53
|
+
/**
|
|
54
|
+
* @param {ShapingPlanLike} _plan
|
|
55
|
+
*/
|
|
36
56
|
static planFeatures(_plan) {
|
|
37
57
|
// Do nothing by default. Let subclasses override this.
|
|
38
58
|
}
|
|
39
59
|
|
|
60
|
+
/**
|
|
61
|
+
* @param {ShapingPlanLike} plan
|
|
62
|
+
* @param {FeatureInput | null | undefined} userFeatures
|
|
63
|
+
*/
|
|
40
64
|
static planPostprocessing(plan, userFeatures) {
|
|
41
65
|
plan.add([...COMMON_FEATURES, ...HORIZONTAL_FEATURES]);
|
|
42
66
|
plan.setFeatureOverrides(userFeatures);
|
|
43
67
|
}
|
|
44
68
|
|
|
69
|
+
/**
|
|
70
|
+
* @param {ShapingPlanLike} plan
|
|
71
|
+
* @param {GlyphInfoLike[]} glyphs
|
|
72
|
+
*/
|
|
45
73
|
static assignFeatures(plan, glyphs) {
|
|
46
74
|
// Font-aware NFC before GSUB (HarfBuzz default-shaper behavior).
|
|
47
75
|
composeGlyphs(plan.font, glyphs);
|
|
@@ -75,12 +103,20 @@ export default class DefaultShaper {
|
|
|
75
103
|
}
|
|
76
104
|
}
|
|
77
105
|
|
|
78
|
-
|
|
79
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Compose base+mark clusters when the font has the precomposed glyph.
|
|
108
|
+
* Skip pure mark reorders (same length) so Arabic calt etc. keep expected order.
|
|
109
|
+
* @param {LayoutFont} font
|
|
110
|
+
* @param {GlyphInfoLike[]} glyphs
|
|
111
|
+
*/
|
|
80
112
|
function composeGlyphs(font, glyphs) {
|
|
113
|
+
/**
|
|
114
|
+
* @param {GlyphInfoLike} g
|
|
115
|
+
* @returns {boolean}
|
|
116
|
+
*/
|
|
81
117
|
let singleMark = g => g.codePoints.length === 1 && isMark(g.codePoints[0]);
|
|
82
118
|
|
|
83
|
-
for (let i = 0; i < glyphs.length;
|
|
119
|
+
for (let i = 0; i < glyphs.length;) {
|
|
84
120
|
let base = glyphs[i];
|
|
85
121
|
if (base.codePoints.length !== 1 || isMark(base.codePoints[0])) {
|
|
86
122
|
i++;
|
|
@@ -94,12 +130,15 @@ function composeGlyphs(font, glyphs) {
|
|
|
94
130
|
continue;
|
|
95
131
|
}
|
|
96
132
|
|
|
97
|
-
let input = glyphs.slice(i, end).map(g => g.codePoints[0]);
|
|
98
|
-
let composed = Array.from(String.fromCodePoint(...input).normalize('NFC')).flatMap(char => {
|
|
133
|
+
let input = glyphs.slice(i, end).map(/** @param {GlyphInfoLike} g */ g => g.codePoints[0]);
|
|
134
|
+
let composed = Array.from(String.fromCodePoint(...input).normalize('NFC')).flatMap((char) => {
|
|
99
135
|
let cp = char.codePointAt(0);
|
|
136
|
+
if (cp == null) {
|
|
137
|
+
return /** @type {number[]} */ ([]);
|
|
138
|
+
}
|
|
100
139
|
return font.hasGlyphForCodePoint(cp)
|
|
101
140
|
? [cp]
|
|
102
|
-
: Array.from(char.normalize('NFD'), c => c.codePointAt(0));
|
|
141
|
+
: Array.from(char.normalize('NFD'), c => /** @type {number} */ (c.codePointAt(0)));
|
|
103
142
|
});
|
|
104
143
|
|
|
105
144
|
if (composed.length === input.length) {
|
|
@@ -107,9 +146,14 @@ function composeGlyphs(font, glyphs) {
|
|
|
107
146
|
continue;
|
|
108
147
|
}
|
|
109
148
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
)
|
|
149
|
+
/** @type {GlyphInfo[]} */
|
|
150
|
+
let replacement = [];
|
|
151
|
+
for (let cp of composed) {
|
|
152
|
+
let g = font.glyphForCodePoint(cp);
|
|
153
|
+
if (g) {
|
|
154
|
+
replacement.push(new GlyphInfo(font, g.id, [cp], /** @type {FeatureMap} */ (base.features)));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
113
157
|
glyphs.splice(i, end - i, ...replacement);
|
|
114
158
|
i += replacement.length;
|
|
115
159
|
}
|
|
@@ -1,6 +1,12 @@
|
|
|
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
|
+
|
|
4
10
|
/**
|
|
5
11
|
* This is a shaper for the Hangul script, used by the Korean language.
|
|
6
12
|
* It does the following:
|
|
@@ -25,10 +31,18 @@ import GlyphInfo from '../GlyphInfo';
|
|
|
25
31
|
*/
|
|
26
32
|
export default class HangulShaper extends DefaultShaper {
|
|
27
33
|
static zeroMarkWidths = 'NONE';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {ShapingPlanLike} plan
|
|
37
|
+
*/
|
|
28
38
|
static planFeatures(plan) {
|
|
29
39
|
plan.add(['ljmo', 'vjmo', 'tjmo'], false);
|
|
30
40
|
}
|
|
31
41
|
|
|
42
|
+
/**
|
|
43
|
+
* @param {ShapingPlanLike} plan
|
|
44
|
+
* @param {GlyphInfoLike[]} glyphs
|
|
45
|
+
*/
|
|
32
46
|
static assignFeatures(plan, glyphs) {
|
|
33
47
|
let state = 0;
|
|
34
48
|
let i = 0;
|
|
@@ -38,7 +52,7 @@ export default class HangulShaper extends DefaultShaper {
|
|
|
38
52
|
let code = glyph.codePoints[0];
|
|
39
53
|
let type = getType(code);
|
|
40
54
|
|
|
41
|
-
[
|
|
55
|
+
[action, state] = STATE_TABLE[state][type];
|
|
42
56
|
|
|
43
57
|
switch (action) {
|
|
44
58
|
case DECOMPOSE:
|
|
@@ -69,46 +83,59 @@ export default class HangulShaper extends DefaultShaper {
|
|
|
69
83
|
}
|
|
70
84
|
}
|
|
71
85
|
|
|
72
|
-
const HANGUL_BASE
|
|
73
|
-
const HANGUL_END
|
|
86
|
+
const HANGUL_BASE = 0xac00;
|
|
87
|
+
const HANGUL_END = 0xd7a4;
|
|
74
88
|
const HANGUL_COUNT = HANGUL_END - HANGUL_BASE + 1;
|
|
75
|
-
const L_BASE
|
|
76
|
-
const V_BASE
|
|
77
|
-
const T_BASE
|
|
89
|
+
const L_BASE = 0x1100; // lead
|
|
90
|
+
const V_BASE = 0x1161; // vowel
|
|
91
|
+
const T_BASE = 0x11a7; // trail
|
|
78
92
|
const L_COUNT = 19;
|
|
79
93
|
const V_COUNT = 21;
|
|
80
94
|
const T_COUNT = 28;
|
|
81
|
-
const L_END
|
|
82
|
-
const V_END
|
|
83
|
-
const T_END
|
|
95
|
+
const L_END = L_BASE + L_COUNT - 1;
|
|
96
|
+
const V_END = V_BASE + V_COUNT - 1;
|
|
97
|
+
const T_END = T_BASE + T_COUNT - 1;
|
|
84
98
|
const DOTTED_CIRCLE = 0x25cc;
|
|
85
99
|
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
100
|
+
/** @param {number} code @returns {boolean} */
|
|
101
|
+
const isL = code => 0x1100 <= code && code <= 0x115f || 0xa960 <= code && code <= 0xa97c;
|
|
102
|
+
/** @param {number} code @returns {boolean} */
|
|
103
|
+
const isV = code => 0x1160 <= code && code <= 0x11a7 || 0xd7b0 <= code && code <= 0xd7c6;
|
|
104
|
+
/** @param {number} code @returns {boolean} */
|
|
105
|
+
const isT = code => 0x11a8 <= code && code <= 0x11ff || 0xd7cb <= code && code <= 0xd7fb;
|
|
106
|
+
/** @param {number} code @returns {boolean} */
|
|
89
107
|
const isTone = code => 0x302e <= code && code <= 0x302f;
|
|
90
|
-
|
|
91
|
-
const
|
|
108
|
+
/** @param {number} code @returns {boolean} */
|
|
109
|
+
const isLVT = code => HANGUL_BASE <= code && code <= HANGUL_END;
|
|
110
|
+
/** @param {number} code @returns {boolean} */
|
|
111
|
+
const isLV = code => (code - HANGUL_BASE) < HANGUL_COUNT && (code - HANGUL_BASE) % T_COUNT === 0;
|
|
112
|
+
/** @param {number} code @returns {boolean} */
|
|
92
113
|
const isCombiningL = code => L_BASE <= code && code <= L_END;
|
|
114
|
+
/** @param {number} code @returns {boolean} */
|
|
93
115
|
const isCombiningV = code => V_BASE <= code && code <= V_END;
|
|
94
|
-
|
|
116
|
+
/** @param {number} code @returns {boolean} */
|
|
117
|
+
const isCombiningT = code => T_BASE + 1 <= code && code <= T_END;
|
|
95
118
|
|
|
96
119
|
// Character categories
|
|
97
|
-
const X
|
|
98
|
-
const L
|
|
99
|
-
const V
|
|
100
|
-
const T
|
|
101
|
-
const LV
|
|
120
|
+
const X = 0; // Other character
|
|
121
|
+
const L = 1; // Leading consonant
|
|
122
|
+
const V = 2; // Medial vowel
|
|
123
|
+
const T = 3; // Trailing consonant
|
|
124
|
+
const LV = 4; // Composed <LV> syllable
|
|
102
125
|
const LVT = 5; // Composed <LVT> syllable
|
|
103
|
-
const M
|
|
126
|
+
const M = 6; // Tone mark
|
|
104
127
|
|
|
105
|
-
|
|
128
|
+
/**
|
|
129
|
+
* This function classifies a character using the above categories.
|
|
130
|
+
* @param {number} code
|
|
131
|
+
* @returns {number}
|
|
132
|
+
*/
|
|
106
133
|
function getType(code) {
|
|
107
|
-
if (isL(code))
|
|
108
|
-
if (isV(code))
|
|
109
|
-
if (isT(code))
|
|
110
|
-
if (isLV(code))
|
|
111
|
-
if (isLVT(code))
|
|
134
|
+
if (isL(code)) { return L; }
|
|
135
|
+
if (isV(code)) { return V; }
|
|
136
|
+
if (isT(code)) { return T; }
|
|
137
|
+
if (isLV(code)) { return LV; }
|
|
138
|
+
if (isLVT(code)) { return LVT; }
|
|
112
139
|
if (isTone(code)) { return M; }
|
|
113
140
|
return X;
|
|
114
141
|
}
|
|
@@ -116,31 +143,48 @@ function getType(code) {
|
|
|
116
143
|
// State machine actions
|
|
117
144
|
const NO_ACTION = 0;
|
|
118
145
|
const DECOMPOSE = 1;
|
|
119
|
-
const COMPOSE
|
|
146
|
+
const COMPOSE = 2;
|
|
120
147
|
const TONE_MARK = 4;
|
|
121
|
-
const INVALID
|
|
148
|
+
const INVALID = 5;
|
|
149
|
+
|
|
150
|
+
/** @typedef {[number, number]} HangulTransition */
|
|
122
151
|
|
|
123
152
|
// Build a state machine that accepts valid syllables, and applies actions along the way.
|
|
124
153
|
// The logic this is implementing is documented at the top of the file.
|
|
154
|
+
/** @type {HangulTransition[][]} */
|
|
125
155
|
const STATE_TABLE = [
|
|
126
156
|
// X L V T LV LVT M
|
|
127
157
|
// State 0: start state
|
|
128
|
-
[
|
|
158
|
+
[[NO_ACTION, 0], [NO_ACTION, 1], [NO_ACTION, 0], [NO_ACTION, 0], [DECOMPOSE, 2], [DECOMPOSE, 3], [INVALID, 0]],
|
|
129
159
|
|
|
130
160
|
// State 1: <L>
|
|
131
|
-
[
|
|
161
|
+
[[NO_ACTION, 0], [NO_ACTION, 1], [COMPOSE, 2], [NO_ACTION, 0], [DECOMPOSE, 2], [DECOMPOSE, 3], [INVALID, 0]],
|
|
132
162
|
|
|
133
163
|
// State 2: <L,V> or <LV>
|
|
134
|
-
[
|
|
164
|
+
[[NO_ACTION, 0], [NO_ACTION, 1], [NO_ACTION, 0], [COMPOSE, 3], [DECOMPOSE, 2], [DECOMPOSE, 3], [TONE_MARK, 0]],
|
|
135
165
|
|
|
136
166
|
// State 3: <L,V,T> or <LVT>
|
|
137
|
-
[
|
|
167
|
+
[[NO_ACTION, 0], [NO_ACTION, 1], [NO_ACTION, 0], [NO_ACTION, 0], [DECOMPOSE, 2], [DECOMPOSE, 3], [TONE_MARK, 0]]
|
|
138
168
|
];
|
|
139
169
|
|
|
170
|
+
/**
|
|
171
|
+
* @param {LayoutFont} font
|
|
172
|
+
* @param {number} code
|
|
173
|
+
* @param {FeatureMap | FeatureInput | null | undefined} features
|
|
174
|
+
* @returns {GlyphInfo}
|
|
175
|
+
*/
|
|
140
176
|
function getGlyph(font, code, features) {
|
|
141
|
-
|
|
177
|
+
let g = font.glyphForCodePoint(code);
|
|
178
|
+
// Callers typically check hasGlyphForCodePoint first; .notdef if somehow missing.
|
|
179
|
+
return new GlyphInfo(font, g ? g.id : 0, [code], features);
|
|
142
180
|
}
|
|
143
181
|
|
|
182
|
+
/**
|
|
183
|
+
* @param {GlyphInfoLike[]} glyphs
|
|
184
|
+
* @param {number} i
|
|
185
|
+
* @param {LayoutFont} font
|
|
186
|
+
* @returns {number}
|
|
187
|
+
*/
|
|
144
188
|
function decompose(glyphs, i, font) {
|
|
145
189
|
let glyph = glyphs[i];
|
|
146
190
|
let code = glyph.codePoints[0];
|
|
@@ -152,9 +196,9 @@ function decompose(glyphs, i, font) {
|
|
|
152
196
|
let v = V_BASE + s % V_COUNT;
|
|
153
197
|
|
|
154
198
|
// Don't decompose if all of the components are not available
|
|
155
|
-
if (!font.hasGlyphForCodePoint(l)
|
|
156
|
-
|
|
157
|
-
|
|
199
|
+
if (!font.hasGlyphForCodePoint(l)
|
|
200
|
+
|| !font.hasGlyphForCodePoint(v)
|
|
201
|
+
|| (t !== T_BASE && !font.hasGlyphForCodePoint(t))) {
|
|
158
202
|
return i;
|
|
159
203
|
}
|
|
160
204
|
|
|
@@ -166,7 +210,7 @@ function decompose(glyphs, i, font) {
|
|
|
166
210
|
let vjmo = getGlyph(font, v, glyph.features);
|
|
167
211
|
vjmo.features.vjmo = true;
|
|
168
212
|
|
|
169
|
-
let insert = [
|
|
213
|
+
let insert = [ljmo, vjmo];
|
|
170
214
|
|
|
171
215
|
if (t > T_BASE) {
|
|
172
216
|
let tjmo = getGlyph(font, t, glyph.features);
|
|
@@ -178,6 +222,12 @@ function decompose(glyphs, i, font) {
|
|
|
178
222
|
return i + insert.length - 1;
|
|
179
223
|
}
|
|
180
224
|
|
|
225
|
+
/**
|
|
226
|
+
* @param {GlyphInfoLike[]} glyphs
|
|
227
|
+
* @param {number} i
|
|
228
|
+
* @param {LayoutFont} font
|
|
229
|
+
* @returns {number}
|
|
230
|
+
*/
|
|
181
231
|
function compose(glyphs, i, font) {
|
|
182
232
|
let glyph = glyphs[i];
|
|
183
233
|
let code = glyphs[i].codePoints[0];
|
|
@@ -187,7 +237,14 @@ function compose(glyphs, i, font) {
|
|
|
187
237
|
let prevType = getType(prev);
|
|
188
238
|
|
|
189
239
|
// Figure out what type of syllable we're dealing with
|
|
190
|
-
|
|
240
|
+
/** @type {number | undefined} */
|
|
241
|
+
let lv;
|
|
242
|
+
/** @type {GlyphInfoLike | undefined} */
|
|
243
|
+
let ljmo;
|
|
244
|
+
/** @type {GlyphInfoLike | undefined} */
|
|
245
|
+
let vjmo;
|
|
246
|
+
/** @type {GlyphInfoLike | undefined} */
|
|
247
|
+
let tjmo;
|
|
191
248
|
if (prevType === LV && type === T) {
|
|
192
249
|
// <LV,T>
|
|
193
250
|
lv = prev;
|
|
@@ -242,6 +299,12 @@ function compose(glyphs, i, font) {
|
|
|
242
299
|
return i;
|
|
243
300
|
}
|
|
244
301
|
|
|
302
|
+
/**
|
|
303
|
+
* Length of the Hangul syllable ending at `code` (for tone-mark reordering).
|
|
304
|
+
* Tone marks only attach to LV/LVT/V/T clusters from the state machine.
|
|
305
|
+
* @param {number} code
|
|
306
|
+
* @returns {number}
|
|
307
|
+
*/
|
|
245
308
|
function getLength(code) {
|
|
246
309
|
switch (getType(code)) {
|
|
247
310
|
case LV:
|
|
@@ -251,23 +314,37 @@ function getLength(code) {
|
|
|
251
314
|
return 2;
|
|
252
315
|
case T:
|
|
253
316
|
return 3;
|
|
317
|
+
default:
|
|
318
|
+
return 1;
|
|
254
319
|
}
|
|
255
320
|
}
|
|
256
321
|
|
|
322
|
+
/**
|
|
323
|
+
* @param {GlyphInfoLike[]} glyphs
|
|
324
|
+
* @param {number} i
|
|
325
|
+
* @param {LayoutFont} font
|
|
326
|
+
*/
|
|
257
327
|
function reorderToneMark(glyphs, i, font) {
|
|
258
328
|
let glyph = glyphs[i];
|
|
259
329
|
let code = glyphs[i].codePoints[0];
|
|
260
330
|
|
|
261
331
|
// Move tone mark to the beginning of the previous syllable, unless it is zero width
|
|
262
|
-
|
|
332
|
+
let toneGlyph = font.glyphForCodePoint(code);
|
|
333
|
+
if (!toneGlyph || toneGlyph.advanceWidth === 0) { return; }
|
|
263
334
|
|
|
264
335
|
let prev = glyphs[i - 1].codePoints[0];
|
|
265
336
|
let len = getLength(prev);
|
|
266
337
|
|
|
267
338
|
glyphs.splice(i, 1);
|
|
268
|
-
|
|
339
|
+
glyphs.splice(i - len, 0, glyph);
|
|
269
340
|
}
|
|
270
341
|
|
|
342
|
+
/**
|
|
343
|
+
* @param {GlyphInfoLike[]} glyphs
|
|
344
|
+
* @param {number} i
|
|
345
|
+
* @param {LayoutFont} font
|
|
346
|
+
* @returns {number}
|
|
347
|
+
*/
|
|
271
348
|
function insertDottedCircle(glyphs, i, font) {
|
|
272
349
|
let glyph = glyphs[i];
|
|
273
350
|
let code = glyphs[i].codePoints[0];
|
|
@@ -276,7 +353,8 @@ function insertDottedCircle(glyphs, i, font) {
|
|
|
276
353
|
let dottedCircle = getGlyph(font, DOTTED_CIRCLE, glyph.features);
|
|
277
354
|
|
|
278
355
|
// If the tone mark is zero width, insert the dotted circle before, otherwise after
|
|
279
|
-
let
|
|
356
|
+
let toneGlyph = font.glyphForCodePoint(code);
|
|
357
|
+
let idx = (!toneGlyph || toneGlyph.advanceWidth === 0) ? i : i + 1;
|
|
280
358
|
glyphs.splice(idx, 0, dottedCircle);
|
|
281
359
|
i++;
|
|
282
360
|
}
|