@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xterm/addon-ligatures",
3
- "version": "0.11.0-beta.9",
3
+ "version": "0.11.0-beta.90",
4
4
  "description": "Add support for programming ligatures to xterm.js",
5
5
  "author": {
6
6
  "name": "The xterm.js authors",
@@ -32,18 +32,19 @@
32
32
  ],
33
33
  "license": "MIT",
34
34
  "dependencies": {
35
- "font-finder": "^1.1.0",
36
- "font-ligatures": "^1.4.1"
35
+ "lru-cache": "^6.0.0",
36
+ "opentype.js": "^0.8.0"
37
37
  },
38
38
  "devDependencies": {
39
- "@types/sinon": "^5.0.1",
39
+ "@types/lru-cache": "^5.1.0",
40
+ "@types/opentype.js": "^0.7.0",
40
41
  "axios": "^1.6.0",
42
+ "font-finder": "^1.1.0",
41
43
  "mkdirp": "0.5.5",
42
- "sinon": "6.3.5",
43
44
  "yauzl": "^2.10.0"
44
45
  },
45
- "commit": "269d8c20aed41b71c743690b1fe70d82dbd420d6",
46
+ "commit": "c97abced76fe3ca2191b86f6c03c1275b6d4e696",
46
47
  "peerDependencies": {
47
- "@xterm/xterm": "^6.1.0-beta.9"
48
+ "@xterm/xterm": "^6.1.0-beta.90"
48
49
  }
49
50
  }
package/src/font.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * @license MIT
4
4
  */
5
5
 
6
- import { Font, loadBuffer } from 'font-ligatures';
6
+ import { Font, loadBuffer } from './fontLigatures/index';
7
7
 
8
8
  import parse from './parse';
9
9
 
@@ -0,0 +1,40 @@
1
+ import { ILookupTree, IFlattenedLookupTree, ILookupTreeEntry, IFlattenedLookupTreeEntry } from './types';
2
+
3
+ export default function flatten(tree: ILookupTree, visited: Map<ILookupTreeEntry, IFlattenedLookupTreeEntry> = new Map()): IFlattenedLookupTree {
4
+ const result: IFlattenedLookupTree = {};
5
+ for (const [glyphId, entry] of Object.entries(tree.individual)) {
6
+ result[glyphId] = flattenEntry(entry, visited);
7
+ }
8
+
9
+ for (const { range, entry } of tree.range) {
10
+ const flattened = flattenEntry(entry, visited);
11
+ for (let glyphId = range[0]; glyphId < range[1]; glyphId++) {
12
+ result[glyphId] = flattened;
13
+ }
14
+ }
15
+
16
+ return result;
17
+ }
18
+
19
+ function flattenEntry(entry: ILookupTreeEntry, visited: Map<ILookupTreeEntry, IFlattenedLookupTreeEntry>): IFlattenedLookupTreeEntry {
20
+ if (visited.has(entry)) {
21
+ return visited.get(entry)!;
22
+ }
23
+
24
+ const result: IFlattenedLookupTreeEntry = {};
25
+ visited.set(entry, result);
26
+
27
+ if (entry.forward) {
28
+ result.forward = flatten(entry.forward, visited);
29
+ }
30
+
31
+ if (entry.reverse) {
32
+ result.reverse = flatten(entry.reverse, visited);
33
+ }
34
+
35
+ if (entry.lookup) {
36
+ result.lookup = entry.lookup;
37
+ }
38
+
39
+ return result;
40
+ }
@@ -0,0 +1,262 @@
1
+ import * as opentype from 'opentype.js';
2
+ import LRUCache = require('lru-cache');
3
+
4
+ import { IFont, ILigatureData, IFlattenedLookupTree, ILookupTree, IOptions } from './types';
5
+ import mergeTrees from './merge';
6
+ import walkTree from './walk';
7
+ import mergeRange from './mergeRange';
8
+
9
+ import buildTreeGsubType6Format1 from './processors/6-1';
10
+ import buildTreeGsubType6Format2 from './processors/6-2';
11
+ import buildTreeGsubType6Format3 from './processors/6-3';
12
+ import buildTreeGsubType8Format1 from './processors/8-1';
13
+ import flatten from './flatten';
14
+
15
+ class FontImpl implements IFont {
16
+ private _font: opentype.Font;
17
+ private _lookupTrees: { tree: IFlattenedLookupTree, processForward: boolean }[] = [];
18
+ private _glyphLookups: { [glyphId: string]: number[] } = {};
19
+ private _cache?: LRUCache<string, ILigatureData | [number, number][]>;
20
+
21
+ constructor(font: opentype.Font, options: Required<IOptions>) {
22
+ this._font = font;
23
+
24
+ if (options.cacheSize > 0) {
25
+ this._cache = new LRUCache({
26
+ max: options.cacheSize,
27
+ length: ((val: ILigatureData | [number, number][], key: string) => key.length) as any
28
+ });
29
+ }
30
+
31
+ const caltFeatures = this._font.tables.gsub && this._font.tables.gsub.features.filter((f: { tag: string }) => f.tag === 'calt') || [];
32
+ const lookupIndices: number[] = caltFeatures
33
+ .reduce((acc: number[], val: { feature: { lookupListIndexes: number[] } }) => [...acc, ...val.feature.lookupListIndexes], []);
34
+
35
+ const allLookups = this._font.tables.gsub && this._font.tables.gsub.lookups || [];
36
+ const lookupGroups = allLookups.filter((l: unknown, i: number) => lookupIndices.some(idx => idx === i));
37
+
38
+ for (const [index, lookup] of lookupGroups.entries()) {
39
+ const trees: ILookupTree[] = [];
40
+ switch (lookup.lookupType) {
41
+ case 6:
42
+ for (const [index, table] of lookup.subtables.entries()) {
43
+ switch (table.substFormat) {
44
+ case 1:
45
+ trees.push(buildTreeGsubType6Format1(table, allLookups, index));
46
+ break;
47
+ case 2:
48
+ trees.push(buildTreeGsubType6Format2(table, allLookups, index));
49
+ break;
50
+ case 3:
51
+ trees.push(buildTreeGsubType6Format3(table, allLookups, index));
52
+ break;
53
+ }
54
+ }
55
+ break;
56
+ case 8:
57
+ for (const [index, table] of lookup.subtables.entries()) {
58
+ trees.push(buildTreeGsubType8Format1(table, index));
59
+ }
60
+ break;
61
+ }
62
+
63
+ const tree = flatten(mergeTrees(trees));
64
+
65
+ this._lookupTrees.push({
66
+ tree,
67
+ processForward: lookup.lookupType !== 8
68
+ });
69
+
70
+ for (const glyphId of Object.keys(tree)) {
71
+ if (!this._glyphLookups[glyphId]) {
72
+ this._glyphLookups[glyphId] = [];
73
+ }
74
+
75
+ this._glyphLookups[glyphId].push(index);
76
+ }
77
+ }
78
+ }
79
+
80
+ public findLigatures(text: string): ILigatureData {
81
+ const cached = this._cache && this._cache.get(text);
82
+ if (cached && !Array.isArray(cached)) {
83
+ return cached;
84
+ }
85
+
86
+ const glyphIds: number[] = [];
87
+ for (const char of text) {
88
+ glyphIds.push(this._font.charToGlyphIndex(char));
89
+ }
90
+
91
+ // If there are no lookup groups, there's no point looking for
92
+ // replacements. This gives us a minor performance boost for fonts with
93
+ // no ligatures
94
+ if (this._lookupTrees.length === 0) {
95
+ return {
96
+ inputGlyphs: glyphIds,
97
+ outputGlyphs: glyphIds,
98
+ contextRanges: []
99
+ };
100
+ }
101
+
102
+ const result = this._findInternal(glyphIds.slice());
103
+ const finalResult: ILigatureData = {
104
+ inputGlyphs: glyphIds,
105
+ outputGlyphs: result.sequence,
106
+ contextRanges: result.ranges
107
+ };
108
+ if (this._cache) {
109
+ this._cache.set(text, finalResult);
110
+ }
111
+
112
+ return finalResult;
113
+ }
114
+
115
+ public findLigatureRanges(text: string): [number, number][] {
116
+ // Short circuit the process if there are no possible ligatures in the
117
+ // font
118
+ if (this._lookupTrees.length === 0) {
119
+ return [];
120
+ }
121
+
122
+ const cached = this._cache && this._cache.get(text);
123
+ if (cached) {
124
+ return Array.isArray(cached) ? cached : cached.contextRanges;
125
+ }
126
+
127
+ const glyphIds: number[] = [];
128
+ for (const char of text) {
129
+ glyphIds.push(this._font.charToGlyphIndex(char));
130
+ }
131
+
132
+ const result = this._findInternal(glyphIds);
133
+ if (this._cache) {
134
+ this._cache.set(text, result.ranges);
135
+ }
136
+
137
+ return result.ranges;
138
+ }
139
+
140
+ private _findInternal(sequence: number[]): { sequence: number[], ranges: [number, number][] } {
141
+ const ranges: [number, number][] = [];
142
+
143
+ let nextLookup = this._getNextLookup(sequence, 0);
144
+ while (nextLookup.index !== null) {
145
+ const lookup = this._lookupTrees[nextLookup.index];
146
+ if (lookup.processForward) {
147
+ let lastGlyphIndex = nextLookup.last;
148
+ for (let i = nextLookup.first; i < lastGlyphIndex; i++) {
149
+ const result = walkTree(lookup.tree, sequence, i, i);
150
+ if (result) {
151
+ for (let j = 0; j < result.substitutions.length; j++) {
152
+ const sub = result.substitutions[j];
153
+ if (sub !== null) {
154
+ sequence[i + j] = sub;
155
+ }
156
+ }
157
+
158
+ mergeRange(
159
+ ranges,
160
+ result.contextRange[0] + i,
161
+ result.contextRange[1] + i
162
+ );
163
+
164
+ // Substitutions can end up extending the search range
165
+ if (i + result.length >= lastGlyphIndex) {
166
+ lastGlyphIndex = i + result.length + 1;
167
+ }
168
+
169
+ i += result.length - 1;
170
+ }
171
+ }
172
+ } else {
173
+ // We don't need to do the lastGlyphIndex tracking here because
174
+ // reverse processing isn't allowed to replace more than one
175
+ // character at a time.
176
+ for (let i = nextLookup.last - 1; i >= nextLookup.first; i--) {
177
+ const result = walkTree(lookup.tree, sequence, i, i);
178
+ if (result) {
179
+ for (let j = 0; j < result.substitutions.length; j++) {
180
+ const sub = result.substitutions[j];
181
+ if (sub !== null) {
182
+ sequence[i + j] = sub;
183
+ }
184
+ }
185
+
186
+ mergeRange(
187
+ ranges,
188
+ result.contextRange[0] + i,
189
+ result.contextRange[1] + i
190
+ );
191
+
192
+ i -= result.length - 1;
193
+ }
194
+ }
195
+ }
196
+
197
+ nextLookup = this._getNextLookup(sequence, nextLookup.index + 1);
198
+ }
199
+
200
+ return { sequence, ranges };
201
+ }
202
+
203
+ /**
204
+ * Returns the lookup and glyph range for the first lookup that might
205
+ * contain a match.
206
+ *
207
+ * @param sequence Input glyph sequence
208
+ * @param start The first input to try
209
+ */
210
+ private _getNextLookup(sequence: number[], start: number): { index: number | null, first: number, last: number } {
211
+ const result: { index: number | null, first: number, last: number } = {
212
+ index: null,
213
+ first: Infinity,
214
+ last: -1
215
+ };
216
+
217
+ // Loop through each glyph and find the first valid lookup for it
218
+ for (let i = 0; i < sequence.length; i++) {
219
+ const lookups = this._glyphLookups[sequence[i]];
220
+ if (!lookups) {
221
+ continue;
222
+ }
223
+
224
+ for (let j = 0; j < lookups.length; j++) {
225
+ const lookupIndex = lookups[j];
226
+ if (lookupIndex >= start) {
227
+ // Update the lookup information if it's the one we're
228
+ // storing or earlier than it.
229
+ if (result.index === null || lookupIndex <= result.index) {
230
+ result.index = lookupIndex;
231
+
232
+ if (result.first > i) {
233
+ result.first = i;
234
+ }
235
+
236
+ result.last = i + 1;
237
+ }
238
+
239
+ break;
240
+ }
241
+ }
242
+ }
243
+
244
+ return result;
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Load the font from it's binary data. The returned value can be used to find
250
+ * ligatures for the font.
251
+ *
252
+ * @param buffer ArrayBuffer of the font to load
253
+ */
254
+ export function loadBuffer(buffer: ArrayBuffer, options?: IOptions): IFont {
255
+ const font = opentype.parse(buffer);
256
+ return new FontImpl(font, {
257
+ cacheSize: 0,
258
+ ...options
259
+ });
260
+ }
261
+
262
+ export { IFont as Font, ILigatureData as LigatureData, IOptions as Options };