UNPKG

@daiyam/xterm-tab-addon-ligatures

Version:

Add support for programming ligatures to xterm.js

184 lines (157 loc) 5.38 kB
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; }