7e7e9be75404e4ed8b8ed9f4733d82e2a1cd6d0b.js
1 "use strict";var Path;module.watch(require('./path'),{default(v){Path=v}},0);var sfnt;module.watch(require('./tables/sfnt'),{default(v){sfnt=v}},1);var DefaultEncoding;module.watch(require('./encoding'),{DefaultEncoding(v){DefaultEncoding=v}},2);var glyphset;module.watch(require('./glyphset'),{default(v){glyphset=v}},3);var Substitution;module.watch(require('./substitution'),{default(v){Substitution=v}},4);var isBrowser,checkArgument,arrayBufferToNodeBuffer;module.watch(require('./util'),{isBrowser(v){isBrowser=v},checkArgument(v){checkArgument=v},arrayBufferToNodeBuffer(v){arrayBufferToNodeBuffer=v}},5);var HintingTrueType;module.watch(require('./hintingtt'),{default(v){HintingTrueType=v}},6);// The Font object 2 3 4 5 6 7 8 9 10 11 /** 12 * @typedef FontOptions 13 * @type Object 14 * @property {Boolean} empty - whether to create a new empty font 15 * @property {string} familyName 16 * @property {string} styleName 17 * @property {string=} fullName 18 * @property {string=} postScriptName 19 * @property {string=} designer 20 * @property {string=} designerURL 21 * @property {string=} manufacturer 22 * @property {string=} manufacturerURL 23 * @property {string=} license 24 * @property {string=} licenseURL 25 * @property {string=} version 26 * @property {string=} description 27 * @property {string=} copyright 28 * @property {string=} trademark 29 * @property {Number} unitsPerEm 30 * @property {Number} ascender 31 * @property {Number} descender 32 * @property {Number} createdTimestamp 33 * @property {string=} weightClass 34 * @property {string=} widthClass 35 * @property {string=} fsSelection 36 */ 37 38 /** 39 * A Font represents a loaded OpenType font file. 40 * It contains a set of glyphs and methods to draw text on a drawing context, 41 * or to get a path representing the text. 42 * @exports opentype.Font 43 * @class 44 * @param {FontOptions} 45 * @constructor 46 */ 47 function Font(options) { 48 options = options || {}; 49 50 if (!options.empty) { 51 // Check that we've provided the minimum set of names. 52 checkArgument(options.familyName, 'When creating a new Font object, familyName is required.'); 53 checkArgument(options.styleName, 'When creating a new Font object, styleName is required.'); 54 checkArgument(options.unitsPerEm, 'When creating a new Font object, unitsPerEm is required.'); 55 checkArgument(options.ascender, 'When creating a new Font object, ascender is required.'); 56 checkArgument(options.descender, 'When creating a new Font object, descender is required.'); 57 checkArgument(options.descender < 0, 'Descender should be negative (e.g. -512).'); 58 59 // OS X will complain if the names are empty, so we put a single space everywhere by default. 60 this.names = { 61 fontFamily: {en: options.familyName || ' '}, 62 fontSubfamily: {en: options.styleName || ' '}, 63 fullName: {en: options.fullName || options.familyName + ' ' + options.styleName}, 64 postScriptName: {en: options.postScriptName || options.familyName + options.styleName}, 65 designer: {en: options.designer || ' '}, 66 designerURL: {en: options.designerURL || ' '}, 67 manufacturer: {en: options.manufacturer || ' '}, 68 manufacturerURL: {en: options.manufacturerURL || ' '}, 69 license: {en: options.license || ' '}, 70 licenseURL: {en: options.licenseURL || ' '}, 71 version: {en: options.version || 'Version 0.1'}, 72 description: {en: options.description || ' '}, 73 copyright: {en: options.copyright || ' '}, 74 trademark: {en: options.trademark || ' '} 75 }; 76 this.unitsPerEm = options.unitsPerEm || 1000; 77 this.ascender = options.ascender; 78 this.descender = options.descender; 79 this.createdTimestamp = options.createdTimestamp; 80 this.tables = { os2: { 81 usWeightClass: options.weightClass || this.usWeightClasses.MEDIUM, 82 usWidthClass: options.widthClass || this.usWidthClasses.MEDIUM, 83 fsSelection: options.fsSelection || this.fsSelectionValues.REGULAR 84 } }; 85 } 86 87 this.supported = true; // Deprecated: parseBuffer will throw an error if font is not supported. 88 this.glyphs = new glyphset.GlyphSet(this, options.glyphs || []); 89 this.encoding = new DefaultEncoding(this); 90 this.substitution = new Substitution(this); 91 this.tables = this.tables || {}; 92 93 Object.defineProperty(this, 'hinting', { 94 get: function() { 95 if (this._hinting) return this._hinting; 96 if (this.outlinesFormat === 'truetype') { 97 return (this._hinting = new HintingTrueType(this)); 98 } 99 } 100 }); 101 } 102 103 /** 104 * Check if the font has a glyph for the given character. 105 * @param {string} 106 * @return {Boolean} 107 */ 108 Font.prototype.hasChar = function(c) { 109 return this.encoding.charToGlyphIndex(c) !== null; 110 }; 111 112 /** 113 * Convert the given character to a single glyph index. 114 * Note that this function assumes that there is a one-to-one mapping between 115 * the given character and a glyph; for complex scripts this might not be the case. 116 * @param {string} 117 * @return {Number} 118 */ 119 Font.prototype.charToGlyphIndex = function(s) { 120 return this.encoding.charToGlyphIndex(s); 121 }; 122 123 /** 124 * Convert the given character to a single Glyph object. 125 * Note that this function assumes that there is a one-to-one mapping between 126 * the given character and a glyph; for complex scripts this might not be the case. 127 * @param {string} 128 * @return {opentype.Glyph} 129 */ 130 Font.prototype.charToGlyph = function(c) { 131 const glyphIndex = this.charToGlyphIndex(c); 132 let glyph = this.glyphs.get(glyphIndex); 133 if (!glyph) { 134 // .notdef 135 glyph = this.glyphs.get(0); 136 } 137 138 return glyph; 139 }; 140 141 /** 142 * Convert the given text to a list of Glyph objects. 143 * Note that there is no strict one-to-one mapping between characters and 144 * glyphs, so the list of returned glyphs can be larger or smaller than the 145 * length of the given string. 146 * @param {string} 147 * @param {GlyphRenderOptions} [options] 148 * @return {opentype.Glyph[]} 149 */ 150 Font.prototype.stringToGlyphs = function(s, options) { 151 options = options || this.defaultRenderOptions; 152 // Get glyph indexes 153 const indexes = []; 154 for (let i = 0; i < s.length; i += 1) { 155 const c = s[i]; 156 indexes.push(this.charToGlyphIndex(c)); 157 } 158 let length = indexes.length; 159 160 // Apply substitutions on glyph indexes 161 if (options.features) { 162 const script = options.script || this.substitution.getDefaultScriptName(); 163 let manyToOne = []; 164 if (options.features.liga) manyToOne = manyToOne.concat(this.substitution.getFeature('liga', script, options.language)); 165 if (options.features.rlig) manyToOne = manyToOne.concat(this.substitution.getFeature('rlig', script, options.language)); 166 for (let i = 0; i < length; i += 1) { 167 for (let j = 0; j < manyToOne.length; j++) { 168 const ligature = manyToOne[j]; 169 const components = ligature.sub; 170 const compCount = components.length; 171 let k = 0; 172 while (k < compCount && components[k] === indexes[i + k]) k++; 173 if (k === compCount) { 174 indexes.splice(i, compCount, ligature.by); 175 length = length - compCount + 1; 176 } 177 } 178 } 179 } 180 181 // convert glyph indexes to glyph objects 182 const glyphs = new Array(length); 183 const notdef = this.glyphs.get(0); 184 for (let i = 0; i < length; i += 1) { 185 glyphs[i] = this.glyphs.get(indexes[i]) || notdef; 186 } 187 return glyphs; 188 }; 189 190 /** 191 * @param {string} 192 * @return {Number} 193 */ 194 Font.prototype.nameToGlyphIndex = function(name) { 195 return this.glyphNames.nameToGlyphIndex(name); 196 }; 197 198 /** 199 * @param {string} 200 * @return {opentype.Glyph} 201 */ 202 Font.prototype.nameToGlyph = function(name) { 203 const glyphIndex = this.nameToGlyphIndex(name); 204 let glyph = this.glyphs.get(glyphIndex); 205 if (!glyph) { 206 // .notdef 207 glyph = this.glyphs.get(0); 208 } 209 210 return glyph; 211 }; 212 213 /** 214 * @param {Number} 215 * @return {String} 216 */ 217 Font.prototype.glyphIndexToName = function(gid) { 218 if (!this.glyphNames.glyphIndexToName) { 219 return ''; 220 } 221 222 return this.glyphNames.glyphIndexToName(gid); 223 }; 224 225 /** 226 * Retrieve the value of the kerning pair between the left glyph (or its index) 227 * and the right glyph (or its index). If no kerning pair is found, return 0. 228 * The kerning value gets added to the advance width when calculating the spacing 229 * between glyphs. 230 * @param {opentype.Glyph} leftGlyph 231 * @param {opentype.Glyph} rightGlyph 232 * @return {Number} 233 */ 234 Font.prototype.getKerningValue = function(leftGlyph, rightGlyph) { 235 leftGlyph = leftGlyph.index || leftGlyph; 236 rightGlyph = rightGlyph.index || rightGlyph; 237 const gposKerning = this.getGposKerningValue; 238 return gposKerning ? gposKerning(leftGlyph, rightGlyph) : 239 (this.kerningPairs[leftGlyph + ',' + rightGlyph] || 0); 240 }; 241 242 /** 243 * @typedef GlyphRenderOptions 244 * @type Object 245 * @property {string} [script] - script used to determine which features to apply. By default, 'DFLT' or 'latn' is used. 246 * See https://www.microsoft.com/typography/otspec/scripttags.htm 247 * @property {string} [language='dflt'] - language system used to determine which features to apply. 248 * See https://www.microsoft.com/typography/developers/opentype/languagetags.aspx 249 * @property {boolean} [kerning=true] - whether to include kerning values 250 * @property {object} [features] - OpenType Layout feature tags. Used to enable or disable the features of the given script/language system. 251 * See https://www.microsoft.com/typography/otspec/featuretags.htm 252 */ 253 Font.prototype.defaultRenderOptions = { 254 kerning: true, 255 features: { 256 liga: true, 257 rlig: true 258 } 259 }; 260 261 /** 262 * Helper function that invokes the given callback for each glyph in the given text. 263 * The callback gets `(glyph, x, y, fontSize, options)`.* @param {string} text 264 * @param {string} text - The text to apply. 265 * @param {number} [x=0] - Horizontal position of the beginning of the text. 266 * @param {number} [y=0] - Vertical position of the *baseline* of the text. 267 * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. 268 * @param {GlyphRenderOptions=} options 269 * @param {Function} callback 270 */ 271 Font.prototype.forEachGlyph = function(text, x, y, fontSize, options, callback) { 272 x = x !== undefined ? x : 0; 273 y = y !== undefined ? y : 0; 274 fontSize = fontSize !== undefined ? fontSize : 72; 275 options = options || this.defaultRenderOptions; 276 const fontScale = 1 / this.unitsPerEm * fontSize; 277 const glyphs = this.stringToGlyphs(text, options); 278 for (let i = 0; i < glyphs.length; i += 1) { 279 const glyph = glyphs[i]; 280 callback.call(this, glyph, x, y, fontSize, options); 281 if (glyph.advanceWidth) { 282 x += glyph.advanceWidth * fontScale; 283 } 284 285 if (options.kerning && i < glyphs.length - 1) { 286 const kerningValue = this.getKerningValue(glyph, glyphs[i + 1]); 287 x += kerningValue * fontScale; 288 } 289 290 if (options.letterSpacing) { 291 x += options.letterSpacing * fontSize; 292 } else if (options.tracking) { 293 x += (options.tracking / 1000) * fontSize; 294 } 295 } 296 return x; 297 }; 298 299 /** 300 * Create a Path object that represents the given text. 301 * @param {string} text - The text to create. 302 * @param {number} [x=0] - Horizontal position of the beginning of the text. 303 * @param {number} [y=0] - Vertical position of the *baseline* of the text. 304 * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. 305 * @param {GlyphRenderOptions=} options 306 * @return {opentype.Path} 307 */ 308 Font.prototype.getPath = function(text, x, y, fontSize, options) { 309 const fullPath = new Path(); 310 this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { 311 const glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); 312 fullPath.extend(glyphPath); 313 }); 314 return fullPath; 315 }; 316 317 /** 318 * Create an array of Path objects that represent the glyphs of a given text. 319 * @param {string} text - The text to create. 320 * @param {number} [x=0] - Horizontal position of the beginning of the text. 321 * @param {number} [y=0] - Vertical position of the *baseline* of the text. 322 * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. 323 * @param {GlyphRenderOptions=} options 324 * @return {opentype.Path[]} 325 */ 326 Font.prototype.getPaths = function(text, x, y, fontSize, options) { 327 const glyphPaths = []; 328 this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { 329 const glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); 330 glyphPaths.push(glyphPath); 331 }); 332 333 return glyphPaths; 334 }; 335 336 /** 337 * Returns the advance width of a text. 338 * 339 * This is something different than Path.getBoundingBox() as for example a 340 * suffixed whitespace increases the advanceWidth but not the bounding box 341 * or an overhanging letter like a calligraphic 'f' might have a quite larger 342 * bounding box than its advance width. 343 * 344 * This corresponds to canvas2dContext.measureText(text).width 345 * 346 * @param {string} text - The text to create. 347 * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. 348 * @param {GlyphRenderOptions=} options 349 * @return advance width 350 */ 351 Font.prototype.getAdvanceWidth = function(text, fontSize, options) { 352 return this.forEachGlyph(text, 0, 0, fontSize, options, function() {}); 353 }; 354 355 /** 356 * Draw the text on the given drawing context. 357 * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. 358 * @param {string} text - The text to create. 359 * @param {number} [x=0] - Horizontal position of the beginning of the text. 360 * @param {number} [y=0] - Vertical position of the *baseline* of the text. 361 * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. 362 * @param {GlyphRenderOptions=} options 363 */ 364 Font.prototype.draw = function(ctx, text, x, y, fontSize, options) { 365 this.getPath(text, x, y, fontSize, options).draw(ctx); 366 }; 367 368 /** 369 * Draw the points of all glyphs in the text. 370 * On-curve points will be drawn in blue, off-curve points will be drawn in red. 371 * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. 372 * @param {string} text - The text to create. 373 * @param {number} [x=0] - Horizontal position of the beginning of the text. 374 * @param {number} [y=0] - Vertical position of the *baseline* of the text. 375 * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. 376 * @param {GlyphRenderOptions=} options 377 */ 378 Font.prototype.drawPoints = function(ctx, text, x, y, fontSize, options) { 379 this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { 380 glyph.drawPoints(ctx, gX, gY, gFontSize); 381 }); 382 }; 383 384 /** 385 * Draw lines indicating important font measurements for all glyphs in the text. 386 * Black lines indicate the origin of the coordinate system (point 0,0). 387 * Blue lines indicate the glyph bounding box. 388 * Green line indicates the advance width of the glyph. 389 * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. 390 * @param {string} text - The text to create. 391 * @param {number} [x=0] - Horizontal position of the beginning of the text. 392 * @param {number} [y=0] - Vertical position of the *baseline* of the text. 393 * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. 394 * @param {GlyphRenderOptions=} options 395 */ 396 Font.prototype.drawMetrics = function(ctx, text, x, y, fontSize, options) { 397 this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { 398 glyph.drawMetrics(ctx, gX, gY, gFontSize); 399 }); 400 }; 401 402 /** 403 * @param {string} 404 * @return {string} 405 */ 406 Font.prototype.getEnglishName = function(name) { 407 const translations = this.names[name]; 408 if (translations) { 409 return translations.en; 410 } 411 }; 412 413 /** 414 * Validate 415 */ 416 Font.prototype.validate = function() { 417 const warnings = []; 418 const _this = this; 419 420 function assert(predicate, message) { 421 if (!predicate) { 422 warnings.push(message); 423 } 424 } 425 426 function assertNamePresent(name) { 427 const englishName = _this.getEnglishName(name); 428 assert(englishName && englishName.trim().length > 0, 429 'No English ' + name + ' specified.'); 430 } 431 432 // Identification information 433 assertNamePresent('fontFamily'); 434 assertNamePresent('weightName'); 435 assertNamePresent('manufacturer'); 436 assertNamePresent('copyright'); 437 assertNamePresent('version'); 438 439 // Dimension information 440 assert(this.unitsPerEm > 0, 'No unitsPerEm specified.'); 441 }; 442 443 /** 444 * Convert the font object to a SFNT data structure. 445 * This structure contains all the necessary tables and metadata to create a binary OTF file. 446 * @return {opentype.Table} 447 */ 448 Font.prototype.toTables = function() { 449 return sfnt.fontToTable(this); 450 }; 451 /** 452 * @deprecated Font.toBuffer is deprecated. Use Font.toArrayBuffer instead. 453 */ 454 Font.prototype.toBuffer = function() { 455 console.warn('Font.toBuffer is deprecated. Use Font.toArrayBuffer instead.'); 456 return this.toArrayBuffer(); 457 }; 458 /** 459 * Converts a `opentype.Font` into an `ArrayBuffer` 460 * @return {ArrayBuffer} 461 */ 462 Font.prototype.toArrayBuffer = function() { 463 const sfntTable = this.toTables(); 464 const bytes = sfntTable.encode(); 465 const buffer = new ArrayBuffer(bytes.length); 466 const intArray = new Uint8Array(buffer); 467 for (let i = 0; i < bytes.length; i++) { 468 intArray[i] = bytes[i]; 469 } 470 471 return buffer; 472 }; 473 474 /** 475 * Initiate a download of the OpenType font. 476 */ 477 Font.prototype.download = function(fileName) { 478 const familyName = this.getEnglishName('fontFamily'); 479 const styleName = this.getEnglishName('fontSubfamily'); 480 fileName = fileName || familyName.replace(/\s/g, '') + '-' + styleName + '.otf'; 481 const arrayBuffer = this.toArrayBuffer(); 482 483 if (isBrowser()) { 484 window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; 485 window.requestFileSystem(window.TEMPORARY, arrayBuffer.byteLength, function(fs) { 486 fs.root.getFile(fileName, {create: true}, function(fileEntry) { 487 fileEntry.createWriter(function(writer) { 488 const dataView = new DataView(arrayBuffer); 489 const blob = new Blob([dataView], {type: 'font/opentype'}); 490 writer.write(blob); 491 492 writer.addEventListener('writeend', function() { 493 // Navigating to the file will download it. 494 location.href = fileEntry.toURL(); 495 }, false); 496 }); 497 }); 498 }, 499 function(err) { 500 throw new Error(err.name + ': ' + err.message); 501 }); 502 } else { 503 const fs = require('fs'); 504 const buffer = arrayBufferToNodeBuffer(arrayBuffer); 505 fs.writeFileSync(fileName, buffer); 506 } 507 }; 508 /** 509 * @private 510 */ 511 Font.prototype.fsSelectionValues = { 512 ITALIC: 0x001, //1 513 UNDERSCORE: 0x002, //2 514 NEGATIVE: 0x004, //4 515 OUTLINED: 0x008, //8 516 STRIKEOUT: 0x010, //16 517 BOLD: 0x020, //32 518 REGULAR: 0x040, //64 519 USER_TYPO_METRICS: 0x080, //128 520 WWS: 0x100, //256 521 OBLIQUE: 0x200 //512 522 }; 523 524 /** 525 * @private 526 */ 527 Font.prototype.usWidthClasses = { 528 ULTRA_CONDENSED: 1, 529 EXTRA_CONDENSED: 2, 530 CONDENSED: 3, 531 SEMI_CONDENSED: 4, 532 MEDIUM: 5, 533 SEMI_EXPANDED: 6, 534 EXPANDED: 7, 535 EXTRA_EXPANDED: 8, 536 ULTRA_EXPANDED: 9 537 }; 538 539 /** 540 * @private 541 */ 542 Font.prototype.usWeightClasses = { 543 THIN: 100, 544 EXTRA_LIGHT: 200, 545 LIGHT: 300, 546 NORMAL: 400, 547 MEDIUM: 500, 548 SEMI_BOLD: 600, 549 BOLD: 700, 550 EXTRA_BOLD: 800, 551 BLACK: 900 552 }; 553 554 module.exportDefault(Font);