275 lines
7.7 KiB
Plaintext
275 lines
7.7 KiB
Plaintext
// The Glyph object
|
|
|
|
import check from './check';
|
|
import Path from './path';
|
|
// import glyf from './tables/glyf' Can't be imported here, because it's a circular dependency
|
|
|
|
function getPathDefinition(glyph, path) {
|
|
let _path = path || new Path();
|
|
return {
|
|
configurable: true,
|
|
|
|
get: function () {
|
|
if (typeof _path === 'function') {
|
|
_path = _path();
|
|
}
|
|
|
|
return _path;
|
|
},
|
|
|
|
set: function (p) {
|
|
_path = p;
|
|
},
|
|
};
|
|
}
|
|
/**
|
|
* @typedef GlyphOptions
|
|
* @type Object
|
|
* @property {string} [name] - The glyph name
|
|
* @property {number} [unicode]
|
|
* @property {Array} [unicodes]
|
|
* @property {number} [xMin]
|
|
* @property {number} [yMin]
|
|
* @property {number} [xMax]
|
|
* @property {number} [yMax]
|
|
* @property {number} [advanceWidth]
|
|
*/
|
|
|
|
// A Glyph is an individual mark that often corresponds to a character.
|
|
// Some glyphs, such as ligatures, are a combination of many characters.
|
|
// Glyphs are the basic building blocks of a font.
|
|
//
|
|
// The `Glyph` class contains utility methods for drawing the path and its points.
|
|
/**
|
|
* @exports opentype.Glyph
|
|
* @class
|
|
* @param {GlyphOptions}
|
|
* @constructor
|
|
*/
|
|
function Glyph(options) {
|
|
// By putting all the code on a prototype function (which is only declared once)
|
|
// we reduce the memory requirements for larger fonts by some 2%
|
|
this.bindConstructorValues(options);
|
|
}
|
|
|
|
/**
|
|
* @param {GlyphOptions}
|
|
*/
|
|
Glyph.prototype.bindConstructorValues = function (options) {
|
|
this.index = options.index || 0;
|
|
|
|
// These three values cannot be deferred for memory optimization:
|
|
this.name = options.name || null;
|
|
this.unicode = options.unicode || undefined;
|
|
this.unicodes =
|
|
options.unicodes || options.unicode !== undefined
|
|
? [options.unicode]
|
|
: [];
|
|
|
|
// But by binding these values only when necessary, we reduce can
|
|
// the memory requirements by almost 3% for larger fonts.
|
|
if ('xMin' in options) {
|
|
this.xMin = options.xMin;
|
|
}
|
|
|
|
if ('yMin' in options) {
|
|
this.yMin = options.yMin;
|
|
}
|
|
|
|
if ('xMax' in options) {
|
|
this.xMax = options.xMax;
|
|
}
|
|
|
|
if ('yMax' in options) {
|
|
this.yMax = options.yMax;
|
|
}
|
|
|
|
if ('advanceWidth' in options) {
|
|
this.advanceWidth = options.advanceWidth;
|
|
}
|
|
|
|
// The path for a glyph is the most memory intensive, and is bound as a value
|
|
// with a getter/setter to ensure we actually do path parsing only once the
|
|
// path is actually needed by anything.
|
|
Object.defineProperty(this, 'path', getPathDefinition(this, options.path));
|
|
};
|
|
|
|
/**
|
|
* @param {number}
|
|
*/
|
|
Glyph.prototype.addUnicode = function (unicode) {
|
|
if (this.unicodes.length === 0) {
|
|
this.unicode = unicode;
|
|
}
|
|
|
|
this.unicodes.push(unicode);
|
|
};
|
|
|
|
// /**
|
|
// * Calculate the minimum bounding box for this glyph.
|
|
// * @return {opentype.BoundingBox}
|
|
// */
|
|
// Glyph.prototype.getBoundingBox = function() {
|
|
// return this.path.getBoundingBox();
|
|
// };
|
|
|
|
/**
|
|
* Convert the glyph to a Path we can draw on a drawing context.
|
|
* @param {number} [x=0] - Horizontal position of the beginning of the text.
|
|
* @param {number} [y=0] - Vertical position of the *baseline* of the text.
|
|
* @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`.
|
|
* @param {Object=} options - xScale, yScale to stretch the glyph.
|
|
* @param {opentype.Font} if hinting is to be used, the font
|
|
* @return {opentype.Path}
|
|
*/
|
|
Glyph.prototype.getPath = function (x, y, fontSize, options, font) {
|
|
x = x !== undefined ? x : 0;
|
|
y = y !== undefined ? y : 0;
|
|
fontSize = fontSize !== undefined ? fontSize : 72;
|
|
let commands;
|
|
let hPoints;
|
|
if (!options) options = {};
|
|
let xScale = options.xScale;
|
|
let yScale = options.yScale;
|
|
|
|
if (options.hinting && font && font.hinting) {
|
|
// in case of hinting, the hinting engine takes care
|
|
// of scaling the points (not the path) before hinting.
|
|
hPoints = this.path && font.hinting.exec(this, fontSize);
|
|
// in case the hinting engine failed hPoints is undefined
|
|
// and thus reverts to plain rending
|
|
}
|
|
|
|
if (hPoints) {
|
|
// Call font.hinting.getCommands instead of `glyf.getPath(hPoints).commands` to avoid a circular dependency
|
|
commands = font.hinting.getCommands(hPoints);
|
|
x = Math.round(x);
|
|
y = Math.round(y);
|
|
// TODO in case of hinting xyScaling is not yet supported
|
|
xScale = yScale = 1;
|
|
} else {
|
|
commands = this.path.commands;
|
|
const scale = (1 / (this.path.unitsPerEm || 1000)) * fontSize;
|
|
if (xScale === undefined) xScale = scale;
|
|
if (yScale === undefined) yScale = scale;
|
|
}
|
|
|
|
const p = new Path();
|
|
for (let i = 0; i < commands.length; i += 1) {
|
|
const cmd = commands[i];
|
|
if (cmd.type === 'M') {
|
|
p.moveTo(x + cmd.x * xScale, y + -cmd.y * yScale);
|
|
} else if (cmd.type === 'L') {
|
|
p.lineTo(x + cmd.x * xScale, y + -cmd.y * yScale);
|
|
} else if (cmd.type === 'Q') {
|
|
p.quadraticCurveTo(
|
|
x + cmd.x1 * xScale,
|
|
y + -cmd.y1 * yScale,
|
|
x + cmd.x * xScale,
|
|
y + -cmd.y * yScale
|
|
);
|
|
} else if (cmd.type === 'C') {
|
|
p.curveTo(
|
|
x + cmd.x1 * xScale,
|
|
y + -cmd.y1 * yScale,
|
|
x + cmd.x2 * xScale,
|
|
y + -cmd.y2 * yScale,
|
|
x + cmd.x * xScale,
|
|
y + -cmd.y * yScale
|
|
);
|
|
} else if (cmd.type === 'Z') {
|
|
p.closePath();
|
|
}
|
|
}
|
|
|
|
return p;
|
|
};
|
|
|
|
/**
|
|
* Split the glyph into contours.
|
|
* This function is here for backwards compatibility, and to
|
|
* provide raw access to the TrueType glyph outlines.
|
|
* @return {Array}
|
|
*/
|
|
Glyph.prototype.getContours = function () {
|
|
if (this.points === undefined) {
|
|
return [];
|
|
}
|
|
|
|
const contours = [];
|
|
let currentContour = [];
|
|
for (let i = 0; i < this.points.length; i += 1) {
|
|
const pt = this.points[i];
|
|
currentContour.push(pt);
|
|
if (pt.lastPointOfContour) {
|
|
contours.push(currentContour);
|
|
currentContour = [];
|
|
}
|
|
}
|
|
|
|
check.argument(
|
|
currentContour.length === 0,
|
|
'There are still points left in the current contour.'
|
|
);
|
|
return contours;
|
|
};
|
|
|
|
/**
|
|
* Calculate the xMin/yMin/xMax/yMax/lsb/rsb for a Glyph.
|
|
* @return {Object}
|
|
*/
|
|
Glyph.prototype.getMetrics = function () {
|
|
const commands = this.path.commands;
|
|
const xCoords = [];
|
|
const yCoords = [];
|
|
for (let i = 0; i < commands.length; i += 1) {
|
|
const cmd = commands[i];
|
|
if (cmd.type !== 'Z') {
|
|
xCoords.push(cmd.x);
|
|
yCoords.push(cmd.y);
|
|
}
|
|
|
|
if (cmd.type === 'Q' || cmd.type === 'C') {
|
|
xCoords.push(cmd.x1);
|
|
yCoords.push(cmd.y1);
|
|
}
|
|
|
|
if (cmd.type === 'C') {
|
|
xCoords.push(cmd.x2);
|
|
yCoords.push(cmd.y2);
|
|
}
|
|
}
|
|
|
|
const metrics = {
|
|
xMin: Math.min.apply(null, xCoords),
|
|
yMin: Math.min.apply(null, yCoords),
|
|
xMax: Math.max.apply(null, xCoords),
|
|
yMax: Math.max.apply(null, yCoords),
|
|
leftSideBearing: this.leftSideBearing,
|
|
};
|
|
|
|
if (!isFinite(metrics.xMin)) {
|
|
metrics.xMin = 0;
|
|
}
|
|
|
|
if (!isFinite(metrics.xMax)) {
|
|
metrics.xMax = this.advanceWidth;
|
|
}
|
|
|
|
if (!isFinite(metrics.yMin)) {
|
|
metrics.yMin = 0;
|
|
}
|
|
|
|
if (!isFinite(metrics.yMax)) {
|
|
metrics.yMax = 0;
|
|
}
|
|
|
|
metrics.rightSideBearing =
|
|
this.advanceWidth -
|
|
metrics.leftSideBearing -
|
|
(metrics.xMax - metrics.xMin);
|
|
return metrics;
|
|
};
|
|
|
|
export default Glyph;
|