Current File : /home/tradevaly/www/node_modules/fontkit/src/TTFFont.js |
import r from 'restructure';
import { cache } from './decorators';
import fontkit from './base';
import Directory from './tables/directory';
import tables from './tables';
import CmapProcessor from './CmapProcessor';
import LayoutEngine from './layout/LayoutEngine';
import TTFGlyph from './glyph/TTFGlyph';
import CFFGlyph from './glyph/CFFGlyph';
import SBIXGlyph from './glyph/SBIXGlyph';
import COLRGlyph from './glyph/COLRGlyph';
import GlyphVariationProcessor from './glyph/GlyphVariationProcessor';
import TTFSubset from './subset/TTFSubset';
import CFFSubset from './subset/CFFSubset';
import BBox from './glyph/BBox';
/**
* This is the base class for all SFNT-based font formats in fontkit.
* It supports TrueType, and PostScript glyphs, and several color glyph formats.
*/
export default class TTFFont {
static probe(buffer) {
let format = buffer.toString('ascii', 0, 4);
return format === 'true' || format === 'OTTO' || format === String.fromCharCode(0, 1, 0, 0);
}
constructor(stream, variationCoords = null) {
this.defaultLanguage = null;
this.stream = stream;
this.variationCoords = variationCoords;
this._directoryPos = this.stream.pos;
this._tables = {};
this._glyphs = {};
this._decodeDirectory();
// define properties for each table to lazily parse
for (let tag in this.directory.tables) {
let table = this.directory.tables[tag];
if (tables[tag] && table.length > 0) {
Object.defineProperty(this, tag, {
get: this._getTable.bind(this, table)
});
}
}
}
setDefaultLanguage(lang = null) {
this.defaultLanguage = lang;
}
_getTable(table) {
if (!(table.tag in this._tables)) {
try {
this._tables[table.tag] = this._decodeTable(table);
} catch (e) {
if (fontkit.logErrors) {
console.error(`Error decoding table ${table.tag}`);
console.error(e.stack);
}
}
}
return this._tables[table.tag];
}
_getTableStream(tag) {
let table = this.directory.tables[tag];
if (table) {
this.stream.pos = table.offset;
return this.stream;
}
return null;
}
_decodeDirectory() {
return this.directory = Directory.decode(this.stream, {_startOffset: 0});
}
_decodeTable(table) {
let pos = this.stream.pos;
let stream = this._getTableStream(table.tag);
let result = tables[table.tag].decode(stream, this, table.length);
this.stream.pos = pos;
return result;
}
/**
* Gets a string from the font's `name` table
* `lang` is a BCP-47 language code.
* @return {string}
*/
getName(key, lang = this.defaultLanguage || fontkit.defaultLanguage) {
let record = this.name && this.name.records[key];
if (record) {
// Attempt to retrieve the entry, depending on which translation is available:
return (
record[lang]
|| record[this.defaultLanguage]
|| record[fontkit.defaultLanguage]
|| record['en']
|| record[Object.keys(record)[0]] // Seriously, ANY language would be fine
|| null
);
}
return null;
}
/**
* The unique PostScript name for this font, e.g. "Helvetica-Bold"
* @type {string}
*/
get postscriptName() {
return this.getName('postscriptName');
}
/**
* The font's full name, e.g. "Helvetica Bold"
* @type {string}
*/
get fullName() {
return this.getName('fullName');
}
/**
* The font's family name, e.g. "Helvetica"
* @type {string}
*/
get familyName() {
return this.getName('fontFamily');
}
/**
* The font's sub-family, e.g. "Bold".
* @type {string}
*/
get subfamilyName() {
return this.getName('fontSubfamily');
}
/**
* The font's copyright information
* @type {string}
*/
get copyright() {
return this.getName('copyright');
}
/**
* The font's version number
* @type {string}
*/
get version() {
return this.getName('version');
}
/**
* The font’s [ascender](https://en.wikipedia.org/wiki/Ascender_(typography))
* @type {number}
*/
get ascent() {
return this.hhea.ascent;
}
/**
* The font’s [descender](https://en.wikipedia.org/wiki/Descender)
* @type {number}
*/
get descent() {
return this.hhea.descent;
}
/**
* The amount of space that should be included between lines
* @type {number}
*/
get lineGap() {
return this.hhea.lineGap;
}
/**
* The offset from the normal underline position that should be used
* @type {number}
*/
get underlinePosition() {
return this.post.underlinePosition;
}
/**
* The weight of the underline that should be used
* @type {number}
*/
get underlineThickness() {
return this.post.underlineThickness;
}
/**
* If this is an italic font, the angle the cursor should be drawn at to match the font design
* @type {number}
*/
get italicAngle() {
return this.post.italicAngle;
}
/**
* The height of capital letters above the baseline.
* See [here](https://en.wikipedia.org/wiki/Cap_height) for more details.
* @type {number}
*/
get capHeight() {
let os2 = this['OS/2'];
return os2 ? os2.capHeight : this.ascent;
}
/**
* The height of lower case letters in the font.
* See [here](https://en.wikipedia.org/wiki/X-height) for more details.
* @type {number}
*/
get xHeight() {
let os2 = this['OS/2'];
return os2 ? os2.xHeight : 0;
}
/**
* The number of glyphs in the font.
* @type {number}
*/
get numGlyphs() {
return this.maxp.numGlyphs;
}
/**
* The size of the font’s internal coordinate grid
* @type {number}
*/
get unitsPerEm() {
return this.head.unitsPerEm;
}
/**
* The font’s bounding box, i.e. the box that encloses all glyphs in the font.
* @type {BBox}
*/
@cache
get bbox() {
return Object.freeze(new BBox(this.head.xMin, this.head.yMin, this.head.xMax, this.head.yMax));
}
@cache
get _cmapProcessor() {
return new CmapProcessor(this.cmap);
}
/**
* An array of all of the unicode code points supported by the font.
* @type {number[]}
*/
@cache
get characterSet() {
return this._cmapProcessor.getCharacterSet();
}
/**
* Returns whether there is glyph in the font for the given unicode code point.
*
* @param {number} codePoint
* @return {boolean}
*/
hasGlyphForCodePoint(codePoint) {
return !!this._cmapProcessor.lookup(codePoint);
}
/**
* Maps a single unicode code point to a Glyph object.
* Does not perform any advanced substitutions (there is no context to do so).
*
* @param {number} codePoint
* @return {Glyph}
*/
glyphForCodePoint(codePoint) {
return this.getGlyph(this._cmapProcessor.lookup(codePoint), [codePoint]);
}
/**
* Returns an array of Glyph objects for the given string.
* This is only a one-to-one mapping from characters to glyphs.
* For most uses, you should use font.layout (described below), which
* provides a much more advanced mapping supporting AAT and OpenType shaping.
*
* @param {string} string
* @return {Glyph[]}
*/
glyphsForString(string) {
let glyphs = [];
let len = string.length;
let idx = 0;
let last = -1;
let state = -1;
while (idx <= len) {
let code = 0;
let nextState = 0;
if (idx < len) {
// Decode the next codepoint from UTF 16
code = string.charCodeAt(idx++);
if (0xd800 <= code && code <= 0xdbff && idx < len) {
let next = string.charCodeAt(idx);
if (0xdc00 <= next && next <= 0xdfff) {
idx++;
code = ((code & 0x3ff) << 10) + (next & 0x3ff) + 0x10000;
}
}
// Compute the next state: 1 if the next codepoint is a variation selector, 0 otherwise.
nextState = ((0xfe00 <= code && code <= 0xfe0f) || (0xe0100 <= code && code <= 0xe01ef)) ? 1 : 0;
} else {
idx++;
}
if (state === 0 && nextState === 1) {
// Variation selector following normal codepoint.
glyphs.push(this.getGlyph(this._cmapProcessor.lookup(last, code), [last, code]));
} else if (state === 0 && nextState === 0) {
// Normal codepoint following normal codepoint.
glyphs.push(this.glyphForCodePoint(last));
}
last = code;
state = nextState;
}
return glyphs;
}
@cache
get _layoutEngine() {
return new LayoutEngine(this);
}
/**
* Returns a GlyphRun object, which includes an array of Glyphs and GlyphPositions for the given string.
*
* @param {string} string
* @param {string[]} [userFeatures]
* @param {string} [script]
* @param {string} [language]
* @param {string} [direction]
* @return {GlyphRun}
*/
layout(string, userFeatures, script, language, direction) {
return this._layoutEngine.layout(string, userFeatures, script, language, direction);
}
/**
* Returns an array of strings that map to the given glyph id.
* @param {number} gid - glyph id
*/
stringsForGlyph(gid) {
return this._layoutEngine.stringsForGlyph(gid);
}
/**
* An array of all [OpenType feature tags](https://www.microsoft.com/typography/otspec/featuretags.htm)
* (or mapped AAT tags) supported by the font.
* The features parameter is an array of OpenType feature tags to be applied in addition to the default set.
* If this is an AAT font, the OpenType feature tags are mapped to AAT features.
*
* @type {string[]}
*/
get availableFeatures() {
return this._layoutEngine.getAvailableFeatures();
}
getAvailableFeatures(script, language) {
return this._layoutEngine.getAvailableFeatures(script, language);
}
_getBaseGlyph(glyph, characters = []) {
if (!this._glyphs[glyph]) {
if (this.directory.tables.glyf) {
this._glyphs[glyph] = new TTFGlyph(glyph, characters, this);
} else if (this.directory.tables['CFF '] || this.directory.tables.CFF2) {
this._glyphs[glyph] = new CFFGlyph(glyph, characters, this);
}
}
return this._glyphs[glyph] || null;
}
/**
* Returns a glyph object for the given glyph id.
* You can pass the array of code points this glyph represents for
* your use later, and it will be stored in the glyph object.
*
* @param {number} glyph
* @param {number[]} characters
* @return {Glyph}
*/
getGlyph(glyph, characters = []) {
if (!this._glyphs[glyph]) {
if (this.directory.tables.sbix) {
this._glyphs[glyph] = new SBIXGlyph(glyph, characters, this);
} else if ((this.directory.tables.COLR) && (this.directory.tables.CPAL)) {
this._glyphs[glyph] = new COLRGlyph(glyph, characters, this);
} else {
this._getBaseGlyph(glyph, characters);
}
}
return this._glyphs[glyph] || null;
}
/**
* Returns a Subset for this font.
* @return {Subset}
*/
createSubset() {
if (this.directory.tables['CFF ']) {
return new CFFSubset(this);
}
return new TTFSubset(this);
}
/**
* Returns an object describing the available variation axes
* that this font supports. Keys are setting tags, and values
* contain the axis name, range, and default value.
*
* @type {object}
*/
@cache
get variationAxes() {
let res = {};
if (!this.fvar) {
return res;
}
for (let axis of this.fvar.axis) {
res[axis.axisTag.trim()] = {
name: axis.name.en,
min: axis.minValue,
default: axis.defaultValue,
max: axis.maxValue
};
}
return res;
}
/**
* Returns an object describing the named variation instances
* that the font designer has specified. Keys are variation names
* and values are the variation settings for this instance.
*
* @type {object}
*/
@cache
get namedVariations() {
let res = {};
if (!this.fvar) {
return res;
}
for (let instance of this.fvar.instance) {
let settings = {};
for (let i = 0; i < this.fvar.axis.length; i++) {
let axis = this.fvar.axis[i];
settings[axis.axisTag.trim()] = instance.coord[i];
}
res[instance.name.en] = settings;
}
return res;
}
/**
* Returns a new font with the given variation settings applied.
* Settings can either be an instance name, or an object containing
* variation tags as specified by the `variationAxes` property.
*
* @param {object} settings
* @return {TTFFont}
*/
getVariation(settings) {
if (!(this.directory.tables.fvar && ((this.directory.tables.gvar && this.directory.tables.glyf) || this.directory.tables.CFF2))) {
throw new Error('Variations require a font with the fvar, gvar and glyf, or CFF2 tables.');
}
if (typeof settings === 'string') {
settings = this.namedVariations[settings];
}
if (typeof settings !== 'object') {
throw new Error('Variation settings must be either a variation name or settings object.');
}
// normalize the coordinates
let coords = this.fvar.axis.map((axis, i) => {
let axisTag = axis.axisTag.trim();
if (axisTag in settings) {
return Math.max(axis.minValue, Math.min(axis.maxValue, settings[axisTag]));
} else {
return axis.defaultValue;
}
});
let stream = new r.DecodeStream(this.stream.buffer);
stream.pos = this._directoryPos;
let font = new TTFFont(stream, coords);
font._tables = this._tables;
return font;
}
@cache
get _variationProcessor() {
if (!this.fvar) {
return null;
}
let variationCoords = this.variationCoords;
// Ignore if no variation coords and not CFF2
if (!variationCoords && !this.CFF2) {
return null;
}
if (!variationCoords) {
variationCoords = this.fvar.axis.map(axis => axis.defaultValue);
}
return new GlyphVariationProcessor(this, variationCoords);
}
// Standardized format plugin API
getFont(name) {
return this.getVariation(name);
}
}