芝麻web文件管理V1.00
编辑当前文件:/home/freeclou/app.optimyar.com/backend/node_modules/opentype.js/src/font.js
// The Font object import Path from './path'; import sfnt from './tables/sfnt'; import { DefaultEncoding } from './encoding'; import glyphset from './glyphset'; import Substitution from './substitution'; import { isBrowser, checkArgument, arrayBufferToNodeBuffer } from './util'; import HintingTrueType from './hintingtt'; /** * @typedef FontOptions * @type Object * @property {Boolean} empty - whether to create a new empty font * @property {string} familyName * @property {string} styleName * @property {string=} fullName * @property {string=} postScriptName * @property {string=} designer * @property {string=} designerURL * @property {string=} manufacturer * @property {string=} manufacturerURL * @property {string=} license * @property {string=} licenseURL * @property {string=} version * @property {string=} description * @property {string=} copyright * @property {string=} trademark * @property {Number} unitsPerEm * @property {Number} ascender * @property {Number} descender * @property {Number} createdTimestamp * @property {string=} weightClass * @property {string=} widthClass * @property {string=} fsSelection */ /** * A Font represents a loaded OpenType font file. * It contains a set of glyphs and methods to draw text on a drawing context, * or to get a path representing the text. * @exports opentype.Font * @class * @param {FontOptions} * @constructor */ function Font(options) { options = options || {}; if (!options.empty) { // Check that we've provided the minimum set of names. checkArgument(options.familyName, 'When creating a new Font object, familyName is required.'); checkArgument(options.styleName, 'When creating a new Font object, styleName is required.'); checkArgument(options.unitsPerEm, 'When creating a new Font object, unitsPerEm is required.'); checkArgument(options.ascender, 'When creating a new Font object, ascender is required.'); checkArgument(options.descender, 'When creating a new Font object, descender is required.'); checkArgument(options.descender < 0, 'Descender should be negative (e.g. -512).'); // OS X will complain if the names are empty, so we put a single space everywhere by default. this.names = { fontFamily: {en: options.familyName || ' '}, fontSubfamily: {en: options.styleName || ' '}, fullName: {en: options.fullName || options.familyName + ' ' + options.styleName}, postScriptName: {en: options.postScriptName || options.familyName + options.styleName}, designer: {en: options.designer || ' '}, designerURL: {en: options.designerURL || ' '}, manufacturer: {en: options.manufacturer || ' '}, manufacturerURL: {en: options.manufacturerURL || ' '}, license: {en: options.license || ' '}, licenseURL: {en: options.licenseURL || ' '}, version: {en: options.version || 'Version 0.1'}, description: {en: options.description || ' '}, copyright: {en: options.copyright || ' '}, trademark: {en: options.trademark || ' '} }; this.unitsPerEm = options.unitsPerEm || 1000; this.ascender = options.ascender; this.descender = options.descender; this.createdTimestamp = options.createdTimestamp; this.tables = { os2: { usWeightClass: options.weightClass || this.usWeightClasses.MEDIUM, usWidthClass: options.widthClass || this.usWidthClasses.MEDIUM, fsSelection: options.fsSelection || this.fsSelectionValues.REGULAR } }; } this.supported = true; // Deprecated: parseBuffer will throw an error if font is not supported. this.glyphs = new glyphset.GlyphSet(this, options.glyphs || []); this.encoding = new DefaultEncoding(this); this.substitution = new Substitution(this); this.tables = this.tables || {}; Object.defineProperty(this, 'hinting', { get: function() { if (this._hinting) return this._hinting; if (this.outlinesFormat === 'truetype') { return (this._hinting = new HintingTrueType(this)); } } }); } /** * Check if the font has a glyph for the given character. * @param {string} * @return {Boolean} */ Font.prototype.hasChar = function(c) { return this.encoding.charToGlyphIndex(c) !== null; }; /** * Convert the given character to a single glyph index. * Note that this function assumes that there is a one-to-one mapping between * the given character and a glyph; for complex scripts this might not be the case. * @param {string} * @return {Number} */ Font.prototype.charToGlyphIndex = function(s) { return this.encoding.charToGlyphIndex(s); }; /** * Convert the given character to a single Glyph object. * Note that this function assumes that there is a one-to-one mapping between * the given character and a glyph; for complex scripts this might not be the case. * @param {string} * @return {opentype.Glyph} */ Font.prototype.charToGlyph = function(c) { const glyphIndex = this.charToGlyphIndex(c); let glyph = this.glyphs.get(glyphIndex); if (!glyph) { // .notdef glyph = this.glyphs.get(0); } return glyph; }; /** * Convert the given text to a list of Glyph objects. * Note that there is no strict one-to-one mapping between characters and * glyphs, so the list of returned glyphs can be larger or smaller than the * length of the given string. * @param {string} * @param {GlyphRenderOptions} [options] * @return {opentype.Glyph[]} */ Font.prototype.stringToGlyphs = function(s, options) { options = options || this.defaultRenderOptions; // Get glyph indexes const indexes = []; for (let i = 0; i < s.length; i += 1) { const c = s[i]; indexes.push(this.charToGlyphIndex(c)); } let length = indexes.length; // Apply substitutions on glyph indexes if (options.features) { const script = options.script || this.substitution.getDefaultScriptName(); let manyToOne = []; if (options.features.liga) manyToOne = manyToOne.concat(this.substitution.getFeature('liga', script, options.language)); if (options.features.rlig) manyToOne = manyToOne.concat(this.substitution.getFeature('rlig', script, options.language)); for (let i = 0; i < length; i += 1) { for (let j = 0; j < manyToOne.length; j++) { const ligature = manyToOne[j]; const components = ligature.sub; const compCount = components.length; let k = 0; while (k < compCount && components[k] === indexes[i + k]) k++; if (k === compCount) { indexes.splice(i, compCount, ligature.by); length = length - compCount + 1; } } } } // convert glyph indexes to glyph objects const glyphs = new Array(length); const notdef = this.glyphs.get(0); for (let i = 0; i < length; i += 1) { glyphs[i] = this.glyphs.get(indexes[i]) || notdef; } return glyphs; }; /** * @param {string} * @return {Number} */ Font.prototype.nameToGlyphIndex = function(name) { return this.glyphNames.nameToGlyphIndex(name); }; /** * @param {string} * @return {opentype.Glyph} */ Font.prototype.nameToGlyph = function(name) { const glyphIndex = this.nameToGlyphIndex(name); let glyph = this.glyphs.get(glyphIndex); if (!glyph) { // .notdef glyph = this.glyphs.get(0); } return glyph; }; /** * @param {Number} * @return {String} */ Font.prototype.glyphIndexToName = function(gid) { if (!this.glyphNames.glyphIndexToName) { return ''; } return this.glyphNames.glyphIndexToName(gid); }; /** * Retrieve the value of the kerning pair between the left glyph (or its index) * and the right glyph (or its index). If no kerning pair is found, return 0. * The kerning value gets added to the advance width when calculating the spacing * between glyphs. * @param {opentype.Glyph} leftGlyph * @param {opentype.Glyph} rightGlyph * @return {Number} */ Font.prototype.getKerningValue = function(leftGlyph, rightGlyph) { leftGlyph = leftGlyph.index || leftGlyph; rightGlyph = rightGlyph.index || rightGlyph; const gposKerning = this.getGposKerningValue; return gposKerning ? gposKerning(leftGlyph, rightGlyph) : (this.kerningPairs[leftGlyph + ',' + rightGlyph] || 0); }; /** * @typedef GlyphRenderOptions * @type Object * @property {string} [script] - script used to determine which features to apply. By default, 'DFLT' or 'latn' is used. * See https://www.microsoft.com/typography/otspec/scripttags.htm * @property {string} [language='dflt'] - language system used to determine which features to apply. * See https://www.microsoft.com/typography/developers/opentype/languagetags.aspx * @property {boolean} [kerning=true] - whether to include kerning values * @property {object} [features] - OpenType Layout feature tags. Used to enable or disable the features of the given script/language system. * See https://www.microsoft.com/typography/otspec/featuretags.htm */ Font.prototype.defaultRenderOptions = { kerning: true, features: { liga: true, rlig: true } }; /** * Helper function that invokes the given callback for each glyph in the given text. * The callback gets `(glyph, x, y, fontSize, options)`.* @param {string} text * @param {string} text - The text to apply. * @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 {GlyphRenderOptions=} options * @param {Function} callback */ Font.prototype.forEachGlyph = function(text, x, y, fontSize, options, callback) { x = x !== undefined ? x : 0; y = y !== undefined ? y : 0; fontSize = fontSize !== undefined ? fontSize : 72; options = options || this.defaultRenderOptions; const fontScale = 1 / this.unitsPerEm * fontSize; const glyphs = this.stringToGlyphs(text, options); for (let i = 0; i < glyphs.length; i += 1) { const glyph = glyphs[i]; callback.call(this, glyph, x, y, fontSize, options); if (glyph.advanceWidth) { x += glyph.advanceWidth * fontScale; } if (options.kerning && i < glyphs.length - 1) { const kerningValue = this.getKerningValue(glyph, glyphs[i + 1]); x += kerningValue * fontScale; } if (options.letterSpacing) { x += options.letterSpacing * fontSize; } else if (options.tracking) { x += (options.tracking / 1000) * fontSize; } } return x; }; /** * Create a Path object that represents the given text. * @param {string} text - The text to create. * @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 {GlyphRenderOptions=} options * @return {opentype.Path} */ Font.prototype.getPath = function(text, x, y, fontSize, options) { const fullPath = new Path(); this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { const glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); fullPath.extend(glyphPath); }); return fullPath; }; /** * Create an array of Path objects that represent the glyphs of a given text. * @param {string} text - The text to create. * @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 {GlyphRenderOptions=} options * @return {opentype.Path[]} */ Font.prototype.getPaths = function(text, x, y, fontSize, options) { const glyphPaths = []; this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { const glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); glyphPaths.push(glyphPath); }); return glyphPaths; }; /** * Returns the advance width of a text. * * This is something different than Path.getBoundingBox() as for example a * suffixed whitespace increases the advanceWidth but not the bounding box * or an overhanging letter like a calligraphic 'f' might have a quite larger * bounding box than its advance width. * * This corresponds to canvas2dContext.measureText(text).width * * @param {string} text - The text to create. * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. * @param {GlyphRenderOptions=} options * @return advance width */ Font.prototype.getAdvanceWidth = function(text, fontSize, options) { return this.forEachGlyph(text, 0, 0, fontSize, options, function() {}); }; /** * Draw the text on the given drawing context. * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. * @param {string} text - The text to create. * @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 {GlyphRenderOptions=} options */ Font.prototype.draw = function(ctx, text, x, y, fontSize, options) { this.getPath(text, x, y, fontSize, options).draw(ctx); }; /** * Draw the points of all glyphs in the text. * On-curve points will be drawn in blue, off-curve points will be drawn in red. * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. * @param {string} text - The text to create. * @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 {GlyphRenderOptions=} options */ Font.prototype.drawPoints = function(ctx, text, x, y, fontSize, options) { this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { glyph.drawPoints(ctx, gX, gY, gFontSize); }); }; /** * Draw lines indicating important font measurements for all glyphs in the text. * Black lines indicate the origin of the coordinate system (point 0,0). * Blue lines indicate the glyph bounding box. * Green line indicates the advance width of the glyph. * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. * @param {string} text - The text to create. * @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 {GlyphRenderOptions=} options */ Font.prototype.drawMetrics = function(ctx, text, x, y, fontSize, options) { this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { glyph.drawMetrics(ctx, gX, gY, gFontSize); }); }; /** * @param {string} * @return {string} */ Font.prototype.getEnglishName = function(name) { const translations = this.names[name]; if (translations) { return translations.en; } }; /** * Validate */ Font.prototype.validate = function() { const warnings = []; const _this = this; function assert(predicate, message) { if (!predicate) { warnings.push(message); } } function assertNamePresent(name) { const englishName = _this.getEnglishName(name); assert(englishName && englishName.trim().length > 0, 'No English ' + name + ' specified.'); } // Identification information assertNamePresent('fontFamily'); assertNamePresent('weightName'); assertNamePresent('manufacturer'); assertNamePresent('copyright'); assertNamePresent('version'); // Dimension information assert(this.unitsPerEm > 0, 'No unitsPerEm specified.'); }; /** * Convert the font object to a SFNT data structure. * This structure contains all the necessary tables and metadata to create a binary OTF file. * @return {opentype.Table} */ Font.prototype.toTables = function() { return sfnt.fontToTable(this); }; /** * @deprecated Font.toBuffer is deprecated. Use Font.toArrayBuffer instead. */ Font.prototype.toBuffer = function() { console.warn('Font.toBuffer is deprecated. Use Font.toArrayBuffer instead.'); return this.toArrayBuffer(); }; /** * Converts a `opentype.Font` into an `ArrayBuffer` * @return {ArrayBuffer} */ Font.prototype.toArrayBuffer = function() { const sfntTable = this.toTables(); const bytes = sfntTable.encode(); const buffer = new ArrayBuffer(bytes.length); const intArray = new Uint8Array(buffer); for (let i = 0; i < bytes.length; i++) { intArray[i] = bytes[i]; } return buffer; }; /** * Initiate a download of the OpenType font. */ Font.prototype.download = function(fileName) { const familyName = this.getEnglishName('fontFamily'); const styleName = this.getEnglishName('fontSubfamily'); fileName = fileName || familyName.replace(/\s/g, '') + '-' + styleName + '.otf'; const arrayBuffer = this.toArrayBuffer(); if (isBrowser()) { window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; window.requestFileSystem(window.TEMPORARY, arrayBuffer.byteLength, function(fs) { fs.root.getFile(fileName, {create: true}, function(fileEntry) { fileEntry.createWriter(function(writer) { const dataView = new DataView(arrayBuffer); const blob = new Blob([dataView], {type: 'font/opentype'}); writer.write(blob); writer.addEventListener('writeend', function() { // Navigating to the file will download it. location.href = fileEntry.toURL(); }, false); }); }); }, function(err) { throw new Error(err.name + ': ' + err.message); }); } else { const fs = require('fs'); const buffer = arrayBufferToNodeBuffer(arrayBuffer); fs.writeFileSync(fileName, buffer); } }; /** * @private */ Font.prototype.fsSelectionValues = { ITALIC: 0x001, //1 UNDERSCORE: 0x002, //2 NEGATIVE: 0x004, //4 OUTLINED: 0x008, //8 STRIKEOUT: 0x010, //16 BOLD: 0x020, //32 REGULAR: 0x040, //64 USER_TYPO_METRICS: 0x080, //128 WWS: 0x100, //256 OBLIQUE: 0x200 //512 }; /** * @private */ Font.prototype.usWidthClasses = { ULTRA_CONDENSED: 1, EXTRA_CONDENSED: 2, CONDENSED: 3, SEMI_CONDENSED: 4, MEDIUM: 5, SEMI_EXPANDED: 6, EXPANDED: 7, EXTRA_EXPANDED: 8, ULTRA_EXPANDED: 9 }; /** * @private */ Font.prototype.usWeightClasses = { THIN: 100, EXTRA_LIGHT: 200, LIGHT: 300, NORMAL: 400, MEDIUM: 500, SEMI_BOLD: 600, BOLD: 700, EXTRA_BOLD: 800, BLACK: 900 }; export default Font;