Skip to content

Commit

Permalink
Fix TTF GSUB table parsing and avoid OoM
Browse files Browse the repository at this point in the history
  • Loading branch information
sedwards2009 committed Jan 7, 2025
1 parent f0b5f69 commit 29a53c6
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 12 deletions.
22 changes: 15 additions & 7 deletions packages/extraterm-font-ligatures/src/processors/6-3.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { ChainingContextualSubstitutionTable, Lookup } from '../tables.js';
import { ChainingContextualSubstitutionTable, CoverageTable, Lookup } from '../tables.js';
import { LookupTree, LookupTreeEntry } from '../types.js';

import { listGlyphsByIndex } from './coverage.js';
import { processInputPosition, processLookaheadPosition, processBacktrackPosition, getInputTree,
EntryMeta } from './helper.js';
Expand Down Expand Up @@ -40,16 +39,18 @@ export function buildTree(table: ChainingContextualSubstitutionTable.Format3, lo
);
}

for (const coverage of table.lookaheadCoverage) {
if (table.lookaheadCoverage.length > 0) {
const glyphsByIndex = coverageTablesToGlyphsByIndex(table.lookaheadCoverage);
currentEntries = processLookaheadPosition(
listGlyphsByIndex(coverage).map(glyph => glyph.glyphId),
glyphsByIndex,
currentEntries
);
}

for (const coverage of table.backtrackCoverage) {
if (table.backtrackCoverage.length > 0) {
const glyphsByIndex = coverageTablesToGlyphsByIndex(table.backtrackCoverage);
currentEntries = processBacktrackPosition(
listGlyphsByIndex(coverage).map(glyph => glyph.glyphId),
glyphsByIndex,
currentEntries
);
}
Expand All @@ -69,6 +70,13 @@ export function buildTree(table: ChainingContextualSubstitutionTable.Format3, lo
};
}
}

return result;
}

function coverageTablesToGlyphsByIndex(tables: CoverageTable[]): (number | [number, number])[] {
let glyphsByIndex: (number | [number, number])[] = [];
for (const coverage of tables) {
glyphsByIndex = glyphsByIndex.concat(listGlyphsByIndex(coverage).map(glyph => glyph.glyphId));
}
return glyphsByIndex;
}
7 changes: 6 additions & 1 deletion packages/extraterm-font-ligatures/src/processors/coverage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ export default function getCoverageGlyphIndex(table: CoverageTable, glyphId: num
}
}

export function listGlyphsByIndex(table: CoverageTable): { glyphId: number | [number, number]; index: number; }[] {
export interface GlyphIdIndex {
glyphId: number | [number, number];
index: number;
}

export function listGlyphsByIndex(table: CoverageTable): GlyphIdIndex[] {
switch (table.format) {
case 1:
return table.glyphs.map((glyphId, index) => ({ glyphId, index }));
Expand Down
41 changes: 37 additions & 4 deletions packages/extraterm-font-ligatures/src/processors/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function processInputPosition(
individual: new Map<number, LookupTreeEntry>(),
range: []
};
for (const glyph of glyphs) {
for (const glyph of dedupGlyphs(glyphs)) {
nextEntries.push(...getInputTree(
currentEntry.entry.forward,
lookupRecords,
Expand All @@ -44,7 +44,7 @@ export function processLookaheadPosition(
): EntryMeta[] {
const nextEntries: EntryMeta[] = [];
for (const currentEntry of currentEntries) {
for (const glyph of glyphs) {
for (const glyph of dedupGlyphs(glyphs)) {
const entry: LookupTreeEntry = {};
if (!currentEntry.entry.forward) {
currentEntry.entry.forward = {
Expand Down Expand Up @@ -76,8 +76,9 @@ export function processBacktrackPosition(
currentEntries: EntryMeta[]
): EntryMeta[] {
const nextEntries: EntryMeta[] = [];

for (const currentEntry of currentEntries) {
for (const glyph of glyphs) {
for (const glyph of dedupGlyphs(glyphs)) {
const entry: LookupTreeEntry = {};
if (!currentEntry.entry.reverse) {
currentEntry.entry.reverse = {
Expand All @@ -100,10 +101,42 @@ export function processBacktrackPosition(
}
}
}

return nextEntries;
}

function dedupGlyphs(glyphs: (number | [number, number])[]): (number | [number, number])[] {
if (glyphs.length < 2) {
return glyphs;
}

const result: (number | [number, number])[] = [];
const singleGlyphs = new Set<number>();
const pairGlyphs = new Set<[number, number]>();

for (const glyph of glyphs) {
if (Array.isArray(glyph)) {
let found = false;
for (const pair of pairGlyphs) {
if (pair[0] === glyph[0] && pair[1] === glyph[1]) {
found = true;
break;
}
}
if (!found) {
result.push(glyph);
pairGlyphs.add(glyph);
}

} else {
if (!singleGlyphs.has(glyph)) {
result.push(glyph);
singleGlyphs.add(glyph);
}
}
}
return result;
}

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; }[] = [];
if (!Array.isArray(glyphId)) {
Expand Down

0 comments on commit 29a53c6

Please sign in to comment.