From ec03109c39cde7ef8f33cee273913eac72726bed Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 3 Jan 2026 09:25:39 -0800 Subject: [PATCH] 2 space indent --- .../src/fontLigatures/flatten.ts | 42 +- .../src/fontLigatures/index.ts | 420 +++++++------- .../src/fontLigatures/merge.ts | 520 +++++++++--------- .../src/fontLigatures/mergeRange.ts | 100 ++-- .../src/fontLigatures/processors/6-1.ts | 122 ++-- .../src/fontLigatures/processors/6-2.ts | 138 ++--- .../src/fontLigatures/processors/6-3.ts | 104 ++-- .../src/fontLigatures/processors/8-1.ts | 100 ++-- .../src/fontLigatures/processors/classDef.ts | 112 ++-- .../src/fontLigatures/processors/coverage.ts | 58 +- .../src/fontLigatures/processors/helper.ts | 238 ++++---- .../fontLigatures/processors/substitution.ts | 76 +-- .../src/fontLigatures/tables.ts | 154 +++--- .../src/fontLigatures/types.ts | 108 ++-- .../addon-ligatures/src/fontLigatures/walk.ts | 100 ++-- 15 files changed, 1196 insertions(+), 1196 deletions(-) diff --git a/addons/addon-ligatures/src/fontLigatures/flatten.ts b/addons/addon-ligatures/src/fontLigatures/flatten.ts index 38e99c3a..f911b986 100644 --- a/addons/addon-ligatures/src/fontLigatures/flatten.ts +++ b/addons/addon-ligatures/src/fontLigatures/flatten.ts @@ -1,35 +1,35 @@ import { LookupTree, FlattenedLookupTree, LookupTreeEntry, FlattenedLookupTreeEntry } from './types'; export default function flatten(tree: LookupTree): FlattenedLookupTree { - const result: FlattenedLookupTree = {}; - for (const [glyphId, entry] of Object.entries(tree.individual)) { - result[glyphId] = flattenEntry(entry); - } + const result: FlattenedLookupTree = {}; + for (const [glyphId, entry] of Object.entries(tree.individual)) { + result[glyphId] = flattenEntry(entry); + } - for (const { range, entry } of tree.range) { - const flattened = flattenEntry(entry); - for (let glyphId = range[0]; glyphId < range[1]; glyphId++) { - result[glyphId] = flattened; - } + for (const { range, entry } of tree.range) { + const flattened = flattenEntry(entry); + for (let glyphId = range[0]; glyphId < range[1]; glyphId++) { + result[glyphId] = flattened; } + } - return result; + return result; } function flattenEntry(entry: LookupTreeEntry): FlattenedLookupTreeEntry { - const result: FlattenedLookupTreeEntry = {}; + const result: FlattenedLookupTreeEntry = {}; - if (entry.forward) { - result.forward = flatten(entry.forward); - } + if (entry.forward) { + result.forward = flatten(entry.forward); + } - if (entry.reverse) { - result.reverse = flatten(entry.reverse); - } + if (entry.reverse) { + result.reverse = flatten(entry.reverse); + } - if (entry.lookup) { - result.lookup = entry.lookup; - } + if (entry.lookup) { + result.lookup = entry.lookup; + } - return result; + return result; } diff --git a/addons/addon-ligatures/src/fontLigatures/index.ts b/addons/addon-ligatures/src/fontLigatures/index.ts index e91eea92..f15bd32a 100644 --- a/addons/addon-ligatures/src/fontLigatures/index.ts +++ b/addons/addon-ligatures/src/fontLigatures/index.ts @@ -13,236 +13,236 @@ import buildTreeGsubType8Format1 from './processors/8-1'; import flatten from './flatten'; class FontImpl implements Font { - private _font: opentype.Font; - private _lookupTrees: { tree: FlattenedLookupTree; processForward: boolean; }[] = []; - private _glyphLookups: { [glyphId: string]: number[] } = {}; - private _cache?: LRUCache; + private _font: opentype.Font; + private _lookupTrees: { tree: FlattenedLookupTree; processForward: boolean; }[] = []; + private _glyphLookups: { [glyphId: string]: number[] } = {}; + private _cache?: LRUCache; - constructor(font: opentype.Font, options: Required) { - this._font = font; + constructor(font: opentype.Font, options: Required) { + 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 - }); - } - - const caltFeatures = this._font.tables.gsub && this._font.tables.gsub.features.filter((f: { tag: string }) => f.tag === 'calt') || []; - const lookupIndices: number[] = caltFeatures - .reduce((acc: number[], val: { feature: { lookupListIndexes: number[] } }) => [...acc, ...val.feature.lookupListIndexes], []); - - const allLookups = this._font.tables.gsub && this._font.tables.gsub.lookups || []; - const lookupGroups = allLookups.filter((l: unknown, i: number) => lookupIndices.some(idx => idx === i)); - - for (const [index, lookup] of lookupGroups.entries()) { - const trees: LookupTree[] = []; - switch (lookup.lookupType) { - case 6: - for (const [index, table] of lookup.subtables.entries()) { - switch (table.substFormat) { - case 1: - trees.push(buildTreeGsubType6Format1(table, allLookups, index)); - break; - case 2: - trees.push(buildTreeGsubType6Format2(table, allLookups, index)); - break; - case 3: - trees.push(buildTreeGsubType6Format3(table, allLookups, index)); - break; - } - } - break; - case 8: - for (const [index, table] of lookup.subtables.entries()) { - trees.push(buildTreeGsubType8Format1(table, index)); - } - break; - } - - const tree = flatten(mergeTrees(trees)); - - this._lookupTrees.push({ - tree, - processForward: lookup.lookupType !== 8 - }); - - for (const glyphId of Object.keys(tree)) { - if (!this._glyphLookups[glyphId]) { - this._glyphLookups[glyphId] = []; - } - - this._glyphLookups[glyphId].push(index); - } - } + if (options.cacheSize > 0) { + this._cache = new LRUCache({ + max: options.cacheSize, + length: ((val: LigatureData | [number, number][], key: string) => key.length) as any + }); } - findLigatures(text: string): LigatureData { - const cached = this._cache && this._cache.get(text); - if (cached && !Array.isArray(cached)) { - return cached; + const caltFeatures = this._font.tables.gsub && this._font.tables.gsub.features.filter((f: { tag: string }) => f.tag === 'calt') || []; + const lookupIndices: number[] = caltFeatures + .reduce((acc: number[], val: { feature: { lookupListIndexes: number[] } }) => [...acc, ...val.feature.lookupListIndexes], []); + + const allLookups = this._font.tables.gsub && this._font.tables.gsub.lookups || []; + const lookupGroups = allLookups.filter((l: unknown, i: number) => lookupIndices.some(idx => idx === i)); + + for (const [index, lookup] of lookupGroups.entries()) { + const trees: LookupTree[] = []; + switch (lookup.lookupType) { + case 6: + for (const [index, table] of lookup.subtables.entries()) { + switch (table.substFormat) { + case 1: + trees.push(buildTreeGsubType6Format1(table, allLookups, index)); + break; + case 2: + trees.push(buildTreeGsubType6Format2(table, allLookups, index)); + break; + case 3: + trees.push(buildTreeGsubType6Format3(table, allLookups, index)); + break; + } + } + break; + case 8: + for (const [index, table] of lookup.subtables.entries()) { + trees.push(buildTreeGsubType8Format1(table, index)); + } + break; + } + + const tree = flatten(mergeTrees(trees)); + + this._lookupTrees.push({ + tree, + processForward: lookup.lookupType !== 8 + }); + + for (const glyphId of Object.keys(tree)) { + if (!this._glyphLookups[glyphId]) { + this._glyphLookups[glyphId] = []; } - const glyphIds: number[] = []; - for (const char of text) { - glyphIds.push(this._font.charToGlyphIndex(char)); - } + this._glyphLookups[glyphId].push(index); + } + } + } - // If there are no lookup groups, there's no point looking for - // replacements. This gives us a minor performance boost for fonts with - // no ligatures - if (this._lookupTrees.length === 0) { - return { - inputGlyphs: glyphIds, - outputGlyphs: glyphIds, - contextRanges: [] - }; - } - - const result = this._findInternal(glyphIds.slice()); - const finalResult: LigatureData = { - inputGlyphs: glyphIds, - outputGlyphs: result.sequence, - contextRanges: result.ranges - }; - if (this._cache) { - this._cache.set(text, finalResult); - } - - return finalResult; + findLigatures(text: string): LigatureData { + const cached = this._cache && this._cache.get(text); + if (cached && !Array.isArray(cached)) { + return cached; } - findLigatureRanges(text: string): [number, number][] { - // Short circuit the process if there are no possible ligatures in the - // font - if (this._lookupTrees.length === 0) { - return []; - } - - const cached = this._cache && this._cache.get(text); - if (cached) { - return Array.isArray(cached) ? cached : cached.contextRanges; - } - - const glyphIds: number[] = []; - for (const char of text) { - glyphIds.push(this._font.charToGlyphIndex(char)); - } - - const result = this._findInternal(glyphIds); - if (this._cache) { - this._cache.set(text, result.ranges); - } - - return result.ranges; + const glyphIds: number[] = []; + for (const char of text) { + glyphIds.push(this._font.charToGlyphIndex(char)); } - private _findInternal(sequence: number[]): { sequence: number[]; ranges: [number, number][]; } { - const ranges: [number, number][] = []; + // If there are no lookup groups, there's no point looking for + // replacements. This gives us a minor performance boost for fonts with + // no ligatures + if (this._lookupTrees.length === 0) { + return { + inputGlyphs: glyphIds, + outputGlyphs: glyphIds, + contextRanges: [] + }; + } - let nextLookup = this._getNextLookup(sequence, 0); - while (nextLookup.index !== null) { - const lookup = this._lookupTrees[nextLookup.index]; - if (lookup.processForward) { - let lastGlyphIndex = nextLookup.last; - for (let i = nextLookup.first; i < lastGlyphIndex; i++) { - const result = walkTree(lookup.tree, sequence, i, i); - if (result) { - for (let j = 0; j < result.substitutions.length; j++) { - const sub = result.substitutions[j]; - if (sub !== null) { - sequence[i + j] = sub; - } - } + const result = this._findInternal(glyphIds.slice()); + const finalResult: LigatureData = { + inputGlyphs: glyphIds, + outputGlyphs: result.sequence, + contextRanges: result.ranges + }; + if (this._cache) { + this._cache.set(text, finalResult); + } - mergeRange( - ranges, - result.contextRange[0] + i, - result.contextRange[1] + i - ); + return finalResult; + } - // Substitutions can end up extending the search range - if (i + result.length >= lastGlyphIndex) { - lastGlyphIndex = i + result.length + 1; - } + findLigatureRanges(text: string): [number, number][] { + // Short circuit the process if there are no possible ligatures in the + // font + if (this._lookupTrees.length === 0) { + return []; + } - i += result.length - 1; - } - } - } else { - // We don't need to do the lastGlyphIndex tracking here because - // reverse processing isn't allowed to replace more than one - // character at a time. - for (let i = nextLookup.last - 1; i >= nextLookup.first; i--) { - const result = walkTree(lookup.tree, sequence, i, i); - if (result) { - for (let j = 0; j < result.substitutions.length; j++) { - const sub = result.substitutions[j]; - if (sub !== null) { - sequence[i + j] = sub; - } - } + const cached = this._cache && this._cache.get(text); + if (cached) { + return Array.isArray(cached) ? cached : cached.contextRanges; + } - mergeRange( - ranges, - result.contextRange[0] + i, - result.contextRange[1] + i - ); + const glyphIds: number[] = []; + for (const char of text) { + glyphIds.push(this._font.charToGlyphIndex(char)); + } - i -= result.length - 1; - } - } + const result = this._findInternal(glyphIds); + if (this._cache) { + this._cache.set(text, result.ranges); + } + + return result.ranges; + } + + private _findInternal(sequence: number[]): { sequence: number[]; ranges: [number, number][]; } { + const ranges: [number, number][] = []; + + let nextLookup = this._getNextLookup(sequence, 0); + while (nextLookup.index !== null) { + const lookup = this._lookupTrees[nextLookup.index]; + if (lookup.processForward) { + let lastGlyphIndex = nextLookup.last; + for (let i = nextLookup.first; i < lastGlyphIndex; i++) { + const result = walkTree(lookup.tree, sequence, i, i); + if (result) { + for (let j = 0; j < result.substitutions.length; j++) { + const sub = result.substitutions[j]; + if (sub !== null) { + sequence[i + j] = sub; + } } - nextLookup = this._getNextLookup(sequence, nextLookup.index + 1); - } + mergeRange( + ranges, + result.contextRange[0] + i, + result.contextRange[1] + i + ); - return { sequence, ranges }; - } - - /** - * Returns the lookup and glyph range for the first lookup that might - * contain a match. - * - * @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; } = { - index: null, - first: Infinity, - last: -1 - }; - - // Loop through each glyph and find the first valid lookup for it - for (let i = 0; i < sequence.length; i++) { - const lookups = this._glyphLookups[sequence[i]]; - if (!lookups) { - continue; + // Substitutions can end up extending the search range + if (i + result.length >= lastGlyphIndex) { + lastGlyphIndex = i + result.length + 1; } - for (let j = 0; j < lookups.length; j++) { - const lookupIndex = lookups[j]; - if (lookupIndex >= start) { - // Update the lookup information if it's the one we're - // storing or earlier than it. - if (result.index === null || lookupIndex <= result.index) { - result.index = lookupIndex; - - if (result.first > i) { - result.first = i; - } - - result.last = i + 1; - } - - break; - } - } + i += result.length - 1; + } } + } else { + // We don't need to do the lastGlyphIndex tracking here because + // reverse processing isn't allowed to replace more than one + // character at a time. + for (let i = nextLookup.last - 1; i >= nextLookup.first; i--) { + const result = walkTree(lookup.tree, sequence, i, i); + if (result) { + for (let j = 0; j < result.substitutions.length; j++) { + const sub = result.substitutions[j]; + if (sub !== null) { + sequence[i + j] = sub; + } + } - return result; + mergeRange( + ranges, + result.contextRange[0] + i, + result.contextRange[1] + i + ); + + i -= result.length - 1; + } + } + } + + nextLookup = this._getNextLookup(sequence, nextLookup.index + 1); } + + return { sequence, ranges }; + } + + /** + * Returns the lookup and glyph range for the first lookup that might + * contain a match. + * + * @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; } = { + index: null, + first: Infinity, + last: -1 + }; + + // Loop through each glyph and find the first valid lookup for it + for (let i = 0; i < sequence.length; i++) { + const lookups = this._glyphLookups[sequence[i]]; + if (!lookups) { + continue; + } + + for (let j = 0; j < lookups.length; j++) { + const lookupIndex = lookups[j]; + if (lookupIndex >= start) { + // Update the lookup information if it's the one we're + // storing or earlier than it. + if (result.index === null || lookupIndex <= result.index) { + result.index = lookupIndex; + + if (result.first > i) { + result.first = i; + } + + result.last = i + 1; + } + + break; + } + } + } + + return result; + } } /** @@ -252,11 +252,11 @@ class FontImpl implements Font { * @param buffer ArrayBuffer of the font to load */ export function loadBuffer(buffer: ArrayBuffer, options?: Options): Font { - const font = opentype.parse(buffer); - return new FontImpl(font, { - cacheSize: 0, - ...options - }); + const font = opentype.parse(buffer); + return new FontImpl(font, { + cacheSize: 0, + ...options + }); } export { Font, LigatureData, Options }; diff --git a/addons/addon-ligatures/src/fontLigatures/merge.ts b/addons/addon-ligatures/src/fontLigatures/merge.ts index d0b1f23e..13c87733 100644 --- a/addons/addon-ligatures/src/fontLigatures/merge.ts +++ b/addons/addon-ligatures/src/fontLigatures/merge.ts @@ -9,16 +9,16 @@ import { LookupTree, LookupTreeEntry } from './types'; * over those in later trees when there is a choice. */ export default function mergeTrees(trees: LookupTree[]): LookupTree { - const result: LookupTree = { - individual: {}, - range: [] - }; + const result: LookupTree = { + individual: {}, + range: [] + }; - for (const tree of trees) { - mergeSubtree(result, tree); - } + for (const tree of trees) { + mergeSubtree(result, tree); + } - return result; + return result; } /** @@ -28,162 +28,162 @@ export default function mergeTrees(trees: LookupTree[]): LookupTree { * @param mergeTree The tree to be merged into the mainTree */ function mergeSubtree(mainTree: LookupTree, mergeTree: LookupTree): 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 - // individual and range values, so if we match an invididual, there - // must not be a range - if (mainTree.individual[glyphId]) { - mergeTreeEntry(mainTree.individual[glyphId], value); - } else { - let matched = false; - for (const [index, { range, entry }] of mainTree.range.entries()) { - const overlap = getIndividualOverlap(Number(glyphId), range); + // 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 + // individual and range values, so if we match an invididual, there + // must not be a range + if (mainTree.individual[glyphId]) { + mergeTreeEntry(mainTree.individual[glyphId], value); + } else { + let matched = false; + for (const [index, { range, entry }] of mainTree.range.entries()) { + const overlap = getIndividualOverlap(Number(glyphId), range); - // Don't overlap - if (overlap.both === null) { - continue; - } - - matched = true; - - // If they overlap, we have to split the range and then - // merge the overlap - mainTree.individual[glyphId] = value; - mergeTreeEntry(mainTree.individual[glyphId], cloneEntry(entry)); - - // When there's an overlap, we also have to fix up the range - // that we had already processed - mainTree.range.splice(index, 1); - for (const glyph of overlap.second) { - if (Array.isArray(glyph)) { - mainTree.range.push({ - range: glyph, - entry: cloneEntry(entry) - }); - } else { - mainTree.individual[glyph] = cloneEntry(entry); - } - } - } - - if (!matched) { - mainTree.individual[glyphId] = value; - } + // Don't overlap + if (overlap.both === null) { + continue; } + + matched = true; + + // If they overlap, we have to split the range and then + // merge the overlap + mainTree.individual[glyphId] = value; + mergeTreeEntry(mainTree.individual[glyphId], cloneEntry(entry)); + + // When there's an overlap, we also have to fix up the range + // that we had already processed + mainTree.range.splice(index, 1); + for (const glyph of overlap.second) { + if (Array.isArray(glyph)) { + mainTree.range.push({ + range: glyph, + entry: cloneEntry(entry) + }); + } else { + mainTree.individual[glyph] = cloneEntry(entry); + } + } + } + + if (!matched) { + mainTree.individual[glyphId] = value; + } } + } - for (const { range, entry } of mergeTree.range) { - // Ranges are more complicated, because they can overlap with - // multiple things, individual and range alike. We start by - // eliminating ranges that are already present in another range - let remainingRanges: (number | [number, number])[] = [range]; + for (const { range, entry } of mergeTree.range) { + // Ranges are more complicated, because they can overlap with + // multiple things, individual and range alike. We start by + // eliminating ranges that are already present in another range + let remainingRanges: (number | [number, number])[] = [range]; - for (let index = 0; index < mainTree.range.length; index++) { - const { range, entry: resultEntry } = mainTree.range[index]; - for (const [remainingIndex, remainingRange] of remainingRanges.entries()) { - if (Array.isArray(remainingRange)) { - const overlap = getRangeOverlap(remainingRange, range); - if (overlap.both === null) { - continue; - } + for (let index = 0; index < mainTree.range.length; index++) { + const { range, entry: resultEntry } = mainTree.range[index]; + for (const [remainingIndex, remainingRange] of remainingRanges.entries()) { + if (Array.isArray(remainingRange)) { + const overlap = getRangeOverlap(remainingRange, range); + if (overlap.both === null) { + continue; + } - mainTree.range.splice(index, 1); - index--; + mainTree.range.splice(index, 1); + index--; - const entryToMerge: LookupTreeEntry = cloneEntry(resultEntry); - if (Array.isArray(overlap.both)) { - mainTree.range.push({ - range: overlap.both, - entry: entryToMerge - }); - } else { - mainTree.individual[overlap.both] = entryToMerge; - } + const entryToMerge: LookupTreeEntry = cloneEntry(resultEntry); + if (Array.isArray(overlap.both)) { + mainTree.range.push({ + range: overlap.both, + entry: entryToMerge + }); + } else { + mainTree.individual[overlap.both] = entryToMerge; + } - mergeTreeEntry(entryToMerge, cloneEntry(entry)); + mergeTreeEntry(entryToMerge, cloneEntry(entry)); - for (const second of overlap.second) { - if (Array.isArray(second)) { - mainTree.range.push({ - range: second, - entry: cloneEntry(resultEntry) - }); - } else { - mainTree.individual[second] = cloneEntry(resultEntry); - } - } - - remainingRanges = overlap.first; - } else { - const overlap = getIndividualOverlap(remainingRange, range); - if (overlap.both === null) { - continue; - } - - // If they overlap, we have to split the range and then - // merge the overlap - mainTree.individual[remainingRange] = cloneEntry(entry); - mergeTreeEntry(mainTree.individual[remainingRange], cloneEntry(resultEntry)); - - // When there's an overlap, we also have to fix up the range - // that we had already processed - mainTree.range.splice(index, 1); - index--; - - for (const glyph of overlap.second) { - if (Array.isArray(glyph)) { - mainTree.range.push({ - range: glyph, - entry: cloneEntry(resultEntry) - }); - } else { - mainTree.individual[glyph] = cloneEntry(resultEntry); - } - } - - remainingRanges.splice(remainingIndex, 1, ...overlap.first); - break; - } - } - } - - // Next, we run the same against any individual glyphs - for (const glyphId of Object.keys(mainTree.individual)) { - for (const [remainingIndex, remainingRange] of remainingRanges.entries()) { - if (Array.isArray(remainingRange)) { - const overlap = getIndividualOverlap(Number(glyphId), remainingRange); - if (overlap.both === null) { - continue; - } - - // If they overlap, we have to merge the overlap - mergeTreeEntry(mainTree.individual[glyphId], cloneEntry(entry)); - - // Update the remaining ranges - remainingRanges.splice(remainingIndex, 1, ...overlap.second); - break; - } else { - if (Number(glyphId) === remainingRange) { - mergeTreeEntry(mainTree.individual[glyphId], cloneEntry(entry)); - break; - } - } - } - } - - // Any remaining ranges should just be added directly - for (const remainingRange of remainingRanges) { - if (Array.isArray(remainingRange)) { - mainTree.range.push({ - range: remainingRange, - entry: cloneEntry(entry) - }); + for (const second of overlap.second) { + if (Array.isArray(second)) { + mainTree.range.push({ + range: second, + entry: cloneEntry(resultEntry) + }); } else { - mainTree.individual[remainingRange] = cloneEntry(entry); + mainTree.individual[second] = cloneEntry(resultEntry); } + } + + remainingRanges = overlap.first; + } else { + const overlap = getIndividualOverlap(remainingRange, range); + if (overlap.both === null) { + continue; + } + + // If they overlap, we have to split the range and then + // merge the overlap + mainTree.individual[remainingRange] = cloneEntry(entry); + mergeTreeEntry(mainTree.individual[remainingRange], cloneEntry(resultEntry)); + + // When there's an overlap, we also have to fix up the range + // that we had already processed + mainTree.range.splice(index, 1); + index--; + + for (const glyph of overlap.second) { + if (Array.isArray(glyph)) { + mainTree.range.push({ + range: glyph, + entry: cloneEntry(resultEntry) + }); + } else { + mainTree.individual[glyph] = cloneEntry(resultEntry); + } + } + + remainingRanges.splice(remainingIndex, 1, ...overlap.first); + break; } + } } + + // Next, we run the same against any individual glyphs + for (const glyphId of Object.keys(mainTree.individual)) { + for (const [remainingIndex, remainingRange] of remainingRanges.entries()) { + if (Array.isArray(remainingRange)) { + const overlap = getIndividualOverlap(Number(glyphId), remainingRange); + if (overlap.both === null) { + continue; + } + + // If they overlap, we have to merge the overlap + mergeTreeEntry(mainTree.individual[glyphId], cloneEntry(entry)); + + // Update the remaining ranges + remainingRanges.splice(remainingIndex, 1, ...overlap.second); + break; + } else { + if (Number(glyphId) === remainingRange) { + mergeTreeEntry(mainTree.individual[glyphId], cloneEntry(entry)); + break; + } + } + } + } + + // Any remaining ranges should just be added directly + for (const remainingRange of remainingRanges) { + if (Array.isArray(remainingRange)) { + mainTree.range.push({ + range: remainingRange, + entry: cloneEntry(entry) + }); + } else { + mainTree.individual[remainingRange] = cloneEntry(entry); + } + } + } } /** @@ -193,37 +193,37 @@ function mergeSubtree(mainTree: LookupTree, mergeTree: LookupTree): void { * @param mergeTree The entry to merge into the mainTree */ function mergeTreeEntry(mainTree: LookupTreeEntry, mergeTree: LookupTreeEntry): void { - if ( - mergeTree.lookup && ( - !mainTree.lookup || - mainTree.lookup.index > mergeTree.lookup.index || - (mainTree.lookup.index === mergeTree.lookup.index && mainTree.lookup.subIndex > mergeTree.lookup.subIndex) - ) - ) { - mainTree.lookup = mergeTree.lookup; - } + if ( + mergeTree.lookup && ( + !mainTree.lookup || + mainTree.lookup.index > mergeTree.lookup.index || + (mainTree.lookup.index === mergeTree.lookup.index && mainTree.lookup.subIndex > mergeTree.lookup.subIndex) + ) + ) { + mainTree.lookup = mergeTree.lookup; + } - if (mergeTree.forward) { - if (!mainTree.forward) { - mainTree.forward = mergeTree.forward; - } else { - mergeSubtree(mainTree.forward, mergeTree.forward); - } + if (mergeTree.forward) { + if (!mainTree.forward) { + mainTree.forward = mergeTree.forward; + } else { + mergeSubtree(mainTree.forward, mergeTree.forward); } + } - if (mergeTree.reverse) { - if (!mainTree.reverse) { - mainTree.reverse = mergeTree.reverse; - } else { - mergeSubtree(mainTree.reverse, mergeTree.reverse); - } + if (mergeTree.reverse) { + if (!mainTree.reverse) { + mainTree.reverse = mergeTree.reverse; + } else { + mergeSubtree(mainTree.reverse, mergeTree.reverse); } + } } interface Overlap { - first: (number | [number, number])[]; - second: (number | [number, number])[]; - both: number | [number, number] | null; + first: (number | [number, number])[]; + second: (number | [number, number])[]; + both: number | [number, number] | null; } /** @@ -234,42 +234,42 @@ interface Overlap { * @param second Second range */ function getRangeOverlap(first: [number, number], second: [number, number]): Overlap { - const result: Overlap = { - first: [], - second: [], - both: null - }; + const result: Overlap = { + first: [], + second: [], + both: null + }; - // Both - if (first[0] < second[1] && second[0] < first[1]) { - const start = Math.max(first[0], second[0]); - const end = Math.min(first[1], second[1]); - result.both = rangeOrIndividual(start, end); - } + // Both + if (first[0] < second[1] && second[0] < first[1]) { + const start = Math.max(first[0], second[0]); + const end = Math.min(first[1], second[1]); + result.both = rangeOrIndividual(start, end); + } - // Before - if (first[0] < second[0]) { - const start = first[0]; - const end = Math.min(second[0], first[1]); - result.first.push(rangeOrIndividual(start, end)); - } else if (second[0] < first[0]) { - const start = second[0]; - const end = Math.min(second[1], first[0]); - result.second.push(rangeOrIndividual(start, end)); - } + // Before + if (first[0] < second[0]) { + const start = first[0]; + const end = Math.min(second[0], first[1]); + result.first.push(rangeOrIndividual(start, end)); + } else if (second[0] < first[0]) { + const start = second[0]; + const end = Math.min(second[1], first[0]); + result.second.push(rangeOrIndividual(start, end)); + } - // After - if (first[1] > second[1]) { - const start = Math.max(first[0], second[1]); - const end = first[1]; - result.first.push(rangeOrIndividual(start, end)); - } else if (second[1] > first[1]) { - const start = Math.max(first[1], second[0]); - const end = second[1]; - result.second.push(rangeOrIndividual(start, end)); - } + // After + if (first[1] > second[1]) { + const start = Math.max(first[0], second[1]); + const end = first[1]; + result.first.push(rangeOrIndividual(start, end)); + } else if (second[1] > first[1]) { + const start = Math.max(first[1], second[0]); + const end = second[1]; + result.second.push(rangeOrIndividual(start, end)); + } - return result; + return result; } /** @@ -281,30 +281,30 @@ function getRangeOverlap(first: [number, number], second: [number, number]): Ove * @param second Range */ function getIndividualOverlap(first: number, second: [number, number]): Overlap { - // Disjoint - if (first < second[0] || first > second[1]) { - return { - first: [first], - second: [second], - both: null - }; - } - - const result: Overlap = { - first: [], - second: [], - both: first + // Disjoint + if (first < second[0] || first > second[1]) { + return { + first: [first], + second: [second], + both: null }; + } - if (second[0] < first) { - result.second.push(rangeOrIndividual(second[0], first)); - } + const result: Overlap = { + first: [], + second: [], + both: first + }; - if (second[1] > first) { - result.second.push(rangeOrIndividual(first + 1, second[1])); - } + if (second[0] < first) { + result.second.push(rangeOrIndividual(second[0], first)); + } - return result; + if (second[1] > first) { + result.second.push(rangeOrIndividual(first + 1, second[1])); + } + + return result; } /** @@ -315,11 +315,11 @@ function getIndividualOverlap(first: number, second: [number, number]): Overlap * @param end End of the range (exclusive) */ function rangeOrIndividual(start: number, end: number): number | [number, number] { - if (end - start === 1) { - return start; - } else { - return [start, end]; - } + if (end - start === 1) { + return start; + } else { + return [start, end]; + } } /** @@ -328,27 +328,27 @@ function rangeOrIndividual(start: number, end: number): number | [number, number * @param entry Lookup tree entry to clone */ function cloneEntry(entry: LookupTreeEntry): LookupTreeEntry { - const result: LookupTreeEntry = {}; + const result: LookupTreeEntry = {}; - if (entry.forward) { - result.forward = cloneTree(entry.forward); - } + if (entry.forward) { + result.forward = cloneTree(entry.forward); + } - if (entry.reverse) { - result.reverse = cloneTree(entry.reverse); - } + if (entry.reverse) { + result.reverse = cloneTree(entry.reverse); + } - if (entry.lookup) { - result.lookup = { - contextRange: entry.lookup.contextRange.slice() as [number, number], - index: entry.lookup.index, - length: entry.lookup.length, - subIndex: entry.lookup.subIndex, - substitutions: entry.lookup.substitutions.slice() - }; - } + if (entry.lookup) { + result.lookup = { + contextRange: entry.lookup.contextRange.slice() as [number, number], + index: entry.lookup.index, + length: entry.lookup.length, + subIndex: entry.lookup.subIndex, + substitutions: entry.lookup.substitutions.slice() + }; + } - return result; + return result; } /** @@ -357,16 +357,16 @@ function cloneEntry(entry: LookupTreeEntry): LookupTreeEntry { * @param tree Lookup tree to clone */ function cloneTree(tree: LookupTree): LookupTree { - const individual: { [glyphId: string]: LookupTreeEntry; } = {}; - for (const [glyphId, entry] of Object.entries(tree.individual)) { - individual[glyphId] = cloneEntry(entry); - } + const individual: { [glyphId: string]: LookupTreeEntry; } = {}; + for (const [glyphId, entry] of Object.entries(tree.individual)) { + individual[glyphId] = cloneEntry(entry); + } - return { - individual, - range: tree.range.map(({ range, entry }) => ({ - range: range.slice() as [number, number], - entry: cloneEntry(entry) - })) - }; + return { + individual, + range: tree.range.map(({ range, entry }) => ({ + range: range.slice() as [number, number], + entry: cloneEntry(entry) + })) + }; } diff --git a/addons/addon-ligatures/src/fontLigatures/mergeRange.ts b/addons/addon-ligatures/src/fontLigatures/mergeRange.ts index 7c872a8a..2b9ac6b7 100644 --- a/addons/addon-ligatures/src/fontLigatures/mergeRange.ts +++ b/addons/addon-ligatures/src/fontLigatures/mergeRange.ts @@ -8,57 +8,57 @@ * @param newRangeEnd End position of range to merge, exclusive */ export default function mergeRange(ranges: [number, number][], newRangeStart: number, newRangeEnd: number): [number, number][] { - let inRange = false; - for (let i = 0; i < ranges.length; i++) { - const range = ranges[i]; - if (!inRange) { - if (newRangeEnd <= range[0]) { - // Case 1: New range is before the search range - ranges.splice(i, 0, [newRangeStart, newRangeEnd]); - return ranges; - } else 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]) { - // Case 3: New range either wholly contains the search range - // or overlaps with the end of it - range[0] = Math.min(newRangeStart, range[0]); - inRange = true; - } else { - // Case 4: New range starts after the search range - continue; - } - } else { - if (newRangeEnd <= range[0]) { - // Case 5: New range extends from previous range but doesn't - // reach the current one - ranges[i - 1][1] = newRangeEnd; - return ranges; - } else 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--; - } - } - } - - if (inRange) { - // Case 8: New range extends past the last existing range - ranges[ranges.length - 1][1] = newRangeEnd; + let inRange = false; + for (let i = 0; i < ranges.length; i++) { + const range = ranges[i]; + if (!inRange) { + if (newRangeEnd <= range[0]) { + // Case 1: New range is before the search range + ranges.splice(i, 0, [newRangeStart, newRangeEnd]); + return ranges; + } else 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]) { + // Case 3: New range either wholly contains the search range + // or overlaps with the end of it + range[0] = Math.min(newRangeStart, range[0]); + inRange = true; + } else { + // Case 4: New range starts after the search range + continue; + } } else { - // Case 9: New range starts after the last existing range - ranges.push([newRangeStart, newRangeEnd]); + if (newRangeEnd <= range[0]) { + // Case 5: New range extends from previous range but doesn't + // reach the current one + ranges[i - 1][1] = newRangeEnd; + return ranges; + } else 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--; + } } + } - return ranges; + if (inRange) { + // Case 8: New range extends past the last existing range + ranges[ranges.length - 1][1] = newRangeEnd; + } else { + // Case 9: New range starts after the last existing range + ranges.push([newRangeStart, newRangeEnd]); + } + + return ranges; } diff --git a/addons/addon-ligatures/src/fontLigatures/processors/6-1.ts b/addons/addon-ligatures/src/fontLigatures/processors/6-1.ts index 2f0dad18..dc2e8917 100644 --- a/addons/addon-ligatures/src/fontLigatures/processors/6-1.ts +++ b/addons/addon-ligatures/src/fontLigatures/processors/6-1.ts @@ -13,70 +13,70 @@ import { processInputPosition, processLookaheadPosition, processBacktrackPositio * @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 = { - individual: {}, - range: [] - }; + const result: LookupTree = { + individual: {}, + range: [] + }; - const firstGlyphs = listGlyphsByIndex(table.coverage); + const firstGlyphs = listGlyphsByIndex(table.coverage); - for (const { glyphId, index } of firstGlyphs) { - const chainRuleSet = table.chainRuleSets[index]; + for (const { glyphId, index } of firstGlyphs) { + const chainRuleSet = table.chainRuleSets[index]; - // If the chain rule set is null there's nothing to do with this table. - if (!chainRuleSet) { - continue; - } - - for (const [subIndex, subTable] of chainRuleSet.entries()) { - let currentEntries: EntryMeta[] = getInputTree( - result, - subTable.lookupRecords, - lookups, - 0, - glyphId - ).map(({ entry, substitution }) => ({ entry, substitutions: [substitution] })); - - // We walk forward, then backward - for (const [index, glyph] of subTable.input.entries()) { - currentEntries = processInputPosition( - [glyph], - index + 1, - currentEntries, - subTable.lookupRecords, - lookups - ); - } - - for (const glyph of subTable.lookahead) { - currentEntries = processLookaheadPosition( - [glyph], - currentEntries - ); - } - - for (const glyph of subTable.backtrack) { - currentEntries = processBacktrackPosition( - [glyph], - currentEntries - ); - } - - // When we get to the end, insert the lookup information - for (const { entry, substitutions } of currentEntries) { - entry.lookup = { - substitutions, - length: subTable.input.length + 1, - index: tableIndex, - subIndex, - contextRange: [ - -1 * subTable.backtrack.length, - 1 + subTable.input.length + subTable.lookahead.length - ] - }; - } - } + // If the chain rule set is null there's nothing to do with this table. + if (!chainRuleSet) { + continue; } - return result; + for (const [subIndex, subTable] of chainRuleSet.entries()) { + let currentEntries: EntryMeta[] = getInputTree( + result, + subTable.lookupRecords, + lookups, + 0, + glyphId + ).map(({ entry, substitution }) => ({ entry, substitutions: [substitution] })); + + // We walk forward, then backward + for (const [index, glyph] of subTable.input.entries()) { + currentEntries = processInputPosition( + [glyph], + index + 1, + currentEntries, + subTable.lookupRecords, + lookups + ); + } + + for (const glyph of subTable.lookahead) { + currentEntries = processLookaheadPosition( + [glyph], + currentEntries + ); + } + + for (const glyph of subTable.backtrack) { + currentEntries = processBacktrackPosition( + [glyph], + currentEntries + ); + } + + // When we get to the end, insert the lookup information + for (const { entry, substitutions } of currentEntries) { + entry.lookup = { + substitutions, + length: subTable.input.length + 1, + index: tableIndex, + subIndex, + contextRange: [ + -1 * subTable.backtrack.length, + 1 + subTable.input.length + subTable.lookahead.length + ] + }; + } + } + } + + return result; } diff --git a/addons/addon-ligatures/src/fontLigatures/processors/6-2.ts b/addons/addon-ligatures/src/fontLigatures/processors/6-2.ts index 9cd8eaf3..f0f9245c 100644 --- a/addons/addon-ligatures/src/fontLigatures/processors/6-2.ts +++ b/addons/addon-ligatures/src/fontLigatures/processors/6-2.ts @@ -15,82 +15,82 @@ import { processInputPosition, processLookaheadPosition, processBacktrackPositio * @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[] = []; + const results: LookupTree[] = []; - const firstGlyphs = listGlyphsByIndex(table.coverage); + const firstGlyphs = listGlyphsByIndex(table.coverage); - for (const { glyphId } of firstGlyphs) { - const firstInputClass = getGlyphClass(table.inputClassDef, glyphId); - for (const [glyphId, inputClass] of firstInputClass.entries()) { - // istanbul ignore next - invalid font - if (inputClass === null) { - continue; - } + for (const { glyphId } of firstGlyphs) { + const firstInputClass = getGlyphClass(table.inputClassDef, glyphId); + for (const [glyphId, inputClass] of firstInputClass.entries()) { + // istanbul ignore next - invalid font + if (inputClass === null) { + continue; + } - const classSet = table.chainClassSet[inputClass]; + const classSet = table.chainClassSet[inputClass]; - // If the class set is null there's nothing to do with this table. - if (!classSet) { - continue; - } + // If the class set is null there's nothing to do with this table. + if (!classSet) { + continue; + } - for (const [subIndex, subTable] of classSet.entries()) { - const result: LookupTree = { - individual: {}, - range: [] - }; + for (const [subIndex, subTable] of classSet.entries()) { + const result: LookupTree = { + individual: {}, + range: [] + }; - let currentEntries: EntryMeta[] = getInputTree( - result, - subTable.lookupRecords, - lookups, - 0, - glyphId - ).map(({ entry, substitution }) => ({ entry, substitutions: [substitution] })); + let currentEntries: EntryMeta[] = getInputTree( + result, + subTable.lookupRecords, + lookups, + 0, + glyphId + ).map(({ entry, substitution }) => ({ entry, substitutions: [substitution] })); - for (const [index, classNum] of subTable.input.entries()) { - currentEntries = processInputPosition( - listClassGlyphs(table.inputClassDef, classNum), - index + 1, - currentEntries, - subTable.lookupRecords, - lookups - ); - } - - for (const classNum of subTable.lookahead) { - currentEntries = processLookaheadPosition( - listClassGlyphs(table.lookaheadClassDef, classNum), - currentEntries - ); - } - - for (const classNum of subTable.backtrack) { - currentEntries = processBacktrackPosition( - listClassGlyphs(table.backtrackClassDef, classNum), - currentEntries - ); - } - - // When we get to the end, all of the entries we've accumulated - // should have a lookup defined - for (const { entry, substitutions } of currentEntries) { - entry.lookup = { - substitutions, - index: tableIndex, - subIndex, - length: subTable.input.length + 1, - contextRange: [ - -1 * subTable.backtrack.length, - 1 + subTable.input.length + subTable.lookahead.length - ] - }; - } - - results.push(result); - } + for (const [index, classNum] of subTable.input.entries()) { + currentEntries = processInputPosition( + listClassGlyphs(table.inputClassDef, classNum), + index + 1, + currentEntries, + subTable.lookupRecords, + lookups + ); } - } - return mergeTrees(results); + for (const classNum of subTable.lookahead) { + currentEntries = processLookaheadPosition( + listClassGlyphs(table.lookaheadClassDef, classNum), + currentEntries + ); + } + + for (const classNum of subTable.backtrack) { + currentEntries = processBacktrackPosition( + listClassGlyphs(table.backtrackClassDef, classNum), + currentEntries + ); + } + + // When we get to the end, all of the entries we've accumulated + // should have a lookup defined + for (const { entry, substitutions } of currentEntries) { + entry.lookup = { + substitutions, + index: tableIndex, + subIndex, + length: subTable.input.length + 1, + contextRange: [ + -1 * subTable.backtrack.length, + 1 + subTable.input.length + subTable.lookahead.length + ] + }; + } + + results.push(result); + } + } + } + + return mergeTrees(results); } diff --git a/addons/addon-ligatures/src/fontLigatures/processors/6-3.ts b/addons/addon-ligatures/src/fontLigatures/processors/6-3.ts index fc671e7b..07e4681f 100644 --- a/addons/addon-ligatures/src/fontLigatures/processors/6-3.ts +++ b/addons/addon-ligatures/src/fontLigatures/processors/6-3.ts @@ -13,61 +13,61 @@ import { processInputPosition, processLookaheadPosition, processBacktrackPositio * @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 = { - individual: {}, - range: [] - }; + const result: LookupTree = { + individual: {}, + range: [] + }; - const firstGlyphs = listGlyphsByIndex(table.inputCoverage[0]); + const firstGlyphs = listGlyphsByIndex(table.inputCoverage[0]); - for (const { glyphId } of firstGlyphs) { - let currentEntries: EntryMeta[] = getInputTree( - result, - table.lookupRecords, - lookups, - 0, - glyphId - ).map(({ entry, substitution }) => ({ entry, substitutions: [substitution] })); + for (const { glyphId } of firstGlyphs) { + let currentEntries: EntryMeta[] = getInputTree( + result, + table.lookupRecords, + lookups, + 0, + glyphId + ).map(({ entry, substitution }) => ({ entry, substitutions: [substitution] })); - for (const [index, coverage] of table.inputCoverage.slice(1).entries()) { - currentEntries = processInputPosition( - listGlyphsByIndex(coverage).map(glyph => glyph.glyphId), - index + 1, - currentEntries, - table.lookupRecords, - lookups - ); - } - - for (const coverage of table.lookaheadCoverage) { - currentEntries = processLookaheadPosition( - listGlyphsByIndex(coverage).map(glyph => glyph.glyphId), - currentEntries - ); - } - - for (const coverage of table.backtrackCoverage) { - currentEntries = processBacktrackPosition( - listGlyphsByIndex(coverage).map(glyph => glyph.glyphId), - currentEntries - ); - } - - // When we get to the end, all of the entries we've accumulated - // should have a lookup defined - for (const { entry, substitutions } of currentEntries) { - entry.lookup = { - substitutions, - index: tableIndex, - subIndex: 0, - length: table.inputCoverage.length, - contextRange: [ - -1 * table.backtrackCoverage.length, - table.inputCoverage.length + table.lookaheadCoverage.length - ] - }; - } + for (const [index, coverage] of table.inputCoverage.slice(1).entries()) { + currentEntries = processInputPosition( + listGlyphsByIndex(coverage).map(glyph => glyph.glyphId), + index + 1, + currentEntries, + table.lookupRecords, + lookups + ); } - return result; + for (const coverage of table.lookaheadCoverage) { + currentEntries = processLookaheadPosition( + listGlyphsByIndex(coverage).map(glyph => glyph.glyphId), + currentEntries + ); + } + + for (const coverage of table.backtrackCoverage) { + currentEntries = processBacktrackPosition( + listGlyphsByIndex(coverage).map(glyph => glyph.glyphId), + currentEntries + ); + } + + // When we get to the end, all of the entries we've accumulated + // should have a lookup defined + for (const { entry, substitutions } of currentEntries) { + entry.lookup = { + substitutions, + index: tableIndex, + subIndex: 0, + length: table.inputCoverage.length, + contextRange: [ + -1 * table.backtrackCoverage.length, + table.inputCoverage.length + table.lookaheadCoverage.length + ] + }; + } + } + + return result; } diff --git a/addons/addon-ligatures/src/fontLigatures/processors/8-1.ts b/addons/addon-ligatures/src/fontLigatures/processors/8-1.ts index 620d2e20..8f127f6b 100644 --- a/addons/addon-ligatures/src/fontLigatures/processors/8-1.ts +++ b/addons/addon-ligatures/src/fontLigatures/processors/8-1.ts @@ -12,58 +12,58 @@ import { processLookaheadPosition, processBacktrackPosition, EntryMeta } from '. * @param tableIndex Index of this table in the overall lookup */ export default function buildTree(table: ReverseChainingContextualSingleSubstitutionTable, tableIndex: number): LookupTree { - const result: LookupTree = { - individual: {}, - range: [] - }; + const result: LookupTree = { + individual: {}, + range: [] + }; - const glyphs = listGlyphsByIndex(table.coverage); + const glyphs = listGlyphsByIndex(table.coverage); - for (const { glyphId, index } of glyphs) { - const initialEntry: LookupTreeEntry = {}; - if (Array.isArray(glyphId)) { - result.range.push({ - entry: initialEntry, - range: glyphId - }); - } else { - result.individual[glyphId] = initialEntry; - } - - let currentEntries: EntryMeta[] = [{ - entry: initialEntry, - substitutions: [table.substitutes[index]] - }]; - - // We walk forward, then backward - for (const coverage of table.lookaheadCoverage) { - currentEntries = processLookaheadPosition( - listGlyphsByIndex(coverage).map(glyph => glyph.glyphId), - currentEntries - ); - } - - for (const coverage of table.backtrackCoverage) { - currentEntries = processBacktrackPosition( - listGlyphsByIndex(coverage).map(glyph => glyph.glyphId), - currentEntries - ); - } - - // When we get to the end, insert the lookup information - for (const { entry, substitutions } of currentEntries) { - entry.lookup = { - substitutions, - index: tableIndex, - subIndex: 0, - length: 1, - contextRange: [ - -1 * table.backtrackCoverage.length, - 1 + table.lookaheadCoverage.length - ] - }; - } + for (const { glyphId, index } of glyphs) { + const initialEntry: LookupTreeEntry = {}; + if (Array.isArray(glyphId)) { + result.range.push({ + entry: initialEntry, + range: glyphId + }); + } else { + result.individual[glyphId] = initialEntry; } - return result; + let currentEntries: EntryMeta[] = [{ + entry: initialEntry, + substitutions: [table.substitutes[index]] + }]; + + // We walk forward, then backward + for (const coverage of table.lookaheadCoverage) { + currentEntries = processLookaheadPosition( + listGlyphsByIndex(coverage).map(glyph => glyph.glyphId), + currentEntries + ); + } + + for (const coverage of table.backtrackCoverage) { + currentEntries = processBacktrackPosition( + listGlyphsByIndex(coverage).map(glyph => glyph.glyphId), + currentEntries + ); + } + + // When we get to the end, insert the lookup information + for (const { entry, substitutions } of currentEntries) { + entry.lookup = { + substitutions, + index: tableIndex, + subIndex: 0, + length: 1, + contextRange: [ + -1 * table.backtrackCoverage.length, + 1 + table.lookaheadCoverage.length + ] + }; + } + } + + return result; } diff --git a/addons/addon-ligatures/src/fontLigatures/processors/classDef.ts b/addons/addon-ligatures/src/fontLigatures/processors/classDef.ts index 3f2dc21e..bcf98ea6 100644 --- a/addons/addon-ligatures/src/fontLigatures/processors/classDef.ts +++ b/addons/addon-ligatures/src/fontLigatures/processors/classDef.ts @@ -8,78 +8,78 @@ import { ClassDefTable } from '../tables'; * @param glyphId Index of the glyph to look for */ export default function getGlyphClass(table: ClassDefTable, glyphId: number | [number, number]): Map { - switch (table.format) { - // https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table-format-2 - case 2: - if (Array.isArray(glyphId)) { - return getRangeGlyphClass(table, glyphId); - } else { - 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]]); - } + switch (table.format) { + // https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table-format-2 + case 2: + if (Array.isArray(glyphId)) { + return getRangeGlyphClass(table, glyphId); + } else { + 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 { - let classStart: number = glyphId[0]; - let currentClass: number | null = getIndividualGlyphClass(table, classStart); - let search: number = glyphId[0] + 1; + let classStart: number = glyphId[0]; + let currentClass: number | null = getIndividualGlyphClass(table, classStart); + let search: number = glyphId[0] + 1; - const result = new Map<[number, number] | number, number | null>(); + const result = new Map<[number, number] | number, number | null>(); - while (search < glyphId[1]) { - const clazz = getIndividualGlyphClass(table, search); - if (clazz !== currentClass) { - if (search - classStart <= 1) { - result.set(classStart, currentClass); - } else { - result.set([classStart, search], currentClass); - } - } - search++; - } - - if (search - classStart <= 1) { + while (search < glyphId[1]) { + const clazz = getIndividualGlyphClass(table, search); + if (clazz !== currentClass) { + if (search - classStart <= 1) { result.set(classStart, currentClass); - } else { + } else { result.set([classStart, search], currentClass); + } } + search++; + } - return result; + if (search - classStart <= 1) { + result.set(classStart, currentClass); + } else { + result.set([classStart, search], currentClass); + } + + return result; } function getIndividualGlyphClass(table: ClassDefTable.Format2, glyphId: number): number | null { - for (const range of table.ranges) { - if (range.start <= glyphId && range.end >= glyphId) { - return range.classId; - } + for (const range of table.ranges) { + if (range.start <= glyphId && range.end >= glyphId) { + return range.classId; } + } - return null; + return null; } export function listClassGlyphs(table: ClassDefTable, index: number): (number | [number, number])[] { - switch (table.format) { - case 2: - const results: (number | [number, number])[] = []; - for (const range of table.ranges) { - if (range.classId !== index) { - continue; - } + switch (table.format) { + case 2: + const results: (number | [number, number])[] = []; + for (const range of table.ranges) { + if (range.classId !== index) { + continue; + } - if (range.end === range.start) { - results.push(range.start); - } else { - results.push([range.start, range.end + 1]); - } - } - return results; - default: - return []; - } + if (range.end === range.start) { + results.push(range.start); + } else { + results.push([range.start, range.end + 1]); + } + } + return results; + default: + return []; + } } diff --git a/addons/addon-ligatures/src/fontLigatures/processors/coverage.ts b/addons/addon-ligatures/src/fontLigatures/processors/coverage.ts index 0ee3f42d..02d518d0 100644 --- a/addons/addon-ligatures/src/fontLigatures/processors/coverage.ts +++ b/addons/addon-ligatures/src/fontLigatures/processors/coverage.ts @@ -8,36 +8,36 @@ import { CoverageTable } from '../tables'; * @param glyphId Index of the glyph to look for */ export default function getCoverageGlyphIndex(table: CoverageTable, glyphId: number): number | null { - switch (table.format) { - // https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-format-1 - case 1: - const index = table.glyphs.indexOf(glyphId); - return index !== -1 - ? index - : null; - // https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-format-2 - case 2: - const range = table.ranges - .find(range => range.start <= glyphId && range.end >= glyphId); - return range - ? range.index - : null; - } + switch (table.format) { + // https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-format-1 + case 1: + const index = table.glyphs.indexOf(glyphId); + return index !== -1 + ? index + : null; + // https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-format-2 + case 2: + const range = table.ranges + .find(range => range.start <= glyphId && range.end >= glyphId); + return range + ? range.index + : null; + } } 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; }[] = []; - for (const [index, range] of table.ranges.entries()) { - if (range.end === range.start) { - results.push({ glyphId: range.start, index }); - } else { - results.push({ glyphId: [range.start, range.end + 1], index }); - } - } - return results; - } + switch (table.format) { + case 1: + return table.glyphs.map((glyphId, index) => ({ glyphId, index })); + case 2: + let 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 }); + } else { + results.push({ glyphId: [range.start, range.end + 1], index }); + } + } + return results; + } } diff --git a/addons/addon-ligatures/src/fontLigatures/processors/helper.ts b/addons/addon-ligatures/src/fontLigatures/processors/helper.ts index 7186a22d..c15c0c92 100644 --- a/addons/addon-ligatures/src/fontLigatures/processors/helper.ts +++ b/addons/addon-ligatures/src/fontLigatures/processors/helper.ts @@ -4,160 +4,160 @@ import { SubstitutionLookupRecord, Lookup } from '../tables'; import { getIndividualSubstitutionGlyph, getRangeSubstitutionGlyphs } from './substitution'; export interface EntryMeta { - entry: LookupTreeEntry; - substitutions: (number | null)[]; + entry: LookupTreeEntry; + substitutions: (number | null)[]; } export function processInputPosition( - glyphs: (number | [number, number])[], - position: number, - currentEntries: EntryMeta[], - lookupRecords: SubstitutionLookupRecord[], - lookups: Lookup[] + glyphs: (number | [number, number])[], + position: number, + currentEntries: EntryMeta[], + lookupRecords: SubstitutionLookupRecord[], + lookups: Lookup[] ): EntryMeta[] { - const nextEntries: EntryMeta[] = []; - 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] - }))); - } + const nextEntries: EntryMeta[] = []; + 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; + return nextEntries; } export function processLookaheadPosition( - glyphs: (number | [number, number])[], - currentEntries: EntryMeta[] + glyphs: (number | [number, number])[], + currentEntries: EntryMeta[] ): EntryMeta[] { - const nextEntries: EntryMeta[] = []; - for (const currentEntry of currentEntries) { - for (const glyph of glyphs) { - const entry: LookupTreeEntry = {}; - if (!currentEntry.entry.forward) { - currentEntry.entry.forward = { - individual: {}, - range: [] - }; - } - nextEntries.push({ - entry, - substitutions: currentEntry.substitutions - }); + const nextEntries: EntryMeta[] = []; + for (const currentEntry of currentEntries) { + for (const glyph of glyphs) { + const entry: LookupTreeEntry = {}; + if (!currentEntry.entry.forward) { + currentEntry.entry.forward = { + individual: {}, + range: [] + }; + } + nextEntries.push({ + entry, + substitutions: currentEntry.substitutions + }); - if (Array.isArray(glyph)) { - currentEntry.entry.forward.range.push({ - entry, - range: glyph - }); - } else { - currentEntry.entry.forward.individual[glyph] = entry; - } - } + if (Array.isArray(glyph)) { + currentEntry.entry.forward.range.push({ + entry, + range: glyph + }); + } else { + currentEntry.entry.forward.individual[glyph] = entry; + } } + } - return nextEntries; + return nextEntries; } export function processBacktrackPosition( - glyphs: (number | [number, number])[], - currentEntries: EntryMeta[] + glyphs: (number | [number, number])[], + currentEntries: EntryMeta[] ): EntryMeta[] { - const nextEntries: EntryMeta[] = []; - for (const currentEntry of currentEntries) { - for (const glyph of glyphs) { - const entry: LookupTreeEntry = {}; - if (!currentEntry.entry.reverse) { - currentEntry.entry.reverse = { - individual: {}, - range: [] - }; - } - nextEntries.push({ - entry, - substitutions: currentEntry.substitutions - }); + const nextEntries: EntryMeta[] = []; + for (const currentEntry of currentEntries) { + for (const glyph of glyphs) { + const entry: LookupTreeEntry = {}; + if (!currentEntry.entry.reverse) { + currentEntry.entry.reverse = { + individual: {}, + range: [] + }; + } + nextEntries.push({ + entry, + substitutions: currentEntry.substitutions + }); - if (Array.isArray(glyph)) { - currentEntry.entry.reverse.range.push({ - entry, - range: glyph - }); - } else { - currentEntry.entry.reverse.individual[glyph] = entry; - } - } + if (Array.isArray(glyph)) { + currentEntry.entry.reverse.range.push({ + entry, + range: glyph + }); + } else { + currentEntry.entry.reverse.individual[glyph] = entry; + } } + } - return nextEntries; + 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; }[] = []; - 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: LookupTreeEntry = {}; - if (Array.isArray(range)) { - tree.range.push({ range, entry }); - } else { - tree.individual[range] = {}; - } - result.push({ entry, substitution }); - } + const result: { entry: LookupTreeEntry; 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: LookupTreeEntry = {}; + if (Array.isArray(range)) { + tree.range.push({ range, entry }); + } else { + tree.individual[range] = {}; + } + result.push({ entry, substitution }); } + } - return result; + return result; } function getSubstitutionAtPositionRange(substitutions: SubstitutionLookupRecord[], lookups: Lookup[], index: number, range: [number, number]): Map { - for (const substitution of substitutions.filter(s => s.sequenceIndex === index)) { - for (const substitutionTable of (lookups[substitution.lookupListIndex] as Lookup.Type1).subtables) { - const sub = getRangeSubstitutionGlyphs( - substitutionTable, - range - ); + for (const substitution of substitutions.filter(s => s.sequenceIndex === index)) { + for (const substitutionTable of (lookups[substitution.lookupListIndex] as Lookup.Type1).subtables) { + const sub = getRangeSubstitutionGlyphs( + substitutionTable, + range + ); - if (!Array.from(sub.values()).every(val => val !== null)) { - return sub; - } - } + if (!Array.from(sub.values()).every(val => val !== null)) { + return sub; + } } + } - return new Map([[range, null]]); + return new Map([[range, null]]); } function getSubstitutionAtPosition(substitutions: SubstitutionLookupRecord[], 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) { - const sub = getIndividualSubstitutionGlyph( - substitutionTable, - glyphId - ); + for (const substitution of substitutions.filter(s => s.sequenceIndex === index)) { + for (const substitutionTable of (lookups[substitution.lookupListIndex] as Lookup.Type1).subtables) { + const sub = getIndividualSubstitutionGlyph( + substitutionTable, + glyphId + ); - if (sub !== null) { - return sub; - } - } + if (sub !== null) { + return sub; + } } + } - return null; + return null; } diff --git a/addons/addon-ligatures/src/fontLigatures/processors/substitution.ts b/addons/addon-ligatures/src/fontLigatures/processors/substitution.ts index 975d7a25..e277bd49 100644 --- a/addons/addon-ligatures/src/fontLigatures/processors/substitution.ts +++ b/addons/addon-ligatures/src/fontLigatures/processors/substitution.ts @@ -10,53 +10,53 @@ 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); - let search: number = glyphId[0] + 1; + let replacementStart: number = glyphId[0]; + let currentReplacement: number | null = getIndividualSubstitutionGlyph(table, replacementStart); + let search: number = glyphId[0] + 1; - const result = new Map<[number, number] | number, number | null>(); + const result = new Map<[number, number] | number, number | null>(); - while (search < glyphId[1]) { - const sub = getIndividualSubstitutionGlyph(table, search); - if (sub !== currentReplacement) { - if (search - replacementStart <= 1) { - result.set(replacementStart, currentReplacement); - } else { - result.set([replacementStart, search], currentReplacement); - } - } - - search++; - } - - if (search - replacementStart <= 1) { + while (search < glyphId[1]) { + const sub = getIndividualSubstitutionGlyph(table, search); + if (sub !== currentReplacement) { + if (search - replacementStart <= 1) { result.set(replacementStart, currentReplacement); - } else { + } else { result.set([replacementStart, search], currentReplacement); + } } - return result; + search++; + } + + if (search - replacementStart <= 1) { + result.set(replacementStart, currentReplacement); + } else { + result.set([replacementStart, search], currentReplacement); + } + + return result; } export function getIndividualSubstitutionGlyph(table: SubstitutionTable, glyphId: number): number | null { - const coverageIndex = getCoverageGlyphIndex(table.coverage, glyphId); + const coverageIndex = getCoverageGlyphIndex(table.coverage, glyphId); - // istanbul ignore next - invalid font - if (coverageIndex === null) { - return null; - } + // istanbul ignore next - invalid font + if (coverageIndex === null) { + return null; + } - switch (table.substFormat) { - // https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#11-single-substitution-format-1 - case 1: - // TODO: determine if there's a rhyme or reason to the 16-bit - // wraparound and if it can ever be a different number - 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 - return table.substitute[coverageIndex] != null - ? table.substitute[coverageIndex] - : null; - } + switch (table.substFormat) { + // https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#11-single-substitution-format-1 + case 1: + // TODO: determine if there's a rhyme or reason to the 16-bit + // wraparound and if it can ever be a different number + 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 + return table.substitute[coverageIndex] != null + ? table.substitute[coverageIndex] + : null; + } } diff --git a/addons/addon-ligatures/src/fontLigatures/tables.ts b/addons/addon-ligatures/src/fontLigatures/tables.ts index d3eccd08..ec6f3604 100644 --- a/addons/addon-ligatures/src/fontLigatures/tables.ts +++ b/addons/addon-ligatures/src/fontLigatures/tables.ts @@ -1,112 +1,112 @@ export type SubstitutionTable = SubstitutionTable.Format1 | SubstitutionTable.Format2; export namespace SubstitutionTable { - export interface Format1 { - substFormat: 1; - coverage: CoverageTable; - deltaGlyphId: number; - } + export interface Format1 { + substFormat: 1; + coverage: CoverageTable; + deltaGlyphId: number; + } - export interface Format2 { - substFormat: 2; - coverage: CoverageTable; - substitute: number[]; - } + export interface Format2 { + substFormat: 2; + coverage: CoverageTable; + substitute: number[]; + } } export type CoverageTable = CoverageTable.Format1 | CoverageTable.Format2; export namespace CoverageTable { - export interface Format1 { - format: 1; - glyphs: number[]; - } + export interface Format1 { + format: 1; + glyphs: number[]; + } - export interface Format2 { - format: 2; - ranges: { - start: number; - end: number; - index: number; - }[]; - } + export interface Format2 { + format: 2; + ranges: { + start: number; + end: number; + index: number; + }[]; + } } export type ChainingContextualSubstitutionTable = ChainingContextualSubstitutionTable.Format1 | - ChainingContextualSubstitutionTable.Format2 | ChainingContextualSubstitutionTable.Format3; + ChainingContextualSubstitutionTable.Format2 | ChainingContextualSubstitutionTable.Format3; export namespace ChainingContextualSubstitutionTable { - export interface Format1 { - substFormat: 1; - coverage: CoverageTable; - chainRuleSets: ChainSubRuleTable[][]; - } + export interface Format1 { + substFormat: 1; + coverage: CoverageTable; + chainRuleSets: ChainSubRuleTable[][]; + } - export interface Format2 { - substFormat: 2; - coverage: CoverageTable; - backtrackClassDef: ClassDefTable; - inputClassDef: ClassDefTable; - lookaheadClassDef: ClassDefTable; - chainClassSet: (null | ChainSubClassRuleTable[])[]; - } + export interface Format2 { + substFormat: 2; + coverage: CoverageTable; + backtrackClassDef: ClassDefTable; + inputClassDef: ClassDefTable; + lookaheadClassDef: ClassDefTable; + chainClassSet: (null | ChainSubClassRuleTable[])[]; + } - export interface Format3 { - substFormat: 3; - backtrackCoverage: CoverageTable[]; - inputCoverage: CoverageTable[]; - lookaheadCoverage: CoverageTable[]; - lookupRecords: SubstitutionLookupRecord[]; - } + export interface Format3 { + substFormat: 3; + backtrackCoverage: CoverageTable[]; + inputCoverage: CoverageTable[]; + lookaheadCoverage: CoverageTable[]; + lookupRecords: SubstitutionLookupRecord[]; + } } export interface ReverseChainingContextualSingleSubstitutionTable { - substFormat: 1; - coverage: CoverageTable; - backtrackCoverage: CoverageTable[]; - lookaheadCoverage: CoverageTable[]; - substitutes: number[]; + substFormat: 1; + coverage: CoverageTable; + backtrackCoverage: CoverageTable[]; + lookaheadCoverage: CoverageTable[]; + substitutes: number[]; } export type ClassDefTable = ClassDefTable.Format2; export namespace ClassDefTable { - export interface Format2 { - format: 2; - ranges: { - start: number; - end: number; - classId: number; - }[]; - } + export interface Format2 { + format: 2; + ranges: { + start: number; + end: number; + classId: number; + }[]; + } } export interface SubstitutionLookupRecord { - sequenceIndex: number; - lookupListIndex: number; + sequenceIndex: number; + lookupListIndex: number; } export type ChainSubRuleTable = ChainSubClassRuleTable; export interface ChainSubClassRuleTable { - backtrack: number[]; - input: number[]; - lookahead: number[]; - lookupRecords: SubstitutionLookupRecord[]; + backtrack: number[]; + input: number[]; + lookahead: number[]; + lookupRecords: SubstitutionLookupRecord[]; } export type Lookup = Lookup.Type1 | Lookup.Type6 | Lookup.Type8; export namespace Lookup { - export interface Type1 { - lookupType: 1; - lookupFlag: number; - subtables: SubstitutionTable[]; - } + export interface Type1 { + lookupType: 1; + lookupFlag: number; + subtables: SubstitutionTable[]; + } - export interface Type6 { - lookupType: 6; - lookupFlag: number; - subtables: ChainingContextualSubstitutionTable[]; - } + export interface Type6 { + lookupType: 6; + lookupFlag: number; + subtables: ChainingContextualSubstitutionTable[]; + } - export interface Type8 { - lookupType: 8; - lookupFlag: number; - subtables: ReverseChainingContextualSingleSubstitutionTable[]; - } + export interface Type8 { + lookupType: 8; + lookupFlag: number; + subtables: ReverseChainingContextualSingleSubstitutionTable[]; + } } diff --git a/addons/addon-ligatures/src/fontLigatures/types.ts b/addons/addon-ligatures/src/fontLigatures/types.ts index 9364c75c..789fed38 100644 --- a/addons/addon-ligatures/src/fontLigatures/types.ts +++ b/addons/addon-ligatures/src/fontLigatures/types.ts @@ -1,86 +1,86 @@ export interface SubstitutionResult { - index: number; - contextRange: [number, number]; + index: number; + contextRange: [number, number]; } /** * Information about ligatures found in a sequence of text */ export interface LigatureData { - /** - * The list of font glyphs in the input text. - */ - inputGlyphs: number[]; + /** + * The list of font glyphs in the input text. + */ + inputGlyphs: number[]; - /** - * The list of font glyphs after performing replacements for font ligatures. - */ - outputGlyphs: number[]; + /** + * The list of font glyphs after performing replacements for font ligatures. + */ + outputGlyphs: number[]; - /** - * Sorted array of ranges that must be rendered together to produce the - * ligatures in the output sequence. The ranges are inclusive on the left and - * exclusive on the right. - */ - contextRanges: [number, number][]; + /** + * Sorted array of ranges that must be rendered together to produce the + * ligatures in the output sequence. The ranges are inclusive on the left and + * exclusive on the right. + */ + contextRanges: [number, number][]; } export interface Font { - /** - * 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; + /** + * 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; - /** - * Scans the provided text for font ligatures, returning an array of ranges - * where ligatures are located. - * - * @param text String to search for ligatures - */ - findLigatureRanges(text: string): [number, number][]; + /** + * Scans the provided text for font ligatures, returning an array of ranges + * where ligatures are located. + * + * @param text String to search for ligatures + */ + findLigatureRanges(text: string): [number, number][]; } export interface Options { - /** - * Optional size of previous results to store, measured in total number of - * characters from input strings. Defaults to no cache (0) - */ - cacheSize?: number; + /** + * Optional size of previous results to store, measured in total number of + * characters from input strings. Defaults to no cache (0) + */ + cacheSize?: number; } export interface LookupTree { - individual: { - [glyphId: string]: LookupTreeEntry; - }; - range: { - range: [number, number]; - entry: LookupTreeEntry; - }[]; + individual: { + [glyphId: string]: LookupTreeEntry; + }; + range: { + range: [number, number]; + entry: LookupTreeEntry; + }[]; } export interface LookupTreeEntry { - lookup?: LookupResult; - forward?: LookupTree; - reverse?: LookupTree; + lookup?: LookupResult; + forward?: LookupTree; + reverse?: LookupTree; } export interface LookupResult { - substitutions: (number | null)[]; - length: number; - index: number; - subIndex: number; - contextRange: [number, number]; + substitutions: (number | null)[]; + length: number; + index: number; + subIndex: number; + contextRange: [number, number]; } export interface FlattenedLookupTree { - [glyphId: string]: FlattenedLookupTreeEntry; + [glyphId: string]: FlattenedLookupTreeEntry; } export interface FlattenedLookupTreeEntry { - lookup?: LookupResult; - forward?: FlattenedLookupTree; - reverse?: FlattenedLookupTree; + lookup?: LookupResult; + forward?: FlattenedLookupTree; + reverse?: FlattenedLookupTree; } diff --git a/addons/addon-ligatures/src/fontLigatures/walk.ts b/addons/addon-ligatures/src/fontLigatures/walk.ts index 56d6eac1..b122897e 100644 --- a/addons/addon-ligatures/src/fontLigatures/walk.ts +++ b/addons/addon-ligatures/src/fontLigatures/walk.ts @@ -1,67 +1,67 @@ import { FlattenedLookupTree, LookupResult } from './types'; export default function walkTree(tree: FlattenedLookupTree, sequence: number[], startIndex: number, index: number): LookupResult | undefined { - const glyphId = sequence[index]; - let subtree = tree[glyphId]; - if (!subtree) { - return undefined; - } + const glyphId = sequence[index]; + let subtree = tree[glyphId]; + if (!subtree) { + return undefined; + } - let lookup = subtree.lookup; - if (subtree.reverse) { - const reverseLookup = walkReverse(subtree.reverse, sequence, startIndex); - - if ( - (!lookup && reverseLookup) || - ( - reverseLookup && lookup && ( - lookup.index > reverseLookup.index || - (lookup.index === reverseLookup.index && lookup.subIndex > reverseLookup.subIndex) - ) - ) - ) { - lookup = reverseLookup; - } - } - - if (++index >= sequence.length || !subtree.forward) { - return lookup; - } - - const forwardLookup = walkTree(subtree.forward, sequence, startIndex, index); + let lookup = subtree.lookup; + if (subtree.reverse) { + const reverseLookup = walkReverse(subtree.reverse, sequence, startIndex); if ( - (!lookup && forwardLookup) || - ( - forwardLookup && lookup && ( - lookup.index > forwardLookup.index || - (lookup.index === forwardLookup.index && lookup.subIndex > forwardLookup.subIndex) - ) + (!lookup && reverseLookup) || + ( + reverseLookup && lookup && ( + lookup.index > reverseLookup.index || + (lookup.index === reverseLookup.index && lookup.subIndex > reverseLookup.subIndex) ) + ) ) { - lookup = forwardLookup; + lookup = reverseLookup; } + } + if (++index >= sequence.length || !subtree.forward) { return lookup; + } + + const forwardLookup = walkTree(subtree.forward, sequence, startIndex, index); + + if ( + (!lookup && forwardLookup) || + ( + forwardLookup && lookup && ( + lookup.index > forwardLookup.index || + (lookup.index === forwardLookup.index && lookup.subIndex > forwardLookup.subIndex) + ) + ) + ) { + lookup = forwardLookup; + } + + return lookup; } function walkReverse(tree: FlattenedLookupTree, sequence: number[], index: number): LookupResult | undefined { - let subtree = tree[sequence[--index]]; - let lookup: LookupResult | undefined = subtree && subtree.lookup; - while (subtree) { - if ( - (!lookup && subtree.lookup) || - (subtree.lookup && lookup && lookup.index > subtree.lookup.index) - ) { - lookup = subtree.lookup; - } - - if (--index < 0 || !subtree.reverse) { - break; - } - - subtree = subtree.reverse[sequence[index]]; + let subtree = tree[sequence[--index]]; + let lookup: LookupResult | undefined = subtree && subtree.lookup; + while (subtree) { + if ( + (!lookup && subtree.lookup) || + (subtree.lookup && lookup && lookup.index > subtree.lookup.index) + ) { + lookup = subtree.lookup; } - return lookup; + if (--index < 0 || !subtree.reverse) { + break; + } + + subtree = subtree.reverse[sequence[index]]; + } + + return lookup; }