|
|
import { cache } from '../decorators';
|
|
|
import Path from './Path';
|
|
|
import unicode from 'unicode-properties';
|
|
|
import StandardNames from './StandardNames';
|
|
|
|
|
|
/**
|
|
|
* Glyph objects represent a glyph in the font. They have various properties for accessing metrics and
|
|
|
* the actual vector path the glyph represents, and methods for rendering the glyph to a graphics context.
|
|
|
*
|
|
|
* You do not create glyph objects directly. They are created by various methods on the font object.
|
|
|
* There are several subclasses of the base Glyph class internally that may be returned depending
|
|
|
* on the font format, but they all inherit from this class.
|
|
|
*/
|
|
|
export default class Glyph {
|
|
|
constructor(id, codePoints, font) {
|
|
|
/**
|
|
|
* The glyph id in the font
|
|
|
* @type {number}
|
|
|
*/
|
|
|
this.id = id;
|
|
|
|
|
|
/**
|
|
|
* An array of unicode code points that are represented by this glyph.
|
|
|
* There can be multiple code points in the case of ligatures and other glyphs
|
|
|
* that represent multiple visual characters.
|
|
|
* @type {number[]}
|
|
|
*/
|
|
|
this.codePoints = codePoints;
|
|
|
this._font = font;
|
|
|
|
|
|
// TODO: get this info from GDEF if available
|
|
|
this.isMark = this.codePoints.length > 0 && this.codePoints.every(unicode.isMark);
|
|
|
this.isLigature = this.codePoints.length > 1;
|
|
|
}
|
|
|
|
|
|
_getPath() {
|
|
|
return new Path();
|
|
|
}
|
|
|
|
|
|
_getCBox() {
|
|
|
return this.path.cbox;
|
|
|
}
|
|
|
|
|
|
_getBBox() {
|
|
|
return this.path.bbox;
|
|
|
}
|
|
|
|
|
|
_getTableMetrics(table) {
|
|
|
if (this.id < table.metrics.length) {
|
|
|
return table.metrics.get(this.id);
|
|
|
}
|
|
|
|
|
|
let metric = table.metrics.get(table.metrics.length - 1);
|
|
|
let res = {
|
|
|
advance: metric ? metric.advance : 0,
|
|
|
bearing: table.bearings.get(this.id - table.metrics.length) || 0
|
|
|
};
|
|
|
|
|
|
return res;
|
|
|
}
|
|
|
|
|
|
_getMetrics(cbox) {
|
|
|
if (this._metrics) { return this._metrics; }
|
|
|
|
|
|
let {advance:advanceWidth, bearing:leftBearing} = this._getTableMetrics(this._font.hmtx);
|
|
|
|
|
|
// For vertical metrics, use vmtx if available, or fall back to global data from OS/2 or hhea
|
|
|
if (this._font.vmtx) {
|
|
|
var {advance:advanceHeight, bearing:topBearing} = this._getTableMetrics(this._font.vmtx);
|
|
|
|
|
|
} else {
|
|
|
let os2;
|
|
|
if (typeof cbox === 'undefined' || cbox === null) { ({ cbox } = this); }
|
|
|
|
|
|
if ((os2 = this._font['OS/2']) && os2.version > 0) {
|
|
|
var advanceHeight = Math.abs(os2.typoAscender - os2.typoDescender);
|
|
|
var topBearing = os2.typoAscender - cbox.maxY;
|
|
|
|
|
|
} else {
|
|
|
let { hhea } = this._font;
|
|
|
var advanceHeight = Math.abs(hhea.ascent - hhea.descent);
|
|
|
var topBearing = hhea.ascent - cbox.maxY;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (this._font._variationProcessor && this._font.HVAR) {
|
|
|
advanceWidth += this._font._variationProcessor.getAdvanceAdjustment(this.id, this._font.HVAR);
|
|
|
}
|
|
|
|
|
|
return this._metrics = { advanceWidth, advanceHeight, leftBearing, topBearing };
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* The glyph’s control box.
|
|
|
* This is often the same as the bounding box, but is faster to compute.
|
|
|
* Because of the way bezier curves are defined, some of the control points
|
|
|
* can be outside of the bounding box. Where `bbox` takes this into account,
|
|
|
* `cbox` does not. Thus, cbox is less accurate, but faster to compute.
|
|
|
* See [here](http://www.freetype.org/freetype2/docs/glyphs/glyphs-6.html#section-2)
|
|
|
* for a more detailed description.
|
|
|
*
|
|
|
* @type {BBox}
|
|
|
*/
|
|
|
@cache
|
|
|
get cbox() {
|
|
|
return this._getCBox();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* The glyph’s bounding box, i.e. the rectangle that encloses the
|
|
|
* glyph outline as tightly as possible.
|
|
|
* @type {BBox}
|
|
|
*/
|
|
|
@cache
|
|
|
get bbox() {
|
|
|
return this._getBBox();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* A vector Path object representing the glyph outline.
|
|
|
* @type {Path}
|
|
|
*/
|
|
|
@cache
|
|
|
get path() {
|
|
|
// Cache the path so we only decode it once
|
|
|
// Decoding is actually performed by subclasses
|
|
|
return this._getPath();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Returns a path scaled to the given font size.
|
|
|
* @param {number} size
|
|
|
* @return {Path}
|
|
|
*/
|
|
|
getScaledPath(size) {
|
|
|
let scale = 1 / this._font.unitsPerEm * size;
|
|
|
return this.path.scale(scale);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* The glyph's advance width.
|
|
|
* @type {number}
|
|
|
*/
|
|
|
@cache
|
|
|
get advanceWidth() {
|
|
|
return this._getMetrics().advanceWidth;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* The glyph's advance height.
|
|
|
* @type {number}
|
|
|
*/
|
|
|
@cache
|
|
|
get advanceHeight() {
|
|
|
return this._getMetrics().advanceHeight;
|
|
|
}
|
|
|
|
|
|
get ligatureCaretPositions() {}
|
|
|
|
|
|
_getName() {
|
|
|
let { post } = this._font;
|
|
|
if (!post) {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
switch (post.version) {
|
|
|
case 1:
|
|
|
return StandardNames[this.id];
|
|
|
|
|
|
case 2:
|
|
|
let id = post.glyphNameIndex[this.id];
|
|
|
if (id < StandardNames.length) {
|
|
|
return StandardNames[id];
|
|
|
}
|
|
|
|
|
|
return post.names[id - StandardNames.length];
|
|
|
|
|
|
case 2.5:
|
|
|
return StandardNames[this.id + post.offsets[this.id]];
|
|
|
|
|
|
case 4:
|
|
|
return String.fromCharCode(post.map[this.id]);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* The glyph's name
|
|
|
* @type {string}
|
|
|
*/
|
|
|
@cache
|
|
|
get name() {
|
|
|
return this._getName();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Renders the glyph to the given graphics context, at the specified font size.
|
|
|
* @param {CanvasRenderingContext2d} ctx
|
|
|
* @param {number} size
|
|
|
*/
|
|
|
render(ctx, size) {
|
|
|
ctx.save();
|
|
|
|
|
|
let scale = 1 / this._font.head.unitsPerEm * size;
|
|
|
ctx.scale(scale, scale);
|
|
|
|
|
|
let fn = this.path.toFunction();
|
|
|
fn(ctx);
|
|
|
ctx.fill();
|
|
|
|
|
|
ctx.restore();
|
|
|
}
|
|
|
}
|
|
|
|