Show More
Commit Description:
change logger to be limited by file size
Commit Description:
change logger to be limited by file size
References:
File last commit:
Show/Diff file:
Action:
node_modules/fontkit/src/aat/AATMorxProcessor.js
| 425 lines
| 12.0 KiB
| application/javascript
| JavascriptGenshiLexer
|
r789 | import AATStateMachine from './AATStateMachine'; | |||
import AATLookupTable from './AATLookupTable'; | ||||
import {cache} from '../decorators'; | ||||
// indic replacement flags | ||||
const MARK_FIRST = 0x8000; | ||||
const MARK_LAST = 0x2000; | ||||
const VERB = 0x000F; | ||||
// contextual substitution and glyph insertion flag | ||||
const SET_MARK = 0x8000; | ||||
// ligature entry flags | ||||
const SET_COMPONENT = 0x8000; | ||||
const PERFORM_ACTION = 0x2000; | ||||
// ligature action masks | ||||
const LAST_MASK = 0x80000000; | ||||
const STORE_MASK = 0x40000000; | ||||
const OFFSET_MASK = 0x3FFFFFFF; | ||||
const VERTICAL_ONLY = 0x800000; | ||||
const REVERSE_DIRECTION = 0x400000; | ||||
const HORIZONTAL_AND_VERTICAL = 0x200000; | ||||
// glyph insertion flags | ||||
const CURRENT_IS_KASHIDA_LIKE = 0x2000; | ||||
const MARKED_IS_KASHIDA_LIKE = 0x1000; | ||||
const CURRENT_INSERT_BEFORE = 0x0800; | ||||
const MARKED_INSERT_BEFORE = 0x0400; | ||||
const CURRENT_INSERT_COUNT = 0x03E0; | ||||
const MARKED_INSERT_COUNT = 0x001F; | ||||
export default class AATMorxProcessor { | ||||
constructor(font) { | ||||
this.processIndicRearragement = this.processIndicRearragement.bind(this); | ||||
this.processContextualSubstitution = this.processContextualSubstitution.bind(this); | ||||
this.processLigature = this.processLigature.bind(this); | ||||
this.processNoncontextualSubstitutions = this.processNoncontextualSubstitutions.bind(this); | ||||
this.processGlyphInsertion = this.processGlyphInsertion.bind(this); | ||||
this.font = font; | ||||
this.morx = font.morx; | ||||
this.inputCache = null; | ||||
} | ||||
// Processes an array of glyphs and applies the specified features | ||||
// Features should be in the form of {featureType:{featureSetting:true}} | ||||
process(glyphs, features = {}) { | ||||
for (let chain of this.morx.chains) { | ||||
let flags = chain.defaultFlags; | ||||
// enable/disable the requested features | ||||
for (let feature of chain.features) { | ||||
let f; | ||||
if ((f = features[feature.featureType]) && f[feature.featureSetting]) { | ||||
flags &= feature.disableFlags; | ||||
flags |= feature.enableFlags; | ||||
} | ||||
} | ||||
for (let subtable of chain.subtables) { | ||||
if (subtable.subFeatureFlags & flags) { | ||||
this.processSubtable(subtable, glyphs); | ||||
} | ||||
} | ||||
} | ||||
// remove deleted glyphs | ||||
let index = glyphs.length - 1; | ||||
while (index >= 0) { | ||||
if (glyphs[index].id === 0xffff) { | ||||
glyphs.splice(index, 1); | ||||
} | ||||
index--; | ||||
} | ||||
return glyphs; | ||||
} | ||||
processSubtable(subtable, glyphs) { | ||||
this.subtable = subtable; | ||||
this.glyphs = glyphs; | ||||
if (this.subtable.type === 4) { | ||||
this.processNoncontextualSubstitutions(this.subtable, this.glyphs); | ||||
return; | ||||
} | ||||
this.ligatureStack = []; | ||||
this.markedGlyph = null; | ||||
this.firstGlyph = null; | ||||
this.lastGlyph = null; | ||||
this.markedIndex = null; | ||||
let stateMachine = this.getStateMachine(subtable); | ||||
let process = this.getProcessor(); | ||||
let reverse = !!(this.subtable.coverage & REVERSE_DIRECTION); | ||||
return stateMachine.process(this.glyphs, reverse, process); | ||||
} | ||||
@cache | ||||
getStateMachine(subtable) { | ||||
return new AATStateMachine(subtable.table.stateTable); | ||||
} | ||||
getProcessor() { | ||||
switch (this.subtable.type) { | ||||
case 0: | ||||
return this.processIndicRearragement; | ||||
case 1: | ||||
return this.processContextualSubstitution; | ||||
case 2: | ||||
return this.processLigature; | ||||
case 4: | ||||
return this.processNoncontextualSubstitutions; | ||||
case 5: | ||||
return this.processGlyphInsertion; | ||||
default: | ||||
throw new Error(`Invalid morx subtable type: ${this.subtable.type}`); | ||||
} | ||||
} | ||||
processIndicRearragement(glyph, entry, index) { | ||||
if (entry.flags & MARK_FIRST) { | ||||
this.firstGlyph = index; | ||||
} | ||||
if (entry.flags & MARK_LAST) { | ||||
this.lastGlyph = index; | ||||
} | ||||
reorderGlyphs(this.glyphs, entry.flags & VERB, this.firstGlyph, this.lastGlyph); | ||||
} | ||||
processContextualSubstitution(glyph, entry, index) { | ||||
let subsitutions = this.subtable.table.substitutionTable.items; | ||||
if (entry.markIndex !== 0xffff) { | ||||
let lookup = subsitutions.getItem(entry.markIndex); | ||||
let lookupTable = new AATLookupTable(lookup); | ||||
glyph = this.glyphs[this.markedGlyph]; | ||||
var gid = lookupTable.lookup(glyph.id); | ||||
if (gid) { | ||||
this.glyphs[this.markedGlyph] = this.font.getGlyph(gid, glyph.codePoints); | ||||
} | ||||
} | ||||
if (entry.currentIndex !== 0xffff) { | ||||
let lookup = subsitutions.getItem(entry.currentIndex); | ||||
let lookupTable = new AATLookupTable(lookup); | ||||
glyph = this.glyphs[index]; | ||||
var gid = lookupTable.lookup(glyph.id); | ||||
if (gid) { | ||||
this.glyphs[index] = this.font.getGlyph(gid, glyph.codePoints); | ||||
} | ||||
} | ||||
if (entry.flags & SET_MARK) { | ||||
this.markedGlyph = index; | ||||
} | ||||
} | ||||
processLigature(glyph, entry, index) { | ||||
if (entry.flags & SET_COMPONENT) { | ||||
this.ligatureStack.push(index); | ||||
} | ||||
if (entry.flags & PERFORM_ACTION) { | ||||
let actions = this.subtable.table.ligatureActions; | ||||
let components = this.subtable.table.components; | ||||
let ligatureList = this.subtable.table.ligatureList; | ||||
let actionIndex = entry.action; | ||||
let last = false; | ||||
let ligatureIndex = 0; | ||||
let codePoints = []; | ||||
let ligatureGlyphs = []; | ||||
while (!last) { | ||||
let componentGlyph = this.ligatureStack.pop(); | ||||
codePoints.unshift(...this.glyphs[componentGlyph].codePoints); | ||||
let action = actions.getItem(actionIndex++); | ||||
last = !!(action & LAST_MASK); | ||||
let store = !!(action & STORE_MASK); | ||||
let offset = (action & OFFSET_MASK) << 2 >> 2; // sign extend 30 to 32 bits | ||||
offset += this.glyphs[componentGlyph].id; | ||||
let component = components.getItem(offset); | ||||
ligatureIndex += component; | ||||
if (last || store) { | ||||
let ligatureEntry = ligatureList.getItem(ligatureIndex); | ||||
this.glyphs[componentGlyph] = this.font.getGlyph(ligatureEntry, codePoints); | ||||
ligatureGlyphs.push(componentGlyph); | ||||
ligatureIndex = 0; | ||||
codePoints = []; | ||||
} else { | ||||
this.glyphs[componentGlyph] = this.font.getGlyph(0xffff); | ||||
} | ||||
} | ||||
// Put ligature glyph indexes back on the stack | ||||
this.ligatureStack.push(...ligatureGlyphs); | ||||
} | ||||
} | ||||
processNoncontextualSubstitutions(subtable, glyphs, index) { | ||||
let lookupTable = new AATLookupTable(subtable.table.lookupTable); | ||||
for (index = 0; index < glyphs.length; index++) { | ||||
let glyph = glyphs[index]; | ||||
if (glyph.id !== 0xffff) { | ||||
let gid = lookupTable.lookup(glyph.id); | ||||
if (gid) { // 0 means do nothing | ||||
glyphs[index] = this.font.getGlyph(gid, glyph.codePoints); | ||||
} | ||||
} | ||||
} | ||||
} | ||||
_insertGlyphs(glyphIndex, insertionActionIndex, count, isBefore) { | ||||
let insertions = []; | ||||
while (count--) { | ||||
let gid = this.subtable.table.insertionActions.getItem(insertionActionIndex++); | ||||
insertions.push(this.font.getGlyph(gid)); | ||||
} | ||||
if (!isBefore) { | ||||
glyphIndex++; | ||||
} | ||||
this.glyphs.splice(glyphIndex, 0, ...insertions); | ||||
} | ||||
processGlyphInsertion(glyph, entry, index) { | ||||
if (entry.flags & SET_MARK) { | ||||
this.markedIndex = index; | ||||
} | ||||
if (entry.markedInsertIndex !== 0xffff) { | ||||
let count = (entry.flags & MARKED_INSERT_COUNT) >>> 5; | ||||
let isBefore = !!(entry.flags & MARKED_INSERT_BEFORE); | ||||
this._insertGlyphs(this.markedIndex, entry.markedInsertIndex, count, isBefore); | ||||
} | ||||
if (entry.currentInsertIndex !== 0xffff) { | ||||
let count = (entry.flags & CURRENT_INSERT_COUNT) >>> 5; | ||||
let isBefore = !!(entry.flags & CURRENT_INSERT_BEFORE); | ||||
this._insertGlyphs(index, entry.currentInsertIndex, count, isBefore); | ||||
} | ||||
} | ||||
getSupportedFeatures() { | ||||
let features = []; | ||||
for (let chain of this.morx.chains) { | ||||
for (let feature of chain.features) { | ||||
features.push([feature.featureType, feature.featureSetting]); | ||||
} | ||||
} | ||||
return features; | ||||
} | ||||
generateInputs(gid) { | ||||
if (!this.inputCache) { | ||||
this.generateInputCache(); | ||||
} | ||||
return this.inputCache[gid] || []; | ||||
} | ||||
generateInputCache() { | ||||
this.inputCache = {}; | ||||
for (let chain of this.morx.chains) { | ||||
let flags = chain.defaultFlags; | ||||
for (let subtable of chain.subtables) { | ||||
if (subtable.subFeatureFlags & flags) { | ||||
this.generateInputsForSubtable(subtable); | ||||
} | ||||
} | ||||
} | ||||
} | ||||
generateInputsForSubtable(subtable) { | ||||
// Currently, only supporting ligature subtables. | ||||
if (subtable.type !== 2) { | ||||
return; | ||||
} | ||||
let reverse = !!(subtable.coverage & REVERSE_DIRECTION); | ||||
if (reverse) { | ||||
throw new Error('Reverse subtable, not supported.'); | ||||
} | ||||
this.subtable = subtable; | ||||
this.ligatureStack = []; | ||||
let stateMachine = this.getStateMachine(subtable); | ||||
let process = this.getProcessor(); | ||||
let input = []; | ||||
let stack = []; | ||||
this.glyphs = []; | ||||
stateMachine.traverse({ | ||||
enter: (glyph, entry) => { | ||||
let glyphs = this.glyphs; | ||||
stack.push({ | ||||
glyphs: glyphs.slice(), | ||||
ligatureStack: this.ligatureStack.slice() | ||||
}); | ||||
// Add glyph to input and glyphs to process. | ||||
let g = this.font.getGlyph(glyph); | ||||
input.push(g); | ||||
glyphs.push(input[input.length - 1]); | ||||
// Process ligature substitution | ||||
process(glyphs[glyphs.length - 1], entry, glyphs.length - 1); | ||||
// Add input to result if only one matching (non-deleted) glyph remains. | ||||
let count = 0; | ||||
let found = 0; | ||||
for (let i = 0; i < glyphs.length && count <= 1; i++) { | ||||
if (glyphs[i].id !== 0xffff) { | ||||
count++; | ||||
found = glyphs[i].id; | ||||
} | ||||
} | ||||
if (count === 1) { | ||||
let result = input.map(g => g.id); | ||||
let cache = this.inputCache[found]; | ||||
if (cache) { | ||||
cache.push(result); | ||||
} else { | ||||
this.inputCache[found] = [result]; | ||||
} | ||||
} | ||||
}, | ||||
exit: () => { | ||||
({glyphs: this.glyphs, ligatureStack: this.ligatureStack} = stack.pop()); | ||||
input.pop(); | ||||
} | ||||
}); | ||||
} | ||||
} | ||||
// swaps the glyphs in rangeA with those in rangeB | ||||
// reverse the glyphs inside those ranges if specified | ||||
// ranges are in [offset, length] format | ||||
function swap(glyphs, rangeA, rangeB, reverseA = false, reverseB = false) { | ||||
let end = glyphs.splice(rangeB[0] - (rangeB[1] - 1), rangeB[1]); | ||||
if (reverseB) { | ||||
end.reverse(); | ||||
} | ||||
let start = glyphs.splice(rangeA[0], rangeA[1], ...end); | ||||
if (reverseA) { | ||||
start.reverse(); | ||||
} | ||||
glyphs.splice(rangeB[0] - (rangeA[1] - 1), 0, ...start); | ||||
return glyphs; | ||||
} | ||||
function reorderGlyphs(glyphs, verb, firstGlyph, lastGlyph) { | ||||
let length = lastGlyph - firstGlyph + 1; | ||||
switch (verb) { | ||||
case 0: // no change | ||||
return glyphs; | ||||
case 1: // Ax => xA | ||||
return swap(glyphs, [firstGlyph, 1], [lastGlyph, 0]); | ||||
case 2: // xD => Dx | ||||
return swap(glyphs, [firstGlyph, 0], [lastGlyph, 1]); | ||||
case 3: // AxD => DxA | ||||
return swap(glyphs, [firstGlyph, 1], [lastGlyph, 1]); | ||||
case 4: // ABx => xAB | ||||
return swap(glyphs, [firstGlyph, 2], [lastGlyph, 0]); | ||||
case 5: // ABx => xBA | ||||
return swap(glyphs, [firstGlyph, 2], [lastGlyph, 0], true, false); | ||||
case 6: // xCD => CDx | ||||
return swap(glyphs, [firstGlyph, 0], [lastGlyph, 2]); | ||||
case 7: // xCD => DCx | ||||
return swap(glyphs, [firstGlyph, 0], [lastGlyph, 2], false, true); | ||||
case 8: // AxCD => CDxA | ||||
return swap(glyphs, [firstGlyph, 1], [lastGlyph, 2]); | ||||
case 9: // AxCD => DCxA | ||||
return swap(glyphs, [firstGlyph, 1], [lastGlyph, 2], false, true); | ||||
case 10: // ABxD => DxAB | ||||
return swap(glyphs, [firstGlyph, 2], [lastGlyph, 1]); | ||||
case 11: // ABxD => DxBA | ||||
return swap(glyphs, [firstGlyph, 2], [lastGlyph, 1], true, false); | ||||
case 12: // ABxCD => CDxAB | ||||
return swap(glyphs, [firstGlyph, 2], [lastGlyph, 2]); | ||||
case 13: // ABxCD => CDxBA | ||||
return swap(glyphs, [firstGlyph, 2], [lastGlyph, 2], true, false); | ||||
case 14: // ABxCD => DCxAB | ||||
return swap(glyphs, [firstGlyph, 2], [lastGlyph, 2], false, true); | ||||
case 15: // ABxCD => DCxBA | ||||
return swap(glyphs, [firstGlyph, 2], [lastGlyph, 2], true, true); | ||||
default: | ||||
throw new Error(`Unknown verb: ${verb}`); | ||||
} | ||||
} | ||||