This commit is contained in:
Daniel Imms
2026-01-03 09:31:45 -08:00
parent ec03109c39
commit f84384c83a
17 changed files with 170 additions and 169 deletions
@@ -1,7 +1,7 @@
import { LookupTree, FlattenedLookupTree, LookupTreeEntry, FlattenedLookupTreeEntry } from './types';
import { ILookupTree, IFlattenedLookupTree, ILookupTreeEntry, IFlattenedLookupTreeEntry } from './types';
export default function flatten(tree: LookupTree): FlattenedLookupTree {
const result: FlattenedLookupTree = {};
export default function flatten(tree: ILookupTree): IFlattenedLookupTree {
const result: IFlattenedLookupTree = {};
for (const [glyphId, entry] of Object.entries(tree.individual)) {
result[glyphId] = flattenEntry(entry);
}
@@ -16,8 +16,8 @@ export default function flatten(tree: LookupTree): FlattenedLookupTree {
return result;
}
function flattenEntry(entry: LookupTreeEntry): FlattenedLookupTreeEntry {
const result: FlattenedLookupTreeEntry = {};
function flattenEntry(entry: ILookupTreeEntry): IFlattenedLookupTreeEntry {
const result: IFlattenedLookupTreeEntry = {};
if (entry.forward) {
result.forward = flatten(entry.forward);
@@ -8,31 +8,31 @@ import * as fs from 'fs';
import { assert } from 'chai';
import { loadBuffer } from './index';
interface Font {
findLigatures(text: string): { outputGlyphs: number[]; contextRanges: [number, number][] };
interface IFont {
findLigatures(text: string): { outputGlyphs: number[], contextRanges: [number, number][] };
findLigatureRanges(text: string): [number, number][];
}
interface TestCase {
interface ITestCase {
font: string;
input: string;
glyphs: number[];
ranges: [number, number][];
}
const fira = (input: string, glyphs: number[], ranges: [number, number][]): TestCase =>
const fira = (input: string, glyphs: number[], ranges: [number, number][]): ITestCase =>
({ font: 'Fira Code', input, glyphs, ranges });
const iosevka = (input: string, glyphs: number[], ranges: [number, number][]): TestCase =>
const iosevka = (input: string, glyphs: number[], ranges: [number, number][]): ITestCase =>
({ font: 'Iosevka', input, glyphs, ranges });
const monoid = (input: string, glyphs: number[], ranges: [number, number][]): TestCase =>
const monoid = (input: string, glyphs: number[], ranges: [number, number][]): ITestCase =>
({ font: 'Monoid', input, glyphs, ranges });
const ubuntu = (input: string, glyphs: number[], ranges: [number, number][]): TestCase =>
const ubuntu = (input: string, glyphs: number[], ranges: [number, number][]): ITestCase =>
({ font: 'Ubuntu Mono', input, glyphs, ranges });
const firaCases: TestCase[] = [
const firaCases: ITestCase[] = [
fira('abc', [133, 145, 146], []),
fira('.=', [1614, 1081], [[0, 2]]),
fira('..=', [1614, 1614, 1083], [[0, 3]]),
@@ -168,7 +168,7 @@ const firaCases: TestCase[] = [
fira('===>', [1614, 1614, 1486, 1148], [[0, 4]])
];
const iosevkaCases: TestCase[] = [
const iosevkaCases: ITestCase[] = [
iosevka('<-', [31, 3127], [[0, 2]]),
iosevka('<--', [31, 3129, 3139], [[0, 3]]),
iosevka('<---', [31, 3129, 3150, 3139], [[0, 4]]),
@@ -218,7 +218,7 @@ const iosevkaCases: TestCase[] = [
iosevka('!===', [3100, 3085, 3085, 3084], [[0, 4]])
];
const monoidCases: TestCase[] = [
const monoidCases: ITestCase[] = [
monoid('<!--', [775, 775, 775, 643], [[0, 4]]),
monoid('-->', [779, 779, 628], [[0, 3]]),
monoid('<--', [776, 776, 627], [[0, 3]]),
@@ -256,7 +256,7 @@ const monoidCases: TestCase[] = [
monoid('__', [763, 764], [[0, 2]])
];
const ubuntuCases: TestCase[] = [
const ubuntuCases: ITestCase[] = [
ubuntu('==>', [32, 32, 33], [])
];
@@ -267,9 +267,9 @@ const fontPaths: Record<string, string> = {
'Ubuntu Mono': path.join(__dirname, '../../fonts/UbuntuMono-Regular.ttf')
};
const fontCache: Map<string, Font> = new Map();
const fontCache: Map<string, IFont> = new Map();
function loadFont(fontName: string): Font {
function loadFont(fontName: string): IFont {
let font = fontCache.get(fontName);
if (!font) {
const fontPath = fontPaths[fontName];
@@ -1,7 +1,7 @@
import * as opentype from 'opentype.js';
import LRUCache = require('lru-cache');
import { Font, LigatureData, FlattenedLookupTree, LookupTree, Options } from './types';
import { IFont, ILigatureData, IFlattenedLookupTree, ILookupTree, IOptions } from './types';
import mergeTrees from './merge';
import walkTree from './walk';
import mergeRange from './mergeRange';
@@ -12,19 +12,19 @@ import buildTreeGsubType6Format3 from './processors/6-3';
import buildTreeGsubType8Format1 from './processors/8-1';
import flatten from './flatten';
class FontImpl implements Font {
class FontImpl implements IFont {
private _font: opentype.Font;
private _lookupTrees: { tree: FlattenedLookupTree; processForward: boolean; }[] = [];
private _lookupTrees: { tree: IFlattenedLookupTree, processForward: boolean }[] = [];
private _glyphLookups: { [glyphId: string]: number[] } = {};
private _cache?: LRUCache<string, LigatureData | [number, number][]>;
private _cache?: LRUCache<string, ILigatureData | [number, number][]>;
constructor(font: opentype.Font, options: Required<Options>) {
constructor(font: opentype.Font, options: Required<IOptions>) {
this._font = font;
if (options.cacheSize > 0) {
this._cache = new LRUCache({
max: options.cacheSize,
length: ((val: LigatureData | [number, number][], key: string) => key.length) as any
length: ((val: ILigatureData | [number, number][], key: string) => key.length) as any
});
}
@@ -36,7 +36,7 @@ class FontImpl implements Font {
const lookupGroups = allLookups.filter((l: unknown, i: number) => lookupIndices.some(idx => idx === i));
for (const [index, lookup] of lookupGroups.entries()) {
const trees: LookupTree[] = [];
const trees: ILookupTree[] = [];
switch (lookup.lookupType) {
case 6:
for (const [index, table] of lookup.subtables.entries()) {
@@ -77,7 +77,7 @@ class FontImpl implements Font {
}
}
findLigatures(text: string): LigatureData {
public findLigatures(text: string): ILigatureData {
const cached = this._cache && this._cache.get(text);
if (cached && !Array.isArray(cached)) {
return cached;
@@ -100,7 +100,7 @@ class FontImpl implements Font {
}
const result = this._findInternal(glyphIds.slice());
const finalResult: LigatureData = {
const finalResult: ILigatureData = {
inputGlyphs: glyphIds,
outputGlyphs: result.sequence,
contextRanges: result.ranges
@@ -112,7 +112,7 @@ class FontImpl implements Font {
return finalResult;
}
findLigatureRanges(text: string): [number, number][] {
public findLigatureRanges(text: string): [number, number][] {
// Short circuit the process if there are no possible ligatures in the
// font
if (this._lookupTrees.length === 0) {
@@ -137,7 +137,7 @@ class FontImpl implements Font {
return result.ranges;
}
private _findInternal(sequence: number[]): { sequence: number[]; ranges: [number, number][]; } {
private _findInternal(sequence: number[]): { sequence: number[], ranges: [number, number][] } {
const ranges: [number, number][] = [];
let nextLookup = this._getNextLookup(sequence, 0);
@@ -207,8 +207,8 @@ class FontImpl implements Font {
* @param sequence Input glyph sequence
* @param start The first input to try
*/
private _getNextLookup(sequence: number[], start: number): { index: number | null; first: number; last: number; } {
const result: { index: number | null; first: number; last: number; } = {
private _getNextLookup(sequence: number[], start: number): { index: number | null, first: number, last: number } {
const result: { index: number | null, first: number, last: number } = {
index: null,
first: Infinity,
last: -1
@@ -251,7 +251,7 @@ class FontImpl implements Font {
*
* @param buffer ArrayBuffer of the font to load
*/
export function loadBuffer(buffer: ArrayBuffer, options?: Options): Font {
export function loadBuffer(buffer: ArrayBuffer, options?: IOptions): IFont {
const font = opentype.parse(buffer);
return new FontImpl(font, {
cacheSize: 0,
@@ -259,4 +259,4 @@ export function loadBuffer(buffer: ArrayBuffer, options?: Options): Font {
});
}
export { Font, LigatureData, Options };
export { IFont as Font, ILigatureData as LigatureData, IOptions as Options };
@@ -6,7 +6,7 @@
import { assert } from 'chai';
import mergeTrees from './merge';
interface LookupResult {
interface ILookupResult {
contextRange: [number, number];
index: number;
subIndex: number;
@@ -14,7 +14,7 @@ interface LookupResult {
substitutions: number[];
}
function lookup(substitutionGlyph: number, index?: number, subIndex?: number): LookupResult {
function lookup(substitutionGlyph: number, index?: number, subIndex?: number): ILookupResult {
return {
contextRange: [0, 1],
index: index || 0,
@@ -1,4 +1,4 @@
import { LookupTree, LookupTreeEntry } from './types';
import { ILookupTree, ILookupTreeEntry } from './types';
/**
* Merges the provided trees into a single lookup tree. When conflicting lookups
@@ -8,8 +8,8 @@ import { LookupTree, LookupTreeEntry } from './types';
* @param trees Array of trees to merge. Entries in earlier trees are favored
* over those in later trees when there is a choice.
*/
export default function mergeTrees(trees: LookupTree[]): LookupTree {
const result: LookupTree = {
export default function mergeTrees(trees: ILookupTree[]): ILookupTree {
const result: ILookupTree = {
individual: {},
range: []
};
@@ -27,7 +27,7 @@ export default function mergeTrees(trees: LookupTree[]): LookupTree {
* @param mainTree The tree where the values should be merged
* @param mergeTree The tree to be merged into the mainTree
*/
function mergeSubtree(mainTree: LookupTree, mergeTree: LookupTree): void {
function mergeSubtree(mainTree: ILookupTree, mergeTree: ILookupTree): void {
// Need to fix this recursively (and handle lookups)
for (const [glyphId, value] of Object.entries(mergeTree.individual)) {
// The main tree is guaranteed to have no overlaps between the
@@ -91,7 +91,7 @@ function mergeSubtree(mainTree: LookupTree, mergeTree: LookupTree): void {
mainTree.range.splice(index, 1);
index--;
const entryToMerge: LookupTreeEntry = cloneEntry(resultEntry);
const entryToMerge: ILookupTreeEntry = cloneEntry(resultEntry);
if (Array.isArray(overlap.both)) {
mainTree.range.push({
range: overlap.both,
@@ -192,7 +192,7 @@ function mergeSubtree(mainTree: LookupTree, mergeTree: LookupTree): void {
* @param mainTree The entry where the values should be merged
* @param mergeTree The entry to merge into the mainTree
*/
function mergeTreeEntry(mainTree: LookupTreeEntry, mergeTree: LookupTreeEntry): void {
function mergeTreeEntry(mainTree: ILookupTreeEntry, mergeTree: ILookupTreeEntry): void {
if (
mergeTree.lookup && (
!mainTree.lookup ||
@@ -220,7 +220,7 @@ function mergeTreeEntry(mainTree: LookupTreeEntry, mergeTree: LookupTreeEntry):
}
}
interface Overlap {
interface IOverlap {
first: (number | [number, number])[];
second: (number | [number, number])[];
both: number | [number, number] | null;
@@ -233,8 +233,8 @@ interface Overlap {
* @param first First range
* @param second Second range
*/
function getRangeOverlap(first: [number, number], second: [number, number]): Overlap {
const result: Overlap = {
function getRangeOverlap(first: [number, number], second: [number, number]): IOverlap {
const result: IOverlap = {
first: [],
second: [],
both: null
@@ -280,7 +280,7 @@ function getRangeOverlap(first: [number, number], second: [number, number]): Ove
* @param first Individual glyph
* @param second Range
*/
function getIndividualOverlap(first: number, second: [number, number]): Overlap {
function getIndividualOverlap(first: number, second: [number, number]): IOverlap {
// Disjoint
if (first < second[0] || first > second[1]) {
return {
@@ -290,7 +290,7 @@ function getIndividualOverlap(first: number, second: [number, number]): Overlap
};
}
const result: Overlap = {
const result: IOverlap = {
first: [],
second: [],
both: first
@@ -317,9 +317,9 @@ function getIndividualOverlap(first: number, second: [number, number]): Overlap
function rangeOrIndividual(start: number, end: number): number | [number, number] {
if (end - start === 1) {
return start;
} else {
return [start, end];
}
return [start, end];
}
/**
@@ -327,8 +327,8 @@ function rangeOrIndividual(start: number, end: number): number | [number, number
*
* @param entry Lookup tree entry to clone
*/
function cloneEntry(entry: LookupTreeEntry): LookupTreeEntry {
const result: LookupTreeEntry = {};
function cloneEntry(entry: ILookupTreeEntry): ILookupTreeEntry {
const result: ILookupTreeEntry = {};
if (entry.forward) {
result.forward = cloneTree(entry.forward);
@@ -356,8 +356,8 @@ function cloneEntry(entry: LookupTreeEntry): LookupTreeEntry {
*
* @param tree Lookup tree to clone
*/
function cloneTree(tree: LookupTree): LookupTree {
const individual: { [glyphId: string]: LookupTreeEntry; } = {};
function cloneTree(tree: ILookupTree): ILookupTree {
const individual: { [glyphId: string]: ILookupTreeEntry } = {};
for (const [glyphId, entry] of Object.entries(tree.individual)) {
individual[glyphId] = cloneEntry(entry);
}
@@ -16,12 +16,14 @@ export default function mergeRange(ranges: [number, number][], newRangeStart: nu
// Case 1: New range is before the search range
ranges.splice(i, 0, [newRangeStart, newRangeEnd]);
return ranges;
} else if (newRangeEnd <= range[1]) {
}
if (newRangeEnd <= range[1]) {
// Case 2: New range is either wholly contained within the
// search range or overlaps with the front of it
range[0] = Math.min(newRangeStart, range[0]);
return ranges;
} else if (newRangeStart < range[1]) {
}
if (newRangeStart < range[1]) {
// Case 3: New range either wholly contains the search range
// or overlaps with the end of it
range[0] = Math.min(newRangeStart, range[0]);
@@ -36,19 +38,19 @@ export default function mergeRange(ranges: [number, number][], newRangeStart: nu
// reach the current one
ranges[i - 1][1] = newRangeEnd;
return ranges;
} else if (newRangeEnd <= range[1]) {
}
if (newRangeEnd <= range[1]) {
// Case 6: New range extends from prvious range into the
// current range
ranges[i - 1][1] = Math.max(newRangeEnd, range[1]);
ranges.splice(i, 1);
inRange = false;
return ranges;
} else {
// Case 7: New range extends from previous range past the
// end of the current range
ranges.splice(i, 1);
i--;
}
// Case 7: New range extends from previous range past the
// end of the current range
ranges.splice(i, 1);
i--;
}
}
@@ -1,8 +1,8 @@
import { ChainingContextualSubstitutionTable, Lookup } from '../tables';
import { LookupTree } from '../types';
import { ILookupTree } from '../types';
import { listGlyphsByIndex } from './coverage';
import { processInputPosition, processLookaheadPosition, processBacktrackPosition, getInputTree, EntryMeta } from './helper';
import { processInputPosition, processLookaheadPosition, processBacktrackPosition, getInputTree, IEntryMeta } from './helper';
/**
* Build lookup tree for GSUB lookup table 6, format 1.
@@ -12,8 +12,8 @@ import { processInputPosition, processLookaheadPosition, processBacktrackPositio
* @param lookups List of lookup tables
* @param tableIndex Index of this table in the overall lookup
*/
export default function buildTree(table: ChainingContextualSubstitutionTable.Format1, lookups: Lookup[], tableIndex: number): LookupTree {
const result: LookupTree = {
export default function buildTree(table: ChainingContextualSubstitutionTable.IFormat1, lookups: Lookup[], tableIndex: number): ILookupTree {
const result: ILookupTree = {
individual: {},
range: []
};
@@ -29,7 +29,7 @@ export default function buildTree(table: ChainingContextualSubstitutionTable.For
}
for (const [subIndex, subTable] of chainRuleSet.entries()) {
let currentEntries: EntryMeta[] = getInputTree(
let currentEntries: IEntryMeta[] = getInputTree(
result,
subTable.lookupRecords,
lookups,
@@ -1,10 +1,10 @@
import { ChainingContextualSubstitutionTable, Lookup } from '../tables';
import { LookupTree } from '../types';
import { ILookupTree } from '../types';
import mergeTrees from '../merge';
import { listGlyphsByIndex } from './coverage';
import getGlyphClass, { listClassGlyphs } from './classDef';
import { processInputPosition, processLookaheadPosition, processBacktrackPosition, getInputTree, EntryMeta } from './helper';
import { processInputPosition, processLookaheadPosition, processBacktrackPosition, getInputTree, IEntryMeta } from './helper';
/**
* Build lookup tree for GSUB lookup table 6, format 2.
@@ -14,8 +14,8 @@ import { processInputPosition, processLookaheadPosition, processBacktrackPositio
* @param lookups List of lookup tables
* @param tableIndex Index of this table in the overall lookup
*/
export default function buildTree(table: ChainingContextualSubstitutionTable.Format2, lookups: Lookup[], tableIndex: number): LookupTree {
const results: LookupTree[] = [];
export default function buildTree(table: ChainingContextualSubstitutionTable.IFormat2, lookups: Lookup[], tableIndex: number): ILookupTree {
const results: ILookupTree[] = [];
const firstGlyphs = listGlyphsByIndex(table.coverage);
@@ -35,12 +35,12 @@ export default function buildTree(table: ChainingContextualSubstitutionTable.For
}
for (const [subIndex, subTable] of classSet.entries()) {
const result: LookupTree = {
const result: ILookupTree = {
individual: {},
range: []
};
let currentEntries: EntryMeta[] = getInputTree(
let currentEntries: IEntryMeta[] = getInputTree(
result,
subTable.lookupRecords,
lookups,
@@ -1,8 +1,8 @@
import { ChainingContextualSubstitutionTable, Lookup } from '../tables';
import { LookupTree } from '../types';
import { ILookupTree } from '../types';
import { listGlyphsByIndex } from './coverage';
import { processInputPosition, processLookaheadPosition, processBacktrackPosition, getInputTree, EntryMeta } from './helper';
import { processInputPosition, processLookaheadPosition, processBacktrackPosition, getInputTree, IEntryMeta } from './helper';
/**
* Build lookup tree for GSUB lookup table 6, format 3.
@@ -12,8 +12,8 @@ import { processInputPosition, processLookaheadPosition, processBacktrackPositio
* @param lookups List of lookup tables
* @param tableIndex Index of this table in the overall lookup
*/
export default function buildTree(table: ChainingContextualSubstitutionTable.Format3, lookups: Lookup[], tableIndex: number): LookupTree {
const result: LookupTree = {
export default function buildTree(table: ChainingContextualSubstitutionTable.IFormat3, lookups: Lookup[], tableIndex: number): ILookupTree {
const result: ILookupTree = {
individual: {},
range: []
};
@@ -21,7 +21,7 @@ export default function buildTree(table: ChainingContextualSubstitutionTable.For
const firstGlyphs = listGlyphsByIndex(table.inputCoverage[0]);
for (const { glyphId } of firstGlyphs) {
let currentEntries: EntryMeta[] = getInputTree(
let currentEntries: IEntryMeta[] = getInputTree(
result,
table.lookupRecords,
lookups,
@@ -1,8 +1,8 @@
import { ReverseChainingContextualSingleSubstitutionTable } from '../tables';
import { LookupTree, LookupTreeEntry } from '../types';
import { IReverseChainingContextualSingleSubstitutionTable } from '../tables';
import { ILookupTree, ILookupTreeEntry } from '../types';
import { listGlyphsByIndex } from './coverage';
import { processLookaheadPosition, processBacktrackPosition, EntryMeta } from './helper';
import { processLookaheadPosition, processBacktrackPosition, IEntryMeta } from './helper';
/**
* Build lookup tree for GSUB lookup table 8, format 1.
@@ -11,8 +11,8 @@ import { processLookaheadPosition, processBacktrackPosition, EntryMeta } from '.
* @param table JSON representation of the table
* @param tableIndex Index of this table in the overall lookup
*/
export default function buildTree(table: ReverseChainingContextualSingleSubstitutionTable, tableIndex: number): LookupTree {
const result: LookupTree = {
export default function buildTree(table: IReverseChainingContextualSingleSubstitutionTable, tableIndex: number): ILookupTree {
const result: ILookupTree = {
individual: {},
range: []
};
@@ -20,7 +20,7 @@ export default function buildTree(table: ReverseChainingContextualSingleSubstitu
const glyphs = listGlyphsByIndex(table.coverage);
for (const { glyphId, index } of glyphs) {
const initialEntry: LookupTreeEntry = {};
const initialEntry: ILookupTreeEntry = {};
if (Array.isArray(glyphId)) {
result.range.push({
entry: initialEntry,
@@ -30,7 +30,7 @@ export default function buildTree(table: ReverseChainingContextualSingleSubstitu
result.individual[glyphId] = initialEntry;
}
let currentEntries: EntryMeta[] = [{
let currentEntries: IEntryMeta[] = [{
entry: initialEntry,
substitutions: [table.substitutes[index]]
}];
@@ -13,21 +13,20 @@ export default function getGlyphClass(table: ClassDefTable, glyphId: number | [n
case 2:
if (Array.isArray(glyphId)) {
return getRangeGlyphClass(table, glyphId);
} else {
return new Map([[
glyphId,
getIndividualGlyphClass(table, glyphId)
]]);
}
return new Map([[
glyphId,
getIndividualGlyphClass(table, glyphId)
]]);
// https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table-format-1
default:
return new Map([[glyphId, null]]);
}
}
function getRangeGlyphClass(table: ClassDefTable.Format2, glyphId: [number, number]): Map<number | [number, number], number | null> {
let classStart: number = glyphId[0];
let currentClass: number | null = getIndividualGlyphClass(table, classStart);
function getRangeGlyphClass(table: ClassDefTable.IFormat2, glyphId: [number, number]): Map<number | [number, number], number | null> {
const classStart: number = glyphId[0];
const currentClass: number | null = getIndividualGlyphClass(table, classStart);
let search: number = glyphId[0] + 1;
const result = new Map<[number, number] | number, number | null>();
@@ -53,7 +52,7 @@ function getRangeGlyphClass(table: ClassDefTable.Format2, glyphId: [number, numb
return result;
}
function getIndividualGlyphClass(table: ClassDefTable.Format2, glyphId: number): number | null {
function getIndividualGlyphClass(table: ClassDefTable.IFormat2, glyphId: number): number | null {
for (const range of table.ranges) {
if (range.start <= glyphId && range.end >= glyphId) {
return range.classId;
@@ -25,12 +25,12 @@ export default function getCoverageGlyphIndex(table: CoverageTable, glyphId: num
}
}
export function listGlyphsByIndex(table: CoverageTable): { glyphId: number | [number, number]; index: number; }[] {
export function listGlyphsByIndex(table: CoverageTable): { glyphId: number | [number, number], index: number }[] {
switch (table.format) {
case 1:
return table.glyphs.map((glyphId, index) => ({ glyphId, index }));
case 2:
let results: { glyphId: number | [number, number]; index: number; }[] = [];
const results: { glyphId: number | [number, number], index: number }[] = [];
for (const [index, range] of table.ranges.entries()) {
if (range.end === range.start) {
results.push({ glyphId: range.start, index });
@@ -1,21 +1,21 @@
import { LookupTreeEntry, LookupTree } from '../types';
import { SubstitutionLookupRecord, Lookup } from '../tables';
import { ILookupTreeEntry, ILookupTree } from '../types';
import { ISubstitutionLookupRecord, Lookup } from '../tables';
import { getIndividualSubstitutionGlyph, getRangeSubstitutionGlyphs } from './substitution';
export interface EntryMeta {
entry: LookupTreeEntry;
export interface IEntryMeta {
entry: ILookupTreeEntry;
substitutions: (number | null)[];
}
export function processInputPosition(
glyphs: (number | [number, number])[],
position: number,
currentEntries: EntryMeta[],
lookupRecords: SubstitutionLookupRecord[],
currentEntries: IEntryMeta[],
lookupRecords: ISubstitutionLookupRecord[],
lookups: Lookup[]
): EntryMeta[] {
const nextEntries: EntryMeta[] = [];
): IEntryMeta[] {
const nextEntries: IEntryMeta[] = [];
for (const currentEntry of currentEntries) {
currentEntry.entry.forward = {
individual: {},
@@ -40,12 +40,12 @@ export function processInputPosition(
export function processLookaheadPosition(
glyphs: (number | [number, number])[],
currentEntries: EntryMeta[]
): EntryMeta[] {
const nextEntries: EntryMeta[] = [];
currentEntries: IEntryMeta[]
): IEntryMeta[] {
const nextEntries: IEntryMeta[] = [];
for (const currentEntry of currentEntries) {
for (const glyph of glyphs) {
const entry: LookupTreeEntry = {};
const entry: ILookupTreeEntry = {};
if (!currentEntry.entry.forward) {
currentEntry.entry.forward = {
individual: {},
@@ -73,12 +73,12 @@ export function processLookaheadPosition(
export function processBacktrackPosition(
glyphs: (number | [number, number])[],
currentEntries: EntryMeta[]
): EntryMeta[] {
const nextEntries: EntryMeta[] = [];
currentEntries: IEntryMeta[]
): IEntryMeta[] {
const nextEntries: IEntryMeta[] = [];
for (const currentEntry of currentEntries) {
for (const glyph of glyphs) {
const entry: LookupTreeEntry = {};
const entry: ILookupTreeEntry = {};
if (!currentEntry.entry.reverse) {
currentEntry.entry.reverse = {
individual: {},
@@ -104,8 +104,8 @@ export function processBacktrackPosition(
return nextEntries;
}
export function getInputTree(tree: LookupTree, substitutions: SubstitutionLookupRecord[], lookups: Lookup[], inputIndex: number, glyphId: number | [number, number]): { entry: LookupTreeEntry; substitution: number | null; }[] {
const result: { entry: LookupTreeEntry; substitution: number | null; }[] = [];
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({
@@ -115,7 +115,7 @@ export function getInputTree(tree: LookupTree, substitutions: SubstitutionLookup
} else {
const subs = getSubstitutionAtPositionRange(substitutions, lookups, inputIndex, glyphId);
for (const [range, substitution] of subs) {
const entry: LookupTreeEntry = {};
const entry: ILookupTreeEntry = {};
if (Array.isArray(range)) {
tree.range.push({ range, entry });
} else {
@@ -128,9 +128,9 @@ export function getInputTree(tree: LookupTree, substitutions: SubstitutionLookup
return result;
}
function getSubstitutionAtPositionRange(substitutions: SubstitutionLookupRecord[], lookups: Lookup[], index: number, range: [number, number]): Map<number | [number, number], number | null> {
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.Type1).subtables) {
for (const substitutionTable of (lookups[substitution.lookupListIndex] as Lookup.IType1).subtables) {
const sub = getRangeSubstitutionGlyphs(
substitutionTable,
range
@@ -145,9 +145,9 @@ function getSubstitutionAtPositionRange(substitutions: SubstitutionLookupRecord[
return new Map([[range, null]]);
}
function getSubstitutionAtPosition(substitutions: SubstitutionLookupRecord[], lookups: Lookup[], index: number, glyphId: number): number | 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.Type1).subtables) {
for (const substitutionTable of (lookups[substitution.lookupListIndex] as Lookup.IType1).subtables) {
const sub = getIndividualSubstitutionGlyph(
substitutionTable,
glyphId
@@ -10,8 +10,8 @@ import getCoverageGlyphIndex from './coverage';
* @param glyphId The index of the glpyh to find substitutions for
*/
export function getRangeSubstitutionGlyphs(table: SubstitutionTable, glyphId: [number, number]): Map<[number, number] | number, number | null> {
let replacementStart: number = glyphId[0];
let currentReplacement: number | null = getIndividualSubstitutionGlyph(table, replacementStart);
const replacementStart: number = glyphId[0];
const currentReplacement: number | null = getIndividualSubstitutionGlyph(table, replacementStart);
let search: number = glyphId[0] + 1;
const result = new Map<[number, number] | number, number | null>();
@@ -54,7 +54,7 @@ export function getIndividualSubstitutionGlyph(table: SubstitutionTable, glyphId
return (glyphId + table.deltaGlyphId) % (2 ** 16);
// https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#12-single-substitution-format-2
case 2:
// tslint:disable-next-line
// eslint-disable-next-line eqeqeq
return table.substitute[coverageIndex] != null
? table.substitute[coverageIndex]
: null;
@@ -1,26 +1,26 @@
export type SubstitutionTable = SubstitutionTable.Format1 | SubstitutionTable.Format2;
export type SubstitutionTable = SubstitutionTable.IFormat1 | SubstitutionTable.IFormat2;
export namespace SubstitutionTable {
export interface Format1 {
export interface IFormat1 {
substFormat: 1;
coverage: CoverageTable;
deltaGlyphId: number;
}
export interface Format2 {
export interface IFormat2 {
substFormat: 2;
coverage: CoverageTable;
substitute: number[];
}
}
export type CoverageTable = CoverageTable.Format1 | CoverageTable.Format2;
export type CoverageTable = CoverageTable.IFormat1 | CoverageTable.IFormat2;
export namespace CoverageTable {
export interface Format1 {
export interface IFormat1 {
format: 1;
glyphs: number[];
}
export interface Format2 {
export interface IFormat2 {
format: 2;
ranges: {
start: number;
@@ -30,34 +30,34 @@ export namespace CoverageTable {
}
}
export type ChainingContextualSubstitutionTable = ChainingContextualSubstitutionTable.Format1 |
ChainingContextualSubstitutionTable.Format2 | ChainingContextualSubstitutionTable.Format3;
export type ChainingContextualSubstitutionTable = ChainingContextualSubstitutionTable.IFormat1 |
ChainingContextualSubstitutionTable.IFormat2 | ChainingContextualSubstitutionTable.IFormat3;
export namespace ChainingContextualSubstitutionTable {
export interface Format1 {
export interface IFormat1 {
substFormat: 1;
coverage: CoverageTable;
chainRuleSets: ChainSubRuleTable[][];
}
export interface Format2 {
export interface IFormat2 {
substFormat: 2;
coverage: CoverageTable;
backtrackClassDef: ClassDefTable;
inputClassDef: ClassDefTable;
lookaheadClassDef: ClassDefTable;
chainClassSet: (null | ChainSubClassRuleTable[])[];
chainClassSet: (null | IChainSubClassRuleTable[])[];
}
export interface Format3 {
export interface IFormat3 {
substFormat: 3;
backtrackCoverage: CoverageTable[];
inputCoverage: CoverageTable[];
lookaheadCoverage: CoverageTable[];
lookupRecords: SubstitutionLookupRecord[];
lookupRecords: ISubstitutionLookupRecord[];
}
}
export interface ReverseChainingContextualSingleSubstitutionTable {
export interface IReverseChainingContextualSingleSubstitutionTable {
substFormat: 1;
coverage: CoverageTable;
backtrackCoverage: CoverageTable[];
@@ -65,9 +65,9 @@ export interface ReverseChainingContextualSingleSubstitutionTable {
substitutes: number[];
}
export type ClassDefTable = ClassDefTable.Format2;
export type ClassDefTable = ClassDefTable.IFormat2;
export namespace ClassDefTable {
export interface Format2 {
export interface IFormat2 {
format: 2;
ranges: {
start: number;
@@ -77,36 +77,36 @@ export namespace ClassDefTable {
}
}
export interface SubstitutionLookupRecord {
export interface ISubstitutionLookupRecord {
sequenceIndex: number;
lookupListIndex: number;
}
export type ChainSubRuleTable = ChainSubClassRuleTable;
export interface ChainSubClassRuleTable {
export type ChainSubRuleTable = IChainSubClassRuleTable;
export interface IChainSubClassRuleTable {
backtrack: number[];
input: number[];
lookahead: number[];
lookupRecords: SubstitutionLookupRecord[];
lookupRecords: ISubstitutionLookupRecord[];
}
export type Lookup = Lookup.Type1 | Lookup.Type6 | Lookup.Type8;
export type Lookup = Lookup.IType1 | Lookup.IType6 | Lookup.IType8;
export namespace Lookup {
export interface Type1 {
export interface IType1 {
lookupType: 1;
lookupFlag: number;
subtables: SubstitutionTable[];
}
export interface Type6 {
export interface IType6 {
lookupType: 6;
lookupFlag: number;
subtables: ChainingContextualSubstitutionTable[];
}
export interface Type8 {
export interface IType8 {
lookupType: 8;
lookupFlag: number;
subtables: ReverseChainingContextualSingleSubstitutionTable[];
subtables: IReverseChainingContextualSingleSubstitutionTable[];
}
}
@@ -1,4 +1,4 @@
export interface SubstitutionResult {
export interface ISubstitutionResult {
index: number;
contextRange: [number, number];
}
@@ -6,7 +6,7 @@ export interface SubstitutionResult {
/**
* Information about ligatures found in a sequence of text
*/
export interface LigatureData {
export interface ILigatureData {
/**
* The list of font glyphs in the input text.
*/
@@ -25,14 +25,14 @@ export interface LigatureData {
contextRanges: [number, number][];
}
export interface Font {
export interface IFont {
/**
* Scans the provided text for font ligatures, returning an object with
* metadata about the text and any ligatures found.
*
* @param text String to search for ligatures
*/
findLigatures(text: string): LigatureData;
findLigatures(text: string): ILigatureData;
/**
* Scans the provided text for font ligatures, returning an array of ranges
@@ -43,7 +43,7 @@ export interface Font {
findLigatureRanges(text: string): [number, number][];
}
export interface Options {
export interface IOptions {
/**
* Optional size of previous results to store, measured in total number of
* characters from input strings. Defaults to no cache (0)
@@ -51,23 +51,23 @@ export interface Options {
cacheSize?: number;
}
export interface LookupTree {
export interface ILookupTree {
individual: {
[glyphId: string]: LookupTreeEntry;
[glyphId: string]: ILookupTreeEntry;
};
range: {
range: [number, number];
entry: LookupTreeEntry;
entry: ILookupTreeEntry;
}[];
}
export interface LookupTreeEntry {
lookup?: LookupResult;
forward?: LookupTree;
reverse?: LookupTree;
export interface ILookupTreeEntry {
lookup?: ILookupResult;
forward?: ILookupTree;
reverse?: ILookupTree;
}
export interface LookupResult {
export interface ILookupResult {
substitutions: (number | null)[];
length: number;
index: number;
@@ -75,12 +75,12 @@ export interface LookupResult {
contextRange: [number, number];
}
export interface FlattenedLookupTree {
[glyphId: string]: FlattenedLookupTreeEntry;
export interface IFlattenedLookupTree {
[glyphId: string]: IFlattenedLookupTreeEntry;
}
export interface FlattenedLookupTreeEntry {
lookup?: LookupResult;
forward?: FlattenedLookupTree;
reverse?: FlattenedLookupTree;
export interface IFlattenedLookupTreeEntry {
lookup?: ILookupResult;
forward?: IFlattenedLookupTree;
reverse?: IFlattenedLookupTree;
}
@@ -1,8 +1,8 @@
import { FlattenedLookupTree, LookupResult } from './types';
import { IFlattenedLookupTree, ILookupResult } from './types';
export default function walkTree(tree: FlattenedLookupTree, sequence: number[], startIndex: number, index: number): LookupResult | undefined {
export default function walkTree(tree: IFlattenedLookupTree, sequence: number[], startIndex: number, index: number): ILookupResult | undefined {
const glyphId = sequence[index];
let subtree = tree[glyphId];
const subtree = tree[glyphId];
if (!subtree) {
return undefined;
}
@@ -45,9 +45,9 @@ export default function walkTree(tree: FlattenedLookupTree, sequence: number[],
return lookup;
}
function walkReverse(tree: FlattenedLookupTree, sequence: number[], index: number): LookupResult | undefined {
function walkReverse(tree: IFlattenedLookupTree, sequence: number[], index: number): ILookupResult | undefined {
let subtree = tree[sequence[--index]];
let lookup: LookupResult | undefined = subtree && subtree.lookup;
let lookup: ILookupResult | undefined = subtree && subtree.lookup;
while (subtree) {
if (
(!lookup && subtree.lookup) ||