├── .editorconfig
├── .gitignore
├── .jscs.json
├── .jshintrc
├── .npmignore
├── .travis.yml
├── Contributing.md
├── Gruntfile.js
├── License.md
├── Pull_Request_Template.md
├── Readme.md
├── package.json
├── tasks
├── bin
│ └── eotlitetool.py
├── engines
│ ├── fontforge.js
│ ├── fontforge
│ │ └── generate.py
│ └── node.js
├── templates
│ ├── bem.css
│ ├── bem.json
│ ├── bootstrap.css
│ ├── bootstrap.json
│ └── demo.html
├── util
│ └── util.js
└── webfont.js
├── test
├── camel
│ ├── MailRu.svg
│ └── PlusOne.svg
├── src
│ ├── mailru.svg
│ ├── odnoklassniki.svg
│ ├── pinterest.svg
│ ├── plusone.svg
│ └── single.svg
├── src_duplicate_names
│ ├── one
│ │ ├── mailru.svg
│ │ ├── odnoklassniki.svg
│ │ └── pinterest.svg
│ └── two
│ │ ├── mailru.svg
│ │ └── odnoklassniki.svg
├── src_filename_length
│ └── length.svg
├── src_folders
│ ├── icons
│ │ ├── facebook.svg
│ │ ├── github.svg
│ │ ├── twitter.svg
│ │ └── vkontakte.svg
│ ├── more
│ │ ├── mailru.svg
│ │ └── odnoklassniki.svg
│ ├── paths.json
│ ├── pinterest.svg
│ ├── plusone.svg
│ └── single.svg
├── src_ligatures
│ ├── git-hub.svg
│ ├── mailru.svg
│ └── odnoklassniki.svg
├── src_one
│ └── home.svg
├── src_space
│ └── ma il ru.svg
├── templates
│ ├── context-test.html
│ ├── custom.js
│ ├── custom.json
│ ├── template.css
│ ├── template.html
│ ├── template.json
│ ├── template.sass
│ └── template.scss
└── webfont_test.js
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = tab
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [{package.json,.travis.yml}]
12 | indent_style = space
13 | indent_size = 2
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | test/tmp
3 | .cache
4 | Changelog.md
5 |
--------------------------------------------------------------------------------
/.jscs.json:
--------------------------------------------------------------------------------
1 | {
2 | "requireCurlyBraces": ["for", "while", "do"],
3 | "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return"],
4 | "requireParenthesesAroundIIFE": true,
5 | "requireSpacesInFunctionExpression": { "beforeOpeningCurlyBrace": true },
6 | "disallowSpacesInFunctionExpression": { "beforeOpeningRoundBrace": true },
7 | "disallowMultipleVarDecl": true,
8 | "disallowSpacesInsideObjectBrackets": true,
9 | "disallowSpacesInsideArrayBrackets": true,
10 | "disallowSpacesInsideParentheses": true,
11 | "disallowSpaceAfterObjectKeys": true,
12 | "requireCommaBeforeLineBreak": true,
13 | "requireOperatorBeforeLineBreak": ["+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
14 | "disallowSpaceAfterBinaryOperators": ["!"],
15 | "disallowSpaceBeforeBinaryOperators": [","],
16 | "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"],
17 | "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"],
18 | "requireSpaceBeforeBinaryOperators": ["?", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
19 | "requireSpaceAfterBinaryOperators": ["?", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
20 | "requireCamelCaseOrUpperCaseIdentifiers": true,
21 | "disallowKeywords": ["with"],
22 | "disallowMultipleLineStrings": true,
23 | "validateLineBreaks": "LF",
24 | "validateIndentation": "\t",
25 | "disallowMixedSpacesAndTabs": "smart",
26 | "disallowTrailingWhitespace": true,
27 | "requireKeywordsOnNewLine": ["else"],
28 | "requireLineFeedAtFileEnd": true,
29 | "maximumLineLength": 140,
30 | "safeContextKeyword": "that",
31 | "requireDotNotation": true,
32 | "validateJSDoc": {
33 | "checkParamNames": true,
34 | "checkRedundantParams": true,
35 | "requireParamTypes": true
36 | },
37 | "excludeFiles": ["node_modules/**"]
38 | }
39 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "white": false,
4 | "smarttabs": true,
5 | "eqeqeq": true,
6 | "immed": true,
7 | "latedef": false,
8 | "newcap": true,
9 | "undef": true,
10 | "laxbreak": true
11 | }
12 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | Formula
3 | test
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | sudo: false
3 | node_js:
4 | - "0.12"
5 | - "4"
6 | - "5"
7 | - "6"
8 | addons:
9 | apt:
10 | packages:
11 | - fontforge
12 |
--------------------------------------------------------------------------------
/Contributing.md:
--------------------------------------------------------------------------------
1 | # How to contribute
2 |
3 | I love pull requests. And following this simple guidelines will make your pull request easier to merge.
4 |
5 |
6 | ## Submitting pull requests
7 |
8 | 1. Create a new branch, please don’t work in master directly.
9 | 2. Add failing tests (if there’re any tests in project) for the change you want to make. Run tests (usually `grunt` or `npm test`) to see the tests fail.
10 | 3. Hack on.
11 | 4. Run tests to see if the tests pass. Repeat steps 2–4 until done.
12 | 5. Update the documentation to reflect any changes.
13 | 6. Push to your fork and submit a pull request.
14 |
15 |
16 | ## JavaScript code style
17 |
18 | - Tab indentation.
19 | - Single-quotes.
20 | - Semicolon.
21 | - Strict mode.
22 | - No trailing whitespace.
23 | - Variables where needed.
24 | - Multiple variable statements.
25 | - Space after keywords and between arguments and operators.
26 | - Use === and !== over == and !=.
27 | - Return early.
28 | - Limit line lengths to 120 chars.
29 | - Prefer readability over religion.
30 |
31 | Example:
32 |
33 | ```js
34 | 'use strict';
35 |
36 | function foo(bar, fum) {
37 | if (!bar) return;
38 |
39 | var hello = 'Hello';
40 | var ret = 0;
41 | for (var barIdx = 0; barIdx < bar.length; barIdx++) {
42 | if (bar[barIdx] === hello) {
43 | ret += fum(bar[barIdx]);
44 | }
45 | }
46 |
47 | return ret;
48 | }
49 | ```
50 |
51 |
52 | ## Other notes
53 |
54 | - If you have commit access to repo and want to make big change or not sure about something, make a new branch and open pull request.
55 | - Don’t commit generated files: compiled from Stylus CSS, minified JavaScript, etc.
56 | - Don’t change version number and changelog.
57 | - Install [EditorConfig](http://editorconfig.org/) plugin for your code editor.
58 | - If code you change uses different style (probably it’s an old code) use file’s style instead of style described on this page.
59 | - Feel free to [ask me](http://sapegin.me/contacts) anything you need.
60 |
61 |
62 | ## How to run tests
63 |
64 | Install dependencies:
65 |
66 | ```bash
67 | npm install grunt-cli -g
68 | npm install
69 | ```
70 |
71 | Run:
72 |
73 | ```bash
74 | grunt
75 | ```
76 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | /*jshint node:true*/
2 |
3 | var path = require('path');
4 |
5 | module.exports = function(grunt) {
6 | 'use strict';
7 |
8 | require('load-grunt-tasks')(grunt);
9 |
10 | grunt.initConfig({
11 | webfont: {
12 | test1: {
13 | src: 'test/src/*.svg',
14 | dest: 'test/tmp/test1',
15 | options: {
16 | hashes: false
17 | }
18 | },
19 | test2: {
20 | src: 'test/src/*.svg',
21 | dest: 'test/tmp/test2/fonts',
22 | destCss: 'test/tmp/test2',
23 | options: {
24 | font: 'myfont',
25 | types: 'woff,svg',
26 | syntax: 'bootstrap'
27 | }
28 | },
29 | embed: {
30 | src: 'test/src/*.svg',
31 | dest: 'test/tmp/embed',
32 | options: {
33 | hashes: false,
34 | embed: true
35 | }
36 | },
37 | embed_woff: {
38 | src: 'test/src/*.svg',
39 | dest: 'test/tmp/embed_woff',
40 | options: {
41 | types: 'woff',
42 | hashes: false,
43 | embed: true
44 | }
45 | },
46 | embed_ttf: {
47 | src: 'test/src/*.svg',
48 | dest: 'test/tmp/embed_ttf',
49 | options: {
50 | types: 'ttf',
51 | hashes: false,
52 | embed: 'ttf'
53 | }
54 | },
55 | embed_ttf_woff: {
56 | src: 'test/src/*.svg',
57 | dest: 'test/tmp/embed_ttf_woff',
58 | options: {
59 | types: 'ttf,woff',
60 | hashes: false,
61 | embed: 'ttf,woff'
62 | }
63 | },
64 | one: {
65 | src: 'test/src_one/*.svg',
66 | dest: 'test/tmp/one',
67 | options: {
68 | hashes: false
69 | }
70 | },
71 | template: {
72 | src: 'test/src/*.svg',
73 | dest: 'test/tmp/template',
74 | options: {
75 | template: 'test/templates/template.css'
76 | }
77 | },
78 | template_scss: {
79 | src: 'test/src/*.svg',
80 | dest: 'test/tmp/template_scss',
81 | options: {
82 | stylesheet: 'scss',
83 | template: 'test/templates/template.scss'
84 | }
85 | },
86 | template_sass: {
87 | src: 'test/src/*.svg',
88 | dest: 'test/tmp/template_sass',
89 | options: {
90 | template: 'test/templates/template.sass'
91 | }
92 | },
93 | html_template: {
94 | src: 'test/src/*.svg',
95 | dest: 'test/tmp/html_template',
96 | options: {
97 | htmlDemoTemplate: 'test/templates/template.html'
98 | }
99 | },
100 | html_filename: {
101 | src: 'test/src/*.svg',
102 | dest: 'test/tmp/html_filename',
103 | options: {
104 | htmlDemoFilename: 'index'
105 | }
106 | },
107 | relative_path: {
108 | src: 'test/src/*.svg',
109 | dest: 'test/tmp/relative_path',
110 | options: {
111 | relativeFontPath: '../iamrelative',
112 | hashes: false
113 | }
114 | },
115 | sass: {
116 | src: 'test/src/*.svg',
117 | dest: 'test/tmp/sass',
118 | options: {
119 | stylesheet: 'sass'
120 | }
121 | },
122 | less: {
123 | src: 'test/src/*.svg',
124 | dest: 'test/tmp/less',
125 | options: {
126 | stylesheet: 'less'
127 | }
128 | },
129 | css_plus_scss: {
130 | src: 'test/src/*.svg',
131 | dest: 'test/tmp/sass',
132 | destCss: 'test/tmp/css',
133 | destScss: 'test/tmp/scss',
134 | options: {
135 | stylesheets: ['css', 'scss']
136 | }
137 | },
138 | stylus_bem: {
139 | src: 'test/src/*.svg',
140 | dest: 'test/tmp/stylus_bem',
141 | options: {
142 | stylesheet: 'styl'
143 | }
144 | },
145 | stylus_bootstrap: {
146 | src: 'test/src/*.svg',
147 | dest: 'test/tmp/stylus_bootstrap',
148 | options: {
149 | stylesheet: 'styl',
150 | syntax: 'bootstrap'
151 | }
152 | },
153 | spaces: {
154 | src: 'test/src_space/*.svg',
155 | dest: 'test/tmp/spaces'
156 | },
157 | disable_demo: {
158 | src: 'test/src_one/*.svg',
159 | dest: 'test/tmp/disable_demo',
160 | options: {
161 | htmlDemo: false
162 | }
163 | },
164 | non_css_demo: {
165 | src: 'test/src/*.svg',
166 | dest: 'test/tmp/non_css_demo',
167 | options: {
168 | stylesheet: 'less',
169 | relativeFontPath: '../iamrelative',
170 | htmlDemo: true
171 | }
172 | },
173 | parent_source: {
174 | src: '../grunt-webfont/test/src/*.svg',
175 | dest: 'test/tmp/parent_source',
176 | options: {
177 | hashes: false
178 | }
179 | },
180 | // #167: Ligatures with hypen don’t work
181 | ligatures: {
182 | src: 'test/src_ligatures/*.svg',
183 | dest: 'test/tmp/ligatures',
184 | options: {
185 | hashes: false,
186 | ligatures: true
187 | }
188 | },
189 | duplicate_names: {
190 | src: '../grunt-webfont/test/src_duplicate_names/**/*.svg',
191 | dest: 'test/tmp/duplicate_names',
192 | options: {
193 | hashes: false,
194 | rename: function(name) {
195 | return [path.basename(path.dirname(name)), path.basename(name)].join('-');
196 | }
197 | }
198 | },
199 | order: {
200 | src: 'test/src/*.svg',
201 | dest: 'test/tmp/order',
202 | options: {
203 | types: 'woff,svg',
204 | order: 'svg,woff',
205 | hashes: false
206 | }
207 | },
208 | template_options: {
209 | src: 'test/src/*.svg',
210 | dest: 'test/tmp/template_options',
211 | options: {
212 | hashes: false,
213 | syntax: 'bem',
214 | stylesheet: 'less',
215 | templateOptions: {
216 | baseClass: 'glyph-icon',
217 | classPrefix: 'glyph_'
218 | }
219 | }
220 | },
221 | node: {
222 | src: 'test/src/*.svg',
223 | dest: 'test/tmp/node',
224 | options: {
225 | hashes: false,
226 | engine: 'node'
227 | }
228 | },
229 | ie7: {
230 | src: 'test/src/*.svg',
231 | dest: 'test/tmp/ie7',
232 | options: {
233 | hashes: false,
234 | ie7: true,
235 | syntax: 'bem'
236 | }
237 | },
238 | ie7_bootstrap: {
239 | src: 'test/src/*.svg',
240 | dest: 'test/tmp/ie7_bootstrap',
241 | options: {
242 | hashes: false,
243 | ie7: true,
244 | syntax: 'bootstrap'
245 | }
246 | },
247 | optimize_enabled: {
248 | src: 'test/src/*.svg',
249 | dest: 'test/tmp/optimize_enabled',
250 | options: {
251 | engine: 'node',
252 | types: 'svg',
253 | autoHint: false,
254 | optimize: true
255 | }
256 | },
257 | optimize_disabled: {
258 | src: 'test/src/*.svg',
259 | dest: 'test/tmp/optimize_disabled',
260 | options: {
261 | engine: 'node',
262 | types: 'svg',
263 | autoHint: false,
264 | optimize: false
265 | }
266 | },
267 | codepoints: {
268 | src: 'test/src/*.svg',
269 | dest: 'test/tmp/codepoints',
270 | options: {
271 | hashes: false,
272 | startCodepoint: 0x41,
273 | codepoints: {
274 | single: 0x43
275 | }
276 | }
277 | },
278 | camel: {
279 | src: 'test/camel/*.svg',
280 | dest: 'test/tmp/camel',
281 | options: {
282 | hashes: false
283 | }
284 | },
285 | folders: {
286 | src: 'test/src_folders/**/*.svg',
287 | dest: 'test/tmp/folders',
288 | options: {
289 | hashes: false
290 | }
291 | },
292 | woff2: {
293 | src: 'test/src/*.svg',
294 | dest: 'test/tmp/woff2',
295 | options: {
296 | types: 'woff2,woff'
297 | }
298 | },
299 | woff2_node: {
300 | src: 'test/src/*.svg',
301 | dest: 'test/tmp/woff2_node',
302 | options: {
303 | types: 'woff2,woff',
304 | engine: 'node'
305 | }
306 | },
307 | target_overrides: {
308 | src: 'test/src/*.svg',
309 | options: {
310 | dest: 'test/tmp/target_overrides_icons',
311 | destCss: 'test/tmp/target_overrides_css',
312 | }
313 | },
314 | font_family_name: {
315 | src: 'test/src/*.svg',
316 | dest: 'test/tmp/font_family_name',
317 | options: {
318 | fontFamilyName: 'customName',
319 | types: 'ttf',
320 | }
321 | },
322 | custom_output: {
323 | src: 'test/src/*.svg',
324 | options: {
325 | dest: 'test/tmp/custom_output_icons',
326 | destCss: 'test/tmp/custom_output_css',
327 | customOutputs: [{
328 | template: 'test/templates/custom.js',
329 | dest: 'test/tmp/custom_output/test-icon-config.js'
330 | }, {
331 | template: 'test/templates/custom.json',
332 | dest: 'test/tmp/custom_output'
333 | }, {
334 | template: 'test/templates/context-test.html',
335 | dest: 'test/tmp/custom_output',
336 | context: {
337 | testHeading: 'Hello, world!'
338 | }
339 | }]
340 | }
341 | },
342 | enabled_template_variables: {
343 | src: 'test/src/*.svg',
344 | dest: 'test/tmp/enabled_template_variables',
345 | options: {
346 | relativeFontPath: '../iamrelative',
347 | fontPathVariables: true,
348 | stylesheets: ['css', 'scss', 'less']
349 | }
350 | },
351 | filename_length: {
352 | src: 'test/src_filename_length/*.svg',
353 | dest: 'test/tmp/filename_length',
354 | options: {
355 | autoHint: false,
356 | engine: 'node',
357 | hashes: false,
358 | types: 'woff'
359 | }
360 | },
361 | },
362 | nodeunit: {
363 | all: ['test/webfont_test.js']
364 | },
365 | jshint: {
366 | all: ['Gruntfile.js', 'tasks/*.js', 'test/*.js'],
367 | options: {
368 | jshintrc: true
369 | }
370 | },
371 | watch: {
372 | scripts: {
373 | files: '<%= jshint.all %>',
374 | tasks: ['jshint', 'jscs'],
375 | options: {
376 | debounceDelay: 100,
377 | nospawn: true
378 | }
379 | },
380 | },
381 | jscs: {
382 | options: {
383 | config: ".jscs.json",
384 | },
385 | all: ['tasks/*.js']
386 | },
387 | clean: ['test/tmp']
388 | });
389 |
390 | grunt.loadTasks('tasks');
391 |
392 | grunt.registerTask('test', ['nodeunit']);
393 | grunt.registerTask('default', ['jshint', 'jscs', 'clean', 'webfont', 'test', 'clean']);
394 |
395 | };
396 |
--------------------------------------------------------------------------------
/License.md:
--------------------------------------------------------------------------------
1 | The MIT License
2 | ===============
3 |
4 | Copyright © 2014 Artem Sapegin, http://sapegin.me
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining
7 | a copy of this software and associated documentation files (the
8 | 'Software'), to deal in the Software without restriction, including
9 | without limitation the rights to use, copy, modify, merge, publish,
10 | distribute, sublicense, and/or sell copies of the Software, and to
11 | permit persons to whom the Software is furnished to do so, subject to
12 | the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be
15 | included in all copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 |
--------------------------------------------------------------------------------
/Pull_Request_Template.md:
--------------------------------------------------------------------------------
1 | If you want your pull request to be merged, please:
2 |
3 | 1. Explain the use case or bug you’re solving.
4 | 2. Add tests.
5 | 3. Add docs.
6 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # SVG to webfont converter for Grunt
2 |
3 | [](http://sapegin.github.io/powered-by-you/)
4 | [](https://travis-ci.org/sapegin/grunt-webfont)
5 | [](https://www.npmjs.com/package/grunt-webfont)
6 |
7 | Generate custom icon webfonts from SVG files via Grunt. Inspired by [Font Custom](https://github.com/FontCustom/fontcustom).
8 |
9 | This task will make all you need to use font-face icon on your website: font in all needed formats, CSS/Sass/Less/Stylus and HTML demo page.
10 |
11 | ## Features
12 |
13 | * Works on Mac, Windows and Linux.
14 | * Very flexible.
15 | * Supports all web font formats: WOFF, WOFF2, EOT, TTF and SVG.
16 | * Semantic: uses [Unicode private use area](http://bit.ly/ZnkwaT).
17 | * [Cross-browser](http://www.fontspring.com/blog/further-hardening-of-the-bulletproof-syntax/): IE8+.
18 | * BEM or Bootstrap output CSS style.
19 | * CSS preprocessors support.
20 | * Data:uri embedding.
21 | * Ligatures.
22 | * HTML preview.
23 | * Custom templates.
24 |
25 |
26 | ## Installation
27 |
28 | This plugin requires Grunt 0.4. Note that `ttfautohint` is optional, but your generated font will not be properly hinted if it’s not installed. And make sure you don’t use `ttfautohint` 0.97 because that version won’t work.
29 |
30 | ### OS X
31 |
32 | ```
33 | brew install ttfautohint fontforge --with-python
34 | npm install grunt-webfont --save-dev
35 | ```
36 |
37 | *You may need to use `sudo` for `brew`, depending on your setup.*
38 |
39 | *`fontforge` isn’t required for `node` engine (see below).*
40 |
41 | ### Linux
42 |
43 | ```
44 | sudo apt-get install fontforge ttfautohint
45 | npm install grunt-webfont --save-dev
46 | ```
47 |
48 | *`fontforge` isn’t required for the `node` engine (see [below](#available-engines)).*
49 |
50 | ### Windows
51 |
52 | ```
53 | npm install grunt-webfont --save-dev
54 | ```
55 |
56 | Then [install `ttfautohint`](http://www.freetype.org/ttfautohint/#download) (optional).
57 |
58 | Then install `fontforge`.
59 | * Download and install [fontforge](http://fontforge.github.io/en-US/downloads/windows/).
60 | * Add `C:\Program Files (x86)\FontForgeBuilds\bin` to your `PATH` environment variable.
61 |
62 | *`fontforge` isn’t required for the `node` engine (see [below](#available-engines)).*
63 |
64 | ## Available Engines
65 |
66 | There are two font rendering engines available. See also `engine` option below.
67 |
68 | ### fontforge
69 |
70 | #### Pros
71 |
72 | * All features supported.
73 | * The best results.
74 |
75 | #### Cons
76 |
77 | * You have to install `fontforge`.
78 | * Really weird bugs sometimes.
79 |
80 | ### node
81 |
82 | #### Pros
83 |
84 | * No external dependencies (except optional `ttfautohint`).
85 | * Works on all platforms.
86 |
87 | #### Cons
88 |
89 | * Doesn’t work [with some SVG files](https://github.com/fontello/svg2ttf/issues/25).
90 | * Ligatures aren’t supported.
91 |
92 |
93 | ## Configuration
94 |
95 | Add somewhere in your `Gruntfile.js`:
96 |
97 | ```javascript
98 | grunt.loadNpmTasks('grunt-webfont');
99 | ```
100 |
101 | Inside your `Gruntfile.js` file add a section named `webfont`. See Parameters section below for details.
102 |
103 |
104 | ### Parameters
105 |
106 | #### src
107 |
108 | Type: `string|array`
109 |
110 | Glyphs list: SVG. String or array. Wildcards are supported.
111 |
112 | #### dest
113 |
114 | Type: `string`
115 |
116 | Directory for resulting files.
117 |
118 | #### destCss
119 |
120 | Type: `string` Default: _`dest` value_
121 |
122 | Directory for resulting CSS files (if different than font directory). You can also define `destScss`, `destSass`, `destLess` and `destStyl` to specify a directory per stylesheet type.
123 |
124 | #### Options
125 |
126 | All options should be inside `options` object:
127 |
128 | ``` javascript
129 | webfont: {
130 | icons: {
131 | src: 'icons/*.svg',
132 | dest: 'build/fonts',
133 | options: {
134 | ...
135 | }
136 | }
137 | }
138 | ```
139 |
140 | #### font
141 |
142 | Type: `string` Default: `icons`
143 |
144 | Name of font and base name of font files.
145 |
146 | #### fontFilename
147 |
148 | Type: `string` Default: Same as `font` option
149 |
150 | Filename for generated font files, you can add placeholders for the same data that gets passed to the [template](#template).
151 |
152 | For example, to get the hash to be part of the filenames:
153 |
154 | ```js
155 | options: {
156 | fontFilename: 'icons-{hash}'
157 | }
158 | ```
159 |
160 | #### hashes
161 |
162 | Type: `boolean` Default: `true`
163 |
164 | Append font file names with unique string to flush browser cache when you update your icons.
165 |
166 | #### styles
167 |
168 | Type: `string|array` Default: `'font,icon'`
169 |
170 | List of styles to be added to CSS files: `font` (`font-face` declaration), `icon` (base `.icon` class), `extra` (extra stuff for Bootstrap (only for `syntax` = `'bootstrap'`).
171 |
172 | #### types
173 |
174 | Type: `string|array` Default: `'eot,woff,ttf'`, available: `'eot,woff2,woff,ttf,svg'`
175 |
176 | Font files types to generate.
177 |
178 | #### order
179 |
180 | Type: `string|array` Default: `'eot,woff,ttf,svg'`
181 |
182 | Order of `@font-face`’s `src` values in CSS file. (Only file types defined in `types` option will be generated.)
183 |
184 | #### syntax
185 |
186 | Type: `string` Default: `bem`
187 |
188 | Icon classes syntax. `bem` for double class names: `icon icon_awesome` or `bootstrap` for single class names: `icon-awesome`.
189 |
190 | #### template
191 |
192 | Type: `string` Default: ``
193 |
194 | Custom CSS template path (see `tasks/templates` for some examples). Should be used instead of `syntax`. (You probably need to define `htmlDemoTemplate` option too.)
195 |
196 | Template is a pair of CSS and JSON (optional) files with the same name.
197 |
198 | For example, your Gruntfile:
199 |
200 | ```js
201 | options: {
202 | template: 'my_templates/tmpl.css'
203 | }
204 | ```
205 |
206 | `my_templates/tmpl.css`:
207 |
208 | ```css
209 | @font-face {
210 | font-family:"<%= fontBaseName %>";
211 | ...
212 | }
213 | ...
214 | ```
215 |
216 | `my_templates/tmpl.json`:
217 |
218 | ```json
219 | {
220 | "baseClass": "icon",
221 | "classPrefix": "icon_"
222 | }
223 | ```
224 |
225 | Some extra data is available for you in templates:
226 |
227 | * `hash`: a unique string to flush browser cache. Available even if `hashes` option is `false`.
228 |
229 | * `fontRawSrcs`: array of font-face’s src values not merged to a single line:
230 |
231 | ```
232 | [
233 | [
234 | 'url("icons.eot")'
235 | ],
236 | [
237 | 'url("icons.eot?#iefix") format("embedded-opentype")',
238 | 'url("icons.woff") format("woff")',
239 | 'url("icons.ttf") format("truetype")'
240 | ]
241 | ]
242 | ```
243 |
244 |
245 | #### templateOptions
246 |
247 | Type: `object` Default: `{}`
248 |
249 | Extends/overrides CSS template or syntax’s JSON file. Allows custom class names in default css templates.
250 |
251 | ``` javascript
252 | options: {
253 | templateOptions: {
254 | baseClass: 'glyph-icon',
255 | classPrefix: 'glyph_'
256 | }
257 | }
258 | ```
259 |
260 | #### stylesheets
261 |
262 | Type: `array` Default: `['css']` or extension of `template`
263 |
264 | Stylesheet type. Can be `css`, `sass`, `scss` or `less`. If `sass` or `scss` is used, `_` will prefix the file (so it can be a used as a partial). You can define just `stylesheet` if you are generating just one type.
265 |
266 | #### relativeFontPath
267 |
268 | Type: `string` Default: `null`
269 |
270 | Custom font path. Will be used instead of `destCss` *in* CSS file. Useful with CSS preprocessors.
271 |
272 | #### fontPathVariables
273 |
274 | Type: `boolean` Default: `false`
275 |
276 | Create font-path variables for `less`, `scss` and `sass` files. Can be used to override the `relativeFontPath`
277 | in custom preprocessor tasks or configs.
278 |
279 | The variable name is a combination of the `font` name appended with `-font-path`.
280 |
281 |
282 | #### version
283 |
284 | Type: `string` Default: `false`
285 |
286 | Version number added to `.ttf` version of the font (FontForge Engine only). Also used in the heading of the default demo.html template. Useful to align with the version of other assets that are part of a larger system.
287 |
288 | #### htmlDemo
289 |
290 | Type: `boolean` Default: `true`
291 |
292 | If `true`, an HTML file will be available (by default, in `destCSS` folder) to test the render.
293 |
294 | #### htmlDemoTemplate
295 |
296 | Type: `string` Default: `null`
297 |
298 | Custom demo HTML template path (see `tasks/templates/demo.html` for an example) (requires `htmlDemo` option to be true).
299 |
300 | #### htmlDemoFilename
301 |
302 | Type: `string` Default: _`fontBaseName` value_
303 |
304 | Custom name for the demo HTML file (requires `htmlDemo` option to be true). Useful if you want to name the output something like `index.html` instead of the font name.
305 |
306 | #### destHtml
307 |
308 | Type: `string` Default: _`destCss` value_
309 |
310 | Custom demo HTML demo path (requires `htmlDemo` option to be true).
311 |
312 | #### embed
313 |
314 | Type: `string|array` Default: `false`
315 |
316 | If `true` embeds WOFF (*only WOFF*) file as data:uri.
317 |
318 | IF `ttf` or `woff` or `ttf,woff` embeds TTF or/and WOFF file.
319 |
320 | If there are more file types in `types` option they will be included as usual `url(font.type)` CSS links.
321 |
322 | #### ligatures
323 |
324 | Type: `boolean` Default: `false`
325 |
326 | If `true` the generated font files and stylesheets will be generated with opentype ligature features. The character sequences to be replaced by the ligatures are determined by the file name (without extension) of the original SVG.
327 |
328 | For example, you have a heart icon in `love.svg` file. The HTML `
I love you! ` will be rendered as `I ♥ you!`.
329 |
330 | #### rename
331 |
332 | Type: `function` Default: `path.basename`
333 |
334 | You can use this function to change how file names translates to class names (the part after `icon_` or `icon-`). By default it’s a name of a file.
335 |
336 | For example you can group your icons into several folders and add folder name to class name:
337 |
338 | ```js
339 | options: {
340 | rename: function(name) {
341 | // .icon_entypo-add, .icon_fontawesome-add, etc.
342 | return [path.basename(path.dirname(name)), path.basename(name)].join('-');
343 | }
344 | }
345 | ```
346 |
347 | #### skip
348 |
349 | Type: `boolean` Default: `false`
350 |
351 | If `true` task will not be ran. In example, you can skip task on Windows (becase of difficult installation):
352 |
353 | ```javascript
354 | options: {
355 | skip: require('os').platform() === 'win32'
356 | }
357 | ```
358 |
359 | #### engine
360 |
361 | Type: `string` Default: `fontforge`
362 |
363 | Font rendering engine: `fontforge` or `node`. See comparison in [Available Engines](#available-engines) section above.
364 |
365 | #### ie7
366 |
367 | Type: `boolean` Default: `false`
368 |
369 | Adds IE7 support using a `*zoom: expression()` hack.
370 |
371 | #### optimize
372 |
373 | Type: `boolean` Default: `true`
374 |
375 | If `false` the SVGO optimization will not be used. This is useful in cases where the optimizer will produce faulty web fonts by removing relevant SVG paths or attributes.
376 |
377 | #### normalize
378 |
379 | Type: `boolean` Default: `false`
380 |
381 | When using the fontforge engine, if false, glyphs will be generated with a fixed width equal to fontHeight. In most cases, this will produce an extra blank space for each glyph. If set to true, no extra space will be generated. Each glyph will have a width that matches its boundaries.
382 |
383 | #### startCodepoint
384 |
385 | Type: `integer` Default: `0xF101`
386 |
387 | Starting codepoint used for the generated glyphs. Defaults to the start of the Unicode private use area.
388 |
389 | #### codepoints
390 |
391 | Type: `object` Default: `null`
392 |
393 | Specific codepoints to use for certain glyphs. Any glyphs not specified in the codepoints block will be given incremented as usual from the `startCodepoint`, skipping duplicates.
394 |
395 | ```javascript
396 | options: {
397 | codepoints: {
398 | single: 0xE001
399 | }
400 | }
401 | ```
402 |
403 | #### codepointsFile
404 | Type: `string` Default: `null`
405 |
406 | Uses and Saves the codepoint mapping by name to this file.
407 |
408 | NOTE: will overwrite the set codepoints option.
409 |
410 | #### autoHint
411 |
412 | Type: `boolean` Default: `true`
413 |
414 | Enables font auto hinting using `ttfautohint`.
415 |
416 | #### round
417 |
418 | Type: `number` Default: `10e12`
419 |
420 | Setup SVG path rounding.
421 |
422 | #### fontHeight
423 |
424 | Type: `number` Default: `512`
425 |
426 | The output font height.
427 |
428 | #### fontFamilyName
429 |
430 | Type: `string` Default: _`font` value_
431 |
432 | If you’d like your generated fonts to have a name that’s different than the `font` value, you can specify this as a string. This will allow a unique display name within design authoring tools when installing fonts locally. For example, your font’s name could be `GitHub Octicons` with a filename of `octicons.ttf`.
433 |
434 | ```javascript
435 | options: {
436 | fontFamilyName: 'GitHub Octicons',
437 | }
438 | ```
439 |
440 | #### descent
441 |
442 | Type: `number` Default: `64`
443 |
444 | The font descent. The descent should be a positive value. The ascent formula is: `ascent = fontHeight - descent`.
445 |
446 | #### callback
447 |
448 | Type: `function` Default: `null`
449 |
450 | Allows for a callback to be called when the task has completed and passes in the filename of the generated font, an array of the various font types created, an array of all the glyphs created and the hash used to flush browser cache.
451 |
452 | ```javascript
453 | options: {
454 | callback: function(filename, types, glyphs, hash) {
455 | // ...
456 | }
457 | }
458 | ```
459 |
460 | #### customOutputs
461 |
462 | Type: `array` Default: `undefined`
463 |
464 | Allows for custom content to be generated and output in the same way as `htmlDemo`.
465 |
466 | Each entry in `customOutputs` should be an object with the following parameters:
467 |
468 | * `template` - (`string`) the path to the underscore-template you wish to use.
469 | * `dest` - (`string`) the path to the destination where you want the resulting file to live.
470 | * `context` \[optional\] - (`object`) a hash of values to pass into the context of the template
471 |
472 | At compile-time each template will have access to the same context as the compile-time environment of `htmlDemoTemplate` (as extended by the `context` object, if provided. See config-example below.
473 |
474 | #### execMaxBuffer
475 | If you get stderr maxBuffer exceeded warning message, engine probably logged a lot of warning messages. To see this warnings run grunt in verbose mode `grunt --verbose`. To go over this warning you can try to increase buffer size by this option. Default value is `1024 * 200`
476 |
477 | ### Config Examples
478 |
479 | #### Simple font generation
480 |
481 | ```javascript
482 | webfont: {
483 | icons: {
484 | src: 'icons/*.svg',
485 | dest: 'build/fonts'
486 | }
487 | }
488 | ```
489 |
490 | #### Custom font name, fonts and CSS in different folders
491 |
492 | ```javascript
493 | webfont: {
494 | icons: {
495 | src: 'icons/*.svg',
496 | dest: 'build/fonts',
497 | destCss: 'build/fonts/css',
498 | options: {
499 | font: 'ponies'
500 | }
501 | }
502 | }
503 | ```
504 |
505 | #### Custom CSS classes
506 |
507 | ```js
508 | webfont: {
509 | icons: {
510 | src: 'icons/*.svg',
511 | dest: 'build/fonts',
512 | options: {
513 | syntax: 'bem',
514 | templateOptions: {
515 | baseClass: 'glyph-icon',
516 | classPrefix: 'glyph_'
517 | }
518 | }
519 | }
520 | }
521 | ```
522 |
523 | #### To use with CSS preprocessor
524 |
525 | ```javascript
526 | webfont: {
527 | icons: {
528 | src: 'icons/*.svg',
529 | dest: 'build/fonts',
530 | destCss: 'build/styles',
531 | options: {
532 | stylesheet: 'styl',
533 | relativeFontPath: '/build/fonts'
534 | }
535 | }
536 | }
537 | ```
538 |
539 | #### Embedded font file
540 |
541 | ```javascript
542 | webfont: {
543 | icons: {
544 | src: 'icons/*.svg',
545 | dest: 'build/fonts',
546 | options: {
547 | types: 'woff',
548 | embed: true
549 | }
550 | }
551 | }
552 | ```
553 |
554 | #### Custom Outputs
555 |
556 | ```javascript
557 | webfont: {
558 | icons: {
559 | src: 'icons/*.svg',
560 | dest: 'build/fonts',
561 | options: {
562 | customOutputs: [{
563 | template: 'templates/icon-glyph-list-boilerplate.js',
564 | dest: 'build/js/icon-glyph-list.js'
565 | }, {
566 | template: 'templates/icon-glyph-config-boilerplate.json',
567 | dest: 'build/js/icon-glyphs.json'
568 | }, {
569 | template: 'templates/icon-web-home.html',
570 | dest: 'build/',
571 | context: {
572 | homeHeading: 'Your Icon Font',
573 | homeMessage: 'The following glyphs are available in this font:'
574 | }
575 | }]
576 | }
577 | }
578 | }
579 | ```
580 |
581 | We might then include the following corresponding templates.
582 |
583 | The first, for `icon-glyph-list-boilerplate.js`, a file that outputs a list of icon-glyph slugs.
584 |
585 | ```
586 | // file: icon-glyph-list-boilerplate.js
587 |
588 | (function(window) {
589 | 'use strict';
590 |
591 | var iconList = <%= JSON.stringify(glyphs) %>;
592 | window.iconList = iconList;
593 | }(this));
594 | ```
595 |
596 | The second, for `icon-glyph-config-boilerplate.json`, a file that dumps all JSON data in the current template context.
597 |
598 | ```
599 | // file: icon-glyph-config-boilerplate.json
600 |
601 | <%= JSON.stringify(arguments[0], null, '\t') %>
602 | ```
603 |
604 | And finally, the third, for `icon-web-home.html`, a file that has access to the values provided in the `context` object supplied.
605 |
606 | ```
607 | // file: icon-web-home.html
608 |
609 |
610 |
611 |
612 |
613 | Context Test
614 |
615 |
616 | <%= homeHeading %>
617 | <%= homeMessage %>
618 |
619 | <% for (var i = 0; i < glyphs.length; i++) { %>
620 | <%= glyphs[i] %>
621 | <% } %>
622 |
623 |
624 |
625 | ```
626 |
627 | ## CSS Preprocessors Caveats
628 |
629 | You can change CSS file syntax using `stylesheet` option (see above). It change file extension (so you can specify any) with some tweaks. Replace all comments with single line comments (which will be removed after compilation).
630 |
631 | ### Dynamic font-path
632 | You can enable the `fontPathVariables` in combination with `relativeFontPath` to create a overridable font-path.
633 |
634 | For example scss:
635 | ```scss
636 | $icons-font-path : "/relativeFontPath/" !default;
637 | @font-face {
638 | font-family:"icons";
639 | src:url($icons-font-path + "icons.eot");
640 | src:url($icons-font-path + "icons.eot?#iefix") format("embedded-opentype"),
641 | url($icons-font-path + "icons.woff") format("woff"),
642 | url($icons-font-path + "icons.ttf") format("truetype");
643 | font-weight:normal;
644 | font-style:normal;
645 | }
646 | ```
647 |
648 | ### Sass
649 |
650 | If `stylesheet` option is `sass` or `scss`, `_` will prefix the file (so it can be a used as a partial).
651 |
652 | ### Less
653 |
654 | If `stylesheet` option is `less`, regular CSS icon classes will be expanded with corresponding Less mixins.
655 |
656 | The Less mixins then may be used like so:
657 |
658 | ```css
659 | .profile-button {
660 | .icon-profile;
661 | }
662 | ```
663 |
664 | ## Troubleshooting
665 |
666 | ### I have problems displaying the font in Firefox
667 |
668 | Firefox doesn’t allow cross-domain fonts: [Specifications](http://www.w3.org/TR/css3-fonts/#font-fetching-requirements), [Bugzilla Ticket](https://bugzilla.mozilla.org/show_bug.cgi?id=604421), [How to fix it](https://coderwall.com/p/v4uwyq).
669 |
670 | ### My images are getting corrupted
671 |
672 | #### Using the node engine
673 |
674 | * Certain SVG's are not supported. See the [svg2ttf](https://github.com/fontello/svg2ttf) project which is used to convert from SVG to TTF (which is then converted forward to WOFF and WOFF2).
675 | * `autoHint` also adjusts the font file and can cause your font to look different to the SVG, so you could try switching it off (though it may make windows view of the font worse).
676 |
677 | #### Using fontforge
678 |
679 | Check the following...
680 |
681 | * Your paths are clockwise. Anti-clockwise paths may cause fills to occur differently.
682 | * Your paths are not overlapping. Overlapping paths will cause one of the areas to be inverted rather than combined. Use an editor to union your two paths together.
683 | * `autoHint` also adjusts the font file and can cause your font to look different to the SVG, so you could try switching it off (though it may make windows view of the font worse).
684 | * If you get stderr maxBuffer exceeded warning message, fontforge probably logged a lot of warning messages. To see this warnings run grunt in verbose mode `grunt --verbose`. To go over this warning you can try to increase buffer size by [execMaxBuffer](#execMaxBuffer).
685 |
686 | ## Changelog
687 |
688 | The changelog can be found on the [Releases page](https://github.com/sapegin/grunt-webfont/releases).
689 |
690 | ## License
691 |
692 | The MIT License, see the included [License.md](License.md) file.
693 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "grunt-webfont",
3 | "description": "Ultimate SVG to webfont converter for Grunt.",
4 | "version": "1.7.2",
5 | "homepage": "https://github.com/sapegin/grunt-webfont",
6 | "author": {
7 | "name": "Artem Sapegin",
8 | "url": "http://sapegin.me/"
9 | },
10 | "contributors": [
11 | "Maxime Thirouin (http://moox.io/)",
12 | "Aaron Lampros (https://github.com/alampros)",
13 | "Cyrille Meichel (https://github.com/landru29)"
14 | ],
15 | "repository": {
16 | "type": "git",
17 | "url": "git://github.com/sapegin/grunt-webfont.git"
18 | },
19 | "bugs": {
20 | "url": "https://github.com/sapegin/grunt-webfont/issues"
21 | },
22 | "license": "MIT",
23 | "main": "tasks/webfont.js",
24 | "scripts": {
25 | "test": "grunt --stack"
26 | },
27 | "engines": {
28 | "node": ">=0.12.0"
29 | },
30 | "dependencies": {
31 | "async": "~1.5.2",
32 | "chalk": "~1.1.1",
33 | "glob": "~7.0.0",
34 | "lodash": "~4.17.10",
35 | "memorystream": "~0.3.1",
36 | "mkdirp": "~0.5.1",
37 | "svg2ttf": "~2.1.1",
38 | "svgicons2svgfont": "~1.1.0",
39 | "svgo": "~0.6.1",
40 | "temp": "~0.8.3",
41 | "ttf2eot": "~1.3.0",
42 | "ttf2woff": "~1.3.0",
43 | "ttf2woff2": "~2.0.3",
44 | "underscore.string": "~3.2.3",
45 | "winston": "~2.1.1"
46 | },
47 | "devDependencies": {
48 | "grunt": "~0.4.5",
49 | "grunt-cli": "~0.1.13",
50 | "grunt-contrib-clean": "~1.0.0",
51 | "grunt-contrib-jshint": "~0.11.3",
52 | "grunt-contrib-nodeunit": "~0.4.1",
53 | "grunt-contrib-watch": "~0.6.1",
54 | "grunt-jscs": "~1.0.0",
55 | "load-grunt-tasks": "~3.4.0",
56 | "stylus": "~0.53.0",
57 | "xml2js": "~0.4.16"
58 | },
59 | "peerDependencies": {
60 | "grunt": ">=0.4.0"
61 | },
62 | "keywords": [
63 | "gruntplugin",
64 | "font",
65 | "webfont",
66 | "fontforge",
67 | "font-face",
68 | "woff",
69 | "woff2",
70 | "ttf",
71 | "svg",
72 | "eot",
73 | "truetype",
74 | "css",
75 | "icon"
76 | ]
77 | }
78 |
--------------------------------------------------------------------------------
/tasks/bin/eotlitetool.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | #
4 | # This is special grunt-webfont verion of eotlitetool.py.
5 | # https://github.com/sapegin/grunt-webfont
6 | #
7 | # Changes:
8 | # * Output option now works.
9 | # * Compatible with Python 3.
10 | #
11 |
12 | # ***** BEGIN LICENSE BLOCK *****
13 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1
14 | #
15 | # The contents of this file are subject to the Mozilla Public License Version
16 | # 1.1 (the "License"); you may not use this file except in compliance with
17 | # the License. You may obtain a copy of the License at
18 | # http://www.mozilla.org/MPL/
19 | #
20 | # Software distributed under the License is distributed on an "AS IS" basis,
21 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
22 | # for the specific language governing rights and limitations under the
23 | # License.
24 | #
25 | # The Original Code is font utility code.
26 | #
27 | # The Initial Developer of the Original Code is Mozilla Corporation.
28 | # Portions created by the Initial Developer are Copyright (C) 2009
29 | # the Initial Developer. All Rights Reserved.
30 | #
31 | # Contributor(s):
32 | # John Daggett
33 | #
34 | # Alternatively, the contents of this file may be used under the terms of
35 | # either the GNU General Public License Version 2 or later (the "GPL"), or
36 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
37 | # in which case the provisions of the GPL or the LGPL are applicable instead
38 | # of those above. If you wish to allow use of your version of this file only
39 | # under the terms of either the GPL or the LGPL, and not to allow others to
40 | # use your version of this file under the terms of the MPL, indicate your
41 | # decision by deleting the provisions above and replace them with the notice
42 | # and other provisions required by the GPL or the LGPL. If you do not delete
43 | # the provisions above, a recipient may use your version of this file under
44 | # the terms of any one of the MPL, the GPL or the LGPL.
45 | #
46 | # ***** END LICENSE BLOCK ***** */
47 |
48 | # eotlitetool.py - create EOT version of OpenType font for use with IE
49 | #
50 | # Usage: eotlitetool.py [-o output-filename] font1 [font2 ...]
51 | #
52 |
53 | # OpenType file structure
54 | # http://www.microsoft.com/typography/otspec/otff.htm
55 | #
56 | # Types:
57 | #
58 | # BYTE 8-bit unsigned integer.
59 | # CHAR 8-bit signed integer.
60 | # USHORT 16-bit unsigned integer.
61 | # SHORT 16-bit signed integer.
62 | # ULONG 32-bit unsigned integer.
63 | # Fixed 32-bit signed fixed-point number (16.16)
64 | # LONGDATETIME Date represented in number of seconds since 12:00 midnight, January 1, 1904. The value is represented as a signed 64-bit integer.
65 | #
66 | # SFNT Header
67 | #
68 | # Fixed sfnt version // 0x00010000 for version 1.0.
69 | # USHORT numTables // Number of tables.
70 | # USHORT searchRange // (Maximum power of 2 <= numTables) x 16.
71 | # USHORT entrySelector // Log2(maximum power of 2 <= numTables).
72 | # USHORT rangeShift // NumTables x 16-searchRange.
73 | #
74 | # Table Directory
75 | #
76 | # ULONG tag // 4-byte identifier.
77 | # ULONG checkSum // CheckSum for this table.
78 | # ULONG offset // Offset from beginning of TrueType font file.
79 | # ULONG length // Length of this table.
80 | #
81 | # OS/2 Table (Version 4)
82 | #
83 | # USHORT version // 0x0004
84 | # SHORT xAvgCharWidth
85 | # USHORT usWeightClass
86 | # USHORT usWidthClass
87 | # USHORT fsType
88 | # SHORT ySubscriptXSize
89 | # SHORT ySubscriptYSize
90 | # SHORT ySubscriptXOffset
91 | # SHORT ySubscriptYOffset
92 | # SHORT ySuperscriptXSize
93 | # SHORT ySuperscriptYSize
94 | # SHORT ySuperscriptXOffset
95 | # SHORT ySuperscriptYOffset
96 | # SHORT yStrikeoutSize
97 | # SHORT yStrikeoutPosition
98 | # SHORT sFamilyClass
99 | # BYTE panose[10]
100 | # ULONG ulUnicodeRange1 // Bits 0-31
101 | # ULONG ulUnicodeRange2 // Bits 32-63
102 | # ULONG ulUnicodeRange3 // Bits 64-95
103 | # ULONG ulUnicodeRange4 // Bits 96-127
104 | # CHAR achVendID[4]
105 | # USHORT fsSelection
106 | # USHORT usFirstCharIndex
107 | # USHORT usLastCharIndex
108 | # SHORT sTypoAscender
109 | # SHORT sTypoDescender
110 | # SHORT sTypoLineGap
111 | # USHORT usWinAscent
112 | # USHORT usWinDescent
113 | # ULONG ulCodePageRange1 // Bits 0-31
114 | # ULONG ulCodePageRange2 // Bits 32-63
115 | # SHORT sxHeight
116 | # SHORT sCapHeight
117 | # USHORT usDefaultChar
118 | # USHORT usBreakChar
119 | # USHORT usMaxContext
120 | #
121 | #
122 | # The Naming Table is organized as follows:
123 | #
124 | # [name table header]
125 | # [name records]
126 | # [string data]
127 | #
128 | # Name Table Header
129 | #
130 | # USHORT format // Format selector (=0).
131 | # USHORT count // Number of name records.
132 | # USHORT stringOffset // Offset to start of string storage (from start of table).
133 | #
134 | # Name Record
135 | #
136 | # USHORT platformID // Platform ID.
137 | # USHORT encodingID // Platform-specific encoding ID.
138 | # USHORT languageID // Language ID.
139 | # USHORT nameID // Name ID.
140 | # USHORT length // String length (in bytes).
141 | # USHORT offset // String offset from start of storage area (in bytes).
142 | #
143 | # head Table
144 | #
145 | # Fixed tableVersion // Table version number 0x00010000 for version 1.0.
146 | # Fixed fontRevision // Set by font manufacturer.
147 | # ULONG checkSumAdjustment // To compute: set it to 0, sum the entire font as ULONG, then store 0xB1B0AFBA - sum.
148 | # ULONG magicNumber // Set to 0x5F0F3CF5.
149 | # USHORT flags
150 | # USHORT unitsPerEm // Valid range is from 16 to 16384. This value should be a power of 2 for fonts that have TrueType outlines.
151 | # LONGDATETIME created // Number of seconds since 12:00 midnight, January 1, 1904. 64-bit integer
152 | # LONGDATETIME modified // Number of seconds since 12:00 midnight, January 1, 1904. 64-bit integer
153 | # SHORT xMin // For all glyph bounding boxes.
154 | # SHORT yMin
155 | # SHORT xMax
156 | # SHORT yMax
157 | # USHORT macStyle
158 | # USHORT lowestRecPPEM // Smallest readable size in pixels.
159 | # SHORT fontDirectionHint
160 | # SHORT indexToLocFormat // 0 for short offsets, 1 for long.
161 | # SHORT glyphDataFormat // 0 for current format.
162 | #
163 | #
164 | #
165 | # Embedded OpenType (EOT) file format
166 | # http://www.w3.org/Submission/EOT/
167 | #
168 | # EOT version 0x00020001
169 | #
170 | # An EOT font consists of a header with the original OpenType font
171 | # appended at the end. Most of the data in the EOT header is simply a
172 | # copy of data from specific tables within the font data. The exceptions
173 | # are the 'Flags' field and the root string name field. The root string
174 | # is a set of names indicating domains for which the font data can be
175 | # used. A null root string implies the font data can be used anywhere.
176 | # The EOT header is in little-endian byte order but the font data remains
177 | # in big-endian order as specified by the OpenType spec.
178 | #
179 | # Overall structure:
180 | #
181 | # [EOT header]
182 | # [EOT name records]
183 | # [font data]
184 | #
185 | # EOT header
186 | #
187 | # ULONG eotSize // Total structure length in bytes (including string and font data)
188 | # ULONG fontDataSize // Length of the OpenType font (FontData) in bytes
189 | # ULONG version // Version number of this format - 0x00020001
190 | # ULONG flags // Processing Flags (0 == no special processing)
191 | # BYTE fontPANOSE[10] // OS/2 Table panose
192 | # BYTE charset // DEFAULT_CHARSET (0x01)
193 | # BYTE italic // 0x01 if ITALIC in OS/2 Table fsSelection is set, 0 otherwise
194 | # ULONG weight // OS/2 Table usWeightClass
195 | # USHORT fsType // OS/2 Table fsType (specifies embedding permission flags)
196 | # USHORT magicNumber // Magic number for EOT file - 0x504C.
197 | # ULONG unicodeRange1 // OS/2 Table ulUnicodeRange1
198 | # ULONG unicodeRange2 // OS/2 Table ulUnicodeRange2
199 | # ULONG unicodeRange3 // OS/2 Table ulUnicodeRange3
200 | # ULONG unicodeRange4 // OS/2 Table ulUnicodeRange4
201 | # ULONG codePageRange1 // OS/2 Table ulCodePageRange1
202 | # ULONG codePageRange2 // OS/2 Table ulCodePageRange2
203 | # ULONG checkSumAdjustment // head Table CheckSumAdjustment
204 | # ULONG reserved[4] // Reserved - must be 0
205 | # USHORT padding1 // Padding - must be 0
206 | #
207 | # EOT name records
208 | #
209 | # USHORT FamilyNameSize // Font family name size in bytes
210 | # BYTE FamilyName[FamilyNameSize] // Font family name (name ID = 1), little-endian UTF-16
211 | # USHORT Padding2 // Padding - must be 0
212 | #
213 | # USHORT StyleNameSize // Style name size in bytes
214 | # BYTE StyleName[StyleNameSize] // Style name (name ID = 2), little-endian UTF-16
215 | # USHORT Padding3 // Padding - must be 0
216 | #
217 | # USHORT VersionNameSize // Version name size in bytes
218 | # bytes VersionName[VersionNameSize] // Version name (name ID = 5), little-endian UTF-16
219 | # USHORT Padding4 // Padding - must be 0
220 | #
221 | # USHORT FullNameSize // Full name size in bytes
222 | # BYTE FullName[FullNameSize] // Full name (name ID = 4), little-endian UTF-16
223 | # USHORT Padding5 // Padding - must be 0
224 | #
225 | # USHORT RootStringSize // Root string size in bytes
226 | # BYTE RootString[RootStringSize] // Root string, little-endian UTF-16
227 |
228 |
229 |
230 | import optparse
231 | import struct
232 |
233 | class FontError(Exception):
234 | """Error related to font handling"""
235 | pass
236 |
237 | def multichar(str):
238 | vals = struct.unpack('4B', (str[:4]).encode())
239 | return (vals[0] << 24) + (vals[1] << 16) + (vals[2] << 8) + vals[3]
240 |
241 | def multicharval(v):
242 | return struct.pack('4B', (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF)
243 |
244 | class EOT:
245 | EOT_VERSION = 0x00020001
246 | EOT_MAGIC_NUMBER = 0x504c
247 | EOT_DEFAULT_CHARSET = 0x01
248 | EOT_FAMILY_NAME_INDEX = 0 # order of names in variable portion of EOT header
249 | EOT_STYLE_NAME_INDEX = 1
250 | EOT_VERSION_NAME_INDEX = 2
251 | EOT_FULL_NAME_INDEX = 3
252 | EOT_NUM_NAMES = 4
253 |
254 | EOT_HEADER_PACK = '<4L10B2BL2H7L18x'
255 |
256 | class OpenType:
257 | SFNT_CFF = multichar('OTTO') # Postscript CFF SFNT version
258 | SFNT_TRUE = 0x10000 # Standard TrueType version
259 | SFNT_APPLE = multichar('true') # Apple TrueType version
260 |
261 | SFNT_UNPACK = '>I4H'
262 | TABLE_DIR_UNPACK = '>4I'
263 |
264 | TABLE_HEAD = multichar('head') # TrueType table tags
265 | TABLE_NAME = multichar('name')
266 | TABLE_OS2 = multichar('OS/2')
267 | TABLE_GLYF = multichar('glyf')
268 | TABLE_CFF = multichar('CFF ')
269 |
270 | OS2_FSSELECTION_ITALIC = 0x1
271 | OS2_UNPACK = '>4xH2xH22x10B4L4xH14x2L'
272 |
273 | HEAD_UNPACK = '>8xL'
274 |
275 | NAME_RECORD_UNPACK = '>6H'
276 | NAME_ID_FAMILY = 1
277 | NAME_ID_STYLE = 2
278 | NAME_ID_UNIQUE = 3
279 | NAME_ID_FULL = 4
280 | NAME_ID_VERSION = 5
281 | NAME_ID_POSTSCRIPT = 6
282 | PLATFORM_ID_UNICODE = 0 # Mac OS uses this typically
283 | PLATFORM_ID_MICROSOFT = 3
284 | ENCODING_ID_MICROSOFT_UNICODEBMP = 1 # with Microsoft platformID BMP-only Unicode encoding
285 | LANG_ID_MICROSOFT_EN_US = 0x0409 # with Microsoft platformID EN US lang code
286 |
287 | def eotname(ttf):
288 | i = ttf.rfind('.')
289 | if i != -1:
290 | ttf = ttf[:i]
291 | return ttf + '.eotlite'
292 |
293 | def readfont(f):
294 | data = open(f, 'rb').read()
295 | return data
296 |
297 | def get_table_directory(data):
298 | """read the SFNT header and table directory"""
299 | datalen = len(data)
300 | sfntsize = struct.calcsize(OpenType.SFNT_UNPACK)
301 | if sfntsize > datalen:
302 | raise FontError('truncated font data')
303 | sfntvers, numTables = struct.unpack(OpenType.SFNT_UNPACK, data[:sfntsize])[:2]
304 | if sfntvers != OpenType.SFNT_CFF and sfntvers != OpenType.SFNT_TRUE:
305 | raise FontError('invalid font type')
306 |
307 | font = {}
308 | font['version'] = sfntvers
309 | font['numTables'] = numTables
310 |
311 | # create set of offsets, lengths for tables
312 | table_dir_size = struct.calcsize(OpenType.TABLE_DIR_UNPACK)
313 | if sfntsize + table_dir_size * numTables > datalen:
314 | raise FontError('truncated font data, table directory extends past end of data')
315 | table_dir = {}
316 | for i in range(0, numTables):
317 | start = sfntsize + i * table_dir_size
318 | end = start + table_dir_size
319 | tag, check, bongo, dirlen = struct.unpack(OpenType.TABLE_DIR_UNPACK, data[start:end])
320 | table_dir[tag] = {'offset': bongo, 'length': dirlen, 'checksum': check}
321 |
322 | font['tableDir'] = table_dir
323 |
324 | return font
325 |
326 | def get_name_records(nametable):
327 | """reads through the name records within name table"""
328 | name = {}
329 | # read the header
330 | headersize = 6
331 | count, strOffset = struct.unpack('>2H', nametable[2:6])
332 | namerecsize = struct.calcsize(OpenType.NAME_RECORD_UNPACK)
333 | if count * namerecsize + headersize > len(nametable):
334 | raise FontError('names exceed size of name table')
335 | name['count'] = count
336 | name['strOffset'] = strOffset
337 |
338 | # read through the name records
339 | namerecs = {}
340 | for i in range(0, count):
341 | start = headersize + i * namerecsize
342 | end = start + namerecsize
343 | platformID, encodingID, languageID, nameID, namelen, offset = struct.unpack(OpenType.NAME_RECORD_UNPACK, nametable[start:end])
344 | if platformID != OpenType.PLATFORM_ID_MICROSOFT or \
345 | encodingID != OpenType.ENCODING_ID_MICROSOFT_UNICODEBMP or \
346 | languageID != OpenType.LANG_ID_MICROSOFT_EN_US:
347 | continue
348 | namerecs[nameID] = {'offset': offset, 'length': namelen}
349 |
350 | name['namerecords'] = namerecs
351 | return name
352 |
353 | def make_eot_name_headers(fontdata, nameTableDir):
354 | """extracts names from the name table and generates the names header portion of the EOT header"""
355 | nameoffset = nameTableDir['offset']
356 | namelen = nameTableDir['length']
357 | name = get_name_records(fontdata[nameoffset : nameoffset + namelen])
358 | namestroffset = name['strOffset']
359 | namerecs = name['namerecords']
360 |
361 | eotnames = (OpenType.NAME_ID_FAMILY, OpenType.NAME_ID_STYLE, OpenType.NAME_ID_VERSION, OpenType.NAME_ID_FULL)
362 | nameheaders = []
363 | for nameid in eotnames:
364 | if nameid in namerecs:
365 | namerecord = namerecs[nameid]
366 | noffset = namerecord['offset']
367 | nlen = namerecord['length']
368 | nformat = '%dH' % (nlen / 2) # length is in number of bytes
369 | start = nameoffset + namestroffset + noffset
370 | end = start + nlen
371 | nstr = struct.unpack('>' + nformat, fontdata[start:end])
372 | nameheaders.append(struct.pack(' os2Dir['length']:
411 | raise FontError('OS/2 table invalid length')
412 |
413 | os2fields = struct.unpack(OpenType.OS2_UNPACK, fontdata[os2offset : os2offset + os2size])
414 |
415 | panose = []
416 | urange = []
417 | codepage = []
418 |
419 | weight, fsType = os2fields[:2]
420 | panose[:10] = os2fields[2:12]
421 | urange[:4] = os2fields[12:16]
422 | fsSelection = os2fields[16]
423 | codepage[:2] = os2fields[17:19]
424 |
425 | italic = fsSelection & OpenType.OS2_FSSELECTION_ITALIC
426 |
427 | # read in values from head table
428 | headDir = tableDir[OpenType.TABLE_HEAD]
429 | headoffset = headDir['offset']
430 | headsize = struct.calcsize(OpenType.HEAD_UNPACK)
431 |
432 | if headsize > headDir['length']:
433 | raise FontError('head table invalid length')
434 |
435 | headfields = struct.unpack(OpenType.HEAD_UNPACK, fontdata[headoffset : headoffset + headsize])
436 | checkSumAdjustment = headfields[0]
437 |
438 | # make name headers
439 | nameheaders = make_eot_name_headers(fontdata, tableDir[OpenType.TABLE_NAME])
440 | rootstring = make_root_string()
441 |
442 | # calculate the total eot size
443 | eotSize = struct.calcsize(EOT.EOT_HEADER_PACK) + len(nameheaders) + len(rootstring) + fontDataSize
444 | fixed = struct.pack(EOT.EOT_HEADER_PACK,
445 | *([eotSize, fontDataSize, version, flags] + panose + [charset, italic] +
446 | [weight, fsType, magicNumber] + urange + codepage + [checkSumAdjustment]))
447 |
448 | return ''.join((fixed, nameheaders, rootstring))
449 |
450 |
451 | def write_eot_font(eot, header, data):
452 | open(eot,'wb').write(''.join((header, data)))
453 | return
454 |
455 | def main():
456 |
457 | # deal with options
458 | p = optparse.OptionParser()
459 | p.add_option('--output', '-o')
460 | options, args = p.parse_args()
461 |
462 | # iterate over font files
463 | for f in args:
464 | data = readfont(f)
465 | if len(data) == 0:
466 | print('Error reading %s' % f)
467 | else:
468 | eot = options.output or eotname(f)
469 | header = make_eot_header(data)
470 | write_eot_font(eot, header, data)
471 |
472 |
473 | if __name__ == '__main__':
474 | main()
475 |
--------------------------------------------------------------------------------
/tasks/engines/fontforge.js:
--------------------------------------------------------------------------------
1 | /**
2 | * grunt-webfont: fontforge engine
3 | *
4 | * @requires fontforge, ttfautohint 1.00+ (optional), eotlitetool.py
5 | * @author Artem Sapegin (http://sapegin.me)
6 | */
7 |
8 | module.exports = function(o, allDone) {
9 | 'use strict';
10 |
11 | var fs = require('fs');
12 | var path = require('path');
13 | var temp = require('temp');
14 | var async = require('async');
15 | var exec = require('child_process').exec;
16 | var chalk = require('chalk');
17 | var _ = require('lodash');
18 | var logger = o.logger || require('winston');
19 | var wf = require('../util/util');
20 |
21 | // Copy source files to temporary directory
22 | var tempDir = temp.mkdirSync();
23 | o.files.forEach(function(file) {
24 | fs.writeFileSync(path.join(tempDir, o.rename(file)), fs.readFileSync(file));
25 | });
26 |
27 | // Run Fontforge
28 | var args = [
29 | 'fontforge',
30 | '-script',
31 | '"' + path.join(__dirname, 'fontforge/generate.py') + '"'
32 | ].join(' ');
33 |
34 | var proc = exec(args, {maxBuffer: o.execMaxBuffer}, function(err, out, code) {
35 | if (err instanceof Error && err.code === 127) {
36 | return fontforgeNotFound();
37 | }
38 | else if (err) {
39 | if (err instanceof Error) {
40 | return error(err.message);
41 | }
42 |
43 | // Skip some fontforge output such as copyrights. Show warnings only when no font files was created
44 | // or in verbose mode.
45 | var success = !!wf.generatedFontFiles(o);
46 | var notError = /(Copyright|License |with many parts BSD |Executable based on sources from|Library based on sources from|Based on source from git)/;
47 | var version = /(Executable based on sources from|Library based on sources from)/;
48 | var lines = err.split('\n');
49 |
50 | var warn = [];
51 | lines.forEach(function(line) {
52 | if (!line.match(notError) && !success) {
53 | warn.push(line);
54 | }
55 | else {
56 | logger.verbose(chalk.grey('fontforge: ') + line);
57 | }
58 | });
59 |
60 | if (warn.length) {
61 | return error(warn.join('\n'));
62 | }
63 | }
64 |
65 | // Trim fontforge result
66 | var json = out.replace(/^[^{]+/, '').replace(/[^}]+$/, '');
67 |
68 | // Parse json
69 | var result;
70 | try {
71 | result = JSON.parse(json);
72 | }
73 | catch (e) {
74 | logger.verbose('Webfont did not receive a proper JSON result from Python script: ' + e);
75 | return error(
76 | 'Something went wrong when running fontforge. Probably fontforge wasn’t installed correctly or one of your SVGs is too complicated for fontforge.\n\n' +
77 | '1. Try to run Grunt in verbose mode: ' + chalk.bold('grunt --verbose webfont') + ' and see what fontforge says. Then search GitHub issues for the solution: ' + chalk.underline('https://github.com/sapegin/grunt-webfont/issues') + '.\n\n' +
78 | '2. Try to use “node” engine instead of “fontforge”: ' + chalk.underline('https://github.com/sapegin/grunt-webfont#engine') + '\n\n' +
79 | '3. To find “bad” icon try to remove SVGs one by one until error disappears. Then try to simplify this SVG in Sketch, Illustrator, etc.\n\n'
80 | );
81 | }
82 |
83 | allDone({
84 | fontName: path.basename(result.file)
85 | });
86 | });
87 |
88 | // Send JSON with params
89 | if (!proc) return;
90 | proc.stdin.on('error', function(err) {
91 | if (err.code === 'EPIPE') {
92 | fontforgeNotFound();
93 | }
94 | });
95 |
96 | proc.stderr.on('data', function (data) {
97 | logger.verbose(data);
98 | });
99 | proc.stdout.on('data', function (data) {
100 | logger.verbose(data);
101 | });
102 | proc.on('exit', function (code, signal) {
103 | if (code !== 0) {
104 | logger.log( // cannot use error() because it will stop execution of callback of exec (which shows error message)
105 | "fontforge process has unexpectedly closed.\n" +
106 | "1. Try to run grunt in verbose mode to see fontforge output: " + chalk.bold('grunt --verbose webfont') + ".\n" +
107 | "2. If stderr maxBuffer exceeded try to increase " + chalk.bold('execMaxBuffer') + ", see " +
108 | chalk.underline('https://github.com/sapegin/grunt-webfont#execMaxBuffer') + ". "
109 | );
110 | }
111 | return true;
112 | });
113 |
114 | var params = _.extend(o, {
115 | inputDir: tempDir
116 | });
117 | proc.stdin.write(JSON.stringify(params));
118 | proc.stdin.end();
119 |
120 | function error() {
121 | logger.error.apply(null, arguments);
122 | allDone(false);
123 | return false;
124 | }
125 |
126 | function fontforgeNotFound() {
127 | error('fontforge not found. Please install fontforge and all other requirements: ' + chalk.underline('https://github.com/sapegin/grunt-webfont#installation'));
128 | }
129 |
130 | };
131 |
--------------------------------------------------------------------------------
/tasks/engines/fontforge/generate.py:
--------------------------------------------------------------------------------
1 | # Based on https://github.com/FontCustom/fontcustom/blob/master/lib/fontcustom/scripts/generate.py
2 |
3 | import fontforge
4 | import os
5 | import sys
6 | import json
7 | import re
8 | from subprocess import call
9 | from distutils.spawn import find_executable
10 |
11 | args = json.load(sys.stdin)
12 |
13 | f = fontforge.font()
14 | f.encoding = 'UnicodeFull'
15 | f.copyright = ''
16 | f.design_size = 16
17 | f.em = args['fontHeight']
18 | f.descent = args['descent']
19 | f.ascent = args['fontHeight'] - args['descent']
20 | if args['version']:
21 | f.version = args['version']
22 | if args['normalize']:
23 | f.autoWidth(0, 0, args['fontHeight'])
24 |
25 | KERNING = 15
26 |
27 |
28 | def create_empty_char(f, c):
29 | pen = f.createChar(ord(c), c).glyphPen()
30 | pen.moveTo((0, 0))
31 | pen = None
32 |
33 |
34 | if args['addLigatures']:
35 | f.addLookup('liga', 'gsub_ligature', (), (('liga', (('latn', ('dflt')), )), ))
36 | f.addLookupSubtable('liga', 'liga')
37 |
38 | for dirname, dirnames, filenames in os.walk(args['inputDir']):
39 | for filename in sorted(filenames):
40 | name, ext = os.path.splitext(filename)
41 | filePath = os.path.join(dirname, filename)
42 | size = os.path.getsize(filePath)
43 |
44 | if ext in ['.svg']:
45 | # HACK: Remove tags
46 | svgfile = open(filePath, 'r+')
47 | svgtext = svgfile.read()
48 | svgfile.seek(0)
49 |
50 | # Replace the tags with nothing
51 | svgtext = svgtext.replace('', '')
52 | svgtext = svgtext.replace(' ', '')
53 |
54 | if args['normalize']:
55 | # Replace the width and the height
56 | svgtext = re.sub(r'(]*)width="[^"]*"([^>]*>)', r'\1\2', svgtext)
57 | svgtext = re.sub(r'(]*)height="[^"]*"([^>]*>)', r'\1\2', svgtext)
58 |
59 | # Remove all contents of file so that we can write out the new contents
60 | svgfile.truncate()
61 | svgfile.write(svgtext)
62 | svgfile.close()
63 |
64 | cp = args['codepoints'][name]
65 |
66 | if args['addLigatures']:
67 | name = str(name) # Convert Unicode to a regular string because addPosSub doesn't work with Unicode
68 | for char in name:
69 | create_empty_char(f, char)
70 | glyph = f.createChar(cp, name)
71 | glyph.addPosSub('liga', tuple(name))
72 | else:
73 | glyph = f.createChar(cp, str(name))
74 | glyph.importOutlines(filePath)
75 |
76 | if args['normalize']:
77 | glyph.left_side_bearing = glyph.right_side_bearing = 0
78 | else:
79 | glyph.width = args['fontHeight']
80 |
81 | if args['round']:
82 | glyph.round(int(args['round']))
83 |
84 | fontfile = args['dest'] + os.path.sep + args['fontFilename']
85 |
86 | f.fontname = args['fontFilename']
87 | f.familyname = args['fontFamilyName']
88 | f.fullname = args['fontFamilyName']
89 |
90 | if args['addLigatures']:
91 | def generate(filename):
92 | f.generate(filename, flags=('opentype'))
93 | else:
94 | def generate(filename):
95 | f.generate(filename)
96 |
97 |
98 | # TTF
99 | generate(fontfile + '.ttf')
100 |
101 | # Hint the TTF file
102 | # ttfautohint is optional
103 | if (find_executable('ttfautohint') and args['autoHint']):
104 | call('ttfautohint --symbol --fallback-script=latn --no-info "%(font)s.ttf" "%(font)s-hinted.ttf" && mv "%(font)s-hinted.ttf" "%(font)s.ttf"' % {'font': fontfile}, shell=True)
105 | f = fontforge.open(fontfile + '.ttf')
106 |
107 | # SVG
108 | if 'svg' in args['types']:
109 | generate(fontfile + '.svg')
110 |
111 | # Fix SVG header for webkit (from: https://github.com/fontello/font-builder/blob/master/bin/fontconvert.py)
112 | svgfile = open(fontfile + '.svg', 'r+')
113 | svgtext = svgfile.read()
114 | svgfile.seek(0)
115 | svgfile.write(svgtext.replace('', ''))
116 | svgfile.close()
117 |
118 | scriptPath = os.path.dirname(os.path.realpath(__file__))
119 |
120 | # WOFF
121 | if 'woff' in args['types']:
122 | generate(fontfile + '.woff')
123 |
124 | # EOT
125 | if 'eot' in args['types']:
126 | # eotlitetool.py script to generate IE7-compatible .eot fonts
127 | call('python "%(path)s/../../bin/eotlitetool.py" "%(font)s.ttf" --output "%(font)s.eot"' % {'path': scriptPath, 'font': fontfile}, shell=True)
128 |
129 | # Delete TTF if not needed
130 | if (not 'ttf' in args['types']) and (not 'woff2' in args['types']):
131 | os.remove(fontfile + '.ttf')
132 |
133 | print(json.dumps({'file': fontfile}))
134 |
--------------------------------------------------------------------------------
/tasks/engines/node.js:
--------------------------------------------------------------------------------
1 | /**
2 | * grunt-webfont: Node.js engine
3 | *
4 | * @requires ttfautohint 1.00+ (optional)
5 | * @author Artem Sapegin (http://sapegin.me)
6 | */
7 |
8 | module.exports = function(o, allDone) {
9 | 'use strict';
10 |
11 | var fs = require('fs');
12 | var path = require('path');
13 | var async = require('async');
14 | var temp = require('temp');
15 | var exec = require('child_process').exec;
16 | var _ = require('lodash');
17 | var StringDecoder = require('string_decoder').StringDecoder;
18 | var svgicons2svgfont = require('svgicons2svgfont');
19 | var svg2ttf = require('svg2ttf');
20 | var ttf2woff = require('ttf2woff');
21 | var ttf2eot = require('ttf2eot');
22 | var SVGO = require('svgo');
23 | var MemoryStream = require('memorystream');
24 | var logger = o.logger || require('winston');
25 | var wf = require('../util/util');
26 |
27 | // @todo Ligatures
28 |
29 | var fonts = {};
30 |
31 | var generators = {
32 | svg: function(done) {
33 | var font = '';
34 | var decoder = new StringDecoder('utf8');
35 | svgFilesToStreams(o.files, function(streams) {
36 | var stream = svgicons2svgfont(streams, {
37 | fontName: o.fontFamilyName,
38 | fontHeight: o.fontHeight,
39 | descent: o.descent,
40 | normalize: o.normalize,
41 | round: o.round,
42 | log: logger.verbose.bind(logger),
43 | error: logger.error.bind(logger)
44 | });
45 | stream.on('data', function(chunk) {
46 | font += decoder.write(chunk);
47 | });
48 | stream.on('end', function() {
49 | fonts.svg = font;
50 | done(font);
51 | });
52 | });
53 | },
54 |
55 | ttf: function(done) {
56 | getFont('svg', function(svgFont) {
57 | var font = svg2ttf(svgFont, {});
58 | font = new Buffer(font.buffer);
59 | autohintTtfFont(font, function(hintedFont) {
60 | // ttfautohint is optional
61 | if (hintedFont) {
62 | font = hintedFont;
63 | }
64 | fonts.ttf = font;
65 | done(font);
66 | });
67 | });
68 | },
69 |
70 | woff: function(done) {
71 | getFont('ttf', function(ttfFont) {
72 | var font = ttf2woff(new Uint8Array(ttfFont), {});
73 | font = new Buffer(font.buffer);
74 | fonts.woff = font;
75 | done(font);
76 | });
77 | },
78 |
79 | woff2: function(done) {
80 | // Will be converted from TTF later
81 | done();
82 | },
83 |
84 | eot: function(done) {
85 | getFont('ttf', function(ttfFont) {
86 | var font = ttf2eot(new Uint8Array(ttfFont));
87 | font = new Buffer(font.buffer);
88 | fonts.eot = font;
89 | done(font);
90 | });
91 | }
92 | };
93 |
94 | var steps = [];
95 |
96 | // Font types
97 | var typesToGenerate = o.types.slice();
98 | if (o.types.indexOf('woff2') !== -1 && o.types.indexOf('ttf' === -1)) typesToGenerate.push('ttf');
99 | typesToGenerate.forEach(function(type) {
100 | steps.push(createFontWriter(type));
101 | });
102 |
103 | // Run!
104 | async.waterfall(steps, allDone);
105 |
106 | function getFont(type, done) {
107 | if (fonts[type]) {
108 | done(fonts[type]);
109 | }
110 | else {
111 | generators[type](done);
112 | }
113 | }
114 |
115 | function createFontWriter(type) {
116 | return function(done) {
117 | getFont(type, function(font) {
118 | fs.writeFileSync(wf.getFontPath(o, type), font);
119 | done();
120 | });
121 | };
122 | }
123 |
124 | function svgFilesToStreams(files, done) {
125 |
126 | async.map(files, function(file, fileDone) {
127 |
128 | function fileStreamed(name, stream) {
129 | fileDone(null, {
130 | codepoint: o.codepoints[name],
131 | name: name,
132 | stream: stream
133 | });
134 | }
135 |
136 | function streamSVG(name, file) {
137 | var stream = fs.createReadStream(file);
138 | fileStreamed(name, stream);
139 | }
140 |
141 | function streamSVGO(name, file) {
142 | var svg = fs.readFileSync(file, 'utf8');
143 | var svgo = new SVGO();
144 | try {
145 | svgo.optimize(svg, function(res) {
146 | var stream = new MemoryStream(res.data, {
147 | writable: false
148 | });
149 | fileStreamed(name, stream);
150 | });
151 | } catch(err) {
152 | logger.error('Can’t simplify SVG file with SVGO.\n\n' + err);
153 | fileDone(err);
154 | }
155 | }
156 |
157 | var idx = files.indexOf(file);
158 | var name = o.glyphs[idx];
159 |
160 | if(o.optimize === true) {
161 | streamSVGO(name, file);
162 | } else {
163 | streamSVG(name, file);
164 | }
165 | }, function(err, streams) {
166 | if (err) {
167 | logger.error('Can’t stream SVG file.\n\n' + err);
168 | allDone(false);
169 | }
170 | else {
171 | done(streams);
172 | }
173 | });
174 | }
175 |
176 | function autohintTtfFont(font, done) {
177 | var tempDir = temp.mkdirSync();
178 | var originalFilepath = path.join(tempDir, 'font.ttf');
179 | var hintedFilepath = path.join(tempDir, 'hinted.ttf');
180 |
181 | if (!o.autoHint){
182 | done(false);
183 | return;
184 | }
185 | // Save original font to temporary directory
186 | fs.writeFileSync(originalFilepath, font);
187 |
188 | // Run ttfautohint
189 | var args = [
190 | 'ttfautohint',
191 | '--symbol',
192 | '--fallback-script=latn',
193 | '--windows-compatibility',
194 | '--no-info',
195 | originalFilepath,
196 | hintedFilepath
197 | ].join(' ');
198 |
199 | exec(args, {maxBuffer: o.execMaxBuffer}, function(err, out, code) {
200 | if (err) {
201 | if (err.code === 127) {
202 | logger.verbose('Hinting skipped, ttfautohint not found.');
203 | done(false);
204 | return;
205 | }
206 | logger.error('Can’t run ttfautohint.\n\n' + err.message);
207 | done(false);
208 | return;
209 | }
210 |
211 | // Read hinted font back
212 | var hintedFont = fs.readFileSync(hintedFilepath);
213 | done(hintedFont);
214 | });
215 | }
216 |
217 | };
218 |
--------------------------------------------------------------------------------
/tasks/templates/bem.css:
--------------------------------------------------------------------------------
1 | /* Generated by grunt-webfont */
2 |
3 | <% if (fontfaceStyles) { %>
4 | <% if (fontPathVariables && stylesheet !== 'css') { %>
5 | <%= fontPathVariable %>
6 | <% } %>
7 | <% if (fontSrc1 && embed.length) { %>
8 | @font-face {
9 | font-family:"<%= fontFamilyName %>";
10 | src:<%= fontSrc1 %>;
11 | font-weight:normal;
12 | font-style:normal;
13 | }
14 | <% } %>@font-face {
15 | font-family:"<%= fontFamilyName %>";<% if (fontSrc1) { %>
16 | src:<%= fontSrc1 %>;<% }%>
17 | src:<%= fontSrc2 %>;
18 | font-weight:normal;
19 | font-style:normal;
20 | }
21 | <% } %>
22 | <% if (baseStyles) { %>.<%= baseClass %><% if (addLigatures) { %>,
23 | .ligature-icons<% } %> {
24 | <% if (stylesheet === 'less') { %>&:before {<% } %>
25 | font-family:"<%= fontFamilyName %>";
26 | <% if (stylesheet === 'less') { %>}<% } %>
27 | display:inline-block;
28 | line-height:1;
29 | font-weight:normal;
30 | font-style:normal;
31 | speak:none;
32 | text-decoration:inherit;
33 | text-transform:none;
34 | text-rendering:auto;
35 | -webkit-font-smoothing:antialiased;
36 | -moz-osx-font-smoothing:grayscale;
37 | }
38 | <% } %>
39 |
40 | <% if (iconsStyles) { %>/* Icons */<% for (var glyphIdx = 0; glyphIdx < glyphs.length; glyphIdx++) { %>
41 | <% if (stylesheet === 'less') { %>
42 | .<%= classPrefix %><%= glyphs[glyphIdx] %> {
43 | &:before {
44 | content:"<% if (addLigatures) { %><%= glyphs[glyphIdx] %><% } else { %>\<%= codepoints[glyphIdx] %><% } %>";
45 | }<% if (ie7) {%>
46 | *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '<%= codepoints[glyphIdx] %>;');
47 | <% } %>
48 | }
49 | <% } else { %>
50 | <% if (ie7) {%>.<%= classPrefix %><%= glyphs[glyphIdx] %> {
51 | *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '<%= codepoints[glyphIdx] %>;');
52 | }
53 | <% } %>
54 | .<%= classPrefix %><%= glyphs[glyphIdx] %>:before {
55 | content:"<% if (addLigatures) { %><%= glyphs[glyphIdx] %><% } else { %>\<%= codepoints[glyphIdx] %><% } %>";
56 | }<% } } } %>
57 |
--------------------------------------------------------------------------------
/tasks/templates/bem.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseClass": "icon",
3 | "classPrefix": "icon_"
4 | }
5 |
--------------------------------------------------------------------------------
/tasks/templates/bootstrap.css:
--------------------------------------------------------------------------------
1 | /* Generated by grunt-webfont */
2 | /* Based on https://github.com/endtwist/fontcustom/blob/master/lib/fontcustom/templates/fontcustom.css */
3 |
4 | <% if (fontfaceStyles) { %>
5 | <% if (fontPathVariables && stylesheet !== 'css') { %>
6 | <%= fontPathVariable %>
7 | <% } %>
8 | <% if (fontSrc1 && embed.length) { %>
9 | @font-face {
10 | font-family:"<%= fontFamilyName %>";
11 | src:<%= fontSrc1 %>;
12 | font-weight:normal;
13 | font-style:normal;
14 | }
15 | <% } %>@font-face {
16 | font-family:"<%= fontFamilyName %>";<% if (fontSrc1) { %>
17 | src:<%= fontSrc1 %>;<% }%>
18 | src:<%= fontSrc2 %>;
19 | font-weight:normal;
20 | font-style:normal;
21 | }
22 | <% } %>
23 | <% if (baseStyles) { %>
24 | /* Bootstrap Overrides */
25 | [class^="<%= classPrefix %>"]:before,
26 | [class*=" <%= classPrefix %>"]:before<% if (ie7) {%>,
27 | [class^="<%= classPrefix %>"],
28 | [class*=" <%= classPrefix %>"]<% } %><% if (addLigatures) { %>,
29 | .ligature-icons<% } %> {
30 | font-family:"<%= fontFamilyName %>";
31 | display:inline-block;
32 | line-height:1;
33 | font-weight:normal;
34 | font-style:normal;
35 | speak:none;
36 | text-decoration:inherit;
37 | text-transform:none;
38 | text-rendering:auto;
39 | -webkit-font-smoothing:antialiased;
40 | -moz-osx-font-smoothing:grayscale;
41 | }<% } %>
42 | <% if (iconsStyles && stylesheet === 'less') { %>
43 | /* Mixins */
44 | <% for (var glyphIdx = 0; glyphIdx < glyphs.length; glyphIdx++) { %>
45 | .<%= classPrefix %><%= glyphs[glyphIdx] %><% if(glyphIdx === glyphs.length-1) { %> { <% } else { %>, <% } } %>
46 | &:before {
47 | font-family:"<%= fontFamilyName %>";
48 | display:inline-block;
49 | font-weight:normal;
50 | font-style:normal;
51 | text-decoration:inherit;
52 | }
53 | }<% } %>
54 | <% if (extraStyles) { %>
55 | a [class^="<%= classPrefix %>"],
56 | a [class*=" <%= classPrefix %>"] {
57 | display:inline-block;
58 | text-decoration:inherit;
59 | }
60 | /* Makes the font 33% larger relative to the icon container */
61 | .<%= classPrefix %>large:before {
62 | vertical-align:top;
63 | font-size:1.333em;
64 | }
65 | /* Keeps button heights with and without icons the same */
66 | .btn [class^="<%= classPrefix %>"],
67 | .btn [class*=" <%= classPrefix %>"] {
68 | line-height:0.9em;
69 | }
70 | li [class^="<%= classPrefix %>"],
71 | li [class*=" <%= classPrefix %>"] {
72 | display:inline-block;
73 | width:1.25em;
74 | text-align:center;
75 | }
76 | /* 1.5 increased font size for <%= classPrefix %>large * 1.25 width */
77 | li .<%= classPrefix %>large[class^="<%= classPrefix %>"],
78 | li .<%= classPrefix %>large[class*=" <%= classPrefix %>"] {
79 | width:1.875em;
80 | }
81 | li[class^="<%= classPrefix %>"],
82 | li[class*=" <%= classPrefix %>"] {
83 | margin-left:0;
84 | list-style-type:none;
85 | }
86 | li[class^="<%= classPrefix %>"]:before,
87 | li[class*=" <%= classPrefix %>"]:before {
88 | text-indent:-2em;
89 | text-align:center;
90 | }
91 | li[class^="<%= classPrefix %>"].<%= classPrefix %>large:before,
92 | li[class*=" <%= classPrefix %>"].<%= classPrefix %>large:before {
93 | text-indent:-1.333em;
94 | }
95 | <% } %>
96 |
97 | <% if (iconsStyles) { %>/* Icons */<% for (var glyphIdx = 0; glyphIdx < glyphs.length; glyphIdx++) { %>
98 | <% if (stylesheet === 'less') { %>
99 | .<%= classPrefix %><%= glyphs[glyphIdx] %> {
100 | &:before {
101 | content:"<% if (addLigatures) { %><%= glyphs[glyphIdx] %><% } else { %>\<%= codepoints[glyphIdx] %><% } %>";
102 | }
103 | <% if (ie7) {%>
104 | *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '<%= codepoints[glyphIdx] %>;');
105 | <% } %>
106 | }<% } else { %>
107 | <% if (ie7) {%>.<%= classPrefix %><%= glyphs[glyphIdx] %> {
108 | *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '<%= codepoints[glyphIdx] %>;');
109 | }
110 | <% } %>
111 | .<%= classPrefix %><%= glyphs[glyphIdx] %>:before {
112 | content:"<% if (addLigatures) { %><%= glyphs[glyphIdx] %><% } else { %>\<%= codepoints[glyphIdx] %><% } %>";
113 | }<% } %>
114 | <% } } %>
115 |
--------------------------------------------------------------------------------
/tasks/templates/bootstrap.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseClass": "",
3 | "classPrefix": "icon-"
4 | }
5 |
--------------------------------------------------------------------------------
/tasks/templates/demo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | <%= fontFamilyName %>
6 |
60 |
61 |
62 | <%= fontFamilyName %><% if (version) { %>version <%= version %> <% } %>
63 |
64 |
65 | <% for (var glyphIdx = 0; glyphIdx < glyphs.length; glyphIdx++) { var glyph = glyphs[glyphIdx] %>
66 |
<%= classPrefix %><%= glyph %>
67 | <% } %>
68 |
69 |
70 | <% if (addLigatures) { %>
71 |
72 | <% for (var glyphIdx = 0; glyphIdx < glyphs.length; glyphIdx++) { var glyph = glyphs[glyphIdx]; %>
73 | <%= glyph %>
74 | <% } %>
75 |
76 | <% } %>
77 |
78 | Usage
79 | <i class="<%= baseClass ? baseClass + ' ' : '' %><%= classPrefix %>name "></i>
80 | <% if (addLigatures) { %>
81 | <i class="ligature-icons">name </i>
82 | <% } %>
83 |
84 |
85 |
86 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/tasks/util/util.js:
--------------------------------------------------------------------------------
1 | /**
2 | * grunt-webfont: common stuff
3 | *
4 | * @author Artem Sapegin (http://sapegin.me)
5 | */
6 |
7 | var path = require('path');
8 | var glob = require('glob');
9 |
10 | var exports = {};
11 |
12 | /**
13 | * Unicode Private Use Area start.
14 | * http://en.wikipedia.org/wiki/Private_Use_(Unicode)
15 | * @type {Number}
16 | */
17 | exports.UNICODE_PUA_START = 0xF101;
18 |
19 | /**
20 | * @font-face’s src values generation rules.
21 | * @type {Object}
22 | */
23 | exports.fontsSrcsMap = {
24 | eot: [
25 | {
26 | ext: '.eot'
27 | },
28 | {
29 | ext: '.eot?#iefix',
30 | format: 'embedded-opentype'
31 | }
32 | ],
33 | woff: [
34 | false,
35 | {
36 | ext: '.woff',
37 | format: 'woff',
38 | embeddable: true
39 | },
40 | ],
41 | woff2: [
42 | false,
43 | {
44 | ext: '.woff2',
45 | format: 'woff2',
46 | embeddable: true
47 | },
48 | ],
49 | ttf: [
50 | false,
51 | {
52 | ext: '.ttf',
53 | format: 'truetype',
54 | embeddable: true
55 | },
56 | ],
57 | svg: [
58 | false,
59 | {
60 | ext: '.svg#{fontBaseName}',
61 | format: 'svg'
62 | },
63 | ]
64 | };
65 |
66 | /**
67 | * CSS fileaname prefixes: _icons.scss.
68 | * @type {Object}
69 | */
70 | exports.cssFilePrefixes = {
71 | _default: '',
72 | sass: '_',
73 | scss: '_'
74 | };
75 |
76 | /**
77 | * @font-face’s src parts seperators.
78 | * @type {Object}
79 | */
80 | exports.fontSrcSeparators = {
81 | _default: ',\n\t\t',
82 | styl: ', '
83 | };
84 |
85 | /**
86 | * List of available font formats.
87 | * @type {String}
88 | */
89 | exports.fontFormats = 'eot,woff2,woff,ttf,svg';
90 |
91 | /**
92 | * Returns list of all generated font files.
93 | *
94 | * @param {Object} o Options.
95 | * @return {Array}
96 | */
97 | exports.generatedFontFiles = function(o) {
98 | var mask = '*.{' + o.types + '}';
99 | return glob.sync(path.join(o.dest, o.fontFilename + mask));
100 | };
101 |
102 | /**
103 | * Returns path to font of specified format.
104 | *
105 | * @param {Object} o Options.
106 | * @param {String} type Font type (see `wf.fontFormats`).
107 | * @return {String}
108 | */
109 | exports.getFontPath = function(o, type) {
110 | return path.join(o.dest, o.fontFilename + '.' + type);
111 | };
112 |
113 | // Expose
114 | module.exports = exports;
115 |
--------------------------------------------------------------------------------
/tasks/webfont.js:
--------------------------------------------------------------------------------
1 | /**
2 | * SVG to webfont converter for Grunt
3 | *
4 | * @requires ttfautohint
5 | * @author Artem Sapegin (http://sapegin.me)
6 | */
7 |
8 | module.exports = function(grunt) {
9 | 'use strict';
10 |
11 | var fs = require('fs');
12 | var path = require('path');
13 | var async = require('async');
14 | var glob = require('glob');
15 | var chalk = require('chalk');
16 | var mkdirp = require('mkdirp');
17 | var crypto = require('crypto');
18 | var ttf2woff2 = require('ttf2woff2');
19 | var _ = require('lodash');
20 | var _s = require('underscore.string');
21 | var wf = require('./util/util');
22 |
23 | grunt.registerMultiTask('webfont', 'Compile separate SVG files to webfont', function() {
24 |
25 | /**
26 | * Winston to Grunt logger adapter.
27 | */
28 | var logger = {
29 | warn: function() {
30 | grunt.log.warn.apply(null, arguments);
31 | },
32 | error: function() {
33 | grunt.warn.apply(null, arguments);
34 | },
35 | log: function() {
36 | grunt.log.writeln.apply(null, arguments);
37 | },
38 | verbose: function() {
39 | grunt.verbose.writeln.apply(null, arguments);
40 | }
41 | };
42 |
43 | var allDone = this.async();
44 | var params = this.data;
45 | var options = this.options();
46 | var md5 = crypto.createHash('md5');
47 |
48 | /*
49 | * Check for `src` param on target config
50 | */
51 | this.requiresConfig([this.name, this.target, 'src'].join('.'));
52 |
53 | /*
54 | * Check for `dest` param on either target config or global options object
55 | */
56 | if (_.isUndefined(params.dest) && _.isUndefined(options.dest)) {
57 | logger.warn('Required property ' + [this.name, this.target, 'dest'].join('.')
58 | + ' or ' + [this.name, this.target, 'options.dest'].join('.') + ' missing.');
59 | }
60 |
61 | if (options.skip) {
62 | completeTask();
63 | return;
64 | }
65 |
66 | // Source files
67 | var files = _.filter(this.filesSrc, isSvgFile);
68 | if (!files.length) {
69 | logger.warn('Specified empty list of source SVG files.');
70 | completeTask();
71 | return;
72 | }
73 |
74 | // path must be a string, see https://nodejs.org/api/path.html#path_path_extname_path
75 | if (typeof options.template !== 'string') {
76 | options.template = '';
77 | }
78 |
79 | // Options
80 | var o = {
81 | logger: logger,
82 | fontBaseName: options.font || 'icons',
83 | destCss: options.destCss || params.destCss || params.dest,
84 | destScss: options.destScss || params.destScss || params.destCss || params.dest,
85 | destSass: options.destSass || params.destSass || params.destCss || params.dest,
86 | destLess: options.destLess || params.destLess || params.destCss || params.dest,
87 | destStyl: options.destStyl || params.destStyl || params.destCss || params.dest,
88 | dest: options.dest || params.dest,
89 | relativeFontPath: options.relativeFontPath,
90 | fontPathVariables: options.fontPathVariables || false,
91 | addHashes: options.hashes !== false,
92 | addLigatures: options.ligatures === true,
93 | template: options.template,
94 | syntax: options.syntax || 'bem',
95 | templateOptions: options.templateOptions || {},
96 | stylesheets: options.stylesheets || [options.stylesheet || path.extname(options.template).replace(/^\./, '') || 'css'],
97 | htmlDemo: options.htmlDemo !== false,
98 | htmlDemoTemplate: options.htmlDemoTemplate,
99 | htmlDemoFilename: options.htmlDemoFilename,
100 | styles: optionToArray(options.styles, 'font,icon'),
101 | types: optionToArray(options.types, 'eot,woff,ttf'),
102 | order: optionToArray(options.order, wf.fontFormats),
103 | embed: options.embed === true ? ['woff'] : optionToArray(options.embed, false),
104 | rename: options.rename || path.basename,
105 | engine: options.engine || 'fontforge',
106 | autoHint: options.autoHint !== false,
107 | codepoints: options.codepoints,
108 | codepointsFile: options.codepointsFile,
109 | startCodepoint: options.startCodepoint || wf.UNICODE_PUA_START,
110 | ie7: options.ie7 === true,
111 | normalize: options.normalize === true,
112 | optimize: options.optimize === false ? false : true,
113 | round: options.round !== undefined ? options.round : 10e12,
114 | fontHeight: options.fontHeight !== undefined ? options.fontHeight : 512,
115 | descent: options.descent !== undefined ? options.descent : 64,
116 | version: options.version !== undefined ? options.version : false,
117 | cache: options.cache || path.join(__dirname, '..', '.cache'),
118 | callback: options.callback,
119 | customOutputs: options.customOutputs,
120 | execMaxBuffer: options.execMaxBuffer || 1024 * 200
121 | };
122 |
123 | o = _.extend(o, {
124 | fontName: o.fontBaseName,
125 | destCssPaths: {
126 | css: o.destCss,
127 | scss: o.destScss,
128 | sass: o.destSass,
129 | less: o.destLess,
130 | styl: o.destStyl
131 | },
132 | relativeFontPath: o.relativeFontPath || path.relative(o.destCss, o.dest),
133 | destHtml: options.destHtml || o.destCss,
134 | fontfaceStyles: has(o.styles, 'font'),
135 | baseStyles: has(o.styles, 'icon'),
136 | extraStyles: has(o.styles, 'extra'),
137 | files: files,
138 | glyphs: []
139 | });
140 |
141 | o.hash = getHash();
142 | o.fontFilename = template(options.fontFilename || o.fontBaseName, o);
143 | o.fontFamilyName = template(options.fontFamilyName || o.fontBaseName, o);
144 |
145 | // “Rename” files
146 | o.glyphs = o.files.map(function(file) {
147 | return o.rename(file).replace(path.extname(file), '');
148 | });
149 |
150 | // Check or generate codepoints
151 | // @todo Codepoint can be a Unicode code or character.
152 | var currentCodepoint = o.startCodepoint;
153 | if (!o.codepoints) o.codepoints = {};
154 | if (o.codepointsFile) o.codepoints = readCodepointsFromFile();
155 | o.glyphs.forEach(function(name) {
156 | if (!o.codepoints[name]) {
157 | o.codepoints[name] = getNextCodepoint();
158 | }
159 | });
160 | if (o.codepointsFile) saveCodepointsToFile();
161 |
162 | // Check if we need to generate font
163 | var previousHash = readHash(this.name, this.target);
164 | logger.verbose('New hash:', o.hash, '- previous hash:', previousHash);
165 | if (o.hash === previousHash) {
166 | logger.verbose('Config and source files weren’t changed since last run, checking resulting files...');
167 | var regenerationNeeded = false;
168 |
169 | var generatedFiles = wf.generatedFontFiles(o);
170 | if (!generatedFiles.length){
171 | regenerationNeeded = true;
172 | }
173 | else {
174 | generatedFiles.push(getDemoFilePath());
175 | o.stylesheets.forEach(function(stylesheet) {
176 | generatedFiles.push(getCssFilePath(stylesheet));
177 | });
178 |
179 | regenerationNeeded = _.some(generatedFiles, function(filename) {
180 | if (!filename) return false;
181 | if (!fs.existsSync(filename)) {
182 | logger.verbose('File', filename, ' is missed.');
183 | return true;
184 | }
185 | return false;
186 | });
187 | }
188 | if (!regenerationNeeded) {
189 | logger.log('Font ' + chalk.cyan(o.fontName) + ' wasn’t changed since last run.');
190 | completeTask();
191 | return;
192 | }
193 | }
194 |
195 | // Save new hash and run
196 | saveHash(this.name, this.target, o.hash);
197 | async.waterfall([
198 | createOutputDirs,
199 | cleanOutputDir,
200 | generateFont,
201 | generateWoff2Font,
202 | generateStylesheets,
203 | generateDemoHtml,
204 | generateCustomOutputs,
205 | printDone
206 | ], completeTask);
207 |
208 | /**
209 | * Call callback function if it was specified in the options.
210 | */
211 | function completeTask() {
212 | if (o && _.isFunction(o.callback)) {
213 | o.callback(o.fontName, o.types, o.glyphs, o.hash);
214 | }
215 | allDone();
216 | }
217 |
218 | /**
219 | * Calculate hash to flush browser cache.
220 | * Hash is based on source SVG files contents, task options and grunt-webfont version.
221 | *
222 | * @return {String}
223 | */
224 | function getHash() {
225 | // Source SVG files contents
226 | o.files.forEach(function(file) {
227 | md5.update(fs.readFileSync(file, 'utf8'));
228 | });
229 |
230 | // Options
231 | md5.update(JSON.stringify(o));
232 |
233 | // grunt-webfont version
234 | var packageJson = require('../package.json');
235 | md5.update(packageJson.version);
236 |
237 | // Templates
238 | if (o.template) {
239 | md5.update(fs.readFileSync(o.template, 'utf8'));
240 | }
241 | if (o.htmlDemoTemplate) {
242 | md5.update(fs.readFileSync(o.htmlDemoTemplate, 'utf8'));
243 | }
244 |
245 | return md5.digest('hex');
246 | }
247 |
248 | /**
249 | * Create output directory
250 | *
251 | * @param {Function} done
252 | */
253 | function createOutputDirs(done) {
254 | o.stylesheets.forEach(function(stylesheet) {
255 | mkdirp.sync(option(o.destCssPaths, stylesheet));
256 | });
257 | mkdirp.sync(o.dest);
258 | done();
259 | }
260 |
261 | /**
262 | * Clean output directory
263 | *
264 | * @param {Function} done
265 | */
266 | function cleanOutputDir(done) {
267 | var htmlDemoFileMask = path.join(o.destCss, o.fontBaseName + '*.{css,html}');
268 | var files = glob.sync(htmlDemoFileMask).concat(wf.generatedFontFiles(o));
269 | async.forEach(files, function(file, next) {
270 | fs.unlink(file, next);
271 | }, done);
272 | }
273 |
274 | /**
275 | * Generate font using selected engine
276 | *
277 | * @param {Function} done
278 | */
279 | function generateFont(done) {
280 | var engine = require('./engines/' + o.engine);
281 | engine(o, function(result) {
282 | if (result === false) {
283 | // Font was not created, exit
284 | completeTask();
285 | return;
286 | }
287 |
288 | if (result) {
289 | o = _.extend(o, result);
290 | }
291 |
292 | done();
293 | });
294 | }
295 |
296 | /**
297 | * Converts TTF font to WOFF2.
298 | *
299 | * @param {Function} done
300 | */
301 | function generateWoff2Font(done) {
302 | if (!has(o.types, 'woff2')) {
303 | done();
304 | return;
305 | }
306 |
307 | // Read TTF font
308 | var ttfFontPath = wf.getFontPath(o, 'ttf');
309 | var ttfFont = fs.readFileSync(ttfFontPath);
310 |
311 | // Remove TTF font if not needed
312 | if (!has(o.types, 'ttf')) {
313 | fs.unlinkSync(ttfFontPath);
314 | }
315 |
316 | // Convert to WOFF2
317 | var woffFont = ttf2woff2(ttfFont);
318 |
319 | // Save
320 | var woff2FontPath = wf.getFontPath(o, 'woff2');
321 | fs.writeFile(woff2FontPath, woffFont, function() {done();});
322 | }
323 |
324 | /**
325 | * Generate CSS
326 | *
327 | * @param {Function} done
328 | */
329 | function generateStylesheets(done) {
330 | // Convert codepoints to array of strings
331 | var codepoints = [];
332 | _.each(o.glyphs, function(name) {
333 | codepoints.push(o.codepoints[name].toString(16));
334 | });
335 | o.codepoints = codepoints;
336 |
337 | // Prepage glyph names to use as CSS classes
338 | o.glyphs = _.map(o.glyphs, classnameize);
339 |
340 | o.stylesheets.sort(function(a, b) {
341 | return a === 'css' ? 1 : -1;
342 | }).forEach(generateStylesheet);
343 |
344 | done();
345 | }
346 |
347 | /**
348 | * Generate CSS
349 | *
350 | * @param {String} stylesheet type: css, scss, ...
351 | */
352 | function generateStylesheet(stylesheet) {
353 | o.relativeFontPath = normalizePath(o.relativeFontPath);
354 |
355 | // Generate font URLs to use in @font-face
356 | var fontSrcs = [[], []];
357 | o.order.forEach(function(type) {
358 | if (!has(o.types, type)) return;
359 | wf.fontsSrcsMap[type].forEach(function(font, idx) {
360 | if (font) {
361 | fontSrcs[idx].push(generateFontSrc(type, font, stylesheet));
362 | }
363 | });
364 | });
365 |
366 | // Convert urls to strings that could be used in CSS
367 | var fontSrcSeparator = option(wf.fontSrcSeparators, stylesheet);
368 | fontSrcs.forEach(function(font, idx) {
369 | // o.fontSrc1, o.fontSrc2
370 | o['fontSrc'+(idx+1)] = font.join(fontSrcSeparator);
371 | });
372 | o.fontRawSrcs = fontSrcs;
373 |
374 | // Read JSON file corresponding to CSS template
375 | var templateJson = readTemplate(o.template, o.syntax, '.json', true);
376 | if (templateJson) o = _.extend(o, JSON.parse(templateJson.template));
377 |
378 | // Now override values with templateOptions
379 | if (o.templateOptions) o = _.extend(o, o.templateOptions);
380 |
381 | // Generate CSS
382 | var ext = path.extname(o.template) || '.css'; // Use extension of o.template file if given, or default to .css
383 | o.cssTemplate = readTemplate(o.template, o.syntax, ext);
384 | var cssContext = _.extend(o, {
385 | iconsStyles: true,
386 | stylesheet: stylesheet
387 | });
388 |
389 | var css = renderTemplate(o.cssTemplate, cssContext);
390 |
391 | // Fix CSS preprocessors comments: single line comments will be removed after compilation
392 | if (has(['sass', 'scss', 'less', 'styl'], stylesheet)) {
393 | css = css.replace(/\/\* *(.*?) *\*\//g, '// $1');
394 | }
395 |
396 | // Save file
397 | fs.writeFileSync(getCssFilePath(stylesheet), css);
398 | }
399 |
400 | /**
401 | * Gets the codepoints from the set filepath in o.codepointsFile
402 | */
403 | function readCodepointsFromFile(){
404 | if (!o.codepointsFile) return {};
405 | if (!fs.existsSync(o.codepointsFile)){
406 | logger.verbose('Codepoints file not found');
407 | return {};
408 | }
409 |
410 | var buffer = fs.readFileSync(o.codepointsFile);
411 | return JSON.parse(buffer.toString());
412 | }
413 |
414 | /**
415 | * Saves the codespoints to the set file
416 | */
417 | function saveCodepointsToFile(){
418 | if (!o.codepointsFile) return;
419 | var codepointsToString = JSON.stringify(o.codepoints, null, 4);
420 | try {
421 | fs.writeFileSync(o.codepointsFile, codepointsToString);
422 | logger.verbose('Codepoints saved to file "' + o.codepointsFile + '".');
423 | } catch (err) {
424 | logger.error(err.message);
425 | }
426 | }
427 |
428 | /*
429 | * Prepares base context for templates
430 | */
431 | function prepareBaseTemplateContext() {
432 | var context = _.extend({}, o);
433 | return context;
434 | }
435 |
436 | /*
437 | * Makes custom extends necessary for use with preparing the template context
438 | * object for the HTML demo.
439 | */
440 | function prepareHtmlTemplateContext() {
441 |
442 | var context = prepareBaseTemplateContext();
443 |
444 | var htmlStyles;
445 |
446 | // Prepare relative font paths for injection into @font-face refs in HTML
447 | var relativeRe = new RegExp(_s.escapeRegExp(o.relativeFontPath), 'g');
448 | var htmlRelativeFontPath = normalizePath(path.relative(o.destHtml, o.dest));
449 | var _fontSrc1 = o.fontSrc1.replace(relativeRe, htmlRelativeFontPath);
450 | var _fontSrc2 = o.fontSrc2.replace(relativeRe, htmlRelativeFontPath);
451 |
452 | _.extend(context, {
453 | fontSrc1: _fontSrc1,
454 | fontSrc2: _fontSrc2,
455 | fontfaceStyles: true,
456 | baseStyles: true,
457 | extraStyles: false,
458 | iconsStyles: true,
459 | stylesheet: 'css'
460 | });
461 |
462 | // Prepares CSS for injection into
53 |
54 |
55 | <%= fontBaseName %>
56 |
57 |
58 | <% for (var glyphIdx = 0; glyphIdx < glyphs.length; glyphIdx++) { var glyph = glyphs[glyphIdx] %>
59 |
<%= classPrefix %><%= glyph %>
60 | <% } %>
61 |
62 |
63 | Usage
64 | <i class="<%= baseClass ? baseClass + ' ' : '' %><%= classPrefix %>name "></i>
65 |
66 |
67 |
68 |
76 |
77 |