@xterm/addon-ligatures 0.11.0-beta.9 → 0.11.0-beta.90
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 +25 -1
- package/lib/addon-ligatures.mjs +2 -2
- package/lib/addon-ligatures.mjs.map +4 -4
- package/package.json +8 -7
- package/src/font.ts +1 -1
- package/src/fontLigatures/flatten.ts +40 -0
- package/src/fontLigatures/index.ts +262 -0
- package/src/fontLigatures/merge.ts +393 -0
- package/src/fontLigatures/mergeRange.ts +66 -0
- package/src/fontLigatures/processors/6-1.ts +82 -0
- package/src/fontLigatures/processors/6-2.ts +96 -0
- package/src/fontLigatures/processors/6-3.ts +73 -0
- package/src/fontLigatures/processors/8-1.ts +69 -0
- package/src/fontLigatures/processors/classDef.ts +84 -0
- package/src/fontLigatures/processors/coverage.ts +43 -0
- package/src/fontLigatures/processors/helper.ts +187 -0
- package/src/fontLigatures/processors/substitution.ts +62 -0
- package/src/fontLigatures/tables.ts +112 -0
- package/src/fontLigatures/types.ts +86 -0
- package/src/fontLigatures/walk.ts +67 -0
- package/src/index.ts +1 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { IReverseChainingContextualSingleSubstitutionTable } from '../tables';
|
|
2
|
+
import { ILookupTree, ILookupTreeEntry } from '../types';
|
|
3
|
+
|
|
4
|
+
import { listGlyphsByIndex } from './coverage';
|
|
5
|
+
import { processLookaheadPosition, processBacktrackPosition, IEntryMeta } from './helper';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Build lookup tree for GSUB lookup table 8, format 1.
|
|
9
|
+
* https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#81-reverse-chaining-contextual-single-substitution-format-1-coverage-based-glyph-contexts
|
|
10
|
+
*
|
|
11
|
+
* @param table JSON representation of the table
|
|
12
|
+
* @param tableIndex Index of this table in the overall lookup
|
|
13
|
+
*/
|
|
14
|
+
export default function buildTree(table: IReverseChainingContextualSingleSubstitutionTable, tableIndex: number): ILookupTree {
|
|
15
|
+
const result: ILookupTree = {
|
|
16
|
+
individual: {},
|
|
17
|
+
range: []
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const glyphs = listGlyphsByIndex(table.coverage);
|
|
21
|
+
|
|
22
|
+
for (const { glyphId, index } of glyphs) {
|
|
23
|
+
const initialEntry: ILookupTreeEntry = {};
|
|
24
|
+
if (Array.isArray(glyphId)) {
|
|
25
|
+
result.range.push({
|
|
26
|
+
entry: initialEntry,
|
|
27
|
+
range: glyphId
|
|
28
|
+
});
|
|
29
|
+
} else {
|
|
30
|
+
result.individual[glyphId] = initialEntry;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let currentEntries: IEntryMeta[] = [{
|
|
34
|
+
entry: initialEntry,
|
|
35
|
+
substitutions: [table.substitutes[index]]
|
|
36
|
+
}];
|
|
37
|
+
|
|
38
|
+
// We walk forward, then backward
|
|
39
|
+
for (const coverage of table.lookaheadCoverage) {
|
|
40
|
+
currentEntries = processLookaheadPosition(
|
|
41
|
+
listGlyphsByIndex(coverage).map(glyph => glyph.glyphId),
|
|
42
|
+
currentEntries
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (const coverage of table.backtrackCoverage) {
|
|
47
|
+
currentEntries = processBacktrackPosition(
|
|
48
|
+
listGlyphsByIndex(coverage).map(glyph => glyph.glyphId),
|
|
49
|
+
currentEntries
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// When we get to the end, insert the lookup information
|
|
54
|
+
for (const { entry, substitutions } of currentEntries) {
|
|
55
|
+
entry.lookup = {
|
|
56
|
+
substitutions,
|
|
57
|
+
index: tableIndex,
|
|
58
|
+
subIndex: 0,
|
|
59
|
+
length: 1,
|
|
60
|
+
contextRange: [
|
|
61
|
+
-1 * table.backtrackCoverage.length,
|
|
62
|
+
1 + table.lookaheadCoverage.length
|
|
63
|
+
]
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { ClassDefTable } from '../tables';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Get the number of the class to which the glyph belongs, or null if it doesn't
|
|
5
|
+
* belong to any of them.
|
|
6
|
+
*
|
|
7
|
+
* @param table JSON representation of the class def table
|
|
8
|
+
* @param glyphId Index of the glyph to look for
|
|
9
|
+
*/
|
|
10
|
+
export default function getGlyphClass(table: ClassDefTable, glyphId: number | [number, number]): Map<number | [number, number], number | null> {
|
|
11
|
+
switch (table.format) {
|
|
12
|
+
// https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table-format-2
|
|
13
|
+
case 2:
|
|
14
|
+
if (Array.isArray(glyphId)) {
|
|
15
|
+
return getRangeGlyphClass(table, glyphId);
|
|
16
|
+
}
|
|
17
|
+
return new Map([[
|
|
18
|
+
glyphId,
|
|
19
|
+
getIndividualGlyphClass(table, glyphId)
|
|
20
|
+
]]);
|
|
21
|
+
// https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table-format-1
|
|
22
|
+
default:
|
|
23
|
+
return new Map([[glyphId, null]]);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getRangeGlyphClass(table: ClassDefTable.IFormat2, glyphId: [number, number]): Map<number | [number, number], number | null> {
|
|
28
|
+
const classStart: number = glyphId[0];
|
|
29
|
+
const currentClass: number | null = getIndividualGlyphClass(table, classStart);
|
|
30
|
+
let search: number = glyphId[0] + 1;
|
|
31
|
+
|
|
32
|
+
const result = new Map<[number, number] | number, number | null>();
|
|
33
|
+
|
|
34
|
+
while (search < glyphId[1]) {
|
|
35
|
+
const clazz = getIndividualGlyphClass(table, search);
|
|
36
|
+
if (clazz !== currentClass) {
|
|
37
|
+
if (search - classStart <= 1) {
|
|
38
|
+
result.set(classStart, currentClass);
|
|
39
|
+
} else {
|
|
40
|
+
result.set([classStart, search], currentClass);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
search++;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (search - classStart <= 1) {
|
|
47
|
+
result.set(classStart, currentClass);
|
|
48
|
+
} else {
|
|
49
|
+
result.set([classStart, search], currentClass);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getIndividualGlyphClass(table: ClassDefTable.IFormat2, glyphId: number): number | null {
|
|
56
|
+
for (const range of table.ranges) {
|
|
57
|
+
if (range.start <= glyphId && range.end >= glyphId) {
|
|
58
|
+
return range.classId;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function listClassGlyphs(table: ClassDefTable, index: number): (number | [number, number])[] {
|
|
66
|
+
switch (table.format) {
|
|
67
|
+
case 2:
|
|
68
|
+
const results: (number | [number, number])[] = [];
|
|
69
|
+
for (const range of table.ranges) {
|
|
70
|
+
if (range.classId !== index) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (range.end === range.start) {
|
|
75
|
+
results.push(range.start);
|
|
76
|
+
} else {
|
|
77
|
+
results.push([range.start, range.end + 1]);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return results;
|
|
81
|
+
default:
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { CoverageTable } from '../tables';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Get the index of the given glyph in the coverage table, or null if it is not
|
|
5
|
+
* present in the table.
|
|
6
|
+
*
|
|
7
|
+
* @param table JSON representation of the coverage table
|
|
8
|
+
* @param glyphId Index of the glyph to look for
|
|
9
|
+
*/
|
|
10
|
+
export default function getCoverageGlyphIndex(table: CoverageTable, glyphId: number): number | null {
|
|
11
|
+
switch (table.format) {
|
|
12
|
+
// https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-format-1
|
|
13
|
+
case 1:
|
|
14
|
+
const index = table.glyphs.indexOf(glyphId);
|
|
15
|
+
return index !== -1
|
|
16
|
+
? index
|
|
17
|
+
: null;
|
|
18
|
+
// https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-format-2
|
|
19
|
+
case 2:
|
|
20
|
+
const range = table.ranges
|
|
21
|
+
.find(range => range.start <= glyphId && range.end >= glyphId);
|
|
22
|
+
return range
|
|
23
|
+
? range.index
|
|
24
|
+
: null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function listGlyphsByIndex(table: CoverageTable): { glyphId: number | [number, number], index: number }[] {
|
|
29
|
+
switch (table.format) {
|
|
30
|
+
case 1:
|
|
31
|
+
return table.glyphs.map((glyphId, index) => ({ glyphId, index }));
|
|
32
|
+
case 2:
|
|
33
|
+
const results: { glyphId: number | [number, number], index: number }[] = [];
|
|
34
|
+
for (const [index, range] of table.ranges.entries()) {
|
|
35
|
+
if (range.end === range.start) {
|
|
36
|
+
results.push({ glyphId: range.start, index });
|
|
37
|
+
} else {
|
|
38
|
+
results.push({ glyphId: [range.start, range.end + 1], index });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return results;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { ILookupTreeEntry, ILookupTree } from '../types';
|
|
2
|
+
import { ISubstitutionLookupRecord, Lookup } from '../tables';
|
|
3
|
+
|
|
4
|
+
import { getIndividualSubstitutionGlyph, getRangeSubstitutionGlyphs } from './substitution';
|
|
5
|
+
|
|
6
|
+
export interface IEntryMeta {
|
|
7
|
+
entry: ILookupTreeEntry;
|
|
8
|
+
substitutions: (number | null)[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function processInputPosition(
|
|
12
|
+
glyphs: (number | [number, number])[],
|
|
13
|
+
position: number,
|
|
14
|
+
currentEntries: IEntryMeta[],
|
|
15
|
+
lookupRecords: ISubstitutionLookupRecord[],
|
|
16
|
+
lookups: Lookup[]
|
|
17
|
+
): IEntryMeta[] {
|
|
18
|
+
const nextEntries: IEntryMeta[] = [];
|
|
19
|
+
for (const currentEntry of currentEntries) {
|
|
20
|
+
currentEntry.entry.forward = {
|
|
21
|
+
individual: {},
|
|
22
|
+
range: []
|
|
23
|
+
};
|
|
24
|
+
for (const glyph of glyphs) {
|
|
25
|
+
nextEntries.push(...getInputTree(
|
|
26
|
+
currentEntry.entry.forward,
|
|
27
|
+
lookupRecords,
|
|
28
|
+
lookups,
|
|
29
|
+
position,
|
|
30
|
+
glyph
|
|
31
|
+
).map(({ entry, substitution }) => ({
|
|
32
|
+
entry,
|
|
33
|
+
substitutions: [...currentEntry.substitutions, substitution]
|
|
34
|
+
})));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return nextEntries;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function processLookaheadPosition(
|
|
42
|
+
glyphs: (number | [number, number])[],
|
|
43
|
+
currentEntries: IEntryMeta[]
|
|
44
|
+
): IEntryMeta[] {
|
|
45
|
+
const nextEntries: IEntryMeta[] = [];
|
|
46
|
+
const processedEntries = new Set<ILookupTreeEntry>();
|
|
47
|
+
|
|
48
|
+
for (const currentEntry of currentEntries) {
|
|
49
|
+
// Skip if we've already processed this entry object
|
|
50
|
+
if (processedEntries.has(currentEntry.entry)) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
processedEntries.add(currentEntry.entry);
|
|
54
|
+
|
|
55
|
+
if (!currentEntry.entry.forward) {
|
|
56
|
+
currentEntry.entry.forward = {
|
|
57
|
+
individual: {},
|
|
58
|
+
range: []
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// All glyphs at this position share ONE entry - lookahead just needs to match,
|
|
63
|
+
// all paths lead to the same result
|
|
64
|
+
const sharedEntry: ILookupTreeEntry = {};
|
|
65
|
+
|
|
66
|
+
for (const glyph of glyphs) {
|
|
67
|
+
if (Array.isArray(glyph)) {
|
|
68
|
+
currentEntry.entry.forward.range.push({
|
|
69
|
+
entry: sharedEntry,
|
|
70
|
+
range: glyph
|
|
71
|
+
});
|
|
72
|
+
} else {
|
|
73
|
+
currentEntry.entry.forward.individual[glyph] = sharedEntry;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
nextEntries.push({
|
|
78
|
+
entry: sharedEntry,
|
|
79
|
+
substitutions: currentEntry.substitutions
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return nextEntries;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function processBacktrackPosition(
|
|
87
|
+
glyphs: (number | [number, number])[],
|
|
88
|
+
currentEntries: IEntryMeta[]
|
|
89
|
+
): IEntryMeta[] {
|
|
90
|
+
const nextEntries: IEntryMeta[] = [];
|
|
91
|
+
const processedEntries = new Set<ILookupTreeEntry>();
|
|
92
|
+
|
|
93
|
+
for (const currentEntry of currentEntries) {
|
|
94
|
+
// Skip if we've already processed this entry object
|
|
95
|
+
if (processedEntries.has(currentEntry.entry)) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
processedEntries.add(currentEntry.entry);
|
|
99
|
+
|
|
100
|
+
if (!currentEntry.entry.reverse) {
|
|
101
|
+
currentEntry.entry.reverse = {
|
|
102
|
+
individual: {},
|
|
103
|
+
range: []
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// All glyphs at this position share ONE entry - backtrack just needs to match,
|
|
108
|
+
// all paths lead to the same result
|
|
109
|
+
const sharedEntry: ILookupTreeEntry = {};
|
|
110
|
+
|
|
111
|
+
for (const glyph of glyphs) {
|
|
112
|
+
if (Array.isArray(glyph)) {
|
|
113
|
+
currentEntry.entry.reverse.range.push({
|
|
114
|
+
entry: sharedEntry,
|
|
115
|
+
range: glyph
|
|
116
|
+
});
|
|
117
|
+
} else {
|
|
118
|
+
currentEntry.entry.reverse.individual[glyph] = sharedEntry;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
nextEntries.push({
|
|
123
|
+
entry: sharedEntry,
|
|
124
|
+
substitutions: currentEntry.substitutions
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return nextEntries;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function getInputTree(tree: ILookupTree, substitutions: ISubstitutionLookupRecord[], lookups: Lookup[], inputIndex: number, glyphId: number | [number, number]): { entry: ILookupTreeEntry, substitution: number | null }[] {
|
|
132
|
+
const result: { entry: ILookupTreeEntry, substitution: number | null }[] = [];
|
|
133
|
+
if (!Array.isArray(glyphId)) {
|
|
134
|
+
tree.individual[glyphId] = {};
|
|
135
|
+
result.push({
|
|
136
|
+
entry: tree.individual[glyphId],
|
|
137
|
+
substitution: getSubstitutionAtPosition(substitutions, lookups, inputIndex, glyphId)
|
|
138
|
+
});
|
|
139
|
+
} else {
|
|
140
|
+
const subs = getSubstitutionAtPositionRange(substitutions, lookups, inputIndex, glyphId);
|
|
141
|
+
for (const [range, substitution] of subs) {
|
|
142
|
+
const entry: ILookupTreeEntry = {};
|
|
143
|
+
if (Array.isArray(range)) {
|
|
144
|
+
tree.range.push({ range, entry });
|
|
145
|
+
} else {
|
|
146
|
+
tree.individual[range] = {};
|
|
147
|
+
}
|
|
148
|
+
result.push({ entry, substitution });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function getSubstitutionAtPositionRange(substitutions: ISubstitutionLookupRecord[], lookups: Lookup[], index: number, range: [number, number]): Map<number | [number, number], number | null> {
|
|
156
|
+
for (const substitution of substitutions.filter(s => s.sequenceIndex === index)) {
|
|
157
|
+
for (const substitutionTable of (lookups[substitution.lookupListIndex] as Lookup.IType1).subtables) {
|
|
158
|
+
const sub = getRangeSubstitutionGlyphs(
|
|
159
|
+
substitutionTable,
|
|
160
|
+
range
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
if (!Array.from(sub.values()).every(val => val !== null)) {
|
|
164
|
+
return sub;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return new Map([[range, null]]);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function getSubstitutionAtPosition(substitutions: ISubstitutionLookupRecord[], lookups: Lookup[], index: number, glyphId: number): number | null {
|
|
173
|
+
for (const substitution of substitutions.filter(s => s.sequenceIndex === index)) {
|
|
174
|
+
for (const substitutionTable of (lookups[substitution.lookupListIndex] as Lookup.IType1).subtables) {
|
|
175
|
+
const sub = getIndividualSubstitutionGlyph(
|
|
176
|
+
substitutionTable,
|
|
177
|
+
glyphId
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
if (sub !== null) {
|
|
181
|
+
return sub;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { SubstitutionTable } from '../tables';
|
|
2
|
+
|
|
3
|
+
import getCoverageGlyphIndex from './coverage';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get the substitution glyph for the givne glyph, or null if the glyph was not
|
|
7
|
+
* found in the table.
|
|
8
|
+
*
|
|
9
|
+
* @param table JSON representation of the substitution table
|
|
10
|
+
* @param glyphId The index of the glpyh to find substitutions for
|
|
11
|
+
*/
|
|
12
|
+
export function getRangeSubstitutionGlyphs(table: SubstitutionTable, glyphId: [number, number]): Map<[number, number] | number, number | null> {
|
|
13
|
+
const replacementStart: number = glyphId[0];
|
|
14
|
+
const currentReplacement: number | null = getIndividualSubstitutionGlyph(table, replacementStart);
|
|
15
|
+
let search: number = glyphId[0] + 1;
|
|
16
|
+
|
|
17
|
+
const result = new Map<[number, number] | number, number | null>();
|
|
18
|
+
|
|
19
|
+
while (search < glyphId[1]) {
|
|
20
|
+
const sub = getIndividualSubstitutionGlyph(table, search);
|
|
21
|
+
if (sub !== currentReplacement) {
|
|
22
|
+
if (search - replacementStart <= 1) {
|
|
23
|
+
result.set(replacementStart, currentReplacement);
|
|
24
|
+
} else {
|
|
25
|
+
result.set([replacementStart, search], currentReplacement);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
search++;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (search - replacementStart <= 1) {
|
|
33
|
+
result.set(replacementStart, currentReplacement);
|
|
34
|
+
} else {
|
|
35
|
+
result.set([replacementStart, search], currentReplacement);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getIndividualSubstitutionGlyph(table: SubstitutionTable, glyphId: number): number | null {
|
|
42
|
+
const coverageIndex = getCoverageGlyphIndex(table.coverage, glyphId);
|
|
43
|
+
|
|
44
|
+
// istanbul ignore next - invalid font
|
|
45
|
+
if (coverageIndex === null) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
switch (table.substFormat) {
|
|
50
|
+
// https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#11-single-substitution-format-1
|
|
51
|
+
case 1:
|
|
52
|
+
// TODO: determine if there's a rhyme or reason to the 16-bit
|
|
53
|
+
// wraparound and if it can ever be a different number
|
|
54
|
+
return (glyphId + table.deltaGlyphId) % (2 ** 16);
|
|
55
|
+
// https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#12-single-substitution-format-2
|
|
56
|
+
case 2:
|
|
57
|
+
// eslint-disable-next-line eqeqeq
|
|
58
|
+
return table.substitute[coverageIndex] != null
|
|
59
|
+
? table.substitute[coverageIndex]
|
|
60
|
+
: null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
export type SubstitutionTable = SubstitutionTable.IFormat1 | SubstitutionTable.IFormat2;
|
|
2
|
+
export namespace SubstitutionTable {
|
|
3
|
+
export interface IFormat1 {
|
|
4
|
+
substFormat: 1;
|
|
5
|
+
coverage: CoverageTable;
|
|
6
|
+
deltaGlyphId: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface IFormat2 {
|
|
10
|
+
substFormat: 2;
|
|
11
|
+
coverage: CoverageTable;
|
|
12
|
+
substitute: number[];
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type CoverageTable = CoverageTable.IFormat1 | CoverageTable.IFormat2;
|
|
17
|
+
export namespace CoverageTable {
|
|
18
|
+
export interface IFormat1 {
|
|
19
|
+
format: 1;
|
|
20
|
+
glyphs: number[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface IFormat2 {
|
|
24
|
+
format: 2;
|
|
25
|
+
ranges: {
|
|
26
|
+
start: number;
|
|
27
|
+
end: number;
|
|
28
|
+
index: number;
|
|
29
|
+
}[];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type ChainingContextualSubstitutionTable = ChainingContextualSubstitutionTable.IFormat1 |
|
|
34
|
+
ChainingContextualSubstitutionTable.IFormat2 | ChainingContextualSubstitutionTable.IFormat3;
|
|
35
|
+
export namespace ChainingContextualSubstitutionTable {
|
|
36
|
+
export interface IFormat1 {
|
|
37
|
+
substFormat: 1;
|
|
38
|
+
coverage: CoverageTable;
|
|
39
|
+
chainRuleSets: ChainSubRuleTable[][];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface IFormat2 {
|
|
43
|
+
substFormat: 2;
|
|
44
|
+
coverage: CoverageTable;
|
|
45
|
+
backtrackClassDef: ClassDefTable;
|
|
46
|
+
inputClassDef: ClassDefTable;
|
|
47
|
+
lookaheadClassDef: ClassDefTable;
|
|
48
|
+
chainClassSet: (null | IChainSubClassRuleTable[])[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface IFormat3 {
|
|
52
|
+
substFormat: 3;
|
|
53
|
+
backtrackCoverage: CoverageTable[];
|
|
54
|
+
inputCoverage: CoverageTable[];
|
|
55
|
+
lookaheadCoverage: CoverageTable[];
|
|
56
|
+
lookupRecords: ISubstitutionLookupRecord[];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface IReverseChainingContextualSingleSubstitutionTable {
|
|
61
|
+
substFormat: 1;
|
|
62
|
+
coverage: CoverageTable;
|
|
63
|
+
backtrackCoverage: CoverageTable[];
|
|
64
|
+
lookaheadCoverage: CoverageTable[];
|
|
65
|
+
substitutes: number[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export type ClassDefTable = ClassDefTable.IFormat2;
|
|
69
|
+
export namespace ClassDefTable {
|
|
70
|
+
export interface IFormat2 {
|
|
71
|
+
format: 2;
|
|
72
|
+
ranges: {
|
|
73
|
+
start: number;
|
|
74
|
+
end: number;
|
|
75
|
+
classId: number;
|
|
76
|
+
}[];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface ISubstitutionLookupRecord {
|
|
81
|
+
sequenceIndex: number;
|
|
82
|
+
lookupListIndex: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export type ChainSubRuleTable = IChainSubClassRuleTable;
|
|
86
|
+
export interface IChainSubClassRuleTable {
|
|
87
|
+
backtrack: number[];
|
|
88
|
+
input: number[];
|
|
89
|
+
lookahead: number[];
|
|
90
|
+
lookupRecords: ISubstitutionLookupRecord[];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type Lookup = Lookup.IType1 | Lookup.IType6 | Lookup.IType8;
|
|
94
|
+
export namespace Lookup {
|
|
95
|
+
export interface IType1 {
|
|
96
|
+
lookupType: 1;
|
|
97
|
+
lookupFlag: number;
|
|
98
|
+
subtables: SubstitutionTable[];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface IType6 {
|
|
102
|
+
lookupType: 6;
|
|
103
|
+
lookupFlag: number;
|
|
104
|
+
subtables: ChainingContextualSubstitutionTable[];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface IType8 {
|
|
108
|
+
lookupType: 8;
|
|
109
|
+
lookupFlag: number;
|
|
110
|
+
subtables: IReverseChainingContextualSingleSubstitutionTable[];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export interface ISubstitutionResult {
|
|
2
|
+
index: number;
|
|
3
|
+
contextRange: [number, number];
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Information about ligatures found in a sequence of text
|
|
8
|
+
*/
|
|
9
|
+
export interface ILigatureData {
|
|
10
|
+
/**
|
|
11
|
+
* The list of font glyphs in the input text.
|
|
12
|
+
*/
|
|
13
|
+
inputGlyphs: number[];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The list of font glyphs after performing replacements for font ligatures.
|
|
17
|
+
*/
|
|
18
|
+
outputGlyphs: number[];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Sorted array of ranges that must be rendered together to produce the
|
|
22
|
+
* ligatures in the output sequence. The ranges are inclusive on the left and
|
|
23
|
+
* exclusive on the right.
|
|
24
|
+
*/
|
|
25
|
+
contextRanges: [number, number][];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface IFont {
|
|
29
|
+
/**
|
|
30
|
+
* Scans the provided text for font ligatures, returning an object with
|
|
31
|
+
* metadata about the text and any ligatures found.
|
|
32
|
+
*
|
|
33
|
+
* @param text String to search for ligatures
|
|
34
|
+
*/
|
|
35
|
+
findLigatures(text: string): ILigatureData;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Scans the provided text for font ligatures, returning an array of ranges
|
|
39
|
+
* where ligatures are located.
|
|
40
|
+
*
|
|
41
|
+
* @param text String to search for ligatures
|
|
42
|
+
*/
|
|
43
|
+
findLigatureRanges(text: string): [number, number][];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface IOptions {
|
|
47
|
+
/**
|
|
48
|
+
* Optional size of previous results to store, measured in total number of
|
|
49
|
+
* characters from input strings. Defaults to no cache (0)
|
|
50
|
+
*/
|
|
51
|
+
cacheSize?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface ILookupTree {
|
|
55
|
+
individual: {
|
|
56
|
+
[glyphId: string]: ILookupTreeEntry;
|
|
57
|
+
};
|
|
58
|
+
range: {
|
|
59
|
+
range: [number, number];
|
|
60
|
+
entry: ILookupTreeEntry;
|
|
61
|
+
}[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ILookupTreeEntry {
|
|
65
|
+
lookup?: ILookupResult;
|
|
66
|
+
forward?: ILookupTree;
|
|
67
|
+
reverse?: ILookupTree;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ILookupResult {
|
|
71
|
+
substitutions: (number | null)[];
|
|
72
|
+
length: number;
|
|
73
|
+
index: number;
|
|
74
|
+
subIndex: number;
|
|
75
|
+
contextRange: [number, number];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface IFlattenedLookupTree {
|
|
79
|
+
[glyphId: string]: IFlattenedLookupTreeEntry;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface IFlattenedLookupTreeEntry {
|
|
83
|
+
lookup?: ILookupResult;
|
|
84
|
+
forward?: IFlattenedLookupTree;
|
|
85
|
+
reverse?: IFlattenedLookupTree;
|
|
86
|
+
}
|