├── .editorconfig ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── TODO.md ├── cli.js ├── fontmin.png ├── fonts ├── HYWenHei-85W-1.ttf ├── SentyBrush.ttf ├── TpldKhangXiDictTrial.otf ├── example.html ├── fontawesome-webfont.svg └── svg │ ├── cesu.svg │ ├── qunzu.svg │ ├── shouye.svg │ └── sousuo.svg ├── index.d.ts ├── index.js ├── lib ├── font-face.tpl ├── mime-types.js └── util.js ├── package.json ├── plugins ├── css.js ├── glyph.js ├── otf2ttf.js ├── svg2ttf.js ├── svgs2ttf.js ├── ttf2eot.js ├── ttf2svg.js ├── ttf2woff.js └── ttf2woff2.js └── test ├── base.spec.js ├── font.spec.js ├── mjs ├── example.mjs └── package.json ├── subset.spec.js ├── svg2ttf.spec.js ├── svgs2ttf.spec.js └── ts ├── example.ts ├── package.json └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [package.json] 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | fonts/dest* 5 | /coverage 6 | package-lock.json 7 | test/ts/example.js 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .editorconfig 3 | .travis.yml 4 | npm-debug.log 5 | fonts/ 6 | test/ 7 | node_modules/ 8 | coverage/ 9 | fontmin.png 10 | TODO.md 11 | CHANGELOG.md 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'lts/*' 4 | - 'node' 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 1.0.1 (2023-8-14) 4 | - support esm import 5 | - add dts support 6 | 7 | ## 0.9.9 (2021-9-10) 8 | - merge #75, #76, #86, #92 9 | - update dependences 10 | 11 | ## 0.9.8 (2019-7-18) 12 | - merge #34, #73 13 | - fix #72 UTF-16-encoded code points 14 | - update ttf2woff2@3 15 | - fix (node:48024) [DEP0005] DeprecationWarning: Buffer() is deprecated due to security and usability issues. 16 | 17 | ## 0.9.7 (2018-5-11) 18 | - fix #31 #40 #39 #43 #68 19 | - add woff2 support 20 | - nodejs >= 8 21 | - update dependences 22 | 23 | ## 0.9.6 (2016-5-6) 24 | - fix #31 add trim optional 25 | - add coverage 26 | 27 | ## 0.9.5 (2016-5-5) 28 | - merge #33 fix File clone bug 29 | - update dependences 30 | 31 | ## 0.9.3 (2016-4-6) 32 | - fix #28 subset of non-existent character 33 | - merge #29 Add fontFamily transform function support for css plugin 34 | - update dependences 35 | 36 | ## 0.9.2 (2016-1-5) 37 | - fix #26 parse name table in ASCII 38 | 39 | ## 0.9.1 (2015-12-22) 40 | - subset at fonteditor-core 41 | - performance optimization thx @akira-cn 42 | 43 | ## 0.9.0-alpha-3 (2015-07-30) 44 | 45 | - fix css plugin glyf bug 46 | - up dependence fonteditor-core 47 | 48 | ## 0.9.0-alpha-2 (2015-05-20) 49 | 50 | - css plugin add option local 51 | 52 | ## 0.9.0-alpha-1 (2015-05-06) 53 | 54 | - add otf2ttf plugin 55 | 56 | ## 0.8.1 (2015-05-05) 57 | 58 | - svgs2ttf plugin add option fontName 59 | 60 | ## 0.8.0 (2015-04-27) 61 | 62 | - add svg2ttf plugin 63 | - add svgs2ttf plugin 64 | 65 | ## 0.7.3 (2015-04-22) 66 | 67 | - glyph plugin add option hinting: keep ttf hint info (fpgm, prep, cvt). default = true. [\#4](https://github.com/ecomfe/fontmin/issues/4) 68 | 69 | ## 0.7.2 (2015-04-20) 70 | 71 | - css plugin add option asFileName: rewrite fontFamily as filename force. default = false 72 | - i18n doc zh-tw, jp, kr, en 73 | 74 | ## 0.7.1 (2015-04-20) 75 | 76 | - css plugin add option fontPath: location of font file. [gulp-fontmin\#1](https://github.com/ecomfe/gulp-fontmin/issues/1) 77 | 78 | ## 0.7 (2015-04-16) 79 | 80 | - plugins option clone as default 81 | 82 | ## 0.6 (2015-04-13) 83 | 84 | - first release 85 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 junmer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 | 5 | 6 |

7 | 8 | # fontmin 9 | **Minify font seamlessly** 10 | 11 | [![NPM version][npm-image]][npm-url] 12 | [![Build Status][travis-image]][travis-url] 13 | [![Downloads][downloads-image]][npm-url] 14 | [![Dependencies][dep-image]][dep-url] 15 | [![Font support][font-image]][font-url] 16 | 17 | ## Homepage 18 | 19 | - [简体中文](http://ecomfe.github.io/fontmin/) 20 | - [繁體中文](http://ecomfe.github.io/fontmin/tw) 21 | - [日本語](http://ecomfe.github.io/fontmin/jp) 22 | - [한국어](http://ecomfe.github.io/fontmin/kr) 23 | - [English](http://ecomfe.github.io/fontmin/en) 24 | 25 | ## Install 26 | 27 | ```sh 28 | $ npm install --save fontmin 29 | ``` 30 | 31 | ### Notice 32 | 33 | **fontmin v2.x only support ES Modules, and run on Node v16+.** 34 | 35 | If you need to use CommonJS version, please install `fontmin v1.x`: 36 | 37 | > npm install --save fontmin@1 38 | 39 | ## Usage 40 | 41 | ```js 42 | import Fontmin from 'fontmin'; 43 | 44 | const fontmin = new Fontmin() 45 | .src('fonts/*.ttf') 46 | .dest('build/fonts'); 47 | 48 | fontmin.run(function (err, files) { 49 | if (err) { 50 | throw err; 51 | } 52 | 53 | console.log(files[0]); 54 | // => { contents: } 55 | }); 56 | ``` 57 | 58 | You can use [gulp-rename](https://github.com/hparra/gulp-rename) to rename your files: 59 | 60 | ```js 61 | import Fontmin from 'fontmin'; 62 | const rename = require('gulp-rename'); 63 | 64 | const fontmin = new Fontmin() 65 | .src('fonts/big.ttf') 66 | .use(rename('small.ttf')); 67 | ``` 68 | 69 | ## API 70 | 71 | ### new Fontmin() 72 | 73 | Creates a new `Fontmin` instance. 74 | 75 | ### .src(file) 76 | 77 | Type: `Array|Buffer|String` 78 | 79 | Set the files to be optimized. Takes a buffer, glob string or an array of glob strings 80 | as argument. 81 | 82 | ### .dest(folder) 83 | 84 | Type: `String` 85 | 86 | Set the destination folder to where your files will be written. If you don't set 87 | any destination no files will be written. 88 | 89 | ### .use(plugin) 90 | 91 | Type: `Function` 92 | 93 | Add a `plugin` to the middleware stack. 94 | 95 | ### .run(cb) 96 | 97 | Type: `Function` 98 | 99 | Optimize your files with the given settings. 100 | 101 | #### cb(err, files, stream) 102 | 103 | The callback will return an array of vinyl files in `files` and a Readable/Writable 104 | stream in `stream` 105 | 106 | ## Plugins 107 | 108 | The following plugins are bundled with fontmin: 109 | 110 | * [glyph](#glyph) — Compress ttf by glyph. 111 | * [ttf2eot](#ttf2eot) — Convert ttf to eot. 112 | * [ttf2woff](#ttf2woff) — Convert ttf to woff. 113 | * [ttf2woff2](#ttf2woff2) — Convert ttf to woff2. 114 | * [ttf2svg](#ttf2svg) — Convert ttf to svg. 115 | * [css](#css) — Generate css from ttf, often used to make iconfont. 116 | * [svg2ttf](#svg2ttf) — Convert font format svg to ttf. 117 | * [svgs2ttf](#svgs2ttf) — Concat svg files to a ttf, just like css sprite. 118 | * [otf2ttf](#otf2ttf) — Convert otf to ttf. 119 | 120 | ### .glyph() 121 | 122 | Compress ttf by glyph. 123 | 124 | ```js 125 | import Fontmin from 'fontmin'; 126 | 127 | const fontmin = new Fontmin() 128 | .use(Fontmin.glyph({ 129 | text: '天地玄黄 宇宙洪荒', 130 | hinting: false // keep ttf hint info (fpgm, prep, cvt). default = true 131 | })); 132 | ``` 133 | 134 | ### .ttf2eot() 135 | 136 | Convert ttf to eot. 137 | 138 | ```js 139 | import Fontmin from 'fontmin'; 140 | 141 | const fontmin = new Fontmin() 142 | .use(Fontmin.ttf2eot()); 143 | ``` 144 | 145 | ### .ttf2woff() 146 | 147 | Convert ttf to woff. 148 | 149 | ```js 150 | import Fontmin from 'fontmin'; 151 | 152 | const fontmin = new Fontmin() 153 | .use(Fontmin.ttf2woff({ 154 | deflate: true // deflate woff. default = false 155 | })); 156 | ``` 157 | 158 | ### .ttf2woff2() 159 | 160 | Convert ttf to woff2. 161 | 162 | ```js 163 | import Fontmin from 'fontmin'; 164 | 165 | const fontmin = new Fontmin() 166 | .use(Fontmin.ttf2woff2()); 167 | ``` 168 | 169 | ### .ttf2svg() 170 | 171 | Convert ttf to svg. 172 | 173 | you can use [imagemin-svgo](https://github.com/imagemin/imagemin-svgo) to compress svg: 174 | 175 | ```js 176 | import Fontmin from 'fontmin'; 177 | const svgo = require('imagemin-svgo'); 178 | 179 | const fontmin = new Fontmin() 180 | .use(Fontmin.ttf2svg()) 181 | .use(svgo()); 182 | 183 | ``` 184 | 185 | ### .css() 186 | 187 | Generate css from ttf, often used to make iconfont. 188 | 189 | ```js 190 | import Fontmin from 'fontmin'; 191 | 192 | const fontmin = new Fontmin() 193 | .use(Fontmin.css({ 194 | fontPath: './', // location of font file 195 | base64: true, // inject base64 data:application/x-font-ttf; (gzip font with css). 196 | // default = false 197 | glyph: true, // generate class for each glyph. default = false 198 | iconPrefix: 'my-icon', // class prefix, only work when glyph is `true`. default to "icon" 199 | fontFamily: 'myfont', // custom fontFamily, default to filename or get from analysed ttf file 200 | asFileName: false, // rewrite fontFamily as filename force. default = false 201 | local: true // boolean to add local font. default = false 202 | })); 203 | ``` 204 | 205 | Alternatively, a transform function can be passed as `fontFamily` option. 206 | ```js 207 | import Fontmin from 'fontmin'; 208 | 209 | const fontmin = new Fontmin() 210 | .use(Fontmin.css({ 211 | // ... 212 | fontFamily: function(fontInfo, ttf) { 213 | return "Transformed Font Family Name" 214 | }, 215 | // ... 216 | })); 217 | ``` 218 | 219 | ### .svg2ttf() 220 | 221 | Convert font format svg to ttf. 222 | 223 | ```js 224 | import Fontmin from 'fontmin'; 225 | 226 | const fontmin = new Fontmin() 227 | .src('font.svg') 228 | .use(Fontmin.svg2ttf()); 229 | ``` 230 | 231 | ### .svgs2ttf() 232 | 233 | Concat svg files to a ttf, just like css sprite. 234 | 235 | awesome work with [css](#css) plugin: 236 | 237 | ```js 238 | import Fontmin from 'fontmin'; 239 | 240 | const fontmin = new Fontmin() 241 | .src('svgs/*.svg') 242 | .use(Fontmin.svgs2ttf('font.ttf', {fontName: 'iconfont'})) 243 | .use(Fontmin.css({ 244 | glyph: true 245 | })); 246 | ``` 247 | 248 | ### .otf2ttf() 249 | 250 | Convert otf to ttf. 251 | 252 | ```js 253 | import Fontmin from 'fontmin'; 254 | 255 | const fontmin = new Fontmin() 256 | .src('fonts/*.otf') 257 | .use(Fontmin.otf2ttf()); 258 | ``` 259 | 260 | ## CLI 261 | 262 | ```bash 263 | $ npm install -g fontmin 264 | ``` 265 | 266 | ```sh 267 | $ fontmin --help 268 | 269 | Usage 270 | $ fontmin [] 271 | $ fontmin [] 272 | $ fontmin > 273 | $ cat | fontmin > 274 | 275 | Example 276 | $ fontmin fonts/* build 277 | $ fontmin fonts build 278 | $ fontmin foo.ttf > foo-optimized.ttf 279 | $ cat foo.ttf | fontmin > foo-optimized.ttf 280 | 281 | Options 282 | -t, --text require glyphs by text 283 | -b, --basic-text require glyphs with base chars 284 | -d, --deflate-woff deflate woff 285 | --font-family font-family for @font-face CSS 286 | --css-glyph generate class for each glyf. default = false 287 | -T, --show-time show time fontmin cost 288 | ``` 289 | 290 | you can use `curl` to generate font for websites running on PHP, ASP, Rails and more: 291 | 292 | ```sh 293 | $ text=`curl www.baidu.com` && fontmin -t "$text" font.ttf 294 | ``` 295 | or you can use [html-to-text](https://www.npmjs.com/package/html-to-text) to make it smaller: 296 | 297 | ```sh 298 | $ npm install -g html-to-text 299 | $ text=`curl www.baidu.com | html-to-text` && fontmin -t "$text" font.ttf 300 | ``` 301 | 302 | what is more, you can use [phantom-fetch-cli](https://www.npmjs.com/package/phantom-fetch-cli) to generate font for `SPA` running JS template: 303 | 304 | ```sh 305 | $ npm install -g phantom-fetch-cli 306 | $ text=`phantom-fetch http://www.chinaw3c.org` && fontmin -t "$text" font.ttf 307 | ``` 308 | 309 | ## Related 310 | 311 | - [fontmin-app](https://github.com/ecomfe/fontmin-app) 312 | - [gulp-fontmin](https://github.com/ecomfe/gulp-fontmin) 313 | - [fonteditor](https://github.com/ecomfe/fonteditor) 314 | 315 | ## Thanks 316 | 317 | - [imagemin](https://github.com/imagemin/imagemin) 318 | - [free chinese font](http://zenozeng.github.io/Free-Chinese-Fonts/) 319 | - [浙江民间书刻体][font-url] 320 | 321 | ## License 322 | 323 | MIT © [fontmin](https://raw.githubusercontent.com/ecomfe/fontmin/master/LICENSE) 324 | 325 | 326 | [downloads-image]: http://img.shields.io/npm/dm/fontmin.svg 327 | [npm-url]: https://npmjs.org/package/fontmin 328 | [npm-image]: http://img.shields.io/npm/v/fontmin.svg 329 | 330 | [travis-url]: https://travis-ci.org/ecomfe/fontmin 331 | [travis-image]: http://img.shields.io/travis/ecomfe/fontmin.svg 332 | 333 | [dep-url]: https://david-dm.org/ecomfe/fontmin 334 | [dep-image]: http://img.shields.io/david/ecomfe/fontmin.svg 335 | 336 | [font-image]: https://img.shields.io/badge/font-eonway-blue.svg 337 | [font-url]: http://weibo.com/eonway 338 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | TODO 2 | === 3 | 4 | - [x] glyph plugin 5 | - [x] ttf2eot plugin 6 | - [x] ttf2woff plugin 7 | - [x] ttf2svg plugin 8 | - [x] cli 9 | - [x] test 10 | - [x] travis ci 11 | - [x] independent connect middleware 12 | - [x] independent koa middleware 13 | - [x] refactor plugin code 14 | - [x] font-face css generator 15 | - [x] ttf2woff2 16 | - [ ] use fonteditor-core builtin ttf2woff2 17 | - [x] otf2ttf 18 | - [x] app -> [fontmin-app](https://github.com/ecomfe/fontmin-app) 19 | - [x] ttfmin, only analysis cmap table, get glyph by buffer offset 20 | - [x] get font text from web page - cli 21 | - [x] svgs, merge svgs into one svg 22 | - [x] svg2ttf 23 | - [ ] glyph plugin support otf 24 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * @file cli 5 | * @author junmer 6 | */ 7 | 8 | /* eslint-env node */ 9 | 10 | import * as fs from 'fs'; 11 | import meow from 'meow'; 12 | import * as path from 'path'; 13 | import stdin from 'get-stdin'; 14 | import Fontmin from './index.js'; 15 | import _ from 'lodash'; 16 | 17 | var cli = meow({ 18 | importMeta: import.meta, 19 | help: [ 20 | 'Usage', 21 | ' $ fontmin []', 22 | ' $ fontmin []', 23 | ' $ fontmin > ', 24 | ' $ cat | fontmin > ', 25 | '', 26 | 'Example', 27 | ' $ fontmin fonts/* build', 28 | ' $ fontmin fonts build', 29 | ' $ fontmin foo.ttf > foo-optimized.ttf', 30 | ' $ cat foo.ttf | fontmin > foo-optimized.ttf', 31 | '', 32 | 'Options', 33 | ' -t, --text require glyphs by text', 34 | ' -b, --basic-text require glyphs with base chars', 35 | ' -d, --deflate-woff deflate woff', 36 | ' --font-family font-family for @font-face CSS', 37 | ' --css-glyph generate class for each glyf. default = false', 38 | ' -T, --show-time show time fontmin cost' 39 | ].join('\n'), 40 | flags: { 41 | basicText: { type: 'boolean', shortFlag: 'b' }, 42 | showTime: { type: 'boolean', shortFlag: 'T' }, 43 | deflateWoff: { type: 'boolean', shortFlag: 'd' }, 44 | cssGlyph: { type: 'boolean' }, 45 | text: { type: 'string', shortFlag: 't' }, 46 | fontFamily: { type: 'string' }, 47 | }, 48 | }); 49 | 50 | function isFile(path) { 51 | if (/^[^\s]+\.\w*$/.test(path)) { 52 | return true; 53 | } 54 | 55 | try { 56 | return fs.statSync(path).isFile(); 57 | } 58 | catch (err) { 59 | return false; 60 | } 61 | } 62 | 63 | 64 | function run(src, dest) { 65 | 66 | cli.flags.showTime && console.time('fontmin use'); 67 | 68 | var pluginOpts = _.extend( 69 | {}, 70 | cli.flags, 71 | { 72 | deflate: cli.flags.deflateWoff, 73 | glyph: cli.flags.cssGlyph 74 | } 75 | ); 76 | 77 | var fontmin = new Fontmin() 78 | .src(src) 79 | .use(Fontmin.otf2ttf(pluginOpts)) 80 | .use(Fontmin.glyph(pluginOpts)) 81 | .use(Fontmin.ttf2eot(pluginOpts)) 82 | .use(Fontmin.ttf2svg(pluginOpts)) 83 | .use(Fontmin.ttf2woff(pluginOpts)) 84 | .use(Fontmin.ttf2woff2(pluginOpts)) 85 | .use(Fontmin.css(pluginOpts)); 86 | 87 | if (process.stdout.isTTY) { 88 | fontmin.dest(dest ? dest : 'build'); 89 | } 90 | 91 | fontmin.run(function (err, files) { 92 | if (err) { 93 | console.error(err.stack || err); 94 | process.exit(1); 95 | } 96 | 97 | if (!process.stdout.isTTY) { 98 | files.forEach(function (file) { 99 | process.stdout.write(file.contents); 100 | }); 101 | } 102 | 103 | cli.flags.showTime && console.timeEnd('fontmin use'); 104 | }); 105 | } 106 | 107 | if (process.stdin.isTTY) { 108 | var src = cli.input; 109 | var dest; 110 | 111 | if (!cli.input.length) { 112 | console.error([ 113 | 'Provide at least one file to optimize', 114 | '', 115 | 'Example', 116 | ' fontmin font/* build', 117 | ' fontmin foo.ttf > foo-optimized.ttf', 118 | ' cat foo.ttf | fontmin > foo-optimized.ttf', 119 | '', 120 | 'See `fontmin --help` for more information.' 121 | ].join('\n')); 122 | 123 | process.exit(1); 124 | } 125 | 126 | if (src.length > 1 && !isFile(src[src.length - 1])) { 127 | dest = src[src.length - 1]; 128 | src.pop(); 129 | } 130 | 131 | src = src.map(function (s) { 132 | if (!isFile(s) && fs.existsSync(s)) { 133 | return path.join(s, '**/*'); 134 | } 135 | 136 | return s; 137 | }); 138 | 139 | run(src, dest); 140 | } 141 | else { 142 | stdin.buffer(run); 143 | } 144 | -------------------------------------------------------------------------------- /fontmin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fontmin/1fc536cc0ae901c2e4cb83213310c65233dc4e97/fontmin.png -------------------------------------------------------------------------------- /fonts/HYWenHei-85W-1.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fontmin/1fc536cc0ae901c2e4cb83213310c65233dc4e97/fonts/HYWenHei-85W-1.ttf -------------------------------------------------------------------------------- /fonts/SentyBrush.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fontmin/1fc536cc0ae901c2e4cb83213310c65233dc4e97/fonts/SentyBrush.ttf -------------------------------------------------------------------------------- /fonts/TpldKhangXiDictTrial.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fontmin/1fc536cc0ae901c2e4cb83213310c65233dc4e97/fonts/TpldKhangXiDictTrial.otf -------------------------------------------------------------------------------- /fonts/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | example 6 | 7 | 24 | 25 | 26 |
27 | 天地玄黄    宇宙洪荒    日月盈昃    辰宿列张
28 | 寒来暑往    秋收冬藏    闰馀成岁    律吕调阳
29 | 云腾致雨    露结为霜    金生丽水    玉出昆冈
30 | 剑号巨阙    珠称夜光    果珍李柰    菜重芥姜
31 |     
32 | 33 | 34 | -------------------------------------------------------------------------------- /fonts/svg/cesu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /fonts/svg/qunzu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /fonts/svg/shouye.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /fonts/svg/sousuo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file fontmin 3 | * @author kekee000(kekee000@gmail.com) 4 | */ 5 | import {Transform} from 'stream'; 6 | import {TTF} from 'fonteditor-core' 7 | 8 | type PluginDesc = (...args: any[]) => Transform; 9 | type InternalPlugin = {}> = (opts?: T) => PluginDesc; 10 | 11 | interface GlyphPluginOptions { 12 | /** 13 | * use this text to generate compressed font 14 | */ 15 | text: string; 16 | /** 17 | * add basic chars to glyph, default false 18 | * @example "!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}" 19 | */ 20 | basicText?: boolean; 21 | /** 22 | * keep gylph hinting, defaul true 23 | */ 24 | hinting?: boolean; 25 | /** 26 | * use other plugin 27 | */ 28 | use?: PluginDesc; 29 | } 30 | 31 | interface FontInfo { 32 | fontFile: string; 33 | fontPath: string; 34 | base64: string; 35 | glyph: boolean; 36 | iconPrefix: string; 37 | local: boolean; 38 | } 39 | 40 | interface CssPluginOptions { 41 | /** 42 | * generate class for each glyph. default = false 43 | */ 44 | glyph?: boolean; 45 | /** 46 | * inject base64 data:application/x-font-ttf; (gzip font with css). default = false 47 | */ 48 | base64?: boolean; 49 | /** 50 | * class prefix, only work when glyph is `true`. default = "icon" 51 | */ 52 | iconPrefix?: string; 53 | /** 54 | * rewrite fontFamily from filename force. default = false 55 | */ 56 | asFileName?: boolean; 57 | /** 58 | * location of font file 59 | */ 60 | fontPath?: string; 61 | /** 62 | * custom fontFamily, default = ttf.fontFamily or filename 63 | * 64 | * if opts.fontFamily is funciton, then fontFamily will be function return value 65 | */ 66 | fontFamily?: string | ((fontInfo: FontInfo, ttf: TTF.TTFObject) => string); 67 | /** 68 | * add local font. default = false 69 | */ 70 | local?: boolean; 71 | } 72 | 73 | interface Svgs2ttfPluginOptions { 74 | /** 75 | * set svg font name 76 | */ 77 | fontName?: string; 78 | } 79 | 80 | declare namespace Fontmin { 81 | /* 82 | * get font subset with giving text 83 | */ 84 | const glyph: InternalPlugin; 85 | 86 | /* 87 | * convert ttf to eot 88 | */ 89 | const ttf2eot: InternalPlugin; 90 | 91 | /* 92 | * convert ttf to woff 93 | */ 94 | const ttf2woff: InternalPlugin<{ 95 | /** 96 | * use deflate to transform woff, default false 97 | */ 98 | deflate: boolean; 99 | }>; 100 | 101 | /* 102 | * convert ttf to woff2 103 | */ 104 | const ttf2woff2: InternalPlugin; 105 | 106 | /* 107 | * convert ttf to svg text 108 | */ 109 | const ttf2svg: InternalPlugin; 110 | 111 | /* 112 | * Generate css from ttf, often used to make iconfont. 113 | */ 114 | const css: InternalPlugin; 115 | 116 | /** 117 | * convert font format svg to ttf 118 | */ 119 | const svg2ttf: InternalPlugin<{hinting?: boolean}>; 120 | 121 | /** 122 | * concat svg files to a ttf, just like css sprite 123 | */ 124 | const svgs2ttf: (file: string, opts?: Svgs2ttfPluginOptions) => PluginDesc; 125 | 126 | /** 127 | * convert otf to ttf 128 | */ 129 | const otf2ttf: InternalPlugin; 130 | } 131 | 132 | type PluginNames = keyof typeof Fontmin; 133 | 134 | declare class Fontmin { 135 | static plugins: PluginNames[]; 136 | /** 137 | * Get or set the source files 138 | * @param file files to be optimized 139 | */ 140 | src(src: ArrayLike | Buffer | string): this; 141 | /** 142 | * Get or set the destination folder 143 | * @param dir folder to written 144 | */ 145 | dest(dest: string): this; 146 | 147 | /** 148 | * Add a plugin to the middleware stack 149 | * @param plugin plugin function 150 | */ 151 | use(plugin: PluginDesc): this; 152 | 153 | /** 154 | * run Optimize files with callback 155 | * @param callback plugin function 156 | */ 157 | run(callback: (e: Error, files: Buffer[]) => void): Transform; 158 | 159 | /** 160 | * run Optimize files with return Promise 161 | */ 162 | runAsync(): Promise; 163 | } 164 | 165 | export default Fontmin; 166 | 167 | export const mime: { 168 | '.*': 'application/octet-stream', 169 | 'ttf': 'application/font-sfnt', 170 | 'otf': 'application/font-sfnt', 171 | 'woff': 'application/font-woff', 172 | 'woff2': 'application/font-woff2', 173 | 'eot': 'application/octet-stream', 174 | 'svg': 'image/svg+xml', 175 | 'svgz': 'image/svg+xml' 176 | }; 177 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file fontmin 3 | * @author junmer 4 | */ 5 | 6 | /* eslint-env node */ 7 | 8 | import combine from 'stream-combiner'; 9 | import concat from 'concat-stream'; 10 | import { EventEmitter } from 'events'; 11 | import { inherits } from 'util'; 12 | import * as bufferToVinyl from 'buffer-to-vinyl'; 13 | import vfs from 'vinyl-fs'; 14 | 15 | import * as util from './lib/util.js'; 16 | import mime from './lib/mime-types.js'; 17 | 18 | import glyph from './plugins/glyph.js'; 19 | import ttf2eot from './plugins/ttf2eot.js'; 20 | import ttf2woff from './plugins/ttf2woff.js'; 21 | import ttf2woff2 from './plugins/ttf2woff2.js'; 22 | import ttf2svg from './plugins/ttf2svg.js'; 23 | import css from './plugins/css.js'; 24 | import svg2ttf from './plugins/svg2ttf.js'; 25 | import svgs2ttf from './plugins/svgs2ttf.js'; 26 | import otf2ttf from './plugins/otf2ttf.js'; 27 | 28 | /** 29 | * Initialize Fontmin 30 | * 31 | * @constructor 32 | * @api public 33 | */ 34 | function Fontmin() { 35 | if (!(this instanceof Fontmin)) { 36 | return new Fontmin(); 37 | } 38 | 39 | EventEmitter.call(this); 40 | this.streams = []; 41 | } 42 | 43 | /** 44 | * Inherit from `EventEmitter` 45 | * @type {Class} 46 | */ 47 | inherits(Fontmin, EventEmitter); 48 | 49 | /** 50 | * Get or set the source files 51 | * 52 | * @param {Array|Buffer|string} file files to be optimized 53 | * @return {Object} fontmin 54 | * @api public 55 | */ 56 | Fontmin.prototype.src = function (file) { 57 | if (!arguments.length) { 58 | return this._src; 59 | } 60 | 61 | this._src = arguments; 62 | return this; 63 | }; 64 | 65 | /** 66 | * Get or set the destination folder 67 | * 68 | * @param {string} dir folder to written 69 | * @return {Object} fontmin 70 | * @api public 71 | */ 72 | Fontmin.prototype.dest = function (dir) { 73 | if (!arguments.length) { 74 | return this._dest; 75 | } 76 | 77 | this._dest = arguments; 78 | return this; 79 | }; 80 | 81 | /** 82 | * Add a plugin to the middleware stack 83 | * 84 | * @param {Function} plugin plugin 85 | * @return {Object} fontmin 86 | * @api public 87 | */ 88 | Fontmin.prototype.use = function (plugin) { 89 | this.streams.push(typeof plugin === 'function' ? plugin() : plugin); 90 | return this; 91 | }; 92 | 93 | /** 94 | * Optimize files 95 | * 96 | * @param {Function} cb callback 97 | * @return {Stream} file stream 98 | * @api public 99 | */ 100 | Fontmin.prototype.run = function (cb) { 101 | cb = cb || function () {}; 102 | 103 | var stream = this.createStream(); 104 | 105 | stream.on('error', cb); 106 | stream.pipe(concat(cb.bind(null, null))); 107 | 108 | return stream; 109 | }; 110 | 111 | /** 112 | * run Optimize files with return Promise 113 | * 114 | * @return {Array} file result 115 | * @api public 116 | */ 117 | Fontmin.prototype.runAsync = function () { 118 | return new Promise((resolve, reject) => { 119 | var stream = this.createStream(); 120 | stream.on('error', reject); 121 | 122 | stream.pipe(concat(resolve)); 123 | }); 124 | }; 125 | 126 | 127 | /** 128 | * Create stream 129 | * 130 | * @return {Stream} file stream 131 | * @api private 132 | */ 133 | Fontmin.prototype.createStream = function () { 134 | this.streams.unshift(this.getFiles()); 135 | 136 | if (this.streams.length === 1) { 137 | this.use(Fontmin.otf2ttf()); 138 | this.use(Fontmin.ttf2eot()); 139 | this.use(Fontmin.ttf2woff()); 140 | this.use(Fontmin.ttf2woff2()); 141 | this.use(Fontmin.ttf2svg()); 142 | this.use(Fontmin.css()); 143 | } 144 | 145 | if (this.dest()) { 146 | this.streams.push( 147 | vfs.dest.apply(vfs, this.dest()) 148 | ); 149 | } 150 | 151 | return combine(this.streams); 152 | }; 153 | 154 | /** 155 | * Get files 156 | * 157 | * @return {Stream} file stream 158 | * @api private 159 | */ 160 | Fontmin.prototype.getFiles = function () { 161 | 162 | if (Buffer.isBuffer(this._src[0])) { 163 | return bufferToVinyl.stream(this._src[0]); 164 | } 165 | 166 | var [src, options] = this.src(); 167 | return vfs.src(src, {encoding: false, ...options}); 168 | }; 169 | 170 | /** 171 | * plugins 172 | * 173 | * @type {Array} 174 | */ 175 | Fontmin.plugins = [ 176 | 'glyph', 177 | 'ttf2eot', 178 | 'ttf2woff', 179 | 'ttf2woff2', 180 | 'ttf2svg', 181 | 'css', 182 | 'svg2ttf', 183 | 'svgs2ttf', 184 | 'otf2ttf' 185 | ]; 186 | 187 | // export pkged plugins 188 | Fontmin.glyph = glyph; 189 | Fontmin.ttf2eot = ttf2eot; 190 | Fontmin.ttf2woff = ttf2woff; 191 | Fontmin.ttf2woff2 = ttf2woff2; 192 | Fontmin.ttf2svg = ttf2svg; 193 | Fontmin.css = css; 194 | Fontmin.svg2ttf = svg2ttf; 195 | Fontmin.svgs2ttf = svgs2ttf; 196 | Fontmin.otf2ttf = otf2ttf; 197 | 198 | /** 199 | * Module exports 200 | */ 201 | 202 | export { util, mime }; 203 | export default Fontmin; 204 | Fontmin.util = util; 205 | Fontmin.mime = mime; 206 | -------------------------------------------------------------------------------- /lib/font-face.tpl: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "<%=fontFamily%>"; 3 | src: url("<%=fontPath%><%=fontFile%>.eot"); /* IE9 */ 4 | src: <%if (local) {%>local("<%=local%>"), <%}%>url("<%=fontPath%><%=fontFile%>.eot?#iefix") format("embedded-opentype"), /* IE6-IE8 */<% if (base64) { %> 5 | url(<%=base64%>) format("truetype"), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */<% } else { %> 6 | url("<%=fontPath%><%=fontFile%>.woff2") format("woff2"), /* chrome 36+, firefox 39+,iOS 10+, Android 67+ */ 7 | url("<%=fontPath%><%=fontFile%>.woff") format("woff"), /* chrome, firefox */ 8 | url("<%=fontPath%><%=fontFile%>.ttf") format("truetype"), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */<% } %> 9 | url("<%=fontPath%><%=fontFile%>.svg#<%=fontFamily%>") format("svg"); /* iOS 4.1- */ 10 | font-style: normal; 11 | font-weight: normal; 12 | } 13 | 14 | <% if (glyph) { %> 15 | [class^="<%=iconPrefix%>-"], 16 | [class*=" <%=iconPrefix%>-"]:after { 17 | font-family: "<%=fontFamily%>"; 18 | speak: none; 19 | font-style: normal; 20 | font-weight: normal; 21 | font-variant: normal; 22 | text-transform: none; 23 | line-height: 1; 24 | -webkit-font-smoothing: antialiased; 25 | -moz-osx-font-smoothing: grayscale; 26 | } 27 | 28 | <% _.each(glyfList, function(glyf) { %> 29 | .<%=iconPrefix%>-<%=glyf.name%>:before { 30 | content: "<%=glyf.codeName%>"; 31 | } 32 | <% }); %> 33 | <% }; %> 34 | -------------------------------------------------------------------------------- /lib/mime-types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file mine types 3 | * @author junmer 4 | */ 5 | 6 | /* eslint-env node */ 7 | 8 | export default { 9 | '.*': 'application/octet-stream', 10 | 'ttf': 'application/font-sfnt', 11 | 'otf': 'application/font-sfnt', 12 | 'woff': 'application/font-woff', 13 | 'woff2': 'application/font-woff2', 14 | 'eot': 'application/octet-stream', 15 | 'svg': 'image/svg+xml', 16 | 'svgz': 'image/svg+xml' 17 | }; 18 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file util 3 | * @author junmer 4 | */ 5 | 6 | /* eslint-env node */ 7 | 8 | import * as fs from 'fs'; 9 | import * as path from 'path'; 10 | import _ from 'lodash'; 11 | import codePoints from 'code-points'; 12 | 13 | /** 14 | * getFontFolder 15 | * 16 | * @return {string} fontFolder 17 | */ 18 | export function getFontFolder() { 19 | return path.resolve({ 20 | win32: '/Windows/fonts', 21 | darwin: '/Library/Fonts', 22 | linux: '/usr/share/fonts/truetype' 23 | }[process.platform]); 24 | } 25 | 26 | /** 27 | * getFonts 28 | * 29 | * @param {string} path path 30 | * @return {Array} fonts 31 | */ 32 | export function getFonts() { 33 | return fs.readdirSync(getFontFolder()); 34 | } 35 | 36 | /** 37 | * getPureText 38 | * 39 | * @see https://msdn.microsoft.com/zh-cn/library/ie/2yfce773 40 | * @see http://www.unicode.org/charts/ 41 | * 42 | * @param {string} str target text 43 | * @return {string} pure text 44 | */ 45 | export function getPureText(str) { 46 | 47 | // fix space 48 | var emptyTextMap = {}; 49 | 50 | function replaceEmpty (word) { 51 | emptyTextMap[word] = 1; 52 | return ''; 53 | } 54 | 55 | var pureText = String(str) 56 | .replace(/[\s]/g, replaceEmpty) 57 | .trim() 58 | // .replace(/[\f]/g, '') 59 | // .replace(/[\b]/g, '') 60 | // .replace(/[\n]/g, '') 61 | // .replace(/[\t]/g, '') 62 | // .replace(/[\r]/g, '') 63 | .replace(/[\u2028]/g, '') 64 | .replace(/[\u2029]/g, ''); 65 | 66 | var emptyText = Object.keys(emptyTextMap).join(''); 67 | 68 | return pureText + emptyText; 69 | 70 | } 71 | 72 | /** 73 | * getUniqText 74 | * 75 | * @deprecated since version 0.9.9 76 | * 77 | * @param {string} str target text 78 | * @return {string} uniq text 79 | */ 80 | export function getUniqText(str) { 81 | return _.uniq( 82 | str.split('') 83 | ).join(''); 84 | } 85 | 86 | 87 | /** 88 | * basic chars 89 | * 90 | * "!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}" 91 | * 92 | * @type {string} 93 | */ 94 | var basicText = String.fromCharCode.apply(this, _.range(33, 126)); 95 | 96 | /** 97 | * get subset text 98 | * 99 | * @param {Object} opts opts 100 | * @return {string} subset text 101 | */ 102 | export function getSubsetText(opts) { 103 | 104 | var text = opts.text || ''; 105 | 106 | // trim 107 | text && opts.trim && (text = getPureText(text)); 108 | 109 | // basicText 110 | opts.basicText && (text += basicText); 111 | 112 | return text; 113 | } 114 | 115 | /** 116 | * string to unicodes 117 | * 118 | * @param {string} str string 119 | * @return {Array} unicodes 120 | */ 121 | export function string2unicodes(str) { 122 | return _.uniq(codePoints(str)); 123 | } 124 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fontmin", 3 | "version": "2.0.0", 4 | "description": "Minify font seamlessly, font subsetter, webfont (eot, woff, svg) converter.", 5 | "type": "module", 6 | "main": "index.js", 7 | "types": "index.d.ts", 8 | "keywords": [ 9 | "font", 10 | "webfont", 11 | "icon", 12 | "iconfont", 13 | "font-face", 14 | "compress", 15 | "minify", 16 | "font-cli", 17 | "otf", 18 | "ttf", 19 | "woff", 20 | "woff2", 21 | "eot", 22 | "svg", 23 | "ttf2eot", 24 | "ttf2woff", 25 | "ttf2woff2", 26 | "ttf2svg", 27 | "svg2ttf", 28 | "css", 29 | "base64" 30 | ], 31 | "author": "junmer", 32 | "license": "MIT", 33 | "repository": "ecomfe/fontmin", 34 | "engines": { 35 | "node": ">=16" 36 | }, 37 | "bin": { 38 | "fontmin": "cli.js" 39 | }, 40 | "scripts": { 41 | "test": "mocha test/*.spec.js", 42 | "coverage": "c8 mocha --reporter spec --check-leaks test/*.spec.js" 43 | }, 44 | "exports": { 45 | "require": "./index.js", 46 | "import": "./index.js" 47 | }, 48 | "dependencies": { 49 | "@types/node": "*", 50 | "@types/through2": "^2.0.38", 51 | "b3b": "^0.0.1", 52 | "buffer-to-vinyl": "^1.0.0", 53 | "code-points": "^2.0.0-1", 54 | "concat-stream": "^2.0.0", 55 | "fonteditor-core": "^2.4.0", 56 | "get-stdin": "^9.0.0", 57 | "is-otf": "^0.1.2", 58 | "is-svg": "^5.1.0", 59 | "is-ttf": "^0.2.2", 60 | "lodash": "^4.17.10", 61 | "meow": "^13.2.0", 62 | "pako": "^2.0.3", 63 | "replace-ext": "^2.0.0", 64 | "stream-combiner": "^0.2.1", 65 | "through2": "^4.0.2", 66 | "ttf2woff2": "^6.0.1", 67 | "vinyl-fs": "^4.0.0" 68 | }, 69 | "devDependencies": { 70 | "c8": "^10.1.2", 71 | "chai": "^5.1.2", 72 | "gulp-clean": "^0.4.0", 73 | "is-eot": "^1.0.0", 74 | "is-woff": "^1.0.1", 75 | "is-woff2": "^1.0.0", 76 | "mocha": "^10.8.2" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /plugins/css.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file css 3 | * @author junmer 4 | */ 5 | 6 | /* eslint-env node */ 7 | import _ from 'lodash'; 8 | import * as fs from 'fs'; 9 | import * as path from 'path'; 10 | import * as url from 'url'; 11 | import isTtf from 'is-ttf'; 12 | import through from 'through2'; 13 | import replaceExt from 'replace-ext'; 14 | import { b2a } from 'b3b'; 15 | 16 | /** 17 | * tpl 18 | * 19 | * @type {string} 20 | */ 21 | var tpl = fs.readFileSync( 22 | url.fileURLToPath(new URL('../lib/font-face.tpl', import.meta.url)) 23 | ).toString('utf-8'); 24 | 25 | /** 26 | * renderCss 27 | * 28 | * @type {function} 29 | */ 30 | var renderCss = _.template(tpl); 31 | 32 | 33 | /** 34 | * listUnicode 35 | * 36 | * @param {Array} unicode unicode 37 | * @return {string} unicode string 38 | */ 39 | function listUnicode(unicode) { 40 | return unicode.map(function (u) { 41 | return '\\' + u.toString(16); 42 | }).join(','); 43 | } 44 | 45 | /** 46 | * get glyf list from ttf obj 47 | * 48 | * @param {ttfObject} ttf ttfObject 49 | * @return {Object} icon obj 50 | */ 51 | function getGlyfList(ttf) { 52 | 53 | var glyfList = []; 54 | 55 | // exclude empty glyf 56 | var filtered = ttf.glyf.filter(function (g) { 57 | return g.name !== '.notdef' 58 | && g.name !== '.null' 59 | && g.name !== 'nonmarkingreturn' 60 | && g.unicode && g.unicode.length; 61 | }); 62 | 63 | // format glyf info 64 | filtered.forEach(function (g) { 65 | glyfList.push({ 66 | code: '&#x' + g.unicode[0].toString(16) + ';', 67 | codeName: listUnicode(g.unicode), 68 | name: g.name || 'uni' + g.unicode[0].toString(16) 69 | }); 70 | }); 71 | 72 | return { 73 | glyfList: glyfList 74 | }; 75 | 76 | } 77 | 78 | /** 79 | * get font family name 80 | * 81 | * @param {Object} fontInfo font info object 82 | * @param {ttfObject} ttf ttfObject 83 | * @param {Object} opts opts 84 | * @return {string} font family name 85 | */ 86 | function getFontFamily(fontInfo, ttf, opts) { 87 | var fontFamily = opts.fontFamily; 88 | // Call transform function 89 | if (typeof fontFamily === 'function') { 90 | fontFamily = fontFamily(_.cloneDeep(fontInfo), ttf); 91 | } 92 | return fontFamily || ttf.name.fontFamily || fontInfo.fontFile; 93 | } 94 | 95 | /** 96 | * Transform font family name 97 | * @callback FontFamilyTransform 98 | * @param {Object} font info object 99 | * @param {ttfObject} ttf ttfObject 100 | * @return {string} font family name 101 | */ 102 | // function(fontInfo, ttfObject) { return "Font Name"; } 103 | 104 | /** 105 | * css fontmin plugin 106 | * 107 | * @param {Object} opts opts 108 | * @param {boolean=} opts.glyph generate class for each glyph. default = false 109 | * @param {boolean=} opts.base64 inject base64 110 | * @param {string=} opts.iconPrefix icon prefix 111 | * @param {string=} opts.filename set filename 112 | * @param {(string|FontFamilyTransform)=} opts.fontFamily fontFamily 113 | * @return {Object} stream.Transform instance 114 | * @api public 115 | */ 116 | export default function (opts) { 117 | opts = opts || {}; 118 | 119 | return through.ctor({ 120 | objectMode: true 121 | }, function (file, enc, cb) { 122 | 123 | // check null 124 | if (file.isNull()) { 125 | cb(null, file); 126 | return; 127 | } 128 | 129 | // check stream 130 | if (file.isStream()) { 131 | cb(new Error('Streaming is not supported')); 132 | return; 133 | } 134 | 135 | // check ttf 136 | if (!isTtf(file.contents)) { 137 | cb(null, file); 138 | return; 139 | } 140 | 141 | // clone 142 | this.push(file.clone(false)); 143 | 144 | file.path = replaceExt(file.path, '.css'); 145 | var fontFile = opts.filename || path.basename(file.path, '.css'); 146 | 147 | // font data 148 | var fontInfo = { 149 | fontFile: fontFile, 150 | fontPath: '', 151 | base64: '', 152 | glyph: false, 153 | iconPrefix: 'icon', 154 | local: false 155 | }; 156 | 157 | // opts 158 | _.extend(fontInfo, opts); 159 | 160 | // ttf obj 161 | var ttfObject = file.ttfObject || { 162 | name: {} 163 | }; 164 | 165 | // glyph 166 | if (opts.glyph && ttfObject.glyf) { 167 | _.extend( 168 | fontInfo, 169 | getGlyfList(ttfObject) 170 | ); 171 | } 172 | 173 | // font family 174 | fontInfo.fontFamily = getFontFamily(fontInfo, ttfObject, opts); 175 | 176 | // rewrite font family as filename 177 | if (opts.asFileName) { 178 | fontInfo.fontFamily = fontFile; 179 | } 180 | 181 | // base64 182 | if (opts.base64) { 183 | fontInfo.base64 = '' 184 | + 'data:application/x-font-ttf;charset=utf-8;base64,' 185 | + b2a(file.contents); 186 | } 187 | 188 | // local 189 | if (fontInfo.local === true) { 190 | fontInfo.local = fontInfo.fontFamily; 191 | } 192 | 193 | // render 194 | var output = _.attempt(function (data) { 195 | return Buffer.from(renderCss(data)); 196 | }, fontInfo); 197 | 198 | if (_.isError(output)) { 199 | cb(output, file); 200 | } 201 | else { 202 | file.contents = output; 203 | cb(null, file); 204 | } 205 | 206 | }); 207 | 208 | }; 209 | -------------------------------------------------------------------------------- /plugins/glyph.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file glyph 3 | * @author junmer 4 | */ 5 | 6 | /* eslint-env node */ 7 | 8 | import _ from 'lodash'; 9 | import isTtf from 'is-ttf'; 10 | import through from 'through2'; 11 | import fonteditorCore from 'fonteditor-core'; 12 | import { b2ab, ab2b } from 'b3b'; 13 | import * as util from '../lib/util.js'; 14 | 15 | /** 16 | * getSubsetGlyfs 17 | * 18 | * @param {ttfObject} ttf ttfobj 19 | * @param {Array} subset subset unicode 20 | * @return {Array} glyfs array 21 | */ 22 | function getSubsetGlyfs(ttf, subset) { 23 | 24 | var glyphs = []; 25 | 26 | var indexList = ttf.findGlyf({ 27 | unicode: subset || [] 28 | }); 29 | 30 | if (indexList.length) { 31 | glyphs = ttf.getGlyf(indexList); 32 | } 33 | 34 | glyphs.unshift(ttf.get().glyf[0]); 35 | 36 | return glyphs; 37 | } 38 | 39 | 40 | /** 41 | * minifyFontObject 42 | * 43 | * @param {Object} ttfObject ttfObject 44 | * @param {Array} subset subset 45 | * @param {Function=} plugin use plugin 46 | * @return {Object} ttfObject 47 | */ 48 | function minifyFontObject(ttfObject, subset, plugin) { 49 | 50 | // check null 51 | if (subset.length === 0) { 52 | return ttfObject; 53 | } 54 | 55 | // new TTF Object 56 | var ttf = new fonteditorCore.TTF(ttfObject); 57 | 58 | // get target glyfs then set 59 | ttf.setGlyf(getSubsetGlyfs(ttf, subset)); 60 | 61 | // use plugin 62 | if (_.isFunction(plugin)) { 63 | plugin(ttf); 64 | } 65 | 66 | return ttf.get(); 67 | } 68 | 69 | 70 | /** 71 | * minifyTtf 72 | * 73 | * @param {Buffer|Object} contents contents 74 | * @param {Object} opts opts 75 | * @return {Buffer} buffer 76 | */ 77 | function minifyTtf(contents, opts) { 78 | 79 | opts = opts || {}; 80 | 81 | var ttfobj = contents; 82 | 83 | if (Buffer.isBuffer(contents)) { 84 | ttfobj = new fonteditorCore.TTFReader(opts).read(b2ab(contents)); 85 | } 86 | 87 | var miniObj = minifyFontObject( 88 | ttfobj, 89 | opts.subset, 90 | opts.use 91 | ); 92 | 93 | var ttfBuffer = ab2b( 94 | new fonteditorCore.TTFWriter(Object.assign({writeZeroContoursGlyfData: true}, opts)).write(miniObj) 95 | ); 96 | 97 | return { 98 | object: miniObj, 99 | buffer: ttfBuffer 100 | }; 101 | 102 | } 103 | 104 | 105 | /** 106 | * glyph fontmin plugin 107 | * 108 | * @param {Object} opts opts 109 | * @param {string=} opts.text text 110 | * @param {boolean=} opts.basicText useBasicText 111 | * @param {boolean=} opts.hinting hint 112 | * @param {Function=} opts.use plugin 113 | * @return {Object} stream.Transform instance 114 | * @api public 115 | */ 116 | export default function (opts) { 117 | 118 | opts = _.extend({hinting: true, trim: true}, opts); 119 | 120 | // prepare subset 121 | var subsetText = util.getSubsetText(opts); 122 | opts.subset = util.string2unicodes(subsetText); 123 | 124 | 125 | return through.ctor({ 126 | objectMode: true 127 | }, function (file, enc, cb) { 128 | 129 | // check null 130 | if (file.isNull()) { 131 | cb(null, file); 132 | return; 133 | } 134 | 135 | // check stream 136 | if (file.isStream()) { 137 | cb(new Error('Streaming is not supported')); 138 | return; 139 | } 140 | 141 | // check ttf 142 | if (!isTtf(file.contents)) { 143 | cb(null, file); 144 | return; 145 | } 146 | 147 | try { 148 | 149 | // write file buffer 150 | var miniTtf = minifyTtf( 151 | file.ttfObject || file.contents, 152 | opts 153 | ); 154 | 155 | file.contents = miniTtf.buffer; 156 | file.ttfObject = miniTtf.object; 157 | 158 | cb(null, file); 159 | 160 | } 161 | catch (err) { 162 | cb(err); 163 | } 164 | 165 | }); 166 | 167 | }; 168 | -------------------------------------------------------------------------------- /plugins/otf2ttf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file otf2ttf 3 | * @author junmer 4 | */ 5 | 6 | /* eslint-env node */ 7 | 8 | import isOtf from 'is-otf'; 9 | import through from 'through2'; 10 | import fonteditorCore from 'fonteditor-core'; 11 | import { b2ab, ab2b } from 'b3b'; 12 | import replaceExt from 'replace-ext'; 13 | import _ from 'lodash'; 14 | import * as util from '../lib/util.js'; 15 | 16 | /** 17 | * otf2ttf fontmin plugin 18 | * 19 | * @param {Object} opts opts 20 | * @return {Object} stream.Transform instance 21 | * @api public 22 | */ 23 | export default function (opts) { 24 | 25 | opts = _.extend({clone: false, hinting: true}, opts); 26 | 27 | // prepare subset 28 | var subsetText = util.getSubsetText(opts); 29 | opts.subset = util.string2unicodes(subsetText); 30 | 31 | return through.ctor({ 32 | objectMode: true 33 | }, function (file, enc, cb) { 34 | 35 | // check null 36 | if (file.isNull()) { 37 | cb(null, file); 38 | return; 39 | } 40 | 41 | // check stream 42 | if (file.isStream()) { 43 | cb(new Error('Streaming is not supported')); 44 | return; 45 | } 46 | 47 | // check otf 48 | if (!isOtf(file.contents)) { 49 | cb(null, file); 50 | return; 51 | } 52 | 53 | // clone 54 | if (opts.clone) { 55 | this.push(file.clone(false)); 56 | } 57 | 58 | // replace ext 59 | file.path = replaceExt(file.path, '.ttf'); 60 | 61 | // ttf info 62 | var ttfBuffer; 63 | var ttfObj; 64 | 65 | // try otf2ttf 66 | try { 67 | 68 | ttfObj = fonteditorCore.otf2ttfobject(b2ab(file.contents), opts); 69 | 70 | ttfBuffer = ab2b(new fonteditorCore.TTFWriter(opts).write(ttfObj)); 71 | 72 | } 73 | catch (ex) { 74 | cb(ex); 75 | } 76 | 77 | if (ttfBuffer) { 78 | file.contents = ttfBuffer; 79 | file.ttfObject = ttfObj; 80 | cb(null, file); 81 | } 82 | 83 | }); 84 | 85 | }; 86 | 87 | -------------------------------------------------------------------------------- /plugins/svg2ttf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file svg2ttf 3 | * @author junmer 4 | */ 5 | 6 | /* eslint-env node */ 7 | 8 | import isSvg from 'is-svg'; 9 | import through from 'through2'; 10 | import fonteditorCore from 'fonteditor-core'; 11 | import { ab2b } from 'b3b'; 12 | import replaceExt from 'replace-ext'; 13 | import _ from 'lodash'; 14 | 15 | /** 16 | * svg2ttf fontmin plugin 17 | * 18 | * @param {Object} opts opts 19 | * @return {Object} stream.Transform instance 20 | * @api public 21 | */ 22 | export default function (opts) { 23 | 24 | opts = _.extend({clone: true, hinting: true}, opts); 25 | 26 | return through.ctor({ 27 | objectMode: true 28 | }, function (file, enc, cb) { 29 | 30 | // check null 31 | if (file.isNull()) { 32 | cb(null, file); 33 | return; 34 | } 35 | 36 | // check stream 37 | if (file.isStream()) { 38 | cb(new Error('Streaming is not supported')); 39 | return; 40 | } 41 | 42 | // check svg 43 | if (!isSvg(file.contents.toString())) { 44 | cb(null, file); 45 | return; 46 | } 47 | 48 | // clone 49 | if (opts.clone) { 50 | this.push(file.clone(false)); 51 | } 52 | 53 | // replace ext 54 | file.path = replaceExt(file.path, '.ttf'); 55 | 56 | 57 | // ttf buffer 58 | var output; 59 | 60 | try { 61 | 62 | var ttfObj = fonteditorCore.svg2ttfobject( 63 | file.contents.toString('utf-8') 64 | ); 65 | 66 | output = ab2b(new fonteditorCore.TTFWriter(opts).write(ttfObj)); 67 | 68 | } 69 | catch (ex) { 70 | cb(ex); 71 | } 72 | 73 | if (output) { 74 | file.contents = output; 75 | cb(null, file); 76 | } 77 | 78 | }); 79 | 80 | }; 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /plugins/svgs2ttf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file svgs2ttf 3 | * @author junmer 4 | */ 5 | 6 | /* eslint-env node */ 7 | 8 | import isSvg from 'is-svg'; 9 | import through from 'through2'; 10 | import * as path from 'path'; 11 | import replaceExt from 'replace-ext'; 12 | import { ab2b } from 'b3b'; 13 | import _ from 'lodash'; 14 | import * as bufferToVinyl from 'buffer-to-vinyl'; 15 | import fonteditorCore from 'fonteditor-core'; 16 | import getEmptyttfObject from 'fonteditor-core/lib/ttf/getEmptyttfObject.js'; 17 | 18 | /** 19 | * SvgFont 20 | * 21 | * @constructor 22 | * @param {string} name filename 23 | * @param {Object} opts opts 24 | */ 25 | function SvgFont(name, opts) { 26 | 27 | this.opts = _.extend( 28 | { 29 | adjust: { 30 | leftSideBearing: 0, 31 | rightSideBearing: 0, 32 | ajdustToEmBox: true, 33 | ajdustToEmPadding: 0 34 | }, 35 | name: { 36 | fontFamily: name, 37 | fontSubFamily: name, 38 | uniqueSubFamily: name, 39 | postScriptName: name 40 | } 41 | }, 42 | opts 43 | ); 44 | 45 | // empty ttfobj 46 | var ttfobj = getEmptyttfObject.default(); 47 | 48 | // for save name 49 | ttfobj.post.format = 2; 50 | 51 | // new TTF 52 | this.ttf = new fonteditorCore.TTF(ttfobj); 53 | 54 | // set name 55 | this.ttf.setName(this.opts.name); 56 | 57 | // unicode start 58 | this.startCode = opts.startCode || 0xe001; 59 | 60 | } 61 | 62 | /** 63 | * add svg 64 | * 65 | * @param {string} name svg basename 66 | * @param {buffer} contents svg contents 67 | */ 68 | SvgFont.prototype.add = function (name, contents) { 69 | 70 | var ttfObj = fonteditorCore.svg2ttfobject( 71 | contents.toString('utf-8'), 72 | { 73 | combinePath: true 74 | } 75 | ); 76 | 77 | var glyf = ttfObj.glyf[0]; 78 | 79 | glyf.name = path.basename(name, '.svg'); 80 | 81 | if (!Array.isArray(glyf.unicode)) { 82 | glyf.unicode = [this.startCode++]; 83 | } 84 | 85 | this.ttf.addGlyf(glyf); 86 | 87 | }; 88 | 89 | /** 90 | * compile ttf contents 91 | * 92 | */ 93 | SvgFont.prototype.compile = function () { 94 | 95 | if (this.opts.adjust) { 96 | this.ttf.adjustGlyfPos(null, this.opts.adjust); 97 | this.ttf.adjustGlyf(null, this.opts.adjust); 98 | } 99 | 100 | this.contents = ab2b( 101 | new fonteditorCore.TTFWriter( 102 | this.opts 103 | ) 104 | .write( 105 | this.ttf.ttf 106 | ) 107 | ); 108 | 109 | }; 110 | 111 | 112 | /** 113 | * svgs2ttf fontmin plugin 114 | * 115 | * @param {string} file filename 116 | * @param {Object} opts opts 117 | * @param {string} opts.fontName font name 118 | * @return {Object} stream.Transform instance 119 | * @api public 120 | */ 121 | export default function (file, opts) { 122 | 123 | if (!file) { 124 | throw new Error('Missing file option for fontmin-svg2ttf'); 125 | } 126 | 127 | opts = _.extend({hinting: true}, opts); 128 | 129 | var firstFile; 130 | var fileName; 131 | var svgFont; 132 | 133 | if (typeof file === 'string') { 134 | 135 | // fix file ext 136 | file = replaceExt(file, '.ttf'); 137 | 138 | // set file name 139 | fileName = file; 140 | } 141 | else if (typeof file.path === 'string') { 142 | fileName = path.basename(file.path); 143 | firstFile = bufferToVinyl.file(null, fileName); 144 | } 145 | else { 146 | throw new Error('Missing path in file options for fontmin-svg2ttf'); 147 | } 148 | 149 | 150 | function bufferContents(file, enc, cb) { 151 | 152 | // ignore empty files 153 | if (file.isNull()) { 154 | cb(); 155 | return; 156 | } 157 | 158 | // check stream 159 | if (file.isStream()) { 160 | this.emit('error', new Error('Streaming not supported')); 161 | cb(); 162 | return; 163 | } 164 | 165 | // check svg 166 | if (!isSvg(file.contents.toString())) { 167 | cb(); 168 | return; 169 | } 170 | 171 | // set first file if not already set 172 | if (!firstFile) { 173 | firstFile = file; 174 | } 175 | 176 | // construct SvgFont instance 177 | if (!svgFont) { 178 | var fontName = opts.fontName || path.basename(fileName, '.ttf'); 179 | svgFont = new SvgFont(fontName, opts); 180 | } 181 | 182 | // add file to SvgFont instance 183 | svgFont.add(file.relative, file.contents); 184 | 185 | cb(); 186 | } 187 | 188 | 189 | function endStream(cb) { 190 | // no files passed in, no file goes out 191 | if (!firstFile || !svgFont) { 192 | cb(); 193 | return; 194 | } 195 | 196 | var joinedFile; 197 | 198 | // if file opt was a file path 199 | // clone everything from the first file 200 | if (typeof file === 'string') { 201 | joinedFile = firstFile.clone({ 202 | contents: false 203 | }); 204 | 205 | joinedFile.path = path.join(firstFile.base, file); 206 | } 207 | else { 208 | joinedFile = firstFile; 209 | } 210 | 211 | // complie svgfont 212 | svgFont.compile(); 213 | 214 | // set contents 215 | joinedFile.contents = svgFont.contents; 216 | joinedFile.ttfObject = svgFont.ttf.ttf; 217 | 218 | this.push(joinedFile); 219 | cb(); 220 | } 221 | 222 | return through.obj(bufferContents, endStream); 223 | 224 | }; 225 | -------------------------------------------------------------------------------- /plugins/ttf2eot.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ttf2eot 3 | * @author junmer 4 | */ 5 | 6 | /* eslint-env node */ 7 | 8 | import isTtf from 'is-ttf'; 9 | import through from 'through2'; 10 | import fonteditorCore from 'fonteditor-core'; 11 | import { b2ab, ab2b } from 'b3b'; 12 | import replaceExt from 'replace-ext'; 13 | import _ from 'lodash'; 14 | 15 | function compileTtf(buffer, cb) { 16 | var output; 17 | try { 18 | output = ab2b(fonteditorCore.ttf2eot(b2ab(buffer))); 19 | } 20 | catch (ex) { 21 | cb(ex); 22 | } 23 | 24 | output && cb(null, output); 25 | } 26 | 27 | 28 | /** 29 | * ttf2eot fontmin plugin 30 | * 31 | * @param {Object} opts opts 32 | * @return {Object} stream.Transform instance 33 | * @api public 34 | */ 35 | export default function (opts) { 36 | 37 | opts = _.extend({clone: true}, opts); 38 | 39 | return through.ctor({ 40 | objectMode: true 41 | }, function (file, enc, cb) { 42 | 43 | // check null 44 | if (file.isNull()) { 45 | cb(null, file); 46 | return; 47 | } 48 | 49 | // check stream 50 | if (file.isStream()) { 51 | cb(new Error('Streaming is not supported')); 52 | return; 53 | } 54 | 55 | // check ttf 56 | if (!isTtf(file.contents)) { 57 | cb(null, file); 58 | return; 59 | } 60 | 61 | // clone 62 | if (opts.clone) { 63 | this.push(file.clone(false)); 64 | } 65 | 66 | // replace ext 67 | file.path = replaceExt(file.path, '.eot'); 68 | 69 | compileTtf(file.contents, function (err, buffer) { 70 | 71 | if (err) { 72 | cb(err); 73 | return; 74 | } 75 | 76 | file.contents = buffer; 77 | cb(null, file); 78 | }); 79 | 80 | }); 81 | 82 | }; 83 | 84 | -------------------------------------------------------------------------------- /plugins/ttf2svg.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ttf2svg 3 | * @author junmer 4 | */ 5 | 6 | /* eslint-env node */ 7 | 8 | import isTtf from 'is-ttf'; 9 | import through from 'through2'; 10 | import fonteditorCore from 'fonteditor-core'; 11 | import { b2ab } from 'b3b'; 12 | import replaceExt from 'replace-ext'; 13 | import _ from 'lodash'; 14 | 15 | function compileTtf(buffer, cb) { 16 | var output; 17 | try { 18 | output = Buffer.from(fonteditorCore.ttf2svg(b2ab(buffer))); 19 | } 20 | catch (ex) { 21 | cb(ex); 22 | } 23 | 24 | output && cb(null, output); 25 | } 26 | 27 | /** 28 | * ttf2svg fontmin plugin 29 | * 30 | * @param {Object} opts opts 31 | * @return {Object} stream.Transform instance 32 | * @api public 33 | */ 34 | export default function (opts) { 35 | 36 | opts = _.extend({clone: true}, opts); 37 | 38 | return through.ctor({ 39 | objectMode: true 40 | }, function (file, enc, cb) { 41 | 42 | // check null 43 | if (file.isNull()) { 44 | cb(null, file); 45 | return; 46 | } 47 | 48 | // check stream 49 | if (file.isStream()) { 50 | cb(new Error('Streaming is not supported')); 51 | return; 52 | } 53 | 54 | // check ttf 55 | if (!isTtf(file.contents)) { 56 | cb(null, file); 57 | return; 58 | } 59 | 60 | // clone 61 | if (opts.clone) { 62 | this.push(file.clone(false)); 63 | } 64 | 65 | // replace ext 66 | file.path = replaceExt(file.path, '.svg'); 67 | 68 | compileTtf(file.contents, function (err, buffer) { 69 | 70 | if (err) { 71 | cb(err); 72 | return; 73 | } 74 | 75 | file.contents = buffer; 76 | cb(null, file); 77 | }); 78 | 79 | }); 80 | 81 | }; 82 | 83 | -------------------------------------------------------------------------------- /plugins/ttf2woff.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ttf2woff 3 | * @author junmer 4 | */ 5 | 6 | /* eslint-env node */ 7 | 8 | import isTtf from 'is-ttf'; 9 | import through from 'through2'; 10 | import fonteditorCore from 'fonteditor-core'; 11 | import { b2ab, ab2b } from 'b3b'; 12 | import replaceExt from 'replace-ext'; 13 | import { deflate } from 'pako'; 14 | import _ from 'lodash'; 15 | 16 | function compileTtf(buffer, options, cb) { 17 | var output; 18 | var ttf2woffOpts = {}; 19 | 20 | if (options.deflate) { 21 | ttf2woffOpts.deflate = function (input) { 22 | return deflate(Uint8Array.from(input)); 23 | }; 24 | } 25 | 26 | try { 27 | output = ab2b( 28 | // fix: have problem in some android device, close deflate 29 | fonteditorCore.ttf2woff( 30 | b2ab(buffer), 31 | ttf2woffOpts 32 | ) 33 | ); 34 | } 35 | catch (ex) { 36 | cb(ex); 37 | } 38 | 39 | output && cb(null, output); 40 | } 41 | 42 | /** 43 | * ttf2woff fontmin plugin 44 | * 45 | * @param {Object} opts opts 46 | * @return {Object} stream.Transform instance 47 | * @api public 48 | */ 49 | export default function (opts) { 50 | 51 | opts = _.extend({clone: true}, opts); 52 | 53 | return through.ctor({ 54 | objectMode: true 55 | }, function (file, enc, cb) { 56 | 57 | // check null 58 | if (file.isNull()) { 59 | cb(null, file); 60 | return; 61 | } 62 | 63 | // check stream 64 | if (file.isStream()) { 65 | cb(new Error('Streaming is not supported')); 66 | return; 67 | } 68 | 69 | // check ttf 70 | if (!isTtf(file.contents)) { 71 | cb(null, file); 72 | return; 73 | } 74 | 75 | // clone 76 | if (opts.clone) { 77 | this.push(file.clone(false)); 78 | } 79 | 80 | // replace ext 81 | file.path = replaceExt(file.path, '.woff'); 82 | 83 | compileTtf(file.contents, opts, function (err, buffer) { 84 | 85 | if (err) { 86 | cb(err); 87 | return; 88 | } 89 | 90 | file.contents = buffer; 91 | cb(null, file); 92 | }); 93 | 94 | }); 95 | 96 | }; 97 | 98 | -------------------------------------------------------------------------------- /plugins/ttf2woff2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file wawoff2 3 | * @author junmer 4 | */ 5 | 6 | /* eslint-env node */ 7 | import through from 'through2'; 8 | import replaceExt from 'replace-ext'; 9 | import _ from 'lodash'; 10 | import ttf2woff2 from 'ttf2woff2'; 11 | import isTtf from 'is-ttf'; 12 | 13 | /** 14 | * wawoff2 fontmin plugin 15 | * 16 | * @param {Object} opts opts 17 | * @return {Object} stream.Transform instance 18 | * @api public 19 | */ 20 | export default function (opts) { 21 | 22 | opts = _.extend({clone: true}, opts); 23 | 24 | return through.ctor({ 25 | objectMode: true 26 | }, function (file, enc, cb) { 27 | 28 | // check null 29 | if (file.isNull()) { 30 | cb(null, file); 31 | return; 32 | } 33 | 34 | // check stream 35 | if (file.isStream()) { 36 | cb(new Error('Streaming is not supported')); 37 | return; 38 | } 39 | 40 | // check ttf 41 | if (!isTtf(file.contents)) { 42 | cb(null, file); 43 | return; 44 | } 45 | 46 | // clone 47 | if (opts.clone) { 48 | this.push(file.clone(false)); 49 | } 50 | 51 | // ttf2woff2 52 | var ouput; 53 | try { 54 | ouput = ttf2woff2(file.contents); 55 | } 56 | catch (ex) { 57 | cb(ex, file); 58 | } 59 | 60 | if (ouput) { 61 | file.path = replaceExt(file.path, '.woff2'); 62 | file.contents = ouput; 63 | cb(null, file); 64 | } 65 | 66 | }); 67 | 68 | }; 69 | -------------------------------------------------------------------------------- /test/base.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file fontmin base 3 | * @author junmer 4 | */ 5 | 6 | /* eslint-env node */ 7 | 8 | import { expect } from 'chai'; 9 | import * as url from 'url'; 10 | import Fontmin from '../index.js'; 11 | var fm = Fontmin; 12 | var fontPath = url.fileURLToPath(new URL('../fonts', import.meta.url)); 13 | 14 | describe('Fontmin util', function () { 15 | 16 | it('getFontFolder should be string', function () { 17 | expect(Fontmin.util.getFontFolder()).to.be.a('string'); 18 | }); 19 | 20 | it('getFonts should be array', function () { 21 | expect(Fontmin.util.getFonts()).to.be.a('array'); 22 | }); 23 | 24 | }); 25 | 26 | 27 | describe('Fontmin base', function () { 28 | 29 | it('should run when no cb', function (done) { 30 | 31 | fm() 32 | .src(fontPath + '/**.empty') 33 | .run() 34 | .on('end', function () { 35 | done(); 36 | }); 37 | }); 38 | 39 | 40 | it('should not dest when src buffer', function (done) { 41 | 42 | fm() 43 | .src(Buffer.from('')) 44 | .dest(fontPath + '/dest') 45 | .run(function (err, files, stream) { 46 | done(); 47 | }); 48 | }); 49 | 50 | 51 | it('should run when src null', function (done) { 52 | 53 | var plugins = Fontmin.plugins.filter(function (plugin) { 54 | return plugin !== 'svgs2ttf'; 55 | }); 56 | 57 | var works = plugins.length; 58 | 59 | function usePlugin(plugin) { 60 | 61 | fm() 62 | .src(fontPath + '/SentyBrush.ttf', {read: false}) 63 | .use(Fontmin[plugin]()) 64 | .run(function (err, files, stream) { 65 | 66 | expect(files.length).equal(1); 67 | 68 | if (0 === --works) { 69 | done(); 70 | } 71 | 72 | }); 73 | } 74 | 75 | plugins.forEach(usePlugin); 76 | 77 | }); 78 | 79 | it('should run with runAsync', async function () { 80 | const res = await fm() 81 | .src(Buffer.from('')) 82 | .dest(fontPath + '/dest') 83 | .runAsync(); 84 | console.log(res); 85 | }); 86 | 87 | it('should dest one when clone false', function (done) { 88 | 89 | 90 | var plugins = ['ttf2eot', 'ttf2woff', 'ttf2svg']; 91 | var works = plugins.length; 92 | 93 | function usePlugin(plugin) { 94 | 95 | fm() 96 | .src(fontPath + '/SentyBrush.ttf') 97 | .use(Fontmin.glyph({text: '1'})) 98 | .use(Fontmin[plugin]({clone: false})) 99 | .run(function (err, files, stream) { 100 | 101 | expect(files.length).equal(1); 102 | 103 | if (0 === --works) { 104 | done(); 105 | } 106 | 107 | }); 108 | } 109 | 110 | plugins.forEach(usePlugin); 111 | 112 | 113 | }); 114 | 115 | it('should exclude files not font', function (done) { 116 | 117 | fm() 118 | .src(fontPath + '/**.html', {read: false}) 119 | .dest(fontPath + '/dest') 120 | .run(function (err, files, stream) { 121 | expect(files.length).equal(1); 122 | done(); 123 | }); 124 | }); 125 | 126 | it('should throw `Streaming is not supported`', function (done) { 127 | 128 | var plugins = Fontmin.plugins; 129 | var works = plugins.length; 130 | 131 | function usePlugin(plugin) { 132 | 133 | fm() 134 | .src(fontPath + '/SentyBrush.ttf', {buffer: false}) 135 | .use(Fontmin[plugin]('test')) 136 | .run(function (err, files, stream) { 137 | 138 | expect(err).to.match(/Streaming/); 139 | 140 | if (0 === --works) { 141 | done(); 142 | } 143 | 144 | }); 145 | } 146 | 147 | plugins.forEach(usePlugin); 148 | 149 | }); 150 | 151 | 152 | }); 153 | -------------------------------------------------------------------------------- /test/font.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file fontmin font 3 | * @author junmer 4 | */ 5 | 6 | /* eslint-env node */ 7 | /* global before */ 8 | 9 | import { assert, expect } from 'chai'; 10 | 11 | import * as fs from 'fs'; 12 | import * as path from 'path'; 13 | import * as url from 'url'; 14 | import clean from 'gulp-clean'; 15 | import isTtf from 'is-ttf'; 16 | import isOtf from 'is-otf'; 17 | import isEot from 'is-eot'; 18 | import isWoff from 'is-woff'; 19 | import isWoff2 from 'is-woff2'; 20 | import isSvg from 'is-svg'; 21 | import Fontmin from '../index.js'; 22 | 23 | 24 | var fontName = 'TpldKhangXiDictTrial'; 25 | var srcPath = url.fileURLToPath(new URL('../fonts/' + fontName + '.otf', import.meta.url)); 26 | var destPath = url.fileURLToPath(new URL('../fonts/dest', import.meta.url)); 27 | var destFile = path.resolve(destPath, fontName); 28 | 29 | var text = '' 30 | + '天地玄黄 宇宙洪荒 日月盈昃 辰宿列张' 31 | + '寒来暑往 秋收冬藏 闰馀成岁 律吕调阳' 32 | + '云腾致雨 露结为霜 金生丽水 玉出昆冈' 33 | + '剑号巨阙 珠称夜光 果珍李柰 菜重芥姜'; 34 | 35 | function getFile(files, ext) { 36 | var re = new RegExp(ext + '$'); 37 | var vf = files.filter(function (file) { 38 | return re.test(file.path); 39 | }); 40 | return vf[0]; 41 | } 42 | 43 | var outputFiles; 44 | 45 | before(function (done) { 46 | 47 | this.timeout(5000); 48 | 49 | // clean 50 | new Fontmin() 51 | .src(destPath) 52 | .use(clean()) 53 | .run(next); 54 | 55 | // minfy 56 | var fontmin = new Fontmin() 57 | .src(srcPath) 58 | .use(Fontmin.otf2ttf({ 59 | text: text 60 | })) 61 | .use(Fontmin.glyph({ 62 | text: text 63 | })) 64 | .use(Fontmin.ttf2eot()) 65 | .use(Fontmin.ttf2woff({deflate: true})) 66 | .use(Fontmin.ttf2woff2()) 67 | .use(Fontmin.ttf2svg()) 68 | .use(Fontmin.css({ 69 | glyph: true, 70 | base64: true, 71 | fontPath: './', 72 | local: true, 73 | fontFamily: function (font, ttf) { 74 | return ttf.name.fontFamily + ' - Transformed'; 75 | } 76 | })) 77 | .dest(destPath); 78 | 79 | 80 | function next() { 81 | fontmin.runAsync().then(files => { 82 | outputFiles = files; 83 | done(); 84 | }) 85 | .catch (err => { 86 | console.log(err); 87 | process.exit(-1); 88 | }); 89 | } 90 | 91 | }); 92 | 93 | 94 | 95 | describe('otf2ttf plugin', function () { 96 | 97 | it('input should be otf', function () { 98 | 99 | var srcBuffer = fs.readFileSync(srcPath); 100 | assert(isOtf(srcBuffer)); 101 | 102 | }); 103 | 104 | it('output buffer should be ttf', function () { 105 | assert(isTtf(getFile(outputFiles, 'ttf').contents)); 106 | }); 107 | 108 | it('should keep source when clone true', function (done) { 109 | 110 | new Fontmin() 111 | .src(srcPath) 112 | .use(Fontmin.otf2ttf({clone: true, text: 't'})) 113 | .run(function (err, files) { 114 | assert.equal(files.length, 2); 115 | done(); 116 | }); 117 | 118 | }); 119 | 120 | }); 121 | 122 | describe('glyph plugin', function () { 123 | 124 | it('output buffer should be ttf', function () { 125 | assert(isTtf(getFile(outputFiles, 'ttf').contents)); 126 | }); 127 | 128 | // it('output ttf should have `cvt ` table', function () { 129 | // assert( 130 | // isTtf( 131 | // getFile(outputFiles, 'ttf').contents, { 132 | // tables: ['cvt '] 133 | // } 134 | // ) 135 | // ); 136 | // }); 137 | 138 | it('output should miner than input', function () { 139 | var srcBuffer = fs.readFileSync(srcPath); 140 | assert(srcBuffer.length > getFile(outputFiles, 'ttf').contents.length); 141 | }); 142 | 143 | it('dest file should exist', function () { 144 | assert( 145 | fs.existsSync(destFile + '.ttf') 146 | ); 147 | }); 148 | 149 | it('dest file should be ttf', function () { 150 | try { 151 | assert( 152 | isTtf( 153 | fs.readFileSync(destFile + '.ttf') 154 | ) 155 | ); 156 | } 157 | catch (ex) { 158 | assert(false); 159 | } 160 | }); 161 | 162 | }); 163 | 164 | describe('ttf2eot plugin', function () { 165 | 166 | it('output buffer should be eot', function () { 167 | assert(isEot(getFile(outputFiles, 'eot').contents)); 168 | }); 169 | 170 | it('dest file should exist', function () { 171 | assert( 172 | fs.existsSync(destFile + '.eot') 173 | ); 174 | }); 175 | 176 | it('dest file should be eot', function () { 177 | try { 178 | assert( 179 | isEot( 180 | fs.readFileSync(destFile + '.eot') 181 | ) 182 | ); 183 | } 184 | catch (ex) { 185 | assert(false); 186 | } 187 | }); 188 | 189 | }); 190 | 191 | describe('ttf2woff plugin', function () { 192 | 193 | it('output buffer should be woff', function () { 194 | assert(isWoff(getFile(outputFiles, 'woff').contents)); 195 | }); 196 | 197 | it('dest file should exist woff', function () { 198 | assert( 199 | fs.existsSync(destFile + '.woff') 200 | ); 201 | }); 202 | 203 | it('dest file should be woff', function () { 204 | try { 205 | assert( 206 | isWoff( 207 | fs.readFileSync(destFile + '.woff') 208 | ) 209 | ); 210 | } 211 | catch (ex) { 212 | assert(false); 213 | } 214 | }); 215 | 216 | }); 217 | 218 | describe('ttf2woff2 plugin', function () { 219 | 220 | it('output buffer should be woff2', function () { 221 | assert(isWoff2(getFile(outputFiles, 'woff2').contents)); 222 | }); 223 | 224 | it('dest file should exist woff2', function () { 225 | assert( 226 | fs.existsSync(destFile + '.woff2') 227 | ); 228 | }); 229 | 230 | it('dest file should be woff2', function () { 231 | try { 232 | assert( 233 | isWoff2( 234 | fs.readFileSync(destFile + '.woff2') 235 | ) 236 | ); 237 | } 238 | catch (ex) { 239 | assert(false); 240 | } 241 | }); 242 | 243 | }); 244 | 245 | describe('ttf2svg plugin', function () { 246 | 247 | it('output buffer should be svg', function () { 248 | assert(isSvg(getFile(outputFiles, 'svg').contents.toString())); 249 | }); 250 | 251 | it('dest file should exist svg', function () { 252 | assert( 253 | fs.existsSync(destFile + '.svg') 254 | ); 255 | }); 256 | 257 | it('dest file should be svg', function () { 258 | try { 259 | assert( 260 | isSvg( 261 | fs.readFileSync(destFile + '.svg', 'utf8') 262 | ) 263 | ); 264 | } 265 | catch (ex) { 266 | assert(false); 267 | } 268 | }); 269 | 270 | }); 271 | 272 | describe('css plugin', function () { 273 | 274 | it('dest file should exist css', function () { 275 | assert( 276 | fs.existsSync(destFile + '.css') 277 | ); 278 | }); 279 | 280 | it('dest css should have "@font-face"', function () { 281 | try { 282 | expect(fs.readFileSync(destFile + '.css', { 283 | encoding: 'utf-8' 284 | })).to.have.string('@font-face'); 285 | } 286 | catch (ex) { 287 | assert(false); 288 | } 289 | }); 290 | 291 | it('dest css should match /\.icon-(\w+):before/', function () { 292 | try { 293 | expect(fs.readFileSync(destFile + '.css', { 294 | encoding: 'utf-8' 295 | })).to.match(/\.icon-(\w+):before/); 296 | } 297 | catch (ex) { 298 | assert(false); 299 | } 300 | }); 301 | 302 | it('dest css should have fontPath "./"', function () { 303 | try { 304 | expect(fs.readFileSync(destFile + '.css', { 305 | encoding: 'utf-8' 306 | })).to.have.string('./'); 307 | } 308 | catch (ex) { 309 | assert(false); 310 | } 311 | }); 312 | 313 | 314 | it('dest css should have local()', function () { 315 | try { 316 | expect(fs.readFileSync(destFile + '.css', { 317 | encoding: 'utf-8' 318 | })).to.have.string('local'); 319 | } 320 | catch (ex) { 321 | assert(false); 322 | } 323 | }); 324 | 325 | it('dest css should have transformed @font-family name', function () { 326 | 327 | var content = fs.readFileSync(destFile + '.css', { 328 | encoding: 'utf-8' 329 | }); 330 | var matched = content.match(/font-family: \s*"(.*?)"/); 331 | var fontFamily = matched[1]; 332 | 333 | expect(fontFamily).to.be.a('string') 334 | .that.match(/\s-\sTransformed$/); 335 | 336 | }); 337 | 338 | 339 | }); 340 | -------------------------------------------------------------------------------- /test/mjs/example.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @file esm example 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | import Fontmin from 'fontmin'; 6 | import rename from 'gulp-rename'; 7 | 8 | { 9 | const fontmin = new Fontmin() 10 | .src('fonts/*.ttf') 11 | .dest('build/fonts'); 12 | 13 | fontmin.run(function (err, files) { 14 | if (err) { 15 | throw err; 16 | } 17 | 18 | console.log(files[0]); 19 | }); 20 | } 21 | { 22 | const fontmin = new Fontmin() 23 | .src('fonts/big.ttf') 24 | .use(rename('small.ttf')); 25 | } 26 | 27 | { 28 | const fontmin = new Fontmin() 29 | .use(Fontmin.glyph({ 30 | text: '天地玄黄 宇宙洪荒', 31 | hinting: false // keep ttf hint info (fpgm, prep, cvt). default = true 32 | })); 33 | } 34 | 35 | { 36 | const fontmin = new Fontmin() 37 | .use(Fontmin.ttf2eot()); 38 | } 39 | { 40 | const fontmin = new Fontmin() 41 | .use(Fontmin.ttf2woff({ 42 | deflate: true // deflate woff. default = false 43 | })); 44 | } 45 | 46 | { 47 | const fontmin = new Fontmin() 48 | .use(Fontmin.ttf2woff2()); 49 | } 50 | 51 | 52 | { 53 | const fontmin = new Fontmin() 54 | .use(Fontmin.css({ 55 | fontPath: './', // location of font file 56 | base64: true, // inject base64 data:application/x-font-ttf; (gzip font with css). 57 | // default = false 58 | glyph: true, // generate class for each glyph. default = false 59 | iconPrefix: 'my-icon', // class prefix, only work when glyph is `true`. default to "icon" 60 | fontFamily: 'myfont', // custom fontFamily, default to filename or get from analysed ttf file 61 | asFileName: false, // rewrite fontFamily as filename force. default = false 62 | local: true // boolean to add local font. default = false 63 | })); 64 | } 65 | 66 | { 67 | const fontmin = new Fontmin() 68 | .use(Fontmin.css({ 69 | // ... 70 | fontFamily: function(fontInfo, ttf) { 71 | return "Transformed Font Family Name" 72 | }, 73 | // ... 74 | })); 75 | } 76 | 77 | { 78 | const fontmin = new Fontmin() 79 | .src('font.svg') 80 | .use(Fontmin.svg2ttf()); 81 | } 82 | 83 | { 84 | const fontmin = new Fontmin() 85 | .src('svgs/*.svg') 86 | .use(Fontmin.svgs2ttf('font.ttf', {fontName: 'iconfont'})) 87 | .use(Fontmin.css({ 88 | glyph: true 89 | })); 90 | } 91 | 92 | { 93 | const fontmin = new Fontmin() 94 | .src('fonts/*.otf') 95 | .use(Fontmin.otf2ttf()); 96 | } 97 | -------------------------------------------------------------------------------- /test/mjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mjs", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node example.mjs" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "fontmin": "file:../../", 14 | "gulp-rename": "^2.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/subset.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file fontmin subset 3 | * @author junmer 4 | */ 5 | 6 | /* eslint-env node */ 7 | /* global before */ 8 | 9 | import { expect } from 'chai'; 10 | 11 | import * as fs from 'fs'; 12 | import path from 'path'; 13 | import * as url from 'url'; 14 | import clean from 'gulp-clean'; 15 | import isTtf from 'is-ttf'; 16 | import Fontmin from '../index.js'; 17 | import fonteditorCore from 'fonteditor-core'; 18 | import { b2ab } from 'b3b'; 19 | 20 | var fontName = 'SentyBrush'; 21 | var fontDir = url.fileURLToPath(new URL('../fonts', import.meta.url)); 22 | var srcPath = path.resolve(fontDir, fontName + '.ttf'); 23 | var destPath = path.resolve(fontDir, 'dest_ttf'); 24 | // first mined ttf 25 | var mined; 26 | 27 | // first min 28 | before(function (done) { 29 | 30 | // clean 31 | new Fontmin() 32 | .src(destPath) 33 | .use(clean()) 34 | .run(afterClean); 35 | 36 | // subset first 37 | var fontmin = new Fontmin() 38 | .src(srcPath) 39 | .use(Fontmin.glyph({ 40 | text: 'abcd efg', 41 | // trim: false 42 | })) 43 | .dest(destPath); 44 | 45 | function afterClean() { 46 | fontmin.run(function (err, files, stream) { 47 | mined = files[0].contents; 48 | done(); 49 | }); 50 | } 51 | 52 | 53 | }); 54 | 55 | describe('subset', function () { 56 | 57 | it('input is\'t ttf shoud be pass', function (done) { 58 | 59 | new Fontmin() 60 | .src(fontDir + '/*.html') 61 | .use(Fontmin.glyph({ 62 | text: 'test' 63 | })) 64 | .run(function (err, files) { 65 | var ext = path.extname(files[0].path); 66 | expect(ext).equal('.html'); 67 | done(); 68 | }); 69 | 70 | }); 71 | 72 | it('should be ok when unicodes out of subbset', function () { 73 | 74 | // it ttf 75 | expect(isTtf(mined)).to.be.ok; 76 | 77 | }); 78 | 79 | it('dest should be minier ttf', function () { 80 | 81 | var srcFile = fs.readFileSync(srcPath); 82 | 83 | // minier 84 | expect(mined.length).to.be.below(srcFile.length); 85 | 86 | }); 87 | 88 | // it('should has whitespace when trim false', function () { 89 | 90 | // var ttf = new fonteditorCore.TTFReader().read(b2ab(mined)); 91 | 92 | // // contain whitespace 93 | // expect(ttf.cmap).to.contain.any.keys(['32', '160', '202']); 94 | 95 | // }); 96 | 97 | it('should has whitespace when mixed text and whitespace', function () { 98 | 99 | var ttf = new fonteditorCore.TTFReader().read(b2ab(mined)); 100 | 101 | // contain whitespace 102 | expect(ttf.cmap).to.contain.any.keys(['32']); 103 | 104 | }); 105 | 106 | it('should support empty text', function (done) { 107 | 108 | new Fontmin() 109 | .src(srcPath) 110 | .use(Fontmin.glyph({ 111 | text: '' 112 | })) 113 | .run(done); 114 | 115 | }); 116 | 117 | it('should support UTF-16-encoded text', function (done) { 118 | 119 | new Fontmin() 120 | .src(srcPath) 121 | .use(Fontmin.glyph({ 122 | text: '🐴' 123 | })) 124 | .run(done); 125 | 126 | }); 127 | 128 | it('should support use plugin function', function (done) { 129 | 130 | new Fontmin() 131 | .src(srcPath) 132 | .use(Fontmin.glyph({ 133 | text: 'test', 134 | use: function (ttf) { 135 | expect(ttf).to.have.any.keys('ttf'); 136 | } 137 | })) 138 | .run(done); 139 | 140 | }); 141 | 142 | it('should pass use plugin not function', function (done) { 143 | 144 | new Fontmin() 145 | .src(srcPath) 146 | .use(Fontmin.glyph({ 147 | use: false 148 | })) 149 | .run(done); 150 | 151 | }); 152 | 153 | it('subset of non-existent character shoud be ttf', function (done) { 154 | 155 | var destTtf = path.resolve(destPath, fontName + '.ttf'); 156 | 157 | var fontmin = new Fontmin() 158 | .src(destTtf) 159 | .use(Fontmin.glyph({ 160 | text: '字体里是没有中文字符的', 161 | basicText: true 162 | })); 163 | 164 | fontmin.run(function (err, files, stream) { 165 | 166 | var twiceMined = files[0].contents; 167 | 168 | // it ttf 169 | expect(isTtf(twiceMined)).to.be.ok; 170 | 171 | done(); 172 | }); 173 | 174 | 175 | }); 176 | 177 | 178 | it('字符串中无空格', function (done) { 179 | console.log(path.resolve(fontDir, 'HYWenHei-85W-1.ttf')) 180 | var fontmin = new Fontmin() 181 | .src(path.resolve(fontDir, 'HYWenHei-85W-1.ttf')) 182 | .use(Fontmin.glyph({ 183 | text: '天地玄黄', 184 | basicText: true 185 | })); 186 | 187 | fontmin.run(function (err, files, stream) { 188 | 189 | var twiceMined = files[0].contents; 190 | 191 | // it ttf 192 | expect(isTtf(twiceMined)).to.be.ok; 193 | 194 | var font = fonteditorCore.Font.create(twiceMined, {type: 'ttf'}); 195 | expect(font.get().glyf.length).to.be.eq(5, 'glyf length'); 196 | done(); 197 | }); 198 | }); 199 | 200 | it('中文空格和英文空格', function (done) { 201 | console.log(path.resolve(fontDir, 'HYWenHei-85W-1.ttf')) 202 | var fontmin = new Fontmin() 203 | .src(path.resolve(fontDir, 'HYWenHei-85W-1.ttf')) 204 | .use(Fontmin.glyph({ 205 | text: ' 天地玄黄 \u3000 ', 206 | basicText: true 207 | })); 208 | 209 | fontmin.run(function (err, files, stream) { 210 | 211 | var twiceMined = files[0].contents; 212 | 213 | // it ttf 214 | expect(isTtf(twiceMined)).to.be.ok; 215 | 216 | var font = fonteditorCore.Font.create(twiceMined, {type: 'ttf'}); 217 | expect(font.get().glyf.length).to.be.eq(7, 'glyf length'); 218 | expect(font.get().glyf.some(g => g.unicode && g.unicode.includes(0x3000))).to.be.ok; 219 | expect(font.get().glyf.some(g => g.unicode && g.unicode.includes(0x20))).to.be.ok; 220 | done(); 221 | }); 222 | 223 | 224 | }); 225 | }); 226 | -------------------------------------------------------------------------------- /test/svg2ttf.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file fontmin svg2ttf 3 | * @author junmer 4 | */ 5 | 6 | /* eslint-env node */ 7 | /* global before */ 8 | 9 | import { assert } from 'chai'; 10 | 11 | import * as fs from 'fs'; 12 | import * as path from 'path'; 13 | import * as url from 'url'; 14 | import clean from 'gulp-clean'; 15 | import isTtf from 'is-ttf'; 16 | import Fontmin from '../index.js'; 17 | 18 | var srcPath = url.fileURLToPath(new URL('../fonts/fontawesome-webfont.svg', import.meta.url)); 19 | var destPath = url.fileURLToPath(new URL('../fonts/dest_svg', import.meta.url)); 20 | var destFile = destPath + '/fontawesome-webfont'; 21 | 22 | function getFile(files, ext) { 23 | var re = new RegExp(ext + '$'); 24 | var vf = files.filter(function (file) { 25 | return re.test(file.path); 26 | }); 27 | return vf[0]; 28 | } 29 | 30 | var outputFiles; 31 | 32 | before(function (done) { 33 | 34 | // clean 35 | new Fontmin() 36 | .src(destPath) 37 | .use(clean()) 38 | .run(next); 39 | 40 | // minfy 41 | var fontmin = new Fontmin() 42 | .src(srcPath) 43 | .use(Fontmin.svg2ttf()) 44 | .dest(destPath); 45 | 46 | 47 | function next() { 48 | fontmin.run(function (err, files, stream) { 49 | 50 | if (err) { 51 | console.log(err); 52 | process.exit(-1); 53 | } 54 | 55 | outputFiles = files; 56 | 57 | done(); 58 | }); 59 | } 60 | 61 | 62 | }); 63 | 64 | 65 | describe('svg2ttf plugin', function () { 66 | 67 | it('input is\'t svg shoud be pass', function (done) { 68 | 69 | new Fontmin() 70 | .src(url.fileURLToPath(new URL('../fonts/*.html', import.meta.url))) 71 | .use(Fontmin.svg2ttf()) 72 | .run(function (err, files) { 73 | var ext = path.extname(files[0].path); 74 | assert.equal(ext, '.html'); 75 | done(); 76 | }); 77 | 78 | }); 79 | 80 | it('should dest one when clone false', function (done) { 81 | 82 | new Fontmin() 83 | .src(srcPath) 84 | .use(Fontmin.svg2ttf({clone: false})) 85 | .run(function (err, files) { 86 | assert.equal(files.length, 1); 87 | done(); 88 | }); 89 | 90 | }); 91 | 92 | it('output buffer should be ttf', function () { 93 | assert(isTtf(getFile(outputFiles, 'ttf').contents)); 94 | }); 95 | 96 | it('dest file should exist ttf', function () { 97 | assert( 98 | fs.existsSync(destFile + '.ttf') 99 | ); 100 | }); 101 | 102 | it('dest file should be ttf', function () { 103 | try { 104 | assert( 105 | isTtf( 106 | fs.readFileSync(destFile + '.ttf') 107 | ) 108 | ); 109 | } 110 | catch (ex) { 111 | assert(false); 112 | } 113 | }); 114 | 115 | }); 116 | -------------------------------------------------------------------------------- /test/svgs2ttf.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file fontmin svgs2ttf 3 | * @author junmer 4 | */ 5 | 6 | /* eslint-env node */ 7 | /* global before */ 8 | 9 | import { assert } from 'chai'; 10 | 11 | import * as fs from 'fs'; 12 | import * as url from 'url'; 13 | import clean from 'gulp-clean'; 14 | import isTtf from 'is-ttf'; 15 | import Fontmin from '../index.js'; 16 | 17 | var srcPath = url.fileURLToPath(new URL('../fonts/svg/*.svg', import.meta.url)); 18 | var destPath = url.fileURLToPath(new URL('../fonts/dest_svgs', import.meta.url)); 19 | var destFile = destPath + '/iconfont'; 20 | 21 | 22 | function getFile(files, ext) { 23 | var re = new RegExp(ext + '$'); 24 | var vf = files.filter(function (file) { 25 | return re.test(file.path); 26 | }); 27 | return vf[0]; 28 | } 29 | 30 | var outputFiles; 31 | 32 | before(function (done) { 33 | 34 | // clean 35 | new Fontmin() 36 | .src(destPath) 37 | .use(clean()) 38 | .run(next); 39 | 40 | 41 | 42 | // minfy 43 | var fontmin = new Fontmin() 44 | .src(srcPath) 45 | .use(Fontmin.svgs2ttf('iconfont.ttf')) 46 | .use(Fontmin.ttf2svg()) 47 | .use(Fontmin.css({ 48 | glyph: true 49 | })) 50 | .dest(destPath); 51 | 52 | function next() { 53 | 54 | fontmin.run(function (err, files, stream) { 55 | 56 | if (err) { 57 | console.log(err); 58 | process.exit(-1); 59 | } 60 | 61 | outputFiles = files; 62 | 63 | done(); 64 | }); 65 | } 66 | 67 | }); 68 | 69 | 70 | describe('svgs2ttf plugin', function () { 71 | 72 | it('should require root path', function () { 73 | assert.throws(Fontmin.svgs2ttf.bind(), /Missing file option/); 74 | }); 75 | 76 | it('should require root path in file options', function () { 77 | assert.throws( 78 | Fontmin.svgs2ttf.bind(null, {path: null}), /file options for fontmin-svg2ttf/ 79 | ); 80 | }); 81 | 82 | it('set path in file options', function (done) { 83 | 84 | new Fontmin() 85 | .src(srcPath) 86 | .use(Fontmin.svgs2ttf({path: 'test.ttf'})) 87 | .run(function (err, files) { 88 | assert(isTtf(files[0].contents)); 89 | done(); 90 | }); 91 | }); 92 | 93 | it('input is\'t svg shoud be exclude', function (done) { 94 | 95 | new Fontmin() 96 | .src(url.fileURLToPath(new URL('../fonts/*.html', import.meta.url))) 97 | .use(Fontmin.svgs2ttf('test.ttf')) 98 | .run(function (err, files) { 99 | assert.equal(files.length, 0); 100 | done(); 101 | }); 102 | 103 | }); 104 | 105 | it('output buffer should be ttf', function () { 106 | assert(isTtf(getFile(outputFiles, 'ttf').contents)); 107 | }); 108 | 109 | 110 | it('dest file should exist ttf', function () { 111 | assert( 112 | fs.existsSync(destFile + '.ttf') 113 | ); 114 | }); 115 | 116 | it('dest file should be ttf', function () { 117 | try { 118 | assert( 119 | isTtf( 120 | fs.readFileSync(destFile + '.ttf') 121 | ) 122 | ); 123 | } 124 | catch (ex) { 125 | assert(false); 126 | } 127 | }); 128 | 129 | }); 130 | -------------------------------------------------------------------------------- /test/ts/example.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file mjs 3 | * @author mengke01(kekee000@gmail.com) 4 | */ 5 | import Fontmin from 'fontmin'; 6 | 7 | { 8 | const fontmin = new Fontmin() 9 | .src('fonts/*.ttf') 10 | .dest('build/fonts'); 11 | 12 | fontmin.run(function (err, files) { 13 | if (err) { 14 | throw err; 15 | } 16 | 17 | console.log(files[0]); 18 | }); 19 | } 20 | 21 | { 22 | const fontmin = new Fontmin() 23 | .use(Fontmin.glyph({ 24 | text: '天地玄黄 宇宙洪荒', 25 | hinting: false // keep ttf hint info (fpgm, prep, cvt). default = true 26 | })); 27 | } 28 | 29 | { 30 | const fontmin = new Fontmin() 31 | .use(Fontmin.ttf2eot()); 32 | } 33 | { 34 | const fontmin = new Fontmin() 35 | .use(Fontmin.ttf2woff({ 36 | deflate: true // deflate woff. default = false 37 | })); 38 | } 39 | 40 | { 41 | const fontmin = new Fontmin() 42 | .use(Fontmin.ttf2woff2()); 43 | } 44 | 45 | 46 | { 47 | const fontmin = new Fontmin() 48 | .use(Fontmin.css({ 49 | fontPath: './', // location of font file 50 | base64: true, // inject base64 data:application/x-font-ttf; (gzip font with css). 51 | // default = false 52 | glyph: true, // generate class for each glyph. default = false 53 | iconPrefix: 'my-icon', // class prefix, only work when glyph is `true`. default to "icon" 54 | fontFamily: 'myfont', // custom fontFamily, default to filename or get from analysed ttf file 55 | asFileName: false, // rewrite fontFamily as filename force. default = false 56 | local: true // boolean to add local font. default = false 57 | })); 58 | } 59 | 60 | { 61 | const fontmin = new Fontmin() 62 | .use(Fontmin.css({ 63 | // ... 64 | fontFamily: function(fontInfo, ttf) { 65 | return "Transformed Font Family Name" 66 | }, 67 | // ... 68 | })); 69 | } 70 | 71 | { 72 | const fontmin = new Fontmin() 73 | .src('font.svg') 74 | .use(Fontmin.svg2ttf()); 75 | } 76 | 77 | { 78 | const fontmin = new Fontmin() 79 | .src('svgs/*.svg') 80 | .use(Fontmin.svgs2ttf('font.ttf', {fontName: 'iconfont'})) 81 | .use(Fontmin.css({ 82 | glyph: true 83 | })); 84 | } 85 | 86 | { 87 | const fontmin = new Fontmin() 88 | .src('fonts/*.otf') 89 | .use(Fontmin.otf2ttf()); 90 | } 91 | -------------------------------------------------------------------------------- /test/ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "example.js", 6 | "scripts": { 7 | "test": "tsc && node example.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "fontmin": "file:../../", 14 | "gulp-rename": "^2.0.0", 15 | "typescript": "^5.1.6" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 4 | "module": "commonjs", 5 | "strict": true, /* Enable all strict type-checking options. */ 6 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 7 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 8 | } 9 | } 10 | --------------------------------------------------------------------------------