├── .npmignore ├── bin ├── start_server └── ot ├── .gitignore ├── g └── hello-world.png ├── fonts ├── Roboto-Black.ttf ├── FiraSansOT-Medium.otf └── SourceSansPro-Regular.otf ├── bower.json ├── Gruntfile.js ├── package.json ├── LICENSE ├── README.md ├── index.html └── opentype.js /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | index.html 4 | fonts/ 5 | g/ 6 | -------------------------------------------------------------------------------- /bin/start_server: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | python -m SimpleHTTPServer 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | .DS_Store 4 | /node_modules 5 | /build 6 | -------------------------------------------------------------------------------- /g/hello-world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/opentype.js/master/g/hello-world.png -------------------------------------------------------------------------------- /fonts/Roboto-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/opentype.js/master/fonts/Roboto-Black.ttf -------------------------------------------------------------------------------- /fonts/FiraSansOT-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/opentype.js/master/fonts/FiraSansOT-Medium.otf -------------------------------------------------------------------------------- /fonts/SourceSansPro-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/opentype.js/master/fonts/SourceSansPro-Regular.otf -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opentype.js", 3 | "description": "OpenType font parser", 4 | "version": "0.2.0", 5 | "main": "opentype.js", 6 | "ignore": [ 7 | "**/.*", 8 | "fonts", 9 | "bin", 10 | "g", 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*globals module,grunt*/ 2 | module.exports = function (grunt) { 3 | 'use strict'; 4 | 5 | // Project configuration. 6 | grunt.initConfig({ 7 | pkg: grunt.file.readJSON('package.json'), 8 | uglify: { 9 | options: { 10 | banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n' 11 | }, 12 | build: { 13 | src: 'opentype.js', 14 | dest: 'build/opentype.min.js' 15 | } 16 | } 17 | }); 18 | 19 | // Load plugins. 20 | grunt.loadNpmTasks('grunt-contrib-uglify'); 21 | grunt.loadNpmTasks('grunt-release'); 22 | 23 | // Default task(s). 24 | grunt.registerTask('default', ['uglify']); 25 | 26 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opentype.js", 3 | "description": "OpenType font parser", 4 | "version": "0.2.0", 5 | "author": { 6 | "name": "Frederik De Bleser", 7 | "email": "frederik@burocrazy.com" 8 | }, 9 | "keywords": [ 10 | "graphics", 11 | "fonts", 12 | "type" 13 | ], 14 | "licenses": [ 15 | { 16 | "type": "MIT", 17 | "url": "https://raw.github.com/nodebox/opentype.js/master/LICENSE" 18 | } 19 | ], 20 | "repository": { 21 | "type": "git", 22 | "url": "git://github.com/nodebox/opentype.js.git" 23 | }, 24 | "dependencies": {}, 25 | "main": "opentype.js", 26 | "bin": { 27 | "ot": "./bin/ot" 28 | }, 29 | "devDependencies": { 30 | "grunt": "~0.4.2", 31 | "grunt-contrib-uglify": "~0.3.2", 32 | "grunt-release": "~0.6.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Frederik De Bleser 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /bin/ot: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var opentype = require('../opentype.js'); 6 | 7 | // Print out information about the font on the console. 8 | function printFontInfo(font) { 9 | console.log(' glyphs:', font.glyphs.length); 10 | console.log(' kerning pairs:', Object.keys(font.kerningPairs).length); 11 | } 12 | 13 | // Recursively walk a directory and execute the function for every file. 14 | function walk(dir, fn) { 15 | var files, i, file; 16 | files = fs.readdirSync(dir); 17 | for (i = 0; i < files.length; i += 1) { 18 | file = files[i]; 19 | var fullName = path.join(dir, file); 20 | var stat = fs.statSync(fullName); 21 | if (stat.isFile()) { 22 | fn(fullName); 23 | } else if (stat.isDirectory()) { 24 | walk(fullName, fn); 25 | } 26 | } 27 | } 28 | 29 | // Print out usage information. 30 | function printUsage() { 31 | console.log('Usage: ot command [dir]'); 32 | console.log(); 33 | console.log('Commands:'); 34 | console.log(); 35 | console.log(' info Get information of fonts in the specified directory.'); 36 | console.log(); 37 | } 38 | 39 | function recursiveInfo(fontDirectory) { 40 | walk(fontDirectory, function (file) { 41 | var ext = path.extname(file).toLowerCase(); 42 | if (ext === '.ttf' || ext === '.otf') { 43 | opentype.load(file, function (err, font) { 44 | console.log(path.basename(file)); 45 | if (err) { 46 | console.log(' (Error: ' + error + ')'); 47 | } else if (!font.supported) { 48 | console.log(' (Unsupported)'); 49 | } else { 50 | printFontInfo(font); 51 | } 52 | }); 53 | } 54 | }); 55 | } 56 | 57 | if (process.argv.length < 3) { 58 | printUsage(); 59 | } else { 60 | var command = process.argv[2]; 61 | if (command === 'info') { 62 | var dir = process.argv.length === 3 ? '.' : process.argv[3]; 63 | recursiveInfo(dir); 64 | } else { 65 | printUsage(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | opentype.js 2 | =========== 3 | opentype.js is a JavaScript parser for TrueType and OpenType fonts. 4 | 5 | It gives you access to the letterforms of text from the browser or node.js. 6 | 7 | ![Example of opentype.js](https://raw.github.com/nodebox/opentype.js/master/g/hello-world.png) 8 | 9 | Here's an example. We load a font, then display it on a canvas with id "canvas": 10 | 11 | opentype.load('fonts/Roboto-Black.ttf', function (err, font) { 12 | if (err) { 13 | alert('Font could not be loaded: ' + err); 14 | } else { 15 | var ctx = document.getElementById('canvas').getContext('2d'); 16 | // Construct a Path object containing the letter shapes of the given text. 17 | // The other parameters are x, y and fontSize. 18 | // Note that y is the position of the baseline. 19 | var path = font.getPath('Hello, World!', 0, 150, 72); 20 | // If you just want to draw the text you can also use font.draw(ctx, text, x, y, fontSize). 21 | path.draw(ctx); 22 | } 23 | } 24 | 25 | See [the project website](http://nodebox.github.io/opentype.js/) for a live demo. 26 | 27 | Features 28 | ======== 29 | * Create a bézier path out of a piece of text. 30 | * Support for composite glyphs (accented letters). 31 | * Support for OpenType (glyf) and PostScript (cff) shapes. 32 | * Kerning support for OpenType shapes (configurable and on by default). 33 | * Very efficient. 34 | * Runs in the browser and node.js. 35 | 36 | API 37 | === 38 | ##### `opentype.load(url, callback)` 39 | Load the font from the url and execute the callback. The callback gets `(err, font)` where `font` is a Font object. Check if the `err` is null before using the font. 40 | 41 | ##### `opentype.parse(buffer)` 42 | Parse an `ArrayBuffer` containing OpenType font data and return a `Font` object. This method always returns a Font, but check font.supported to see if the font is in a supported format. The most common cause for unsupported fonts are fonts with Postscript outlines, which we do not yet support. 43 | 44 | #### The Font object 45 | 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. 46 | 47 | * `glyphs`: an indexed list of Glyph objects. 48 | * `unitsPerEm`: X/Y coordinates in fonts are stored as integers. This value determines the size of the grid. Common values are 2048 and 4096. 49 | 50 | ##### `Font.getPath(text, x, y, fontSize, options)` 51 | Create a Path that represents the given text. 52 | * `x`: Horizontal position of the beginning of the text. (default: 0) 53 | * `y`: Vertical position of the *baseline* of the text. (default: 0) 54 | * `fontSize`: Size of the text in pixels (default: 72). 55 | 56 | Options is an optional object containing: 57 | * `kerning`: if true takes kerning information into account (default: true) 58 | 59 | 60 | ##### `Font.draw(ctx, text, x, y, fontSize, options)` 61 | Create a Path that represents the given text. 62 | * `ctx`: A 2D drawing context, like Canvas. 63 | * `x`: Horizontal position of the beginning of the text. (default: 0) 64 | * `y`: Vertical position of the *baseline* of the text. (default: 0) 65 | * `fontSize`: Size of the text in pixels (default: 72). 66 | 67 | Options is an optional object containing: 68 | * `kerning`: if true takes kerning information into account (default: true) 69 | 70 | ##### `Font.drawPoints(ctx, text, x, y, fontSize, options)` 71 | 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. The arguments are the same as `Font.draw`. 72 | 73 | ##### `Font.drawMetrics(ctx, text, x, y, fontSize, options)` 74 | Draw lines indicating important font measurements for all glyphs in the text. 75 | Black lines indicate the origin of the coordinate system (point 0,0). 76 | Blue lines indicate the glyph bounding box. 77 | Green line indicates the advance width of the glyph. 78 | 79 | ##### `Font.stringToGlyphs(string)` 80 | Convert the string to a list of glyph objects. 81 | Note that there is no strict 1-to-1 correspondence between the string and glyph list due to 82 | possible substitutions such as ligatures. The list of returned glyphs can be larger or smaller than the length of the given string. 83 | 84 | ##### `Font.charToGlyph(char)` 85 | Convert the character to a `Glyph` object. Returns null if the glyph could not be found. 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. 86 | 87 | ##### `Font.getKerningValue(leftGlyph, rightGlyph)` 88 | Retrieve the value of the [kerning pair](https://en.wikipedia.org/wiki/Kerning) 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. 89 | 90 | #### The Glyph object 91 | 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. 92 | 93 | * `font`: A reference to the `Font` object. 94 | * `index`: The index number of the glyph. 95 | * `xMin`, `yMin`, `xMax`, `yMax`: The bounding box of the glyph. 96 | * `points`: The list of points in the glyph. Note that it's more convenient to use `glyph.getPath`. 97 | 98 | ##### `Glyph.getPath(x, y, fontSize)` 99 | Convert the glyph to a Path we can draw on a drawing context. 100 | * `x`: Horizontal position of the glyph. (default: 0) 101 | * `y`: Vertical position of the *baseline* of the glyph. (default: 0) 102 | * `fontSize`: Font size in pixels (default: 72). 103 | 104 | ##### `Glyph.draw(ctx, x, y, fontSize)` 105 | Draw the glyph on the given context. 106 | * `ctx`: The drawing context. 107 | * `x`: Horizontal position of the glyph. (default: 0) 108 | * `y`: Vertical position of the *baseline* of the glyph. (default: 0) 109 | * `fontSize`: Font size, in pixels (default: 72). 110 | 111 | ##### `Glyph.drawPoints(ctx, x, y, fontSize)` 112 | Draw the points of the glyph on the given context. 113 | On-curve points will be drawn in blue, off-curve points will be drawn in red. 114 | The arguments are the same as `Glyph.draw`. 115 | 116 | ##### `Glyph.drawMetrics(ctx, x, y, fontSize)` 117 | Draw lines indicating important font measurements for all glyphs in the text. 118 | Black lines indicate the origin of the coordinate system (point 0,0). 119 | Blue lines indicate the glyph bounding box. 120 | Green line indicates the advance width of the glyph. 121 | The arguments are the same as `Glyph.draw`. 122 | 123 | 124 | Planned 125 | ======= 126 | * Kerning support for PostScript shapes. 127 | * Better support for composite glyphs (advanced scaling and transformations). 128 | * Support for ligatures and contextual alternates. 129 | * Support for SVG paths. 130 | 131 | Thanks 132 | ====== 133 | I would like to acknowledge the work of others withouth which opentype.js wouldn't be possible: 134 | 135 | * [pdf.js](http://mozilla.github.io/pdf.js/): for an awesome implementation of font parsing in the browser. 136 | * [FreeType](http://www.freetype.org/): for the nitty-gritty details and filling in the gaps when the spec was incomplete. 137 | * [ttf.js](http://ynakajima.github.io/ttf.js/demo/glyflist/): for hints about the TrueType parsing code. 138 | * [Microsoft Typography](https://www.microsoft.com/typography/OTSPEC/otff.htm): the go-to reference for all things OpenType. 139 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | opentype.js – JavaScript parser for OpenType and TrueType fonts. 5 | 6 | 7 | 8 | 137 | 138 | 139 | 140 | 141 |
142 |
143 |

opentype.js

144 | Fork me on GitHub 145 |
146 |
147 | 148 |
149 | 150 |
151 | opentype.js is an OpenType and TrueType font parser. 152 | It allows you to access the letterforms of text from the browser or node.js. 153 |
154 | 155 | 156 | Roboto-Black 157 | 158 |
159 | 161 | 162 | 163 | 164 | 165 | 166 |
167 | 168 |
169 | Once you have the shapes, you can modify them, for example by snapping them to a virtual grid: 170 |
171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 |
179 | 180 |
181 |

Glyphs

182 | opentype.js provides you with raw access to the glyphs so you can modify them as you please. 183 |
184 | 185 |
186 |
187 |
Only the first 100 glyphs are shown.
188 | 189 |
190 | 191 |
192 |

Free Software

193 |

opentype.js is available on GitHub under the MIT License.

194 |

Copyright © 2014 Frederik De Bleser.

195 |
196 | 197 |
198 |
199 | 200 | 201 | 390 | 391 | 392 | -------------------------------------------------------------------------------- /opentype.js: -------------------------------------------------------------------------------- 1 | // opentype.js 2 | // https://github.com/nodebox/opentype.js 3 | // (c) 2014 Frederik De Bleser 4 | // opentype.js may be freely distributed under the MIT license. 5 | 6 | /*jslint bitwise: true */ 7 | /*global module,define,DataView,XMLHttpRequest,require,toArrayBuffer,ArrayBuffer,Uint8Array */ 8 | (function () { 9 | 'use strict'; 10 | 11 | var root, opentype, getCard8, getCard16, typeOffsets, cffStandardStrings, 12 | cffStandardEncoding, cffExpertEncoding, fs; 13 | 14 | // Establish the root object, `window` in the browser or `exports` on the server. 15 | root = this; 16 | 17 | // The exported object / namespace. 18 | opentype = {}; 19 | 20 | // Precondition function that checks if the given predicate is true. 21 | // If not, it will log an error message to the console. 22 | function checkArgument(predicate, message) { 23 | if (!predicate) { 24 | throw new Error(message); 25 | } 26 | } 27 | 28 | // Path ///////////////////////////////////////////////////////////////// 29 | 30 | // A bézier path containing a set of path commands similar to a SVG path. 31 | // Paths can be drawn on a context using `draw`. 32 | function Path() { 33 | this.commands = []; 34 | this.fill = 'black'; 35 | this.stroke = null; 36 | this.strokeWidth = 1; 37 | } 38 | 39 | Path.prototype.moveTo = function (x, y) { 40 | this.commands.push({type: 'M', x: x, y: y}); 41 | }; 42 | 43 | Path.prototype.lineTo = function (x, y) { 44 | this.commands.push({type: 'L', x: x, y: y}); 45 | }; 46 | 47 | Path.prototype.curveTo = Path.prototype.bezierCurveTo = function (x1, y1, x2, y2, x, y) { 48 | this.commands.push({type: 'C', x1: x1, y1: y1, x2: x2, y2: y2, x: x, y: y}); 49 | }; 50 | 51 | Path.prototype.quadTo = Path.prototype.quadraticCurveTo = function (x1, y1, x, y) { 52 | this.commands.push({type: 'Q', x1: x1, y1: y1, x: x, y: y}); 53 | }; 54 | 55 | Path.prototype.close = Path.prototype.closePath = function () { 56 | this.commands.push({type: 'Z'}); 57 | }; 58 | 59 | // Add the given path or list of commands to the commands of this path. 60 | Path.prototype.extend = function (pathOrCommands) { 61 | if (pathOrCommands.commands) { 62 | pathOrCommands = pathOrCommands.commands; 63 | } 64 | Array.prototype.push.apply(this.commands, pathOrCommands); 65 | }; 66 | 67 | // Draw the path to a 2D context. 68 | Path.prototype.draw = function (ctx) { 69 | var i, cmd; 70 | ctx.beginPath(); 71 | for (i = 0; i < this.commands.length; i += 1) { 72 | cmd = this.commands[i]; 73 | if (cmd.type === 'M') { 74 | ctx.moveTo(cmd.x, cmd.y); 75 | } else if (cmd.type === 'L') { 76 | ctx.lineTo(cmd.x, cmd.y); 77 | } else if (cmd.type === 'C') { 78 | ctx.bezierCurveTo(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); 79 | } else if (cmd.type === 'Q') { 80 | ctx.quadraticCurveTo(cmd.x1, cmd.y1, cmd.x, cmd.y); 81 | } else if (cmd.type === 'Z') { 82 | ctx.closePath(); 83 | } 84 | } 85 | if (this.fill) { 86 | ctx.fillStyle = this.fill; 87 | ctx.fill(); 88 | } 89 | if (this.stroke) { 90 | ctx.strokeStyle = this.stroke; 91 | ctx.lineWidth = this.strokeWidth; 92 | ctx.stroke(); 93 | } 94 | }; 95 | 96 | // Draw a line on the given context from point `x1,y1` to point `x2,y2`. 97 | function line(ctx, x1, y1, x2, y2) { 98 | ctx.beginPath(); 99 | ctx.moveTo(x1, y1); 100 | ctx.lineTo(x2, y2); 101 | ctx.stroke(); 102 | } 103 | 104 | // Parsing utility functions //////////////////////////////////////////// 105 | 106 | // Retrieve an unsigned byte from the DataView. 107 | function getByte(dataView, offset) { 108 | return dataView.getUint8(offset); 109 | } 110 | 111 | getCard8 = getByte; 112 | 113 | // Retrieve an unsigned 16-bit short from the DataView. 114 | // The value is stored in big endian. 115 | function getUShort(dataView, offset) { 116 | return dataView.getUint16(offset, false); 117 | } 118 | 119 | getCard16 = getUShort; 120 | 121 | // Retrieve a signed 16-bit short from the DataView. 122 | // The value is stored in big endian. 123 | function getShort(dataView, offset) { 124 | return dataView.getInt16(offset, false); 125 | } 126 | 127 | // Retrieve an unsigned 32-bit long from the DataView. 128 | // The value is stored in big endian. 129 | function getULong(dataView, offset) { 130 | return dataView.getUint32(offset, false); 131 | } 132 | 133 | // Retrieve a 32-bit signed fixed-point number (16.16) from the DataView. 134 | // The value is stored in big endian. 135 | function getFixed(dataView, offset) { 136 | var decimal, fraction; 137 | decimal = dataView.getInt16(offset, false); 138 | fraction = dataView.getUint16(offset + 2, false); 139 | return decimal + fraction / 65535; 140 | } 141 | 142 | // Retrieve a date-time from the DataView. 143 | function getLongDateTime(dataView, offset) { 144 | var v1, v2; 145 | v1 = dataView.getUint32(offset, false); 146 | v2 = dataView.getUint32(offset + 1, false); 147 | return [v1, v2]; 148 | } 149 | 150 | // Retrieve a 4-character tag from the DataView. 151 | // Tags are used to identify tables. 152 | function getTag(dataView, offset) { 153 | var tag = '', i; 154 | for (i = offset; i < offset + 4; i += 1) { 155 | tag += String.fromCharCode(dataView.getInt8(i)); 156 | } 157 | return tag; 158 | } 159 | 160 | // Retrieve an offset from the DataView. 161 | // Offsets are 1 to 4 bytes in length, depending on the offSize argument. 162 | function getOffset(dataView, offset, offSize) { 163 | var i, v; 164 | v = 0; 165 | for (i = 0; i < offSize; i += 1) { 166 | v <<= 8; 167 | v += dataView.getUint8(offset + i); 168 | } 169 | return v; 170 | } 171 | 172 | // Retrieve a number of bytes from start offset to the end offset from the DataView. 173 | function getBytes(dataView, startOffset, endOffset) { 174 | var bytes, i; 175 | bytes = []; 176 | for (i = startOffset; i < endOffset; i += 1) { 177 | bytes.push(dataView.getUint8(i)); 178 | } 179 | return bytes; 180 | } 181 | 182 | // Convert the list of bytes to a string. 183 | function bytesToString(bytes) { 184 | var s, i; 185 | s = ''; 186 | for (i = 0; i < bytes.length; i += 1) { 187 | s += String.fromCharCode(bytes[i]); 188 | } 189 | return s; 190 | } 191 | 192 | typeOffsets = { 193 | byte: 1, 194 | uShort: 2, 195 | short: 2, 196 | uLong: 4, 197 | fixed: 4, 198 | longDateTime: 8, 199 | tag: 4 200 | }; 201 | 202 | // Return true if the value at the given bit index is set. 203 | function isBitSet(b, bitIndex) { 204 | return ((b >> bitIndex) & 1) === 1; 205 | } 206 | 207 | // A stateful parser that changes the offset whenever a value is retrieved. 208 | // The data can be either a DataView or an array of bytes. 209 | function Parser(data, offset) { 210 | this.data = data; 211 | this.isDataView = data.constructor.name === 'DataView'; 212 | this.offset = offset; 213 | this.relativeOffset = 0; 214 | } 215 | 216 | Parser.prototype.parseByte = function () { 217 | var v; 218 | if (this.isDataView) { 219 | v = getByte(this.data, this.offset + this.relativeOffset); 220 | } else { 221 | v = this.data[this.offset + this.relativeOffset]; 222 | } 223 | this.relativeOffset += 1; 224 | return v; 225 | }; 226 | Parser.prototype.parseCard8 = Parser.prototype.parseByte; 227 | 228 | Parser.prototype.parseUShort = function () { 229 | var v = getUShort(this.data, this.offset + this.relativeOffset); 230 | this.relativeOffset += 2; 231 | return v; 232 | }; 233 | Parser.prototype.parseCard16 = Parser.prototype.parseUShort; 234 | Parser.prototype.parseSID = Parser.prototype.parseUShort; 235 | 236 | Parser.prototype.parseShort = function () { 237 | var v = getShort(this.data, this.offset + this.relativeOffset); 238 | this.relativeOffset += 2; 239 | return v; 240 | }; 241 | 242 | Parser.prototype.parseULong = function () { 243 | var v = getULong(this.data, this.offset + this.relativeOffset); 244 | this.relativeOffset += 4; 245 | return v; 246 | }; 247 | 248 | Parser.prototype.skip = function (type, amount) { 249 | if (amount === undefined) { 250 | amount = 1; 251 | } 252 | this.relativeOffset += typeOffsets[type] * amount; 253 | }; 254 | 255 | // Encoding objects ///////////////////////////////////////////////////// 256 | 257 | function CmapEncoding(cmap) { 258 | this.cmap = cmap; 259 | } 260 | 261 | CmapEncoding.prototype.charToGlyphIndex = function (s) { 262 | var ranges, code, l, c, r; 263 | ranges = this.cmap; 264 | code = s.charCodeAt(0); 265 | l = 0; 266 | r = ranges.length - 1; 267 | while (l < r) { 268 | c = (l + r + 1) >> 1; 269 | if (code < ranges[c].start) { 270 | r = c - 1; 271 | } else { 272 | l = c; 273 | } 274 | } 275 | if (ranges[l].start <= code && code <= ranges[l].end) { 276 | return (ranges[l].idDelta + (ranges[l].ids ? ranges[l].ids[code - ranges[l].start] : code)) & 0xFFFF; 277 | } 278 | return 0; 279 | }; 280 | 281 | function CffEncoding(encoding, charset) { 282 | this.encoding = encoding; 283 | this.charset = charset; 284 | } 285 | 286 | CffEncoding.prototype.charToGlyphIndex = function (s) { 287 | var code, charName; 288 | code = s.charCodeAt(0); 289 | charName = this.encoding[code]; 290 | return this.charset.indexOf(charName); 291 | }; 292 | 293 | // Glyph object ///////////////////////////////////////////////////////// 294 | 295 | // A Glyph is an individual mark that often corresponds to a character. 296 | // Some glyphs, such as ligatures, are a combination of many characters. 297 | // Glyphs are the basic building blocks of a font. 298 | // 299 | // The `Glyph` class is an abstract object that contains utility methods for drawing the path and its points. 300 | // Concrete classes are `TrueTypeGlyph` and `CffGlyph` that implement `getPath`. 301 | function Glyph() { 302 | } 303 | 304 | // Draw the glyph on the given context. 305 | // 306 | // ctx - The drawing context. 307 | // x - Horizontal position of the glyph. (default: 0) 308 | // y - Vertical position of the *baseline* of the glyph. (default: 0) 309 | // fontSize - Font size, in pixels (default: 72). 310 | Glyph.prototype.draw = function (ctx, x, y, fontSize) { 311 | this.getPath(x, y, fontSize).draw(ctx); 312 | }; 313 | 314 | // Draw the points of the glyph. 315 | // On-curve points will be drawn in blue, off-curve points will be drawn in red. 316 | // 317 | // ctx - The drawing context. 318 | // x - Horizontal position of the glyph. (default: 0) 319 | // y - Vertical position of the *baseline* of the glyph. (default: 0) 320 | // fontSize - Font size, in pixels (default: 72). 321 | Glyph.prototype.drawPoints = function (ctx, x, y, fontSize) { 322 | 323 | function drawCircles(l, x, y, scale) { 324 | var j, PI_SQ = Math.PI * 2; 325 | ctx.beginPath(); 326 | for (j = 0; j < l.length; j += 1) { 327 | ctx.moveTo(x + (l[j].x * scale), y + (-l[j].y * scale)); 328 | ctx.arc(x + (l[j].x * scale), y + (-l[j].y * scale), 2, 0, PI_SQ, false); 329 | } 330 | ctx.closePath(); 331 | ctx.fill(); 332 | } 333 | 334 | var scale, points, i, pt, blueCircles, redCircles, path, cmd; 335 | x = x !== undefined ? x : 0; 336 | y = y !== undefined ? y : 0; 337 | fontSize = fontSize !== undefined ? fontSize : 24; 338 | scale = 1 / this.font.unitsPerEm * fontSize; 339 | 340 | blueCircles = []; 341 | redCircles = []; 342 | if (this.points) { 343 | points = this.points; 344 | for (i = 0; i < points.length; i += 1) { 345 | pt = points[i]; 346 | if (pt.onCurve) { 347 | blueCircles.push(pt); 348 | } else { 349 | redCircles.push(pt); 350 | } 351 | } 352 | } else { 353 | path = this.path; 354 | for (i = 0; i < path.commands.length; i += 1) { 355 | cmd = path.commands[i]; 356 | if (cmd.x !== undefined) { 357 | blueCircles.push({x: cmd.x, y: -cmd.y}); 358 | } 359 | if (cmd.x1 !== undefined) { 360 | redCircles.push({x: cmd.x1, y: -cmd.y1}); 361 | } 362 | if (cmd.x2 !== undefined) { 363 | redCircles.push({x: cmd.x2, y: -cmd.y2}); 364 | } 365 | } 366 | } 367 | 368 | ctx.fillStyle = 'blue'; 369 | drawCircles(blueCircles, x, y, scale); 370 | ctx.fillStyle = 'red'; 371 | drawCircles(redCircles, x, y, scale); 372 | }; 373 | 374 | // Draw lines indicating important font measurements. 375 | // Black lines indicate the origin of the coordinate system (point 0,0). 376 | // Blue lines indicate the glyph bounding box. 377 | // Green line indicates the advance width of the glyph. 378 | // 379 | // ctx - The drawing context. 380 | // x - Horizontal position of the glyph. (default: 0) 381 | // y - Vertical position of the *baseline* of the glyph. (default: 0) 382 | // fontSize - Font size, in pixels (default: 72). 383 | Glyph.prototype.drawMetrics = function (ctx, x, y, fontSize) { 384 | var scale; 385 | x = x !== undefined ? x : 0; 386 | y = y !== undefined ? y : 0; 387 | fontSize = fontSize !== undefined ? fontSize : 24; 388 | scale = 1 / this.font.unitsPerEm * fontSize; 389 | ctx.lineWidth = 1; 390 | // Draw the origin 391 | ctx.strokeStyle = 'black'; 392 | line(ctx, x, -10000, x, 10000); 393 | line(ctx, -10000, y, 10000, y); 394 | // Draw the glyph box 395 | ctx.strokeStyle = 'blue'; 396 | line(ctx, x + (this.xMin * scale), -10000, x + (this.xMin * scale), 10000); 397 | line(ctx, x + (this.xMax * scale), -10000, x + (this.xMax * scale), 10000); 398 | line(ctx, -10000, y + (-this.yMin * scale), 10000, y + (-this.yMin * scale)); 399 | line(ctx, -10000, y + (-this.yMax * scale), 10000, y + (-this.yMax * scale)); 400 | // Draw the advance width 401 | ctx.strokeStyle = 'green'; 402 | line(ctx, x + (this.advanceWidth * scale), -10000, x + (this.advanceWidth * scale), 10000); 403 | }; 404 | 405 | // A concrete implementation of glyph for TrueType outline data. 406 | function TrueTypeGlyph(font, index) { 407 | Glyph.call(this); 408 | this.font = font; 409 | this.index = index; 410 | this.numberOfContours = 0; 411 | this.xMin = this.yMin = this.xMax = this.yMax = 0; 412 | this.advanceWidth = 0; 413 | this.points = []; 414 | } 415 | 416 | TrueTypeGlyph.prototype = new Glyph(); 417 | TrueTypeGlyph.prototype.constructor = TrueTypeGlyph; 418 | 419 | // Split the glyph into contours. 420 | TrueTypeGlyph.prototype.getContours = function () { 421 | var contours, currentContour, i, pt; 422 | contours = []; 423 | currentContour = []; 424 | for (i = 0; i < this.points.length; i += 1) { 425 | pt = this.points[i]; 426 | currentContour.push(pt); 427 | if (pt.lastPointOfContour) { 428 | contours.push(currentContour); 429 | currentContour = []; 430 | } 431 | } 432 | checkArgument(currentContour.length === 0, "There are still points left in the current contour."); 433 | return contours; 434 | }; 435 | 436 | // Convert the glyph to a Path we can draw on a drawing context. 437 | // 438 | // x - Horizontal position of the glyph. (default: 0) 439 | // y - Vertical position of the *baseline* of the glyph. (default: 0) 440 | // fontSize - Font size, in pixels (default: 72). 441 | TrueTypeGlyph.prototype.getPath = function (x, y, fontSize) { 442 | var scale, path, contours, i, realFirstPoint, j, contour, pt, firstPt, 443 | prevPt, midPt, curvePt, lastPt; 444 | x = x !== undefined ? x : 0; 445 | y = y !== undefined ? y : 0; 446 | fontSize = fontSize !== undefined ? fontSize : 72; 447 | scale = 1 / this.font.unitsPerEm * fontSize; 448 | path = new Path(); 449 | if (!this.points) { 450 | return path; 451 | } 452 | contours = this.getContours(); 453 | for (i = 0; i < contours.length; i += 1) { 454 | contour = contours[i]; 455 | firstPt = contour[0]; 456 | lastPt = contour[contour.length - 1]; 457 | if (firstPt.onCurve) { 458 | curvePt = null; 459 | // The first point will be consumed by the moveTo command, 460 | // so skip it in the loop. 461 | realFirstPoint = true; 462 | } else { 463 | if (lastPt.onCurve) { 464 | // If the first point is off-curve and the last point is on-curve, 465 | // start at the last point. 466 | firstPt = lastPt; 467 | } else { 468 | // If both first and last points are off-curve, start at their middle. 469 | firstPt = { x: (firstPt.x + lastPt.x) / 2, y: (firstPt.y + lastPt.y) / 2 }; 470 | } 471 | curvePt = firstPt; 472 | // The first point is synthesized, so don't skip the real first point. 473 | realFirstPoint = false; 474 | } 475 | path.moveTo(x + (firstPt.x * scale), y + (-firstPt.y * scale)); 476 | 477 | for (j = realFirstPoint ? 1 : 0; j < contour.length; j += 1) { 478 | pt = contour[j]; 479 | prevPt = j === 0 ? firstPt : contour[j - 1]; 480 | if (prevPt.onCurve && pt.onCurve) { 481 | // This is a straight line. 482 | path.lineTo(x + (pt.x * scale), y + (-pt.y * scale)); 483 | } else if (prevPt.onCurve && !pt.onCurve) { 484 | curvePt = pt; 485 | } else if (!prevPt.onCurve && !pt.onCurve) { 486 | midPt = { x: (prevPt.x + pt.x) / 2, y: (prevPt.y + pt.y) / 2 }; 487 | path.quadraticCurveTo(x + (prevPt.x * scale), y + (-prevPt.y * scale), x + (midPt.x * scale), y + (-midPt.y * scale)); 488 | curvePt = pt; 489 | } else if (!prevPt.onCurve && pt.onCurve) { 490 | // Previous point off-curve, this point on-curve. 491 | path.quadraticCurveTo(x + (curvePt.x * scale), y + (-curvePt.y * scale), x + (pt.x * scale), y + (-pt.y * scale)); 492 | curvePt = null; 493 | } else { 494 | throw new Error("Invalid state."); 495 | } 496 | } 497 | if (firstPt !== lastPt) { 498 | // Connect the last and first points 499 | if (curvePt) { 500 | path.quadraticCurveTo(x + (curvePt.x * scale), y + (-curvePt.y * scale), x + (firstPt.x * scale), y + (-firstPt.y * scale)); 501 | } else { 502 | path.lineTo(x + (firstPt.x * scale), y + (-firstPt.y * scale)); 503 | } 504 | } 505 | } 506 | path.closePath(); 507 | return path; 508 | }; 509 | 510 | // A concrete implementation of glyph for TrueType outline data. 511 | function CffGlyph(font, index) { 512 | Glyph.call(this); 513 | this.font = font; 514 | this.index = index; 515 | this.numberOfContours = 0; 516 | this.xMin = this.yMin = this.xMax = this.yMax = 0; 517 | this.advanceWidth = font.defaultWidthX; 518 | this.path = null; 519 | } 520 | 521 | CffGlyph.prototype = new Glyph(); 522 | CffGlyph.prototype.constructor = CffGlyph; 523 | 524 | // Convert the glyph to a Path we can draw on a drawing context. 525 | // 526 | // x - Horizontal position of the glyph. (default: 0) 527 | // y - Vertical position of the *baseline* of the glyph. (default: 0) 528 | // fontSize - Font size, in pixels (default: 72). 529 | CffGlyph.prototype.getPath = function (x, y, fontSize) { 530 | var scale, newPath, i, cmd; 531 | x = x !== undefined ? x : 0; 532 | y = y !== undefined ? y : 0; 533 | fontSize = fontSize !== undefined ? fontSize : 72; 534 | scale = 1 / this.font.unitsPerEm * fontSize; 535 | newPath = new Path(); 536 | for (i = 0; i < this.path.commands.length; i += 1) { 537 | cmd = this.path.commands[i]; 538 | if (cmd.type === 'M') { 539 | newPath.moveTo(x + (cmd.x * scale), y + (cmd.y * scale)); 540 | } else if (cmd.type === 'L') { 541 | newPath.lineTo(x + (cmd.x * scale), y + (cmd.y * scale)); 542 | } else if (cmd.type === 'C') { 543 | newPath.bezierCurveTo(x + (cmd.x1 * scale), y + (cmd.y1 * scale), 544 | x + (cmd.x2 * scale), y + (cmd.y2 * scale), 545 | x + (cmd.x * scale), y + (cmd.y * scale)); 546 | } else if (cmd.type === 'Q') { 547 | newPath.quadraticCurveTo(x + (cmd.x1 * scale), y + (cmd.y1 * scale), 548 | x + (cmd.x * scale), y + (cmd.y * scale)); 549 | } else if (cmd.type === 'Z') { 550 | newPath.closePath(); 551 | } 552 | } 553 | return newPath; 554 | }; 555 | 556 | // Font object ////////////////////////////////////////////////////////// 557 | 558 | // A Font represents a loaded OpenType font file. 559 | // It contains a set of glyphs and methods to draw text on a drawing context, 560 | // or to get a path representing the text. 561 | function Font() { 562 | this.supported = true; 563 | this.glyphs = []; 564 | this.encoding = null; 565 | } 566 | 567 | // Convert the given character to a single glyph index. 568 | // Note that this function assumes that there is a one-to-one mapping between 569 | // the given character and a glyph; for complex scripts this might not be the case. 570 | Font.prototype.charToGlyphIndex = function (s) { 571 | return this.encoding.charToGlyphIndex(s); 572 | }; 573 | 574 | // Convert the given character to a single Glyph object. 575 | // Note that this function assumes that there is a one-to-one mapping between 576 | // the given character and a glyph; for complex scripts this might not be the case. 577 | Font.prototype.charToGlyph = function (c) { 578 | var glyphIndex, glyph; 579 | glyphIndex = this.charToGlyphIndex(c); 580 | glyph = this.glyphs[glyphIndex]; 581 | if (!glyph) { 582 | glyph = this.glyphs[0]; // .notdef 583 | } 584 | return glyph; 585 | }; 586 | 587 | // Convert the given text to a list of Glyph objects. 588 | // Note that there is no strict one-to-one mapping between characters and 589 | // glyphs, so the list of returned glyphs can be larger or smaller than the 590 | // length of the given string. 591 | Font.prototype.stringToGlyphs = function (s) { 592 | var i, c, glyphs; 593 | glyphs = []; 594 | for (i = 0; i < s.length; i += 1) { 595 | c = s[i]; 596 | glyphs.push(this.charToGlyph(c)); 597 | } 598 | return glyphs; 599 | }; 600 | 601 | // Retrieve the value of the kerning pair between the left glyph (or its index) 602 | // and the right glyph (or its index). If no kerning pair is found, return 0. 603 | // The kerning value gets added to the advance width when calculating the spacing 604 | // between glyphs. 605 | Font.prototype.getKerningValue = function (leftGlyph, rightGlyph) { 606 | leftGlyph = leftGlyph.index || leftGlyph; 607 | rightGlyph = rightGlyph.index || rightGlyph; 608 | return this.kerningPairs[leftGlyph + ',' + rightGlyph] || 0; 609 | }; 610 | 611 | // Helper function that invokes the given callback for each glyph in the given text. 612 | // The callback gets `(glyph, x, y, fontSize, options)`. 613 | Font.prototype.forEachGlyph = function (text, x, y, fontSize, options, callback) { 614 | var kerning, fontScale, glyphs, i, glyph, kerningValue; 615 | if (!this.supported) { 616 | return; 617 | } 618 | x = x !== undefined ? x : 0; 619 | y = y !== undefined ? y : 0; 620 | fontSize = fontSize !== undefined ? fontSize : 72; 621 | options = options || {}; 622 | kerning = options.kerning === undefined ? true : options.kerning; 623 | fontScale = 1 / this.unitsPerEm * fontSize; 624 | glyphs = this.stringToGlyphs(text); 625 | for (i = 0; i < glyphs.length; i += 1) { 626 | glyph = glyphs[i]; 627 | callback(glyph, x, y, fontSize, options); 628 | if (glyph.advanceWidth) { 629 | x += glyph.advanceWidth * fontScale; 630 | } 631 | if (kerning && i < glyphs.length - 1) { 632 | kerningValue = this.getKerningValue(glyph, glyphs[i + 1]); 633 | x += kerningValue * fontScale; 634 | } 635 | } 636 | }; 637 | 638 | // Create a Path object that represents the given text. 639 | // 640 | // text - The text to create. 641 | // x - Horizontal position of the beginning of the text. (default: 0) 642 | // y - Vertical position of the *baseline* of the text. (default: 0) 643 | // fontSize - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. (default: 72) 644 | // Options is an optional object that contains: 645 | // - kerning - Whether to take kerning information into account. (default: true) 646 | // 647 | // Returns a Path object. 648 | Font.prototype.getPath = function (text, x, y, fontSize, options) { 649 | var fullPath = new Path(); 650 | this.forEachGlyph(text, x, y, fontSize, options, function (glyph, x, y, fontSize) { 651 | var path = glyph.getPath(x, y, fontSize); 652 | fullPath.extend(path); 653 | }); 654 | return fullPath; 655 | }; 656 | 657 | // Draw the text on the given drawing context. 658 | // 659 | // ctx - A 2D drawing context, like Canvas. 660 | // text - The text to create. 661 | // x - Horizontal position of the beginning of the text. (default: 0) 662 | // y - Vertical position of the *baseline* of the text. (default: 0) 663 | // fontSize - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. (default: 72) 664 | // Options is an optional object that contains: 665 | // - kerning - Whether to take kerning information into account. (default: true) 666 | Font.prototype.draw = function (ctx, text, x, y, fontSize, options) { 667 | this.getPath(text, x, y, fontSize, options).draw(ctx); 668 | }; 669 | 670 | // Draw the points of all glyphs in the text. 671 | // On-curve points will be drawn in blue, off-curve points will be drawn in red. 672 | // 673 | // ctx - A 2D drawing context, like Canvas. 674 | // text - The text to create. 675 | // x - Horizontal position of the beginning of the text. (default: 0) 676 | // y - Vertical position of the *baseline* of the text. (default: 0) 677 | // fontSize - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. (default: 72) 678 | // Options is an optional object that contains: 679 | // - kerning - Whether to take kerning information into account. (default: true) 680 | Font.prototype.drawPoints = function (ctx, text, x, y, fontSize, options) { 681 | this.forEachGlyph(text, x, y, fontSize, options, function (glyph, x, y, fontSize) { 682 | glyph.drawPoints(ctx, x, y, fontSize); 683 | }); 684 | }; 685 | 686 | // Draw lines indicating important font measurements for all glyphs in the text. 687 | // Black lines indicate the origin of the coordinate system (point 0,0). 688 | // Blue lines indicate the glyph bounding box. 689 | // Green line indicates the advance width of the glyph. 690 | // 691 | // ctx - A 2D drawing context, like Canvas. 692 | // text - The text to create. 693 | // x - Horizontal position of the beginning of the text. (default: 0) 694 | // y - Vertical position of the *baseline* of the text. (default: 0) 695 | // fontSize - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. (default: 72) 696 | // Options is an optional object that contains: 697 | // - kerning - Whether to take kerning information into account. (default: true) 698 | Font.prototype.drawMetrics = function (ctx, text, x, y, fontSize, options) { 699 | this.forEachGlyph(text, x, y, fontSize, options, function (glyph, x, y, fontSize) { 700 | glyph.drawMetrics(ctx, x, y, fontSize); 701 | }); 702 | }; 703 | 704 | // OpenType format parsing ////////////////////////////////////////////// 705 | 706 | cffStandardStrings = [ 707 | '.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', 708 | 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', 709 | 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', 710 | 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 711 | 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', 712 | 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 713 | 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 'exclamdown', 'cent', 'sterling', 714 | 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', 715 | 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl', 'periodcentered', 'paragraph', 716 | 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', 717 | 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', 'ring', 718 | 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 719 | 'ordmasculine', 'ae', 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 'logicalnot', 'mu', 720 | 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 721 | 'threequarters', 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 'copyright', 722 | 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 723 | 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 'Ocircumflex', 724 | 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 725 | 'Ydieresis', 'Zcaron', 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 'eacute', 726 | 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 'idieresis', 'igrave', 'ntilde', 'oacute', 727 | 'ocircumflex', 'odieresis', 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', 728 | 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 'dollaroldstyle', 'dollarsuperior', 729 | 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', '266 ff', 'onedotenleader', 730 | 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 731 | 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior', 732 | 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior', 733 | 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 734 | 'parenleftinferior', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', 735 | 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 736 | 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 737 | 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 'exclamdownsmall', 738 | 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall', 739 | 'Dotaccentsmall', 'Macronsmall', 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', 740 | 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', 741 | 'zerosuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 742 | 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 743 | 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 744 | 'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 745 | 'Aringsmall', 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 746 | 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 747 | 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 748 | 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', 749 | '001.001', '001.002', '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 'Semibold']; 750 | 751 | cffStandardEncoding = [ 752 | '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 753 | '', '', '', '', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', 754 | 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', 755 | 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', 756 | 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 757 | 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', 758 | 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 759 | 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', '', '', '', '', '', '', '', '', 760 | '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 761 | 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 762 | 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', '', 'endash', 'dagger', 763 | 'daggerdbl', 'periodcentered', '', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 764 | 'guillemotright', 'ellipsis', 'perthousand', '', 'questiondown', '', 'grave', 'acute', 'circumflex', 'tilde', 765 | 'macron', 'breve', 'dotaccent', 'dieresis', '', 'ring', 'cedilla', '', 'hungarumlaut', 'ogonek', 'caron', 766 | 'emdash', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'AE', '', 'ordfeminine', '', '', '', 767 | '', 'Lslash', 'Oslash', 'OE', 'ordmasculine', '', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '', 768 | 'lslash', 'oslash', 'oe', 'germandbls']; 769 | 770 | cffExpertEncoding = [ 771 | '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 772 | '', '', '', '', 'space', 'exclamsmall', 'Hungarumlautsmall', '', 'dollaroldstyle', 'dollarsuperior', 773 | 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', 774 | 'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 775 | 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', 776 | 'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'questionsmall', '', 'asuperior', 777 | 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', '', '', 'isuperior', '', '', 'lsuperior', 'msuperior', 778 | 'nsuperior', 'osuperior', '', '', 'rsuperior', 'ssuperior', 'tsuperior', '', 'ff', 'fi', 'fl', 'ffi', 'ffl', 779 | 'parenleftinferior', '', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', 780 | 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 781 | 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 782 | 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', '', '', '', '', '', '', '', 783 | '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 784 | 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', '', '', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 785 | 'Brevesmall', 'Caronsmall', '', 'Dotaccentsmall', '', '', 'Macronsmall', '', '', 'figuredash', 'hypheninferior', 786 | '', '', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', '', '', '', 'onequarter', 'onehalf', 'threequarters', 787 | 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', '', 788 | '', 'zerosuperior', 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior', 789 | 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior', 790 | 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior', 791 | 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', 792 | 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall', 793 | 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall', 794 | 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', 795 | 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall', 796 | 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall']; 797 | 798 | // Parse the coordinate data for a glyph. 799 | function parseGlyphCoordinate(p, flag, previousValue, shortVectorBit, sameBit) { 800 | var v; 801 | if (isBitSet(flag, shortVectorBit)) { 802 | // The coordinate is 1 byte long. 803 | v = p.parseByte(); 804 | // The `same` bit is re-used for short values to signify the sign of the value. 805 | if (!isBitSet(flag, sameBit)) { 806 | v = -v; 807 | } 808 | v = previousValue + v; 809 | } else { 810 | // The coordinate is 2 bytes long. 811 | // If the `same` bit is set, the coordinate is the same as the previous coordinate. 812 | if (isBitSet(flag, sameBit)) { 813 | v = previousValue; 814 | } else { 815 | // Parse the coordinate as a signed 16-bit delta value. 816 | v = previousValue + p.parseShort(); 817 | } 818 | } 819 | return v; 820 | } 821 | 822 | // Parse an OpenType glyph (described in the glyf table). 823 | // http://www.microsoft.com/typography/otspec/glyf.htm 824 | function parseGlyph(data, start, index, font) { 825 | var p, glyph, flag, i, j, flags, 826 | endPointIndices, numberOfCoordinates, repeatCount, points, point, px, py, 827 | component, moreComponents, arg1, arg2, scale, xScale, yScale, scale01, scale10; 828 | p = new Parser(data, start); 829 | glyph = new TrueTypeGlyph(font, index); 830 | glyph.numberOfContours = p.parseShort(); 831 | glyph.xMin = p.parseShort(); 832 | glyph.yMin = p.parseShort(); 833 | glyph.xMax = p.parseShort(); 834 | glyph.yMax = p.parseShort(); 835 | if (glyph.numberOfContours > 0) { 836 | // This glyph is not a composite. 837 | endPointIndices = glyph.endPointIndices = []; 838 | for (i = 0; i < glyph.numberOfContours; i += 1) { 839 | endPointIndices.push(p.parseUShort()); 840 | } 841 | 842 | glyph.instructionLength = p.parseUShort(); 843 | glyph.instructions = []; 844 | for (i = 0; i < glyph.instructionLength; i += 1) { 845 | glyph.instructions.push(p.parseByte()); 846 | } 847 | 848 | numberOfCoordinates = endPointIndices[endPointIndices.length - 1] + 1; 849 | flags = []; 850 | for (i = 0; i < numberOfCoordinates; i += 1) { 851 | flag = p.parseByte(); 852 | flags.push(flag); 853 | // If bit 3 is set, we repeat this flag n times, where n is the next byte. 854 | if (isBitSet(flag, 3)) { 855 | repeatCount = p.parseByte(); 856 | for (j = 0; j < repeatCount; j += 1) { 857 | flags.push(flag); 858 | i += 1; 859 | } 860 | } 861 | } 862 | checkArgument(flags.length === numberOfCoordinates, 'Bad flags.'); 863 | 864 | if (endPointIndices.length > 0) { 865 | points = []; 866 | // X/Y coordinates are relative to the previous point, except for the first point which is relative to 0,0. 867 | if (numberOfCoordinates > 0) { 868 | for (i = 0; i < numberOfCoordinates; i += 1) { 869 | flag = flags[i]; 870 | point = {}; 871 | point.onCurve = isBitSet(flag, 0); 872 | point.lastPointOfContour = endPointIndices.indexOf(i) >= 0; 873 | points.push(point); 874 | } 875 | px = 0; 876 | for (i = 0; i < numberOfCoordinates; i += 1) { 877 | flag = flags[i]; 878 | point = points[i]; 879 | point.x = parseGlyphCoordinate(p, flag, px, 1, 4); 880 | px = point.x; 881 | } 882 | 883 | py = 0; 884 | for (i = 0; i < numberOfCoordinates; i += 1) { 885 | flag = flags[i]; 886 | point = points[i]; 887 | point.y = parseGlyphCoordinate(p, flag, py, 2, 5); 888 | py = point.y; 889 | } 890 | } 891 | glyph.points = points; 892 | } else { 893 | glyph.points = []; 894 | } 895 | } else if (glyph.numberOfContours === 0) { 896 | glyph.points = []; 897 | } else { 898 | glyph.isComposite = true; 899 | glyph.points = []; 900 | glyph.components = []; 901 | moreComponents = true; 902 | while (moreComponents) { 903 | component = {}; 904 | flags = p.parseUShort(); 905 | component.glyphIndex = p.parseUShort(); 906 | if (isBitSet(flags, 0)) { 907 | // The arguments are words 908 | arg1 = p.parseShort(); 909 | arg2 = p.parseShort(); 910 | component.dx = arg1; 911 | component.dy = arg2; 912 | } else { 913 | // The arguments are bytes 914 | arg1 = p.parseByte(); 915 | arg2 = p.parseByte(); 916 | component.dx = arg1; 917 | component.dy = arg2; 918 | } 919 | if (isBitSet(flags, 3)) { 920 | // We have a scale 921 | // TODO parse in 16-bit signed fixed number with the low 14 bits of fraction (2.14). 922 | scale = p.parseShort(); 923 | } else if (isBitSet(flags, 6)) { 924 | // We have an X / Y scale 925 | xScale = p.parseShort(); 926 | yScale = p.parseShort(); 927 | } else if (isBitSet(flags, 7)) { 928 | // We have a 2x2 transformation 929 | xScale = p.parseShort(); 930 | scale01 = p.parseShort(); 931 | scale10 = p.parseShort(); 932 | yScale = p.parseShort(); 933 | } 934 | 935 | glyph.components.push(component); 936 | moreComponents = isBitSet(flags, 5); 937 | } 938 | } 939 | return glyph; 940 | } 941 | 942 | // Transform an array of points and return a new array. 943 | function transformPoints(points, dx, dy) { 944 | var newPoints, i, pt, newPt; 945 | newPoints = []; 946 | for (i = 0; i < points.length; i += 1) { 947 | pt = points[i]; 948 | newPt = { 949 | x: pt.x + dx, 950 | y: pt.y + dy, 951 | onCurve: pt.onCurve, 952 | lastPointOfContour: pt.lastPointOfContour 953 | }; 954 | newPoints.push(newPt); 955 | } 956 | return newPoints; 957 | } 958 | 959 | // Parse all the glyphs according to the offsets from the `loca` table. 960 | function parseGlyfTable(data, start, loca, font) { 961 | var glyphs, i, j, offset, nextOffset, glyph, 962 | component, componentGlyph, transformedPoints; 963 | glyphs = []; 964 | // The last element of the loca table is invalid. 965 | for (i = 0; i < loca.length - 1; i += 1) { 966 | offset = loca[i]; 967 | nextOffset = loca[i + 1]; 968 | if (offset !== nextOffset) { 969 | glyphs.push(parseGlyph(data, start + offset, i, font)); 970 | } else { 971 | glyphs.push(new TrueTypeGlyph(font, i)); 972 | } 973 | } 974 | // Go over the glyphs again, resolving the composite glyphs. 975 | for (i = 0; i < glyphs.length; i += 1) { 976 | glyph = glyphs[i]; 977 | if (glyph.isComposite) { 978 | for (j = 0; j < glyph.components.length; j += 1) { 979 | component = glyph.components[j]; 980 | componentGlyph = glyphs[component.glyphIndex]; 981 | if (componentGlyph.points) { 982 | transformedPoints = transformPoints(componentGlyph.points, component.dx, component.dy); 983 | glyph.points.push.apply(glyph.points, transformedPoints); 984 | } 985 | } 986 | } 987 | } 988 | 989 | return glyphs; 990 | } 991 | 992 | // Parse the `loca` table. This table stores the offsets to the locations of the glyphs in the font, 993 | // relative to the beginning of the glyphData table. 994 | // The number of glyphs stored in the `loca` table is specified in the `maxp` table (under numGlyphs) 995 | // The loca table has two versions: a short version where offsets are stored as uShorts, and a long 996 | // version where offsets are stored as uLongs. The `head` table specifies which version to use 997 | // (under indexToLocFormat). 998 | // https://www.microsoft.com/typography/OTSPEC/loca.htm 999 | function parseLocaTable(data, start, numGlyphs, shortVersion) { 1000 | var p, parseFn, glyphOffsets, glyphOffset, i; 1001 | p = new Parser(data, start); 1002 | parseFn = shortVersion ? p.parseUShort : p.parseULong; 1003 | // There is an extra entry after the last index element to compute the length of the last glyph. 1004 | // That's why we use numGlyphs + 1. 1005 | glyphOffsets = []; 1006 | for (i = 0; i < numGlyphs + 1; i += 1) { 1007 | glyphOffset = parseFn.call(p); 1008 | if (shortVersion) { 1009 | // The short table version stores the actual offset divided by 2. 1010 | glyphOffset *= 2; 1011 | } 1012 | glyphOffsets.push(glyphOffset); 1013 | } 1014 | return glyphOffsets; 1015 | } 1016 | 1017 | 1018 | // Parse the `cmap` table. This table stores the mappings from characters to glyphs. 1019 | // There are many available formats, but we only support the Windows format 4. 1020 | // This function returns a `CmapEncoding` object or null if no supported format could be found. 1021 | // https://www.microsoft.com/typography/OTSPEC/cmap.htm 1022 | function parseCmapTable(data, start) { 1023 | var version, numTables, offset, platformId, encodingId, format, segCount, 1024 | ranges, i, j, parserOffset, idRangeOffset, p; 1025 | version = getUShort(data, start); 1026 | checkArgument(version === 0, "cmap table version should be 0."); 1027 | 1028 | // The cmap table can contain many sub-tables, each with their own format. 1029 | // We're only interested in a "platform 3" table. This is a Windows format. 1030 | numTables = getUShort(data, start + 2); 1031 | offset = -1; 1032 | for (i = 0; i < numTables; i += 1) { 1033 | platformId = getUShort(data, start + 4 + (i * 8)); 1034 | encodingId = getUShort(data, start + 4 + (i * 8) + 2); 1035 | if (platformId === 3 && (encodingId === 1 || encodingId === 0)) { 1036 | offset = getULong(data, start + 4 + (i * 8) + 4); 1037 | break; 1038 | } 1039 | } 1040 | if (offset === -1) { 1041 | // There is no cmap table in the font that we support, so return null. 1042 | // This font will be marked as unsupported. 1043 | return null; 1044 | } 1045 | 1046 | p = new Parser(data, start + offset); 1047 | format = p.parseUShort(); 1048 | checkArgument(format === 4, "Only format 4 cmap tables are supported."); 1049 | // Length in bytes of the sub-tables. 1050 | // Skip length and language; 1051 | p.skip('uShort', 2); 1052 | // segCount is stored x 2. 1053 | segCount = p.parseUShort() >> 1; 1054 | // Skip searchRange, entrySelector, rangeShift. 1055 | p.skip('uShort', 3); 1056 | ranges = []; 1057 | for (i = 0; i < segCount; i += 1) { 1058 | ranges[i] = { end: p.parseUShort() }; 1059 | } 1060 | // Skip a padding value. 1061 | p.skip('uShort'); 1062 | for (i = 0; i < segCount; i += 1) { 1063 | ranges[i].start = p.parseUShort(); 1064 | ranges[i].length = ranges[i].end - ranges[i].start; 1065 | } 1066 | for (i = 0; i < segCount; i += 1) { 1067 | ranges[i].idDelta = p.parseShort(); 1068 | } 1069 | for (i = 0; i < segCount; i += 1) { 1070 | parserOffset = p.offset + p.relativeOffset; 1071 | idRangeOffset = p.parseUShort(); 1072 | if (idRangeOffset > 0) { 1073 | ranges[i].ids = []; 1074 | for (j = 0; j < ranges[i].length; j += 1) { 1075 | ranges[i].ids[j] = getUShort(data, parserOffset + idRangeOffset); 1076 | idRangeOffset += 2; 1077 | } 1078 | } 1079 | } 1080 | 1081 | return new CmapEncoding(ranges); 1082 | } 1083 | 1084 | // Parse a `CFF` INDEX array. 1085 | // An index array consists of a list of offsets, then a list of objects at those offsets. 1086 | function parseCFFIndex(data, start, conversionFn) { 1087 | var offsets, objects, count, endOffset, offsetSize, objectOffset, pos, i, value; 1088 | offsets = []; 1089 | objects = []; 1090 | count = getCard16(data, start); 1091 | if (count !== 0) { 1092 | offsetSize = getByte(data, start + 2); 1093 | objectOffset = start + ((count + 1) * offsetSize) + 2; 1094 | pos = start + 3; 1095 | for (i = 0; i < count + 1; i += 1) { 1096 | offsets.push(getOffset(data, pos, offsetSize)); 1097 | pos += offsetSize; 1098 | } 1099 | // The total size of the index array is 4 header bytes + the value of the last offset. 1100 | endOffset = objectOffset + offsets[count]; 1101 | } else { 1102 | endOffset = start + 2; 1103 | } 1104 | for (i = 0; i < offsets.length - 1; i += 1) { 1105 | value = getBytes(data, objectOffset + offsets[i], objectOffset + offsets[i + 1]); 1106 | if (conversionFn) { 1107 | value = conversionFn(value); 1108 | } 1109 | objects.push(value); 1110 | } 1111 | return {objects: objects, startOffset: start, endOffset: endOffset}; 1112 | } 1113 | 1114 | // Parse a `CFF` DICT real value. 1115 | function parseFloatOperand(parser) { 1116 | var s, eof, lookup, b, n1, n2; 1117 | s = ''; 1118 | eof = 15; 1119 | lookup = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-']; 1120 | while (true) { 1121 | b = parser.parseByte(); 1122 | n1 = b >> 4; 1123 | n2 = b & 15; 1124 | 1125 | if (n1 === eof) { 1126 | break; 1127 | } 1128 | s += lookup[n1]; 1129 | 1130 | if (n2 === eof) { 1131 | break; 1132 | } 1133 | s += lookup[n2]; 1134 | } 1135 | return parseFloat(s); 1136 | } 1137 | 1138 | // Parse a `CFF` DICT operand. 1139 | function parseOperand(parser, b0) { 1140 | var b1, b2, b3, b4; 1141 | if (b0 === 28) { 1142 | b1 = parser.parseByte(); 1143 | b2 = parser.parseByte(); 1144 | return b1 << 8 | b2; 1145 | } 1146 | if (b0 === 29) { 1147 | b1 = parser.parseByte(); 1148 | b2 = parser.parseByte(); 1149 | b3 = parser.parseByte(); 1150 | b4 = parser.parseByte(); 1151 | return b1 << 24 | b2 << 16 | b3 << 8 | b4; 1152 | } 1153 | if (b0 === 30) { 1154 | return parseFloatOperand(parser); 1155 | } 1156 | if (b0 >= 32 && b0 <= 246) { 1157 | return b0 - 139; 1158 | } 1159 | if (b0 >= 247 && b0 <= 250) { 1160 | b1 = parser.parseByte(); 1161 | return (b0 - 247) * 256 + b1 + 108; 1162 | } 1163 | if (b0 >= 251 && b0 <= 254) { 1164 | b1 = parser.parseByte(); 1165 | return -(b0 - 251) * 256 - b1 - 108; 1166 | } 1167 | throw new Error('Invalid b0 ' + b0); 1168 | } 1169 | 1170 | // Convert the entries returned by `parseDict` to a proper dictionary. 1171 | // If a value is a list of one, it is unpacked. 1172 | function entriesToObject(entries) { 1173 | var o, key, values, i, value; 1174 | o = {}; 1175 | for (i = 0; i < entries.length; i += 1) { 1176 | key = entries[i][0]; 1177 | values = entries[i][1]; 1178 | if (values.length === 1) { 1179 | value = values[0]; 1180 | } else { 1181 | value = values; 1182 | } 1183 | if (o.hasOwnProperty(key)) { 1184 | throw new Error('Object ' + o + ' already has key ' + key); 1185 | } 1186 | o[key] = value; 1187 | } 1188 | return o; 1189 | } 1190 | 1191 | // Parse a `CFF` DICT object. 1192 | // A dictionary contains key-value pairs in a compact tokenized format. 1193 | function parseCFFDict(data, start, size) { 1194 | var parser, entries, operands, op; 1195 | start = start !== undefined ? start : 0; 1196 | parser = new Parser(data, start); 1197 | entries = []; 1198 | operands = []; 1199 | size = size !== undefined ? size : data.length; 1200 | 1201 | while (parser.relativeOffset < size) { 1202 | op = parser.parseByte(); 1203 | // The first byte for each dict item distinguishes between operator (key) and operand (value). 1204 | // Values <= 21 are operators. 1205 | if (op <= 21) { 1206 | // Two-byte operators have an initial escape byte of 12. 1207 | if (op === 12) { 1208 | op = 1200 + parser.parseByte(); 1209 | } 1210 | entries.push([op, operands]); 1211 | operands = []; 1212 | } else { 1213 | // Since the operands (values) come before the operators (keys), we store all operands in a list 1214 | // until we encounter an operator. 1215 | operands.push(parseOperand(parser, op)); 1216 | } 1217 | } 1218 | return entriesToObject(entries); 1219 | } 1220 | 1221 | // Given a String Index (SID), return the value of the string. 1222 | // Strings below index 392 are standard CFF strings and are not encoded in the font. 1223 | function getCFFString(strings, index) { 1224 | if (index <= 391) { 1225 | index = cffStandardStrings[index]; 1226 | } else { 1227 | index = strings[index - 391]; 1228 | } 1229 | return index; 1230 | } 1231 | 1232 | // Interpret a dictionary and return a new dictionary with readable keys and values for missing entries. 1233 | // This function takes `meta` which is a list of objects containing `operand`, `name` and `default`. 1234 | function interpretDict(dict, meta, strings) { 1235 | var i, m, value, newDict; 1236 | newDict = {}; 1237 | // Because we also want to include missing values, we start out from the meta list 1238 | // and lookup values in the dict. 1239 | for (i = 0; i < meta.length; i += 1) { 1240 | m = meta[i]; 1241 | value = dict[m.op]; 1242 | if (value === undefined) { 1243 | value = m.value !== undefined ? m.value : null; 1244 | } 1245 | if (m.type === 'SID') { 1246 | value = getCFFString(strings, value); 1247 | } 1248 | newDict[m.name] = value; 1249 | } 1250 | return newDict; 1251 | } 1252 | 1253 | // Parse the CFF header. 1254 | function parseCFFHeader(data, start) { 1255 | var header = {}; 1256 | header.formatMajor = getCard8(data, start); 1257 | header.formatMinor = getCard8(data, start + 1); 1258 | header.size = getCard8(data, start + 2); 1259 | header.offsetSize = getCard8(data, start + 3); 1260 | header.startOffset = start; 1261 | header.endOffset = start + 4; 1262 | return header; 1263 | } 1264 | 1265 | // Parse the CFF top dictionary. A CFF table can contain multiple fonts, each with their own top dictionary. 1266 | // The top dictionary contains the essential metadata for the font, together with the private dictionary. 1267 | function parseCFFTopDict(data, start, strings) { 1268 | var dict, meta; 1269 | meta = [ 1270 | {name: 'version', op: 0, type: 'SID'}, 1271 | {name: 'notice', op: 1, type: 'SID'}, 1272 | {name: 'copyright', op: 1200, type: 'SID'}, 1273 | {name: 'fullName', op: 2, type: 'SID'}, 1274 | {name: 'familyName', op: 3, type: 'SID'}, 1275 | {name: 'weight', op: 4, type: 'SID'}, 1276 | {name: 'isFixedPitch', op: 1201, type: 'number', value: 0}, 1277 | {name: 'italicAngle', op: 1202, type: 'number', value: 0}, 1278 | {name: 'underlinePosition', op: 1203, type: 'number', value: -100}, 1279 | {name: 'underlineThickness', op: 1204, type: 'number', value: 50}, 1280 | {name: 'paintType', op: 1205, type: 'number', value: 0}, 1281 | {name: 'charstringType', op: 1206, type: 'number', value: 2}, 1282 | {name: 'fontMatrix', op: 1207, type: ['number', 'number', 'number', 'number'], value: [0.001, 0, 0, 0.001, 0, 0]}, 1283 | {name: 'uniqueId', op: 13, type: 'number'}, 1284 | {name: 'fontBBox', op: 5, type: ['number', 'number', 'number', 'number'], value: [0, 0, 0, 0]}, 1285 | {name: 'strokeWidth', op: 1208, type: 'number', value: 0}, 1286 | {name: 'xuid', op: 14, type: []}, 1287 | {name: 'charset', op: 15, type: 'offset', value: 0}, 1288 | {name: 'encoding', op: 16, type: 'offset', value: 0}, 1289 | {name: 'charStrings', op: 17, type: 'number', value: 0}, 1290 | {name: 'private', op: 18, type: ['number', 'offset'], value: [0, 0]} 1291 | ]; 1292 | dict = parseCFFDict(data, start); 1293 | return interpretDict(dict, meta, strings); 1294 | } 1295 | 1296 | // Parse the CFF private dictionary. We don't fully parse out all the values, only the ones we need. 1297 | function parseCFFPrivateDict(data, start, size, strings) { 1298 | var dict, meta; 1299 | meta = [ 1300 | {name: 'subrs', op: 19, type: 'offset', value: 0}, 1301 | {name: 'defaultWidthX', op: 20, type: 'number', value: 0}, 1302 | {name: 'nominalWidthX', op: 21, type: 'number', value: 0} 1303 | ]; 1304 | dict = parseCFFDict(data, start, size); 1305 | return interpretDict(dict, meta, strings); 1306 | } 1307 | 1308 | // Parse the CFF charset table, which contains internal names for all the glyphs. 1309 | // This function will return a list of glyph names. 1310 | // See Adobe TN #5176 chapter 13, "Charsets". 1311 | function parseCFFCharset(data, start, nGlyphs, strings) { 1312 | var parser, format, charset, i, sid, count; 1313 | parser = new Parser(data, start); 1314 | // The .notdef glyph is not included, so subtract 1. 1315 | nGlyphs -= 1; 1316 | charset = ['.notdef']; 1317 | 1318 | format = parser.parseCard8(); 1319 | if (format === 0) { 1320 | for (i = 0; i < nGlyphs; i += 1) { 1321 | sid = parser.parseSID(); 1322 | charset.push(getCFFString(strings, sid)); 1323 | } 1324 | } else if (format === 1) { 1325 | while (charset.length <= nGlyphs) { 1326 | sid = parser.parseSID(); 1327 | count = parser.parseCard8(); 1328 | for (i = 0; i <= count; i += 1) { 1329 | charset.push(getCFFString(strings, sid)); 1330 | sid += 1; 1331 | } 1332 | } 1333 | } else if (format === 2) { 1334 | while (charset.length <= nGlyphs) { 1335 | sid = parser.parseSID(); 1336 | count = parser.parseCard16(); 1337 | for (i = 0; i <= count; i += 1) { 1338 | charset.push(getCFFString(strings, sid)); 1339 | sid += 1; 1340 | } 1341 | } 1342 | } else { 1343 | throw new Error('Unknown charset format ' + format); 1344 | } 1345 | 1346 | return charset; 1347 | } 1348 | 1349 | // Parse the CFF encoding data. Only one encoding can be specified per font. 1350 | // See Adobe TN #5176 chapter 12, "Encodings". 1351 | function parseCFFEncoding(data, start, charset) { 1352 | var encoding, parser, format, nCodes, i, code, nRanges, first, nLeft, j; 1353 | encoding = {}; 1354 | parser = new Parser(data, start); 1355 | format = parser.parseCard8(); 1356 | if (format === 0) { 1357 | nCodes = parser.parseCard8(); 1358 | for (i = 0; i < nCodes; i += 1) { 1359 | code = parser.parseCard8(); 1360 | encoding[code] = i; 1361 | } 1362 | } else if (format === 1) { 1363 | nRanges = parser.parseCard8(); 1364 | code = 1; 1365 | for (i = 0; i < nRanges; i += 1) { 1366 | first = parser.parseCard8(); 1367 | nLeft = parser.parseCard8(); 1368 | for (j = first; j <= first + nLeft; j += 1) { 1369 | encoding[j] = code; 1370 | code += 1; 1371 | } 1372 | } 1373 | } else { 1374 | throw new Error('Unknown encoding format ' + format); 1375 | } 1376 | return new CffEncoding(encoding, charset); 1377 | } 1378 | 1379 | // Take in charstring code and return a Glyph object. 1380 | // The encoding is described in the Type 2 Charstring Format 1381 | // https://www.microsoft.com/typography/OTSPEC/charstr2.htm 1382 | function parseCFFCharstring(code, font, index) { 1383 | var path, glyph, stack, nStems, haveWidth, width, x, y, c1x, c1y, c2x, c2y, v; 1384 | path = new Path(); 1385 | stack = []; 1386 | nStems = 0; 1387 | haveWidth = false; 1388 | width = font.nominalWidthX; 1389 | x = y = 0; 1390 | 1391 | function parseStems() { 1392 | var hasWidthArg; 1393 | // The number of stem operators on the stack is always even. 1394 | // If the value is uneven, that means a width is specified. 1395 | hasWidthArg = stack.length % 2 !== 0; 1396 | if (hasWidthArg && !haveWidth) { 1397 | width = stack.shift() + font.nominalWidthX; 1398 | } 1399 | nStems += stack.length >> 1; 1400 | stack.length = 0; 1401 | haveWidth = true; 1402 | } 1403 | 1404 | function parse(code) { 1405 | var i, b1, b2, b3, b4, codeIndex, subrCode; 1406 | i = 0; 1407 | while (i < code.length) { 1408 | v = code[i]; 1409 | i += 1; 1410 | switch (v) { 1411 | case 1: // hstem 1412 | parseStems(); 1413 | break; 1414 | case 3: // vstem 1415 | parseStems(); 1416 | break; 1417 | case 4: // vmoveto 1418 | if (stack.length > 1 && !haveWidth) { 1419 | width = stack.shift() + font.nominalWidthX; 1420 | haveWidth = true; 1421 | } 1422 | y += stack.pop(); 1423 | path.moveTo(x, -y); 1424 | break; 1425 | case 5: // rlineto 1426 | while (stack.length > 0) { 1427 | x += stack.shift(); 1428 | y += stack.shift(); 1429 | path.lineTo(x, -y); 1430 | } 1431 | break; 1432 | case 6: // hlineto 1433 | while (stack.length > 0) { 1434 | x += stack.shift(); 1435 | path.lineTo(x, -y); 1436 | if (stack.length === 0) { 1437 | break; 1438 | } 1439 | y += stack.shift(); 1440 | path.lineTo(x, -y); 1441 | } 1442 | break; 1443 | case 7: // vlineto 1444 | while (stack.length > 0) { 1445 | y += stack.shift(); 1446 | path.lineTo(x, -y); 1447 | if (stack.length === 0) { 1448 | break; 1449 | } 1450 | x += stack.shift(); 1451 | path.lineTo(x, -y); 1452 | } 1453 | break; 1454 | case 8: // rrcurveto 1455 | while (stack.length > 0) { 1456 | c1x = x + stack.shift(); 1457 | c1y = y + stack.shift(); 1458 | c2x = c1x + stack.shift(); 1459 | c2y = c1y + stack.shift(); 1460 | x = c2x + stack.shift(); 1461 | y = c2y + stack.shift(); 1462 | path.curveTo(c1x, -c1y, c2x, -c2y, x, -y); 1463 | } 1464 | break; 1465 | case 10: // callsubr 1466 | codeIndex = stack.pop() + font.subrsBias; 1467 | subrCode = font.subrs[codeIndex]; 1468 | if (subrCode) { 1469 | parse(subrCode); 1470 | } 1471 | break; 1472 | case 11: // return 1473 | return; 1474 | case 12: // escape 1475 | v = code[i]; 1476 | i += 1; 1477 | break; 1478 | case 14: // endchar 1479 | if (stack.length > 0 && !haveWidth) { 1480 | width = stack.shift() + font.nominalWidthX; 1481 | haveWidth = true; 1482 | } 1483 | path.closePath(); 1484 | break; 1485 | case 18: // hstemhm 1486 | parseStems(); 1487 | break; 1488 | case 19: // hintmask 1489 | case 20: // cntrmask 1490 | parseStems(); 1491 | i += (nStems + 7) >> 3; 1492 | break; 1493 | case 21: // rmoveto 1494 | if (stack.length > 2 && !haveWidth) { 1495 | width = stack.shift() + font.nominalWidthX; 1496 | haveWidth = true; 1497 | } 1498 | y += stack.pop(); 1499 | x += stack.pop(); 1500 | path.moveTo(x, -y); 1501 | break; 1502 | case 22: // hmoveto 1503 | if (stack.length > 1 && !haveWidth) { 1504 | width = stack.shift() + font.nominalWidthX; 1505 | haveWidth = true; 1506 | } 1507 | x += stack.pop(); 1508 | path.moveTo(x, -y); 1509 | break; 1510 | case 23: // vstemhm 1511 | parseStems(); 1512 | break; 1513 | case 24: // rcurveline 1514 | while (stack.length > 2) { 1515 | c1x = x + stack.shift(); 1516 | c1y = y + stack.shift(); 1517 | c2x = c1x + stack.shift(); 1518 | c2y = c1y + stack.shift(); 1519 | x = c2x + stack.shift(); 1520 | y = c2y + stack.shift(); 1521 | path.curveTo(c1x, -c1y, c2x, -c2y, x, -y); 1522 | } 1523 | x += stack.shift(); 1524 | y += stack.shift(); 1525 | path.lineTo(x, -y); 1526 | break; 1527 | case 25: // rlinecurve 1528 | while (stack.length > 6) { 1529 | x += stack.shift(); 1530 | y += stack.shift(); 1531 | path.lineTo(x, -y); 1532 | } 1533 | c1x = x + stack.shift(); 1534 | c1y = y + stack.shift(); 1535 | c2x = c1x + stack.shift(); 1536 | c2y = c1y + stack.shift(); 1537 | x = c2x + stack.shift(); 1538 | y = c2y + stack.shift(); 1539 | path.curveTo(c1x, -c1y, c2x, -c2y, x, -y); 1540 | break; 1541 | case 26: // vvcurveto 1542 | if (stack.length % 2) { 1543 | x += stack.shift(); 1544 | } 1545 | while (stack.length > 0) { 1546 | c1x = x; 1547 | c1y = y + stack.shift(); 1548 | c2x = c1x + stack.shift(); 1549 | c2y = c1y + stack.shift(); 1550 | x = c2x; 1551 | y = c2y + stack.shift(); 1552 | path.curveTo(c1x, -c1y, c2x, -c2y, x, -y); 1553 | } 1554 | break; 1555 | case 27: // hhcurveto 1556 | if (stack.length % 2) { 1557 | y += stack.shift(); 1558 | } 1559 | while (stack.length > 0) { 1560 | c1x = x + stack.shift(); 1561 | c1y = y; 1562 | c2x = c1x + stack.shift(); 1563 | c2y = c1y + stack.shift(); 1564 | x = c2x + stack.shift(); 1565 | y = c2y; 1566 | path.curveTo(c1x, -c1y, c2x, -c2y, x, -y); 1567 | } 1568 | break; 1569 | case 28: // shortint 1570 | b1 = code[i]; 1571 | b2 = code[i + 1]; 1572 | stack.push(((b1 << 24) | (b2 << 16)) >> 16); 1573 | i += 2; 1574 | break; 1575 | case 29: // callgsubr 1576 | codeIndex = stack.pop() + font.gsubrsBias; 1577 | subrCode = font.gsubrs[codeIndex]; 1578 | if (subrCode) { 1579 | parse(subrCode); 1580 | } 1581 | break; 1582 | case 30: // vhcurveto 1583 | while (stack.length > 0) { 1584 | c1x = x; 1585 | c1y = y + stack.shift(); 1586 | c2x = c1x + stack.shift(); 1587 | c2y = c1y + stack.shift(); 1588 | x = c2x + stack.shift(); 1589 | y = c2y + (stack.length === 1 ? stack.shift() : 0); 1590 | path.curveTo(c1x, -c1y, c2x, -c2y, x, -y); 1591 | if (stack.length === 0) { 1592 | break; 1593 | } 1594 | c1x = x + stack.shift(); 1595 | c1y = y; 1596 | c2x = c1x + stack.shift(); 1597 | c2y = c1y + stack.shift(); 1598 | y = c2y + stack.shift(); 1599 | x = c2x + (stack.length === 1 ? stack.shift() : 0); 1600 | path.curveTo(c1x, -c1y, c2x, -c2y, x, -y); 1601 | } 1602 | break; 1603 | case 31: // hvcurveto 1604 | while (stack.length > 0) { 1605 | c1x = x + stack.shift(); 1606 | c1y = y; 1607 | c2x = c1x + stack.shift(); 1608 | c2y = c1y + stack.shift(); 1609 | y = c2y + stack.shift(); 1610 | x = c2x + (stack.length === 1 ? stack.shift() : 0); 1611 | path.curveTo(c1x, -c1y, c2x, -c2y, x, -y); 1612 | if (stack.length === 0) { 1613 | break; 1614 | } 1615 | c1x = x; 1616 | c1y = y + stack.shift(); 1617 | c2x = c1x + stack.shift(); 1618 | c2y = c1y + stack.shift(); 1619 | x = c2x + stack.shift(); 1620 | y = c2y + (stack.length === 1 ? stack.shift() : 0); 1621 | path.curveTo(c1x, -c1y, c2x, -c2y, x, -y); 1622 | } 1623 | break; 1624 | default: 1625 | if (v < 32) { 1626 | throw new Error('Glyph ' + index + ': unknown operator ' + v); 1627 | } else if (v < 247) { 1628 | stack.push(v - 139); 1629 | } else if (v < 251) { 1630 | b1 = code[i]; 1631 | i += 1; 1632 | stack.push((v - 247) * 256 + b1 + 108); 1633 | } else if (v < 255) { 1634 | b1 = code[i]; 1635 | i += 1; 1636 | stack.push(-(v - 251) * 256 - b1 - 108); 1637 | } else { 1638 | b1 = code[i]; 1639 | b2 = code[i + 1]; 1640 | b3 = code[i + 2]; 1641 | b4 = code[i + 3]; 1642 | i += 4; 1643 | stack.push(((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) / 65536); 1644 | } 1645 | } 1646 | } 1647 | } 1648 | 1649 | parse(code); 1650 | glyph = new CffGlyph(font, index); 1651 | glyph.path = path; 1652 | glyph.advanceWidth = width; 1653 | return glyph; 1654 | } 1655 | 1656 | // Subroutines are encoded using the negative half of the number space. 1657 | // See type 2 chapter 4.7 "Subroutine operators". 1658 | function calcCFFSubroutineBias(subrs) { 1659 | var bias; 1660 | if (subrs.length < 1240) { 1661 | bias = 107; 1662 | } else if (subrs.length < 33900) { 1663 | bias = 1131; 1664 | } else { 1665 | bias = 32768; 1666 | } 1667 | return bias; 1668 | } 1669 | 1670 | // Parse the `CFF` table, which contains the glyph outlines in PostScript format. 1671 | function parseCFFTable(data, start, font) { 1672 | var header, nameIndex, topDictIndex, stringIndex, globalSubrIndex, topDict, privateDictOffset, privateDict, 1673 | subrOffset, subrIndex, charString, charStringsIndex, charset, i; 1674 | header = parseCFFHeader(data, start); 1675 | nameIndex = parseCFFIndex(data, header.endOffset, bytesToString); 1676 | topDictIndex = parseCFFIndex(data, nameIndex.endOffset); 1677 | stringIndex = parseCFFIndex(data, topDictIndex.endOffset, bytesToString); 1678 | globalSubrIndex = parseCFFIndex(data, stringIndex.endOffset); 1679 | font.gsubrs = globalSubrIndex.objects; 1680 | font.gsubrsBias = calcCFFSubroutineBias(font.gsubrs); 1681 | 1682 | topDict = parseCFFTopDict(topDictIndex.objects[0], 0, stringIndex.objects); 1683 | 1684 | privateDictOffset = start + topDict['private'][1]; 1685 | privateDict = parseCFFPrivateDict(data, privateDictOffset, topDict['private'][0], stringIndex.objects); 1686 | font.defaultWidthX = privateDict.defaultWidthX; 1687 | font.nominalWidthX = privateDict.nominalWidthX; 1688 | 1689 | subrOffset = privateDictOffset + privateDict.subrs; 1690 | subrIndex = parseCFFIndex(data, subrOffset); 1691 | font.subrs = subrIndex.objects; 1692 | font.subrsBias = calcCFFSubroutineBias(font.subrs); 1693 | 1694 | // Offsets in the top dict are relative to the beginning of the CFF data, so add the CFF start offset. 1695 | charStringsIndex = parseCFFIndex(data, start + topDict.charStrings); 1696 | font.nGlyphs = charStringsIndex.objects.length; 1697 | 1698 | charset = parseCFFCharset(data, start + topDict.charset, font.nGlyphs, stringIndex.objects); 1699 | if (topDict.encoding === 0) { // Standard encoding 1700 | font.cffEncoding = new CffEncoding(cffStandardEncoding, charset); 1701 | } else if (topDict.encoding === 1) { // Expert encoding 1702 | font.cffEncoding = new CffEncoding(cffExpertEncoding, charset); 1703 | } else { 1704 | font.cffEncoding = parseCFFEncoding(data, start + topDict.encoding, charset); 1705 | } 1706 | // Prefer the CMAP encoding to the CFF encoding. 1707 | font.encoding = font.encoding || font.cffEncoding; 1708 | 1709 | font.glyphs = []; 1710 | for (i = 0; i < font.nGlyphs; i += 1) { 1711 | charString = charStringsIndex.objects[i]; 1712 | font.glyphs.push(parseCFFCharstring(charString, font, i)); 1713 | } 1714 | } 1715 | 1716 | // Parse the `hmtx` table, which contains the horizontal metrics for all glyphs. 1717 | // This function augments the glyph array, adding the advanceWidth and leftSideBearing to each glyph. 1718 | // https://www.microsoft.com/typography/OTSPEC/hmtx.htm 1719 | function parseHmtxTable(data, start, numMetrics, numGlyphs, glyphs) { 1720 | var p, i, glyph, advanceWidth, leftSideBearing; 1721 | p = new Parser(data, start); 1722 | for (i = 0; i < numGlyphs; i += 1) { 1723 | // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs. 1724 | if (i < numMetrics) { 1725 | advanceWidth = p.parseUShort(); 1726 | leftSideBearing = p.parseShort(); 1727 | } 1728 | glyph = glyphs[i]; 1729 | glyph.advanceWidth = advanceWidth; 1730 | glyph.leftSideBearing = leftSideBearing; 1731 | } 1732 | } 1733 | 1734 | // Parse the `kern` table which contains kerning pairs. 1735 | // Note that some fonts use the GPOS OpenType layout table to specify kerning. 1736 | // https://www.microsoft.com/typography/OTSPEC/kern.htm 1737 | function parseKernTable(data, start) { 1738 | var pairs, p, tableVersion, nTables, subTableVersion, nPairs, 1739 | i, leftIndex, rightIndex, value; 1740 | pairs = {}; 1741 | p = new Parser(data, start); 1742 | tableVersion = p.parseUShort(); 1743 | checkArgument(tableVersion === 0, "Unsupported kern table version."); 1744 | nTables = p.parseUShort(); 1745 | subTableVersion = p.parseUShort(); 1746 | checkArgument(subTableVersion === 0, "Unsupported kern sub-table version."); 1747 | // Skip subTableLength, subTableCoverage 1748 | p.skip('uShort', 2); 1749 | nPairs = p.parseUShort(); 1750 | // Skip searchRange, entrySelector, rangeShift. 1751 | p.skip('uShort', 3); 1752 | for (i = 0; i < nPairs; i += 1) { 1753 | leftIndex = p.parseUShort(); 1754 | rightIndex = p.parseUShort(); 1755 | value = p.parseShort(); 1756 | pairs[leftIndex + ',' + rightIndex] = value; 1757 | } 1758 | return pairs; 1759 | } 1760 | 1761 | // File loaders ///////////////////////////////////////////////////////// 1762 | 1763 | function loadFromFile(path, callback) { 1764 | fs = fs || require('fs'); 1765 | fs.readFile(path, function (err, buffer) { 1766 | if (err) { 1767 | return callback(err.message); 1768 | } 1769 | 1770 | callback(null, toArrayBuffer(buffer)); 1771 | }); 1772 | } 1773 | 1774 | function loadFromUrl(url, callback) { 1775 | var request = new XMLHttpRequest(); 1776 | request.open('get', url, true); 1777 | request.responseType = 'arraybuffer'; 1778 | request.onload = function () { 1779 | if (request.status !== 200) { 1780 | return callback('Font could not be loaded: ' + request.statusText); 1781 | } 1782 | return callback(null, request.response); 1783 | }; 1784 | request.send(); 1785 | } 1786 | 1787 | // Convert a Node.js Buffer to an ArrayBuffer 1788 | function toArrayBuffer(buffer) { 1789 | var i, 1790 | arrayBuffer = new ArrayBuffer(buffer.length), 1791 | data = new Uint8Array(arrayBuffer); 1792 | 1793 | for (i = 0; i < buffer.length; i += 1) { 1794 | data[i] = buffer[i]; 1795 | } 1796 | 1797 | return arrayBuffer; 1798 | } 1799 | 1800 | // Public API /////////////////////////////////////////////////////////// 1801 | 1802 | // Parse the OpenType file data (as an ArrayBuffer) and return a Font object. 1803 | // If the file could not be parsed (most likely because it contains Postscript outlines) 1804 | // we return an empty Font object with the `supported` flag set to `false`. 1805 | opentype.parse = function (buffer) { 1806 | var font, data, version, numTables, i, p, tag, offset, hmtxOffset, glyfOffset, locaOffset, 1807 | cffOffset, kernOffset, magicNumber, indexToLocFormat, numGlyphs, loca, shortVersion; 1808 | // OpenType fonts use big endian byte ordering. 1809 | // We can't rely on typed array view types, because they operate with the endianness of the host computer. 1810 | // Instead we use DataViews where we can specify endianness. 1811 | 1812 | font = new Font(); 1813 | data = new DataView(buffer, 0); 1814 | 1815 | version = getFixed(data, 0); 1816 | if (version === 1.0) { 1817 | font.outlinesFormat = 'truetype'; 1818 | } else { 1819 | version = getTag(data, 0); 1820 | if (version === 'OTTO') { 1821 | font.outlinesFormat = 'cff'; 1822 | } else { 1823 | throw new Error('Unsupported OpenType version ' + version); 1824 | } 1825 | } 1826 | 1827 | numTables = getUShort(data, 4); 1828 | 1829 | // Offset into the table records. 1830 | p = 12; 1831 | for (i = 0; i < numTables; i += 1) { 1832 | tag = getTag(data, p); 1833 | offset = getULong(data, p + 8); 1834 | switch (tag) { 1835 | case 'cmap': 1836 | font.encoding = parseCmapTable(data, offset); 1837 | if (!font.encoding) { 1838 | font.supported = false; 1839 | } 1840 | break; 1841 | case 'head': 1842 | // We're only interested in some values from the header. 1843 | magicNumber = getULong(data, offset + 12); 1844 | checkArgument(magicNumber === 0x5F0F3CF5, 'Font header has wrong magic number.'); 1845 | font.unitsPerEm = getUShort(data, offset + 18); 1846 | indexToLocFormat = getUShort(data, offset + 50); 1847 | break; 1848 | case 'hhea': 1849 | font.ascender = getShort(data, offset + 4); 1850 | font.descender = getShort(data, offset + 6); 1851 | font.numberOfHMetrics = getUShort(data, offset + 34); 1852 | break; 1853 | case 'hmtx': 1854 | hmtxOffset = offset; 1855 | break; 1856 | case 'maxp': 1857 | // We're only interested in the number of glyphs. 1858 | font.numGlyphs = numGlyphs = getUShort(data, offset + 4); 1859 | break; 1860 | case 'glyf': 1861 | glyfOffset = offset; 1862 | break; 1863 | case 'loca': 1864 | locaOffset = offset; 1865 | break; 1866 | case 'CFF ': 1867 | cffOffset = offset; 1868 | break; 1869 | case 'kern': 1870 | kernOffset = offset; 1871 | break; 1872 | } 1873 | p += 16; 1874 | } 1875 | 1876 | if (glyfOffset && locaOffset) { 1877 | shortVersion = indexToLocFormat === 0; 1878 | loca = parseLocaTable(data, locaOffset, numGlyphs, shortVersion); 1879 | font.glyphs = parseGlyfTable(data, glyfOffset, loca, font); 1880 | parseHmtxTable(data, hmtxOffset, font.numberOfHMetrics, font.numGlyphs, font.glyphs); 1881 | if (kernOffset) { 1882 | font.kerningPairs = parseKernTable(data, kernOffset); 1883 | } else { 1884 | font.kerningPairs = {}; 1885 | } 1886 | } else if (cffOffset) { 1887 | parseCFFTable(data, cffOffset, font); 1888 | font.kerningPairs = {}; 1889 | } else { 1890 | font.supported = false; 1891 | } 1892 | 1893 | return font; 1894 | }; 1895 | 1896 | // Asynchronously load the font from a URL or a filesystem. When done, call the callback 1897 | // with two arguments `(err, font)`. The `err` will be null on success, 1898 | // the `font` is a Font object. 1899 | // 1900 | // We use the node.js callback convention so that 1901 | // opentype.js can integrate with frameworks like async.js. 1902 | opentype.load = function (url, callback) { 1903 | var loader = typeof module !== 'undefined' && module.exports ? loadFromFile : loadFromUrl; 1904 | loader(url, function (err, arrayBuffer) { 1905 | if (err) { 1906 | return callback(err); 1907 | } 1908 | var font = opentype.parse(arrayBuffer); 1909 | if (!font.supported) { 1910 | return callback('Font is not supported (is this a Postscript font?)'); 1911 | } 1912 | return callback(null, font); 1913 | }); 1914 | }; 1915 | 1916 | // Module support /////////////////////////////////////////////////////// 1917 | 1918 | if (typeof define === 'function' && define.amd) { 1919 | // AMD / RequireJS 1920 | define([], function () { 1921 | return opentype; 1922 | }); 1923 | } else if (typeof module === 'object' && module.exports) { 1924 | // node.js 1925 | module.exports = opentype; 1926 | } else { 1927 | // Included directly via a