@daiyam/xterm-tab-addon-ligatures
Version:
Add support for programming ligatures to xterm.js
184 lines (157 loc) • 5.38 kB
text/typescript
import { ILookupTreeEntry, ILookupTree } from '../types';
import { ISubstitutionLookupRecord, Lookup } from '../tables';
import { getIndividualSubstitutionGlyph, getRangeSubstitutionGlyphs } from './substitution';
export interface IEntryMeta {
entry: ILookupTreeEntry;
substitutions: (number | null)[];
}
export function processInputPosition(
glyphs: (number | [number, number])[],
position: number,
currentEntries: IEntryMeta[],
lookupRecords: ISubstitutionLookupRecord[],
lookups: Lookup[]
): IEntryMeta[] {
const nextEntries: IEntryMeta[] = [];
for (const currentEntry of currentEntries) {
currentEntry.entry.forward = {
individual: {},
range: []
};
for (const glyph of glyphs) {
nextEntries.push(...getInputTree(
currentEntry.entry.forward,
lookupRecords,
lookups,
position,
glyph
).map(({ entry, substitution }) => ({
entry,
substitutions: [...currentEntry.substitutions, substitution]
})));
}
}
return nextEntries;
}
export function processLookaheadPosition(
glyphs: (number | [number, number])[],
currentEntries: IEntryMeta[]
): IEntryMeta[] {
const nextEntries: IEntryMeta[] = [];
const processedEntries = new Set<ILookupTreeEntry>();
for (const currentEntry of currentEntries) {
// Skip if we've already processed this entry object
if (processedEntries.has(currentEntry.entry)) {
continue;
}
processedEntries.add(currentEntry.entry);
currentEntry.entry.forward ??= {
individual: {},
range: []
};
// All glyphs at this position share ONE entry - lookahead just needs to match,
// all paths lead to the same result
const sharedEntry: ILookupTreeEntry = {};
for (const glyph of glyphs) {
if (Array.isArray(glyph)) {
currentEntry.entry.forward.range.push({
entry: sharedEntry,
range: glyph
});
} else {
currentEntry.entry.forward.individual[glyph] = sharedEntry;
}
}
nextEntries.push({
entry: sharedEntry,
substitutions: currentEntry.substitutions
});
}
return nextEntries;
}
export function processBacktrackPosition(
glyphs: (number | [number, number])[],
currentEntries: IEntryMeta[]
): IEntryMeta[] {
const nextEntries: IEntryMeta[] = [];
const processedEntries = new Set<ILookupTreeEntry>();
for (const currentEntry of currentEntries) {
// Skip if we've already processed this entry object
if (processedEntries.has(currentEntry.entry)) {
continue;
}
processedEntries.add(currentEntry.entry);
currentEntry.entry.reverse ??= {
individual: {},
range: []
};
// All glyphs at this position share ONE entry - backtrack just needs to match,
// all paths lead to the same result
const sharedEntry: ILookupTreeEntry = {};
for (const glyph of glyphs) {
if (Array.isArray(glyph)) {
currentEntry.entry.reverse.range.push({
entry: sharedEntry,
range: glyph
});
} else {
currentEntry.entry.reverse.individual[glyph] = sharedEntry;
}
}
nextEntries.push({
entry: sharedEntry,
substitutions: currentEntry.substitutions
});
}
return nextEntries;
}
export function getInputTree(tree: ILookupTree, substitutions: ISubstitutionLookupRecord[], lookups: Lookup[], inputIndex: number, glyphId: number | [number, number]): { entry: ILookupTreeEntry, substitution: number | null }[] {
const result: { entry: ILookupTreeEntry, substitution: number | null }[] = [];
if (!Array.isArray(glyphId)) {
tree.individual[glyphId] = {};
result.push({
entry: tree.individual[glyphId],
substitution: getSubstitutionAtPosition(substitutions, lookups, inputIndex, glyphId)
});
} else {
const subs = getSubstitutionAtPositionRange(substitutions, lookups, inputIndex, glyphId);
for (const [range, substitution] of subs) {
const entry: ILookupTreeEntry = {};
if (Array.isArray(range)) {
tree.range.push({ range, entry });
} else {
tree.individual[range] = {};
}
result.push({ entry, substitution });
}
}
return result;
}
function getSubstitutionAtPositionRange(substitutions: ISubstitutionLookupRecord[], lookups: Lookup[], index: number, range: [number, number]): Map<number | [number, number], number | null> {
for (const substitution of substitutions.filter(s => s.sequenceIndex === index)) {
for (const substitutionTable of (lookups[substitution.lookupListIndex] as Lookup.IType1).subtables) {
const sub = getRangeSubstitutionGlyphs(
substitutionTable,
range
);
if (!Array.from(sub.values()).every(val => val !== null)) {
return sub;
}
}
}
return new Map([[range, null]]);
}
function getSubstitutionAtPosition(substitutions: ISubstitutionLookupRecord[], lookups: Lookup[], index: number, glyphId: number): number | null {
for (const substitution of substitutions.filter(s => s.sequenceIndex === index)) {
for (const substitutionTable of (lookups[substitution.lookupListIndex] as Lookup.IType1).subtables) {
const sub = getIndividualSubstitutionGlyph(
substitutionTable,
glyphId
);
if (sub !== null) {
return sub;
}
}
}
return null;
}