@cantoo/fontkit 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +274 -0
- package/dist/browser-module.mjs +13660 -0
- package/dist/browser-module.mjs.map +1 -0
- package/dist/browser.cjs +13689 -0
- package/dist/browser.cjs.map +1 -0
- package/dist/main.cjs +13712 -0
- package/dist/main.cjs.map +1 -0
- package/dist/module.mjs +13673 -0
- package/dist/module.mjs.map +1 -0
- package/package.json +99 -0
- package/src/CmapProcessor.js +285 -0
- package/src/DFont.js +113 -0
- package/src/TTFFont.js +621 -0
- package/src/TrueTypeCollection.js +65 -0
- package/src/WOFF2Font.js +222 -0
- package/src/WOFFFont.js +56 -0
- package/src/aat/AATFeatureMap.js +545 -0
- package/src/aat/AATLayoutEngine.js +48 -0
- package/src/aat/AATLookupTable.js +125 -0
- package/src/aat/AATMorxProcessor.js +429 -0
- package/src/aat/AATStateMachine.js +96 -0
- package/src/base.js +29 -0
- package/src/cff/CFFCharsets.js +99 -0
- package/src/cff/CFFDict.js +166 -0
- package/src/cff/CFFEncodings.js +48 -0
- package/src/cff/CFFFont.js +158 -0
- package/src/cff/CFFIndex.js +150 -0
- package/src/cff/CFFOperand.js +135 -0
- package/src/cff/CFFPointer.js +50 -0
- package/src/cff/CFFPrivateDict.js +39 -0
- package/src/cff/CFFStandardStrings.js +71 -0
- package/src/cff/CFFTop.js +236 -0
- package/src/decorators.js +36 -0
- package/src/encodings.js +219 -0
- package/src/fs.js +12 -0
- package/src/glyph/BBox.js +72 -0
- package/src/glyph/CBDTGlyph.js +9 -0
- package/src/glyph/CFFGlyph.js +635 -0
- package/src/glyph/COLRGlyph.js +108 -0
- package/src/glyph/Glyph.js +270 -0
- package/src/glyph/GlyphVariationProcessor.js +487 -0
- package/src/glyph/Path.js +257 -0
- package/src/glyph/SBIXGlyph.js +54 -0
- package/src/glyph/StandardNames.js +27 -0
- package/src/glyph/TTFGlyph.js +408 -0
- package/src/glyph/TTFGlyphEncoder.js +158 -0
- package/src/glyph/WOFF2Glyph.js +70 -0
- package/src/index.js +15 -0
- package/src/layout/GlyphPosition.js +30 -0
- package/src/layout/GlyphRun.js +108 -0
- package/src/layout/KernProcessor.js +97 -0
- package/src/layout/LayoutEngine.js +190 -0
- package/src/layout/Script.js +231 -0
- package/src/layout/UnicodeLayoutEngine.js +250 -0
- package/src/node.js +16 -0
- package/src/opentype/GPOSProcessor.js +311 -0
- package/src/opentype/GSUBProcessor.js +208 -0
- package/src/opentype/GlyphInfo.js +57 -0
- package/src/opentype/GlyphIterator.js +82 -0
- package/src/opentype/OTLayoutEngine.js +117 -0
- package/src/opentype/OTProcessor.js +472 -0
- package/src/opentype/ShapingPlan.js +121 -0
- package/src/opentype/shapers/ArabicShaper.js +124 -0
- package/src/opentype/shapers/DefaultShaper.js +116 -0
- package/src/opentype/shapers/HangulShaper.js +285 -0
- package/src/opentype/shapers/HebrewShaper.js +105 -0
- package/src/opentype/shapers/IndicShaper.js +914 -0
- package/src/opentype/shapers/ThaiShaper.js +238 -0
- package/src/opentype/shapers/UniversalShaper.js +186 -0
- package/src/opentype/shapers/data.trie +0 -0
- package/src/opentype/shapers/gen-indic.js +208 -0
- package/src/opentype/shapers/gen-use.js +287 -0
- package/src/opentype/shapers/generate-data.js +33 -0
- package/src/opentype/shapers/index.js +109 -0
- package/src/opentype/shapers/indic-data.js +175 -0
- package/src/opentype/shapers/indic.json +1 -0
- package/src/opentype/shapers/indic.machine +31 -0
- package/src/opentype/shapers/indic.trie +0 -0
- package/src/opentype/shapers/use.json +1 -0
- package/src/opentype/shapers/use.machine +44 -0
- package/src/opentype/shapers/use.trie +0 -0
- package/src/packages/unicode-properties/LICENSE +8 -0
- package/src/packages/unicode-properties/README.md +12 -0
- package/src/packages/unicode-properties/data-trie.js +1 -0
- package/src/packages/unicode-properties/data.js +1 -0
- package/src/packages/unicode-properties/data.json +1 -0
- package/src/packages/unicode-properties/data.trie +0 -0
- package/src/packages/unicode-properties/dist/index.cjs +257 -0
- package/src/packages/unicode-properties/dist/index.cjs.map +7 -0
- package/src/packages/unicode-properties/dist/index.mjs +234 -0
- package/src/packages/unicode-properties/dist/index.mjs.map +7 -0
- package/src/packages/unicode-properties/generate.js +107 -0
- package/src/packages/unicode-properties/index.js +184 -0
- package/src/packages/unicode-properties/package.json +50 -0
- package/src/packages/unicode-trie/LICENSE +8 -0
- package/src/packages/unicode-trie/README.md +13 -0
- package/src/packages/unicode-trie/builder.js +965 -0
- package/src/packages/unicode-trie/dist/builder.cjs +711 -0
- package/src/packages/unicode-trie/dist/builder.cjs.map +7 -0
- package/src/packages/unicode-trie/dist/builder.mjs +690 -0
- package/src/packages/unicode-trie/dist/builder.mjs.map +7 -0
- package/src/packages/unicode-trie/dist/index.cjs +112 -0
- package/src/packages/unicode-trie/dist/index.cjs.map +7 -0
- package/src/packages/unicode-trie/dist/index.mjs +91 -0
- package/src/packages/unicode-trie/dist/index.mjs.map +7 -0
- package/src/packages/unicode-trie/index.js +135 -0
- package/src/packages/unicode-trie/package.json +49 -0
- package/src/packages/unicode-trie/swap.js +21 -0
- package/src/subset/CFFSubset.js +176 -0
- package/src/subset/Subset.js +63 -0
- package/src/subset/TTFSubset.js +130 -0
- package/src/tables/BASE.js +78 -0
- package/src/tables/COLR.js +25 -0
- package/src/tables/CPAL.js +24 -0
- package/src/tables/DSIG.js +21 -0
- package/src/tables/EBDT.js +91 -0
- package/src/tables/EBLC.js +80 -0
- package/src/tables/GDEF.js +57 -0
- package/src/tables/GPOS.js +209 -0
- package/src/tables/GSUB.js +85 -0
- package/src/tables/HVAR.js +44 -0
- package/src/tables/JSTF.js +43 -0
- package/src/tables/LTSH.js +10 -0
- package/src/tables/OS2.js +84 -0
- package/src/tables/PCLT.js +21 -0
- package/src/tables/STAT.js +99 -0
- package/src/tables/VDMX.js +33 -0
- package/src/tables/VORG.js +14 -0
- package/src/tables/WOFF2Directory.js +73 -0
- package/src/tables/WOFFDirectory.js +37 -0
- package/src/tables/aat.js +157 -0
- package/src/tables/avar.js +19 -0
- package/src/tables/bsln.js +31 -0
- package/src/tables/cmap.js +127 -0
- package/src/tables/cvt.js +6 -0
- package/src/tables/directory.js +58 -0
- package/src/tables/feat.js +28 -0
- package/src/tables/fpgm.js +8 -0
- package/src/tables/fvar.js +31 -0
- package/src/tables/gasp.js +15 -0
- package/src/tables/glyf.js +4 -0
- package/src/tables/gvar.js +27 -0
- package/src/tables/hdmx.js +15 -0
- package/src/tables/head.js +25 -0
- package/src/tables/hhea.js +19 -0
- package/src/tables/hmtx.js +11 -0
- package/src/tables/index.js +119 -0
- package/src/tables/just.js +81 -0
- package/src/tables/kern.js +91 -0
- package/src/tables/loca.js +30 -0
- package/src/tables/maxp.js +20 -0
- package/src/tables/morx.js +79 -0
- package/src/tables/name.js +157 -0
- package/src/tables/opbd.js +15 -0
- package/src/tables/opentype.js +217 -0
- package/src/tables/post.js +34 -0
- package/src/tables/prep.js +6 -0
- package/src/tables/sbix.js +17 -0
- package/src/tables/variations.js +80 -0
- package/src/tables/vhea.js +19 -0
- package/src/tables/vmtx.js +12 -0
- package/src/utils.js +62 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import OTProcessor from './OTProcessor';
|
|
2
|
+
import GlyphInfo from './GlyphInfo';
|
|
3
|
+
|
|
4
|
+
export default class GSUBProcessor extends OTProcessor {
|
|
5
|
+
lookupDirection(lookup) {
|
|
6
|
+
let type = lookup.lookupType === 7 ? lookup.subTables[0]?.lookupType : lookup.lookupType;
|
|
7
|
+
return type === 8 ? -1 : 1;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
applyLookup(lookupType, table) {
|
|
11
|
+
switch (lookupType) {
|
|
12
|
+
case 1: { // Single Substitution
|
|
13
|
+
let index = this.coverageIndex(table.coverage);
|
|
14
|
+
if (index === -1) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let glyph = this.glyphIterator.cur;
|
|
19
|
+
switch (table.version) {
|
|
20
|
+
case 1:
|
|
21
|
+
glyph.id = (glyph.id + table.deltaGlyphID) & 0xffff;
|
|
22
|
+
break;
|
|
23
|
+
|
|
24
|
+
case 2:
|
|
25
|
+
glyph.id = table.substitute.get(index);
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
case 2: { // Multiple Substitution
|
|
33
|
+
let index = this.coverageIndex(table.coverage);
|
|
34
|
+
if (index !== -1) {
|
|
35
|
+
let sequence = table.sequences.get(index);
|
|
36
|
+
|
|
37
|
+
if (sequence.length === 0) {
|
|
38
|
+
// If the sequence length is zero, delete the glyph.
|
|
39
|
+
// The OpenType spec disallows this, but seems like Harfbuzz and Uniscribe allow it.
|
|
40
|
+
this.glyphs.splice(this.glyphIterator.index, 1);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
this.glyphIterator.cur.id = sequence[0];
|
|
45
|
+
this.glyphIterator.cur.ligatureComponent = 0;
|
|
46
|
+
|
|
47
|
+
let features = this.glyphIterator.cur.features;
|
|
48
|
+
let curGlyph = this.glyphIterator.cur;
|
|
49
|
+
let replacement = sequence.slice(1).map((gid, i) => {
|
|
50
|
+
let glyph = new GlyphInfo(this.font, gid, undefined, features);
|
|
51
|
+
glyph.shaperInfo = curGlyph.shaperInfo;
|
|
52
|
+
glyph.isLigated = curGlyph.isLigated;
|
|
53
|
+
glyph.ligatureComponent = i + 1;
|
|
54
|
+
glyph.substituted = true;
|
|
55
|
+
glyph.isMultiplied = true;
|
|
56
|
+
return glyph;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
this.glyphs.splice(this.glyphIterator.index + 1, 0, ...replacement);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
case 3: { // Alternate Substitution
|
|
67
|
+
let index = this.coverageIndex(table.coverage);
|
|
68
|
+
if (index === -1) return false;
|
|
69
|
+
|
|
70
|
+
// 1-based index from user features (e.g. { aalt: 2 }); boolean / missing → first
|
|
71
|
+
let alternates = table.alternateSet.get(index);
|
|
72
|
+
let alt = this.userFeatures?.[this.currentFeature];
|
|
73
|
+
let i = Number.isInteger(alt) && alt > 0 ? alt - 1 : 0;
|
|
74
|
+
this.glyphIterator.cur.id = alternates[i < alternates.length ? i : 0];
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
case 4: { // Ligature Substitution
|
|
79
|
+
let index = this.coverageIndex(table.coverage);
|
|
80
|
+
if (index === -1) return false;
|
|
81
|
+
|
|
82
|
+
for (let ligature of table.ligatureSets.get(index)) {
|
|
83
|
+
let matched = this.sequenceMatchIndices(1, ligature.components);
|
|
84
|
+
if (!matched) continue;
|
|
85
|
+
|
|
86
|
+
let curGlyph = this.glyphIterator.cur;
|
|
87
|
+
|
|
88
|
+
// Concatenate all of the characters the new ligature will represent
|
|
89
|
+
let characters = curGlyph.codePoints.slice();
|
|
90
|
+
for (let index of matched) {
|
|
91
|
+
characters.push(...this.glyphs[index].codePoints);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Create the replacement ligature glyph
|
|
95
|
+
let ligatureGlyph = new GlyphInfo(this.font, ligature.glyph, characters, curGlyph.features);
|
|
96
|
+
ligatureGlyph.shaperInfo = curGlyph.shaperInfo;
|
|
97
|
+
ligatureGlyph.isLigated = true;
|
|
98
|
+
ligatureGlyph.substituted = true;
|
|
99
|
+
|
|
100
|
+
// From Harfbuzz:
|
|
101
|
+
// - If it *is* a mark ligature, we don't allocate a new ligature id, and leave
|
|
102
|
+
// the ligature to keep its old ligature id. This will allow it to attach to
|
|
103
|
+
// a base ligature in GPOS. Eg. if the sequence is: LAM,LAM,SHADDA,FATHA,HEH,
|
|
104
|
+
// and LAM,LAM,HEH for a ligature, they will leave SHADDA and FATHA with a
|
|
105
|
+
// ligature id and component value of 2. Then if SHADDA,FATHA form a ligature
|
|
106
|
+
// later, we don't want them to lose their ligature id/component, otherwise
|
|
107
|
+
// GPOS will fail to correctly position the mark ligature on top of the
|
|
108
|
+
// LAM,LAM,HEH ligature. See https://bugzilla.gnome.org/show_bug.cgi?id=676343
|
|
109
|
+
//
|
|
110
|
+
// - If a ligature is formed of components that some of which are also ligatures
|
|
111
|
+
// themselves, and those ligature components had marks attached to *their*
|
|
112
|
+
// components, we have to attach the marks to the new ligature component
|
|
113
|
+
// positions! Now *that*'s tricky! And these marks may be following the
|
|
114
|
+
// last component of the whole sequence, so we should loop forward looking
|
|
115
|
+
// for them and update them.
|
|
116
|
+
//
|
|
117
|
+
// Eg. the sequence is LAM,LAM,SHADDA,FATHA,HEH, and the font first forms a
|
|
118
|
+
// 'calt' ligature of LAM,HEH, leaving the SHADDA and FATHA with a ligature
|
|
119
|
+
// id and component == 1. Now, during 'liga', the LAM and the LAM-HEH ligature
|
|
120
|
+
// form a LAM-LAM-HEH ligature. We need to reassign the SHADDA and FATHA to
|
|
121
|
+
// the new ligature with a component value of 2.
|
|
122
|
+
//
|
|
123
|
+
// This in fact happened to a font... See https://bugzilla.gnome.org/show_bug.cgi?id=437633
|
|
124
|
+
let isMarkLigature = curGlyph.isMark;
|
|
125
|
+
for (let i = 0; i < matched.length && isMarkLigature; i++) {
|
|
126
|
+
isMarkLigature = this.glyphs[matched[i]].isMark;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
ligatureGlyph.ligatureID = isMarkLigature ? null : this.ligatureID++;
|
|
130
|
+
|
|
131
|
+
let lastLigID = curGlyph.ligatureID;
|
|
132
|
+
let lastNumComps = curGlyph.codePoints.length;
|
|
133
|
+
let curComps = lastNumComps;
|
|
134
|
+
let idx = this.glyphIterator.index + 1;
|
|
135
|
+
|
|
136
|
+
// Set ligatureID and ligatureComponent on glyphs that were skipped in the matched sequence.
|
|
137
|
+
// This allows GPOS to attach marks to the correct ligature components.
|
|
138
|
+
for (let matchIndex of matched) {
|
|
139
|
+
// Don't assign new ligature components for mark ligatures (see above)
|
|
140
|
+
if (isMarkLigature) {
|
|
141
|
+
idx = matchIndex;
|
|
142
|
+
} else {
|
|
143
|
+
while (idx < matchIndex) {
|
|
144
|
+
let ligatureComponent = curComps - lastNumComps + Math.min(this.glyphs[idx].ligatureComponent || 1, lastNumComps);
|
|
145
|
+
this.glyphs[idx].ligatureID = ligatureGlyph.ligatureID;
|
|
146
|
+
this.glyphs[idx].ligatureComponent = ligatureComponent;
|
|
147
|
+
idx++;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
lastLigID = this.glyphs[idx].ligatureID;
|
|
152
|
+
lastNumComps = this.glyphs[idx].codePoints.length;
|
|
153
|
+
curComps += lastNumComps;
|
|
154
|
+
idx++; // skip base glyph
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Adjust ligature components for any marks following
|
|
158
|
+
if (lastLigID && !isMarkLigature) {
|
|
159
|
+
for (let i = idx; i < this.glyphs.length; i++) {
|
|
160
|
+
if (this.glyphs[i].ligatureID === lastLigID) {
|
|
161
|
+
let ligatureComponent = curComps - lastNumComps + Math.min(this.glyphs[i].ligatureComponent || 1, lastNumComps);
|
|
162
|
+
this.glyphs[i].ligatureComponent = ligatureComponent;
|
|
163
|
+
} else {
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Delete the matched glyphs, and replace the current glyph with the ligature glyph
|
|
170
|
+
for (let i = matched.length - 1; i >= 0; i--) {
|
|
171
|
+
this.glyphs.splice(matched[i], 1);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
this.glyphs[this.glyphIterator.index] = ligatureGlyph;
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
case 5: // Contextual Substitution
|
|
182
|
+
return this.applyContext(table);
|
|
183
|
+
|
|
184
|
+
case 6: // Chaining Contextual Substitution
|
|
185
|
+
return this.applyChainingContext(table);
|
|
186
|
+
|
|
187
|
+
case 7: // Extension Substitution
|
|
188
|
+
return this.applyLookup(table.lookupType, table.extension);
|
|
189
|
+
|
|
190
|
+
case 8: { // Reverse Chaining Contextual Single Substitution
|
|
191
|
+
let index = this.coverageIndex(table.coverage);
|
|
192
|
+
if (index === -1) return false;
|
|
193
|
+
|
|
194
|
+
// Backtrack is stored closest-first; coverageSequenceMatches walks furthest-first.
|
|
195
|
+
if (!this.coverageSequenceMatches(-table.backtrackGlyphCount, [...table.backtrackCoverage].reverse())
|
|
196
|
+
|| !this.coverageSequenceMatches(1, table.lookaheadCoverage)) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
this.glyphIterator.cur.id = table.substitute.get(index);
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
default:
|
|
205
|
+
throw new Error(`GSUB lookupType ${lookupType} is not supported`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {isMark} from '../packages/unicode-properties/index.js';
|
|
2
|
+
import OTProcessor from './OTProcessor';
|
|
3
|
+
|
|
4
|
+
export default class GlyphInfo {
|
|
5
|
+
constructor(font, id, codePoints = [], features) {
|
|
6
|
+
this._font = font;
|
|
7
|
+
this.codePoints = codePoints;
|
|
8
|
+
this.id = id;
|
|
9
|
+
|
|
10
|
+
this.features = {};
|
|
11
|
+
if (Array.isArray(features)) {
|
|
12
|
+
for (let i = 0; i < features.length; i++) {
|
|
13
|
+
let feature = features[i];
|
|
14
|
+
this.features[feature] = true;
|
|
15
|
+
}
|
|
16
|
+
} else if (typeof features === 'object') {
|
|
17
|
+
Object.assign(this.features, features);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
this.ligatureID = null;
|
|
21
|
+
this.ligatureComponent = null;
|
|
22
|
+
this.isLigated = false;
|
|
23
|
+
this.cursiveAttachment = null;
|
|
24
|
+
this.markAttachment = null;
|
|
25
|
+
this.shaperInfo = null;
|
|
26
|
+
this.substituted = false;
|
|
27
|
+
this.isMultiplied = false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get id() {
|
|
31
|
+
return this._id;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
set id(id) {
|
|
35
|
+
this._id = id;
|
|
36
|
+
this.substituted = true;
|
|
37
|
+
|
|
38
|
+
let GDEF = this._font.GDEF;
|
|
39
|
+
if (GDEF && GDEF.glyphClassDef) {
|
|
40
|
+
// TODO: clean this up
|
|
41
|
+
let classID = OTProcessor.prototype.getClassID(id, GDEF.glyphClassDef);
|
|
42
|
+
this.isBase = classID === 1;
|
|
43
|
+
this.isLigature = classID === 2;
|
|
44
|
+
this.isMark = classID === 3;
|
|
45
|
+
this.markAttachmentType = GDEF.markAttachClassDef ? OTProcessor.prototype.getClassID(id, GDEF.markAttachClassDef) : 0;
|
|
46
|
+
} else {
|
|
47
|
+
this.isMark = this.codePoints.length > 0 && this.codePoints.every(isMark);
|
|
48
|
+
this.isBase = !this.isMark;
|
|
49
|
+
this.isLigature = this.codePoints.length > 1;
|
|
50
|
+
this.markAttachmentType = 0;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
copy() {
|
|
55
|
+
return new GlyphInfo(this._font, this.id, this.codePoints, this.features);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export default class GlyphIterator {
|
|
2
|
+
constructor(glyphs, options) {
|
|
3
|
+
this.glyphs = glyphs;
|
|
4
|
+
this.reset(options);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
reset(options = {}, index = 0, markFilteringSet = null) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
this.flags = options.flags || {};
|
|
10
|
+
this.markAttachmentType = options.markAttachmentType || 0;
|
|
11
|
+
this.markFilteringSet = markFilteringSet;
|
|
12
|
+
this.index = index;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
get cur() {
|
|
16
|
+
return this.glyphs[this.index] || null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
shouldIgnore(glyph) {
|
|
20
|
+
// Mark skip hierarchy (OT chapter 2): ignoreMarks > mark filtering set > markAttachmentType.
|
|
21
|
+
if (glyph.isMark) {
|
|
22
|
+
if (this.flags.ignoreMarks) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
if (this.flags.useMarkFilteringSet) {
|
|
26
|
+
return !this.markFilteringSet?.has(glyph.id);
|
|
27
|
+
}
|
|
28
|
+
if (this.markAttachmentType) {
|
|
29
|
+
return glyph.markAttachmentType !== this.markAttachmentType;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return (this.flags.ignoreBaseGlyphs && glyph.isBase) ||
|
|
34
|
+
(this.flags.ignoreLigatures && glyph.isLigature);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
move(dir) {
|
|
38
|
+
this.index += dir;
|
|
39
|
+
while (0 <= this.index && this.index < this.glyphs.length && this.shouldIgnore(this.glyphs[this.index])) {
|
|
40
|
+
this.index += dir;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (0 > this.index || this.index >= this.glyphs.length) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return this.glyphs[this.index];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
next() {
|
|
51
|
+
return this.move(+1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
prev() {
|
|
55
|
+
return this.move(-1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
peek(count = 1) {
|
|
59
|
+
let idx = this.index;
|
|
60
|
+
let res = this.increment(count);
|
|
61
|
+
this.index = idx;
|
|
62
|
+
return res;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
peekIndex(count = 1) {
|
|
66
|
+
let idx = this.index;
|
|
67
|
+
this.increment(count);
|
|
68
|
+
let res = this.index;
|
|
69
|
+
this.index = idx;
|
|
70
|
+
return res;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
increment(count = 1) {
|
|
74
|
+
let dir = count < 0 ? -1 : 1;
|
|
75
|
+
count = Math.abs(count);
|
|
76
|
+
while (count--) {
|
|
77
|
+
this.move(dir);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return this.glyphs[this.index];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import ShapingPlan from './ShapingPlan';
|
|
2
|
+
import * as Shapers from './shapers';
|
|
3
|
+
import GlyphInfo from './GlyphInfo';
|
|
4
|
+
import GSUBProcessor from './GSUBProcessor';
|
|
5
|
+
import GPOSProcessor from './GPOSProcessor';
|
|
6
|
+
|
|
7
|
+
export default class OTLayoutEngine {
|
|
8
|
+
constructor(font) {
|
|
9
|
+
this.font = font;
|
|
10
|
+
this.glyphInfos = null;
|
|
11
|
+
this.plan = null;
|
|
12
|
+
this.GSUBProcessor = null;
|
|
13
|
+
this.GPOSProcessor = null;
|
|
14
|
+
this.fallbackPosition = true;
|
|
15
|
+
|
|
16
|
+
if (font.GSUB) {
|
|
17
|
+
this.GSUBProcessor = new GSUBProcessor(font, font.GSUB);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (font.GPOS) {
|
|
21
|
+
this.GPOSProcessor = new GPOSProcessor(font, font.GPOS);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
setup(glyphRun) {
|
|
26
|
+
// Map glyphs to GlyphInfo objects so data can be passed between
|
|
27
|
+
// GSUB and GPOS without mutating the real (shared) Glyph objects.
|
|
28
|
+
this.glyphInfos = glyphRun.glyphs.map(glyph => new GlyphInfo(this.font, glyph.id, [...glyph.codePoints]));
|
|
29
|
+
|
|
30
|
+
// Select an OpenType script for GSUB/GPOS features.
|
|
31
|
+
let script = null;
|
|
32
|
+
if (this.GPOSProcessor) {
|
|
33
|
+
script = this.GPOSProcessor.selectScript(glyphRun.script, glyphRun.language, glyphRun.direction);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (this.GSUBProcessor) {
|
|
37
|
+
script = this.GSUBProcessor.selectScript(glyphRun.script, glyphRun.language, glyphRun.direction);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Choose the complex shaper from the buffer Unicode script (HB model). The OT
|
|
41
|
+
// script may fall back to DFLT/latn even for Thai text; that must not select
|
|
42
|
+
// DefaultShaper or Thai SARA AM / PUA preprocessing is skipped.
|
|
43
|
+
this.shaper = Shapers.choose(glyphRun.script || script);
|
|
44
|
+
this.plan = new ShapingPlan(this.font, script || glyphRun.script, glyphRun.direction);
|
|
45
|
+
this.plan.bufferScript = glyphRun.script;
|
|
46
|
+
this.shaper.plan(this.plan, this.glyphInfos, glyphRun.features);
|
|
47
|
+
|
|
48
|
+
// Enabled features as true, then overlay user values (e.g. aalt: 2) without mutating input.
|
|
49
|
+
let features = {};
|
|
50
|
+
for (let key in this.plan.allFeatures) {
|
|
51
|
+
features[key] = true;
|
|
52
|
+
}
|
|
53
|
+
glyphRun.features = Object.assign(features, glyphRun.features);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
substitute(glyphRun) {
|
|
57
|
+
if (this.GSUBProcessor) {
|
|
58
|
+
this.plan.process(this.GSUBProcessor, this.glyphInfos);
|
|
59
|
+
|
|
60
|
+
// Map glyph infos back to normal Glyph objects
|
|
61
|
+
glyphRun.glyphs = this.glyphInfos.map(glyphInfo => this.font.getGlyph(glyphInfo.id, glyphInfo.codePoints));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
position(glyphRun) {
|
|
66
|
+
if (this.shaper.zeroMarkWidths === 'BEFORE_GPOS') {
|
|
67
|
+
this.zeroMarkAdvances(glyphRun.positions);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (this.GPOSProcessor) {
|
|
71
|
+
this.plan.process(this.GPOSProcessor, this.glyphInfos, glyphRun.positions);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (this.shaper.zeroMarkWidths === 'AFTER_GPOS') {
|
|
75
|
+
this.zeroMarkAdvances(glyphRun.positions);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Reverse the glyphs and positions if the script is right-to-left
|
|
79
|
+
if (glyphRun.direction === 'rtl') {
|
|
80
|
+
glyphRun.glyphs.reverse();
|
|
81
|
+
glyphRun.positions.reverse();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return this.GPOSProcessor && this.GPOSProcessor.features;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
zeroMarkAdvances(positions) {
|
|
88
|
+
for (let i = 0; i < this.glyphInfos.length; i++) {
|
|
89
|
+
if (this.glyphInfos[i].isMark) {
|
|
90
|
+
positions[i].xAdvance = 0;
|
|
91
|
+
positions[i].yAdvance = 0;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
cleanup() {
|
|
97
|
+
this.glyphInfos = null;
|
|
98
|
+
this.plan = null;
|
|
99
|
+
this.shaper = null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
getAvailableFeatures(script, language) {
|
|
103
|
+
let features = [];
|
|
104
|
+
|
|
105
|
+
if (this.GSUBProcessor) {
|
|
106
|
+
this.GSUBProcessor.selectScript(script, language);
|
|
107
|
+
features.push(...Object.keys(this.GSUBProcessor.features));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (this.GPOSProcessor) {
|
|
111
|
+
this.GPOSProcessor.selectScript(script, language);
|
|
112
|
+
features.push(...Object.keys(this.GPOSProcessor.features));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return features;
|
|
116
|
+
}
|
|
117
|
+
}
|