Current File : /home/tradevaly/www/node_modules/fontkit/src/layout/LayoutEngine.js |
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);
}
}