astro-ghostcms/.pnpm-store/v3/files/05/d8b0378f9e5f26df6faf4185f47...

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;