├── .gitignore ├── .eslintrc.yml ├── fixtures ├── test.eot ├── test.ttf ├── test_v0.eot └── test_v0.ttf ├── test.js ├── package.json ├── README.md ├── LICENSE ├── CHANGELOG.md ├── ttf2eot.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | rules: 2 | no-var: 0 3 | padded-blocks: 0 4 | -------------------------------------------------------------------------------- /fixtures/test.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fontello/ttf2eot/HEAD/fixtures/test.eot -------------------------------------------------------------------------------- /fixtures/test.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fontello/ttf2eot/HEAD/fixtures/test.ttf -------------------------------------------------------------------------------- /fixtures/test_v0.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fontello/ttf2eot/HEAD/fixtures/test_v0.eot -------------------------------------------------------------------------------- /fixtures/test_v0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fontello/ttf2eot/HEAD/fixtures/test_v0.ttf -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | /* global it */ 2 | 'use strict' 3 | 4 | var assert = require('assert') 5 | var fs = require('fs') 6 | var ttf2eot = require('.') 7 | 8 | it('bin compare - OS/2 version 0', function () { 9 | var src = new Uint8Array(fs.readFileSync('./fixtures/test_v0.ttf')) 10 | var dst = new Uint8Array(fs.readFileSync('./fixtures/test_v0.eot')) 11 | 12 | assert.deepEqual(ttf2eot(src), dst) 13 | }) 14 | 15 | it('bin compare', function () { 16 | var src = new Uint8Array(fs.readFileSync('./fixtures/test.ttf')) 17 | var dst = new Uint8Array(fs.readFileSync('./fixtures/test.eot')) 18 | 19 | assert.deepEqual(ttf2eot(src), dst) 20 | }) 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ttf2eot", 3 | "version": "3.1.0", 4 | "description": "Convert TTF font to EOT", 5 | "keywords": [ 6 | "font", 7 | "ttf", 8 | "eot", 9 | "convertor" 10 | ], 11 | "author": "Viktor Semykin ", 12 | "license": "MIT", 13 | "repository": "fontello/ttf2eot", 14 | "bin": { 15 | "ttf2eot": "./ttf2eot.js" 16 | }, 17 | "files": [ 18 | "index.js", 19 | "ttf2eot.js" 20 | ], 21 | "scripts": { 22 | "lint": "standardx . -v", 23 | "test": "npm run lint && mocha" 24 | }, 25 | "dependencies": { 26 | "argparse": "^2.0.1" 27 | }, 28 | "devDependencies": { 29 | "mocha": "^9.2.0", 30 | "standardx": "^7.0.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ttf2eot 2 | ======= 3 | 4 | ttf2eot converts TTF fonts to EOT format. That can be useful for different 5 | webfont generation tools. 6 | 7 | This is node.js port of [ttf2eot](http://code.google.com/p/ttf2eot/). 8 | 9 | 10 | Usage 11 | ----- 12 | 13 | Install: 14 | 15 | ``` bash 16 | npm install -g ttf2eot 17 | ``` 18 | 19 | Usage example: 20 | 21 | ``` bash 22 | ttf2eot fontello.ttf fontello.eot 23 | ``` 24 | 25 | Or: 26 | 27 | ``` bash 28 | ttf2eot < fontello.ttf > fontello.eot 29 | ``` 30 | 31 | 32 | Possible problems 33 | ----------------- 34 | 35 | Due to bug in IE, font `FullName` __MUST__ begin with `FamilyName`. For example, 36 | if `FamilyName` is `fontello`, then `FullName` should be `fontello regular` and 37 | so on. 38 | 39 | In this condition is not satisfyed, then font will not be shown in IE. 40 | 41 | 42 | Authors 43 | ------- 44 | 45 | * Viktor Semykin 46 | 47 | 48 | License 49 | ------- 50 | 51 | Copyright (c) 2013-2016 [Vitaly Puzrin](https://github.com/puzrin). 52 | Released under the MIT license. See 53 | [LICENSE](https://github.com/nodeca/ttf2eot/blob/master/LICENSE) for details. 54 | 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (C) 2013-2016 by Vitaly Puzrin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 3.1.0 / 2022-02-04 2 | ------------------ 3 | 4 | - Add support for old fonts with OS/2 v0 tables. 5 | 6 | 7 | 3.0.0 / 2021-07-29 8 | ------------------ 9 | 10 | - Use node.js `Buffer` instead of `microbuffer` module internally. 11 | - `require('ttf2eot')(buf)` now accepts `Uint8Array` and returns `Uint8Array` instead 12 | of `microbuffer` (you now should use `result` directly instead of `result.buffer`). 13 | 14 | 15 | 2.0.0 / 2016-03-06 16 | ------------------ 17 | 18 | - Maintenance release. 19 | - Drop old nodes support. 20 | - Deps, CS update, tests. 21 | 22 | 23 | 1.3.0 / 2013-11-02 24 | ------------------ 25 | 26 | - API change: now lib takes Array/Uint8Array as TTF data, 27 | and return ByteBuffer object. ByteBuffer.buffer contains 28 | Array/Uint8Array with EOT file. 29 | - Removed `Buffer` dependency, should work in browser too. 30 | 31 | 32 | 1.2.0 / 2013-04-21 33 | ------------------ 34 | 35 | - Separated CLI to make script useable as library (node.js package) 36 | 37 | 38 | 0.1.1 / 2013-04-05 39 | ------------------ 40 | 41 | - Fixed compatibility with original CLI params format 42 | 43 | 0.1.0 / 2013-04-04 44 | ------------------ 45 | 46 | - First release. 47 | 48 | -------------------------------------------------------------------------------- /ttf2eot.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | Author: Viktor Semykin 4 | 5 | Written for fontello.com project. 6 | */ 7 | 8 | 'use strict' 9 | 10 | var fs = require('fs') 11 | var ArgumentParser = require('argparse').ArgumentParser 12 | 13 | var ttf2eot = require('./index.js') 14 | 15 | var parser = new ArgumentParser({ 16 | add_help: true, 17 | description: 'TTF to EOT font converter' 18 | }) 19 | 20 | parser.add_argument( 21 | 'infile', 22 | { 23 | nargs: '?', 24 | help: 'Input file (stdin if not defined)' 25 | } 26 | ) 27 | 28 | parser.add_argument( 29 | 'outfile', 30 | { 31 | nargs: '?', 32 | help: 'Output file (stdout if not defined)' 33 | } 34 | ) 35 | 36 | parser.add_argument( 37 | '-v', '--version', 38 | { 39 | action: 'version', 40 | version: require('./package.json').version, 41 | help: "show program's version number and exit" 42 | } 43 | ) 44 | 45 | /* eslint-disable no-console */ 46 | 47 | var args = parser.parse_args() 48 | 49 | var input 50 | 51 | try { 52 | if (args.infile) { 53 | input = fs.readFileSync(args.infile) 54 | } else { 55 | input = fs.readFileSync(process.stdin.fd) 56 | } 57 | } catch (e) { 58 | console.error("Can't open input file (%s)", args.infile || 'stdin') 59 | process.exit(1) 60 | } 61 | 62 | var eot = ttf2eot(input) 63 | 64 | if (args.outfile) { 65 | fs.writeFileSync(args.outfile, eot) 66 | } else { 67 | process.stdout.write(eot) 68 | } 69 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Viktor Semykin 3 | 4 | Written for fontello.com project. 5 | */ 6 | 7 | 'use strict' 8 | 9 | /** 10 | * Offsets in EOT file structure. Refer to EOTPrefix in OpenTypeUtilities.cpp 11 | */ 12 | var EOT_OFFSET = { 13 | LENGTH: 0, 14 | FONT_LENGTH: 4, 15 | VERSION: 8, 16 | CHARSET: 26, 17 | MAGIC: 34, 18 | FONT_PANOSE: 16, 19 | ITALIC: 27, 20 | WEIGHT: 28, 21 | UNICODE_RANGE: 36, 22 | CODEPAGE_RANGE: 52, 23 | CHECKSUM_ADJUSTMENT: 60 24 | } 25 | 26 | /** 27 | * Offsets in different SFNT (TTF) structures. See OpenTypeUtilities.cpp 28 | */ 29 | var SFNT_OFFSET = { 30 | // sfntHeader: 31 | NUMTABLES: 4, 32 | 33 | // TableDirectoryEntry 34 | TABLE_TAG: 0, 35 | TABLE_OFFSET: 8, 36 | TABLE_LENGTH: 12, 37 | 38 | // OS2Table 39 | OS2_WEIGHT: 4, 40 | OS2_FONT_PANOSE: 32, 41 | OS2_UNICODE_RANGE: 42, 42 | OS2_FS_SELECTION: 62, 43 | OS2_CODEPAGE_RANGE: 78, 44 | 45 | // headTable 46 | HEAD_CHECKSUM_ADJUSTMENT: 8, 47 | 48 | // nameTable 49 | NAMETABLE_FORMAT: 0, 50 | NAMETABLE_COUNT: 2, 51 | NAMETABLE_STRING_OFFSET: 4, 52 | 53 | // nameRecord 54 | NAME_PLATFORM_ID: 0, 55 | NAME_ENCODING_ID: 2, 56 | NAME_LANGUAGE_ID: 4, 57 | NAME_NAME_ID: 6, 58 | NAME_LENGTH: 8, 59 | NAME_OFFSET: 10 60 | } 61 | 62 | /** 63 | * Sizes of structures 64 | */ 65 | var SIZEOF = { 66 | SFNT_TABLE_ENTRY: 16, 67 | SFNT_HEADER: 12, 68 | SFNT_NAMETABLE: 6, 69 | SFNT_NAMETABLE_ENTRY: 12, 70 | EOT_PREFIX: 82 71 | } 72 | 73 | /** 74 | * Magic numbers 75 | */ 76 | var MAGIC = { 77 | EOT_VERSION: 0x00020001, 78 | EOT_MAGIC: 0x504c, 79 | EOT_CHARSET: 1, 80 | LANGUAGE_ENGLISH: 0x0409 81 | } 82 | 83 | /** 84 | * Utility function to convert buffer of utf16be chars to buffer of utf16le 85 | * chars prefixed with length and suffixed with zero 86 | */ 87 | function strbuf (str) { 88 | var arr = Buffer.alloc(str.length + 4) 89 | 90 | arr.writeUint16LE(str.length, 0) 91 | 92 | for (var i = 0; i < str.length; i += 2) { 93 | arr.writeUint16LE(str.readUint16BE(i), i + 2) 94 | } 95 | 96 | arr.writeUint16LE(0, arr.length - 2) 97 | 98 | return arr 99 | } 100 | 101 | // Takes TTF font on input and returns Uint8Array with EOT font 102 | // 103 | // Params: 104 | // 105 | // - arr(Uint8Array) 106 | // 107 | function ttf2eot (arr) { 108 | arr = Buffer.from(arr.buffer, arr.byteOffset, arr.length) 109 | 110 | var out = Buffer.alloc(SIZEOF.EOT_PREFIX, 0) 111 | var i, j 112 | 113 | out.writeUint32LE(arr.length, EOT_OFFSET.FONT_LENGTH) 114 | out.writeUint32LE(MAGIC.EOT_VERSION, EOT_OFFSET.VERSION) 115 | out.writeUint8(MAGIC.EOT_CHARSET, EOT_OFFSET.CHARSET) 116 | out.writeUint16LE(MAGIC.EOT_MAGIC, EOT_OFFSET.MAGIC) 117 | 118 | var familyName = Buffer.of(0) 119 | var subfamilyName = Buffer.of(0) 120 | var fullName = Buffer.of(0) 121 | var versionString = Buffer.of(0) 122 | 123 | var haveOS2 = false 124 | var haveName = false 125 | var haveHead = false 126 | 127 | var numTables = arr.readUint16BE(SFNT_OFFSET.NUMTABLES) 128 | 129 | for (i = 0; i < numTables; ++i) { 130 | var data = arr.subarray(SIZEOF.SFNT_HEADER + i * SIZEOF.SFNT_TABLE_ENTRY) 131 | var tableEntry = { 132 | tag: String.fromCharCode.apply(null, data.subarray(SFNT_OFFSET.TABLE_TAG, SFNT_OFFSET.TABLE_TAG + 4)), 133 | offset: data.readUint32BE(SFNT_OFFSET.TABLE_OFFSET), 134 | length: data.readUint32BE(SFNT_OFFSET.TABLE_LENGTH) 135 | } 136 | 137 | var table = arr.subarray(tableEntry.offset, tableEntry.offset + tableEntry.length) 138 | 139 | if (tableEntry.tag === 'OS/2') { 140 | haveOS2 = true 141 | var OS2Version = table.readUint16BE() 142 | 143 | for (j = 0; j < 10; ++j) { 144 | out.writeUint8(table.readUint8(SFNT_OFFSET.OS2_FONT_PANOSE + j), EOT_OFFSET.FONT_PANOSE + j) 145 | } 146 | 147 | out.writeUint8(table.readUint16BE(SFNT_OFFSET.OS2_FS_SELECTION) & 0x01, EOT_OFFSET.ITALIC) 148 | out.writeUint32LE(table.readUint16BE(SFNT_OFFSET.OS2_WEIGHT), EOT_OFFSET.WEIGHT) 149 | 150 | for (j = 0; j < 4; ++j) { 151 | out.writeUint32LE(table.readUint32BE(SFNT_OFFSET.OS2_UNICODE_RANGE + j * 4), 152 | EOT_OFFSET.UNICODE_RANGE + j * 4) 153 | } 154 | 155 | if (OS2Version >= 1) { 156 | for (j = 0; j < 2; ++j) { 157 | out.writeUint32LE(table.readUint32BE(SFNT_OFFSET.OS2_CODEPAGE_RANGE + j * 4), 158 | EOT_OFFSET.CODEPAGE_RANGE + j * 4) 159 | } 160 | } 161 | 162 | } else if (tableEntry.tag === 'head') { 163 | 164 | haveHead = true 165 | out.writeUint32LE(table.readUint32BE(SFNT_OFFSET.HEAD_CHECKSUM_ADJUSTMENT), EOT_OFFSET.CHECKSUM_ADJUSTMENT) 166 | 167 | } else if (tableEntry.tag === 'name') { 168 | 169 | haveName = true 170 | 171 | var nameTable = { 172 | format: table.readUint16BE(SFNT_OFFSET.NAMETABLE_FORMAT), 173 | count: table.readUint16BE(SFNT_OFFSET.NAMETABLE_COUNT), 174 | stringOffset: table.readUint16BE(SFNT_OFFSET.NAMETABLE_STRING_OFFSET) 175 | } 176 | 177 | for (j = 0; j < nameTable.count; ++j) { 178 | var nameRecord = table.subarray(SIZEOF.SFNT_NAMETABLE + j * SIZEOF.SFNT_NAMETABLE_ENTRY) 179 | var name = { 180 | platformID: nameRecord.readUint16BE(SFNT_OFFSET.NAME_PLATFORM_ID), 181 | encodingID: nameRecord.readUint16BE(SFNT_OFFSET.NAME_ENCODING_ID), 182 | languageID: nameRecord.readUint16BE(SFNT_OFFSET.NAME_LANGUAGE_ID), 183 | nameID: nameRecord.readUint16BE(SFNT_OFFSET.NAME_NAME_ID), 184 | length: nameRecord.readUint16BE(SFNT_OFFSET.NAME_LENGTH), 185 | offset: nameRecord.readUint16BE(SFNT_OFFSET.NAME_OFFSET) 186 | } 187 | 188 | if (name.platformID === 3 && name.encodingID === 1 && name.languageID === MAGIC.LANGUAGE_ENGLISH) { 189 | var s = strbuf(table.subarray( 190 | nameTable.stringOffset + name.offset, 191 | nameTable.stringOffset + name.offset + name.length)) 192 | 193 | switch (name.nameID) { 194 | case 1: 195 | familyName = s 196 | break 197 | case 2: 198 | subfamilyName = s 199 | break 200 | case 4: 201 | fullName = s 202 | break 203 | case 5: 204 | versionString = s 205 | break 206 | } 207 | } 208 | } 209 | } 210 | if (haveOS2 && haveName && haveHead) { break } 211 | } 212 | 213 | if (!(haveOS2 && haveName && haveHead)) { 214 | throw new Error('Required section not found') 215 | } 216 | 217 | // Create final buffer with the the same array type as input one. 218 | var eot = Buffer.concat([ 219 | out, 220 | familyName, 221 | subfamilyName, 222 | versionString, 223 | fullName, 224 | Buffer.from([0, 0]), 225 | arr 226 | ]) 227 | 228 | eot.writeUint32LE(eot.length, EOT_OFFSET.LENGTH) // Calculate overall length 229 | 230 | return new Uint8Array(eot.buffer, eot.byteOffset, eot.length) 231 | } 232 | 233 | module.exports = ttf2eot 234 | --------------------------------------------------------------------------------