|
|
import AATLookupTable from './AATLookupTable';
|
|
|
|
|
|
const START_OF_TEXT_STATE = 0;
|
|
|
const START_OF_LINE_STATE = 1;
|
|
|
|
|
|
const END_OF_TEXT_CLASS = 0;
|
|
|
const OUT_OF_BOUNDS_CLASS = 1;
|
|
|
const DELETED_GLYPH_CLASS = 2;
|
|
|
const END_OF_LINE_CLASS = 3;
|
|
|
|
|
|
const DONT_ADVANCE = 0x4000;
|
|
|
|
|
|
export default class AATStateMachine {
|
|
|
constructor(stateTable) {
|
|
|
this.stateTable = stateTable;
|
|
|
this.lookupTable = new AATLookupTable(stateTable.classTable);
|
|
|
}
|
|
|
|
|
|
process(glyphs, reverse, processEntry) {
|
|
|
let currentState = START_OF_TEXT_STATE; // START_OF_LINE_STATE is used for kashida glyph insertions sometimes I think?
|
|
|
let index = reverse ? glyphs.length - 1 : 0;
|
|
|
let dir = reverse ? -1 : 1;
|
|
|
|
|
|
while ((dir === 1 && index <= glyphs.length) || (dir === -1 && index >= -1)) {
|
|
|
let glyph = null;
|
|
|
let classCode = OUT_OF_BOUNDS_CLASS;
|
|
|
let shouldAdvance = true;
|
|
|
|
|
|
if (index === glyphs.length || index === -1) {
|
|
|
classCode = END_OF_TEXT_CLASS;
|
|
|
} else {
|
|
|
glyph = glyphs[index];
|
|
|
if (glyph.id === 0xffff) { // deleted glyph
|
|
|
classCode = DELETED_GLYPH_CLASS;
|
|
|
} else {
|
|
|
classCode = this.lookupTable.lookup(glyph.id);
|
|
|
if (classCode == null) {
|
|
|
classCode = OUT_OF_BOUNDS_CLASS;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
let row = this.stateTable.stateArray.getItem(currentState);
|
|
|
let entryIndex = row[classCode];
|
|
|
let entry = this.stateTable.entryTable.getItem(entryIndex);
|
|
|
|
|
|
if (classCode !== END_OF_TEXT_CLASS && classCode !== DELETED_GLYPH_CLASS) {
|
|
|
processEntry(glyph, entry, index);
|
|
|
shouldAdvance = !(entry.flags & DONT_ADVANCE);
|
|
|
}
|
|
|
|
|
|
currentState = entry.newState;
|
|
|
if (shouldAdvance) {
|
|
|
index += dir;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return glyphs;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Performs a depth-first traversal of the glyph strings
|
|
|
* represented by the state machine.
|
|
|
*/
|
|
|
traverse(opts, state = 0, visited = new Set) {
|
|
|
if (visited.has(state)) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
visited.add(state);
|
|
|
|
|
|
let {nClasses, stateArray, entryTable} = this.stateTable;
|
|
|
let row = stateArray.getItem(state);
|
|
|
|
|
|
// Skip predefined classes
|
|
|
for (let classCode = 4; classCode < nClasses; classCode++) {
|
|
|
let entryIndex = row[classCode];
|
|
|
let entry = entryTable.getItem(entryIndex);
|
|
|
|
|
|
// Try all glyphs in the class
|
|
|
for (let glyph of this.lookupTable.glyphsForValue(classCode)) {
|
|
|
if (opts.enter) {
|
|
|
opts.enter(glyph, entry);
|
|
|
}
|
|
|
|
|
|
if (entry.newState !== 0) {
|
|
|
this.traverse(opts, entry.newState, visited);
|
|
|
}
|
|
|
|
|
|
if (opts.exit) {
|
|
|
opts.exit(glyph, entry);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|