|
|
import KernProcessor from './KernProcessor';
|
|
|
import UnicodeLayoutEngine from './UnicodeLayoutEngine';
|
|
|
import GlyphRun from './GlyphRun';
|
|
|
import GlyphPosition from './GlyphPosition';
|
|
|
import * as Script from './Script';
|
|
|
import unicode from 'unicode-properties';
|
|
|
import AATLayoutEngine from '../aat/AATLayoutEngine';
|
|
|
import OTLayoutEngine from '../opentype/OTLayoutEngine';
|
|
|
|
|
|
export default class LayoutEngine {
|
|
|
constructor(font) {
|
|
|
this.font = font;
|
|
|
this.unicodeLayoutEngine = null;
|
|
|
this.kernProcessor = null;
|
|
|
|
|
|
// Choose an advanced layout engine. We try the AAT morx table first since more
|
|
|
// scripts are currently supported because the shaping logic is built into the font.
|
|
|
if (this.font.morx) {
|
|
|
this.engine = new AATLayoutEngine(this.font);
|
|
|
|
|
|
} else if (this.font.GSUB || this.font.GPOS) {
|
|
|
this.engine = new OTLayoutEngine(this.font);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
layout(string, features, script, language, direction) {
|
|
|
// Make the features parameter optional
|
|
|
if (typeof features === 'string') {
|
|
|
direction = language;
|
|
|
language = script;
|
|
|
script = features;
|
|
|
features = [];
|
|
|
}
|
|
|
|
|
|
// Map string to glyphs if needed
|
|
|
if (typeof string === 'string') {
|
|
|
// Attempt to detect the script from the string if not provided.
|
|
|
if (script == null) {
|
|
|
script = Script.forString(string);
|
|
|
}
|
|
|
|
|
|
var glyphs = this.font.glyphsForString(string);
|
|
|
} else {
|
|
|
// Attempt to detect the script from the glyph code points if not provided.
|
|
|
if (script == null) {
|
|
|
let codePoints = [];
|
|
|
for (let glyph of string) {
|
|
|
codePoints.push(...glyph.codePoints);
|
|
|
}
|
|
|
|
|
|
script = Script.forCodePoints(codePoints);
|
|
|
}
|
|
|
|
|
|
var glyphs = string;
|
|
|
}
|
|
|
|
|
|
let glyphRun = new GlyphRun(glyphs, features, script, language, direction);
|
|
|
|
|
|
// Return early if there are no glyphs
|
|
|
if (glyphs.length === 0) {
|
|
|
glyphRun.positions = [];
|
|
|
return glyphRun;
|
|
|
}
|
|
|
|
|
|
// Setup the advanced layout engine
|
|
|
if (this.engine && this.engine.setup) {
|
|
|
this.engine.setup(glyphRun);
|
|
|
}
|
|
|
|
|
|
// Substitute and position the glyphs
|
|
|
this.substitute(glyphRun);
|
|
|
this.position(glyphRun);
|
|
|
|
|
|
this.hideDefaultIgnorables(glyphRun.glyphs, glyphRun.positions);
|
|
|
|
|
|
// Let the layout engine clean up any state it might have
|
|
|
if (this.engine && this.engine.cleanup) {
|
|
|
this.engine.cleanup();
|
|
|
}
|
|
|
|
|
|
return glyphRun;
|
|
|
}
|
|
|
|
|
|
substitute(glyphRun) {
|
|
|
// Call the advanced layout engine to make substitutions
|
|
|
if (this.engine && this.engine.substitute) {
|
|
|
this.engine.substitute(glyphRun);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
position(glyphRun) {
|
|
|
// Get initial glyph positions
|
|
|
glyphRun.positions = glyphRun.glyphs.map(glyph => new GlyphPosition(glyph.advanceWidth));
|
|
|
let positioned = null;
|
|
|
|
|
|
// Call the advanced layout engine. Returns the features applied.
|
|
|
if (this.engine && this.engine.position) {
|
|
|
positioned = this.engine.position(glyphRun);
|
|
|
}
|
|
|
|
|
|
// if there is no GPOS table, use unicode properties to position marks.
|
|
|
if (!positioned && (!this.engine || this.engine.fallbackPosition)) {
|
|
|
if (!this.unicodeLayoutEngine) {
|
|
|
this.unicodeLayoutEngine = new UnicodeLayoutEngine(this.font);
|
|
|
}
|
|
|
|
|
|
this.unicodeLayoutEngine.positionGlyphs(glyphRun.glyphs, glyphRun.positions);
|
|
|
}
|
|
|
|
|
|
// if kerning is not supported by GPOS, do kerning with the TrueType/AAT kern table
|
|
|
if ((!positioned || !positioned.kern) && glyphRun.features.kern !== false && this.font.kern) {
|
|
|
if (!this.kernProcessor) {
|
|
|
this.kernProcessor = new KernProcessor(this.font);
|
|
|
}
|
|
|
|
|
|
this.kernProcessor.process(glyphRun.glyphs, glyphRun.positions);
|
|
|
glyphRun.features.kern = true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
hideDefaultIgnorables(glyphs, positions) {
|
|
|
let space = this.font.glyphForCodePoint(0x20);
|
|
|
for (let i = 0; i < glyphs.length; i++) {
|
|
|
if (this.isDefaultIgnorable(glyphs[i].codePoints[0])) {
|
|
|
glyphs[i] = space;
|
|
|
positions[i].xAdvance = 0;
|
|
|
positions[i].yAdvance = 0;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
isDefaultIgnorable(ch) {
|
|
|
// From DerivedCoreProperties.txt in the Unicode database,
|
|
|
// minus U+115F, U+1160, U+3164 and U+FFA0, which is what
|
|
|
// Harfbuzz and Uniscribe do.
|
|
|
let plane = ch >> 16;
|
|
|
if (plane === 0) {
|
|
|
// BMP
|
|
|
switch (ch >> 8) {
|
|
|
case 0x00: return ch === 0x00AD;
|
|
|
case 0x03: return ch === 0x034F;
|
|
|
case 0x06: return ch === 0x061C;
|
|
|
case 0x17: return 0x17B4 <= ch && ch <= 0x17B5;
|
|
|
case 0x18: return 0x180B <= ch && ch <= 0x180E;
|
|
|
case 0x20: return (0x200B <= ch && ch <= 0x200F) || (0x202A <= ch && ch <= 0x202E) || (0x2060 <= ch && ch <= 0x206F);
|
|
|
case 0xFE: return (0xFE00 <= ch && ch <= 0xFE0F) || ch === 0xFEFF;
|
|
|
case 0xFF: return 0xFFF0 <= ch && ch <= 0xFFF8;
|
|
|
default: return false;
|
|
|
}
|
|
|
} else {
|
|
|
// Other planes
|
|
|
switch (plane) {
|
|
|
case 0x01: return (0x1BCA0 <= ch && ch <= 0x1BCA3) || (0x1D173 <= ch && ch <= 0x1D17A);
|
|
|
case 0x0E: return 0xE0000 <= ch && ch <= 0xE0FFF;
|
|
|
default: return false;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
getAvailableFeatures(script, language) {
|
|
|
let features = [];
|
|
|
|
|
|
if (this.engine) {
|
|
|
features.push(...this.engine.getAvailableFeatures(script, language));
|
|
|
}
|
|
|
|
|
|
if (this.font.kern && features.indexOf('kern') === -1) {
|
|
|
features.push('kern');
|
|
|
}
|
|
|
|
|
|
return features;
|
|
|
}
|
|
|
|
|
|
stringsForGlyph(gid) {
|
|
|
let result = new Set;
|
|
|
|
|
|
let codePoints = this.font._cmapProcessor.codePointsForGlyph(gid);
|
|
|
for (let codePoint of codePoints) {
|
|
|
result.add(String.fromCodePoint(codePoint));
|
|
|
}
|
|
|
|
|
|
if (this.engine && this.engine.stringsForGlyph) {
|
|
|
for (let string of this.engine.stringsForGlyph(gid)) {
|
|
|
result.add(string);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return Array.from(result);
|
|
|
}
|
|
|
}
|
|
|
|