blank project
This commit is contained in:
554
node_modules/fontkit/src/TTFFont.js
generated
vendored
Normal file
554
node_modules/fontkit/src/TTFFont.js
generated
vendored
Normal file
@@ -0,0 +1,554 @@
|
||||
import * as r from 'restructure';
|
||||
import { cache } from './decorators';
|
||||
import * as 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';
|
||||
import { asciiDecoder } from './utils';
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
type = 'TTF';
|
||||
|
||||
static probe(buffer) {
|
||||
let format = asciiDecoder.decode(buffer.slice(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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user