// 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;