├── .gitignore
├── .gitmodules
├── .jshintignore
├── .jshintrc
├── Makefile
├── README.md
├── bin
├── font-check.js
├── font-dump.js
├── font_merge.py
├── font_mkconfig.py
├── font_remap.py
├── font_transform.py
├── fontbuild.py
├── fontconvert.py
├── fontdemo.py
└── tpl-render.js
├── example
├── config.yml.example
└── merge.yml.example
├── lib
└── font-dump-phantom.js
├── package.json
├── support
├── README.md
└── ttf2eot
│ ├── .gitignore
│ ├── ChangeLog
│ ├── Makefile
│ ├── OpenTypeUtilities.cpp
│ ├── OpenTypeUtilities.h
│ ├── README
│ └── ttf2eot.cpp
└── vendor
├── jquery-1.8.2.min.js
└── raphael.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "support/ttfautohint"]
2 | path = support/ttfautohint
3 | url = git://repo.or.cz/ttfautohint.git
4 |
--------------------------------------------------------------------------------
/.jshintignore:
--------------------------------------------------------------------------------
1 | .git/
2 | doc/
3 | node_modules/
4 | tmp/
5 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | // Enforcing Options /////////////////////////////////////////////////////////
3 |
4 | "bitwise" : true, // Prohibit bitwise operators (&, |, ^, etc.).
5 | "curly" : true, // Require {} for every new block or scope.
6 | "eqeqeq" : true, // Require triple equals i.e. `===`.
7 | "forin" : false, // Tolerate `for in` loops without `hasOwnPrototype`.
8 | "immed" : true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );`
9 | "latedef" : true, // Prohibit hariable use before definition.
10 | "newcap" : true, // Require capitalization of all constructor functions e.g. `new F()`.
11 | "noarg" : true, // Prohibit use of `arguments.caller` and `arguments.callee`.
12 | "noempty" : true, // Prohibit use of empty blocks.
13 | "nonew" : true, // Prohibit use of constructors for side-effects.
14 | "plusplus" : false, // Prohibit use of `++` & `--`.
15 | "regexp" : false, // Prohibit `.` and `[^...]` in regular expressions.
16 | "undef" : true, // Require all non-global variables be declared before they are used.
17 | "strict" : true, // Require `use strict` pragma in every file.
18 | "trailing" : true, // Prohibit trailing whitespaces.
19 |
20 | // Relaxing Options //////////////////////////////////////////////////////////
21 |
22 | "asi" : false, // Tolerate Automatic Semicolon Insertion (no semicolons).
23 | "boss" : false, // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments.
24 | "debug" : false, // Allow debugger statements e.g. browser breakpoints.
25 | "eqnull" : false, // Tolerate use of `== null`.
26 | "es5" : true, // Allow ECMAScript 5 syntax.
27 | "esnext" : false, // Allow ES.next specific features such as const and let
28 | "evil" : false, // Tolerate use of `eval`.
29 | "expr" : false, // Tolerate `ExpressionStatement` as Programs.
30 | "funcscope" : false, // Tolerate declaring variables inside of control structures while accessing them later
31 | "globalstrict" : true, // Allow global "use strict" (also enables 'strict').
32 | "iterator" : false, // Allow usage of __iterator__ property.
33 | "lastsemic" : false, // Tolerate semicolon omited for the last statement.
34 | "laxbreak" : false, // Tolerate unsafe line breaks e.g. `return [\n] x` without semicolons.
35 | "laxcomma" : true, // This option suppresses warnings about comma-first coding style
36 | "loopfunc" : false, // Allow functions to be defined within loops.
37 | "multistr" : false, // Tolerate multi-line strings.
38 | "onecase" : false, // Tolerate swithes with only one case.
39 | "proto" : false, // Allow usage of __proto__ property.
40 | "regexdash" : false, // Tolerate unescaped last dash i.e. `[-...]`.
41 | "scripturl" : true, // Tolerate script-targeted URLs.
42 | "smarttabs" : false, // Allow mixed tabs and spaces when the latter are used for alignmnent only.
43 | "shadow" : false, // Allows re-define variables later in code e.g. `var x=1; x=2;`.
44 | "sub" : true, // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`.
45 | "supernew" : true, // Tolerate `new function () { ... };` and `new Object;`.
46 |
47 | // Environments //////////////////////////////////////////////////////////////
48 |
49 | "browser" : false, // Defines globals exposed by modern browsers
50 | "couch" : false, // Defines globals exposed by CouchDB
51 | "devel" : false, // Allow developments statements e.g. `console.log();`.
52 | "dojo" : false, // Defines globals exposed by the Dojo Toolkit
53 | "jquery" : false, // Defines globals exposed by the jQuery
54 | "mootools" : false, // Defines globals exposed by the MooTools
55 | "node" : true, // Defines globals exposed when running under Node.JS
56 | "nonstandard" : false, // Defines non-standard but widely adopted globals such as escape and unescape
57 | "prototypejs" : false, // Defines globals exposed by the Prototype
58 | "rhino" : false, // Defines globals exposed when running under Rhino
59 | "wsh" : false, // Defines globals exposed when running under WSH
60 |
61 | // Legacy ////////////////////////////////////////////////////////////////////
62 |
63 | "nomen" : false, // Prohibit use of initial or trailing underbars in names.
64 | "onevar" : false, // Allow only one `var` statement per function.
65 | "passfail" : false, // Stop on first error.
66 | "white" : false, // Check against strict whitespace and indentation rules.
67 |
68 | // Undocumented //////////////////////////////////////////////////////////////
69 |
70 | "maxerr" : 100, // Maximum error before stopping.
71 | "indent" : 2 // Specify indentation spacing
72 | }
73 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | PREFIX ?= /usr/local
2 |
3 |
4 | TTF2EOT_BIN := ./support/ttf2eot/ttf2eot
5 | TTFAUTOHINT_BIN := ./support/ttfautohint/frontend/ttfautohint
6 |
7 | PLATFORM := $(shell uname)
8 |
9 | support:
10 | $(MAKE) $(TTF2EOT_BIN)
11 | $(MAKE) $(TTFAUTOHINT_BIN)
12 |
13 | # ttfautohint
14 | # ttf2eot
15 |
16 | support-install: support
17 | cp $(TTFAUTOHINT_BIN) $(PREFIX)/bin
18 | cp $(TTF2EOT_BIN) $(PREFIX)/bin
19 |
20 | support-osx:
21 | @if [[ $(PLATFORM) = "Darwin" ]]; then \
22 | brew install ttf2eot ; \
23 | brew install ttfautohint ; \
24 | else \
25 | echo "this target is only for OS X" >&2 ; \
26 | exit 128 ; \
27 | fi
28 |
29 | $(TTF2EOT_BIN):
30 | cd ./support/ttf2eot \
31 | && $(MAKE) ttf2eot
32 |
33 |
34 | $(TTFAUTOHINT_BIN):
35 | git submodule init support/ttfautohint
36 | git submodule update support/ttfautohint
37 | cd ./support/ttfautohint && \
38 | git submodule init && \
39 | git submodule update && \
40 | ./bootstrap ; \
41 | ./configure --with-qt=no --with-doc=no && make
42 |
43 |
44 | dev-deps:
45 | @if [[ $(PLATFORM) = "Darwin" ]]; then \
46 | brew install python ; \
47 | brew install fontforge ; \
48 | sudo ln -s /opt/X11/lib/libfreetype.6.dylib /usr/local/lib/libfreetype.6.dylib ; \
49 | brew tap sampsyo/py ; \
50 | brew install PyYAML ; \
51 | brew install automake autoconf libtool ; \
52 | pip -q install pystache argparse ; \
53 | else \
54 | if test 0 -ne `id -u` ; then \
55 | echo "root privileges are required" >&2 ; \
56 | exit 128 ; \
57 | fi ; \
58 | apt-get -qq install \
59 | fontforge python python-fontforge libfreetype6-dev \
60 | python-yaml python-pip \
61 | build-essential autoconf automake libtool ; \
62 | pip -q install pystache argparse ; \
63 | fi
64 | .PHONY: support
65 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Font Builder
2 | ============
3 |
4 | Set of scripts to generate iconic fonts. Available operation:
5 |
6 | - Building fonts from SVG images
7 | - Bulk glyphs code change
8 | - Transformations (resize/move glyphs)
9 | - Making CSS/HTML from templates
10 |
11 | This scripts are NOT indended to run standalone. See real usage example
12 | in [fontello](https://github.com/fontello) repos:
13 |
14 | - https://github.com/fontello/entypo
15 | - https://github.com/fontello/awesome-uni.font
16 | - https://github.com/fontello/iconic-uni.font
17 | - https://github.com/fontello/websymbols-uni.font
18 |
19 |
20 | Installation
21 | ------------
22 |
23 | You MUST have node.js 0.8+ installed. In Ubuntu 12.04+, just clone repo and
24 | run in command line:
25 |
26 | sudo make dev-deps
27 | make support
28 | sudo make support-install
29 | npm install
30 |
31 | You can skip `support-install` step, if you don't need to install ttfautohint
32 | & ttf2eot globally.
33 |
34 | ### OS X
35 |
36 | OS X users can use this, too. Make sure you have [Homebrew](http://mxcl.github.com/homebrew/)
37 | installed.
38 |
39 | Then, run:
40 |
41 | make dev-deps
42 | make support-osx
43 | npm install
44 |
45 | This installs Python, pip, and some other build dependencies.
46 | This _will_ globally install ttfautohint & ttf2eot.
47 |
48 | Licence info
49 | ------------
50 |
51 | Code (except `./support` folder) distributed under MIT licence.
52 | `./support` folder contains 3d-party software, see README there.
53 |
54 | (c) 2012 Vitaly Puzrin.
55 |
56 | [Contributors](https://github.com/fontello/font-builder/contributors)
57 |
58 | Special thanks to Werner Lemberg, author of [ttfautohint](http://www.freetype.org/ttfautohint/)
59 | utility, used in this project.
60 |
61 |
--------------------------------------------------------------------------------
/bin/font-check.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 |
4 | 'use strict';
5 |
6 |
7 | // stdlib
8 | var path = require('path');
9 |
10 |
11 | // 3rd-party
12 | var ArgumentParser = require('argparse').ArgumentParser;
13 | var yaml = require('js-yaml');
14 | var _ = require('underscore');
15 |
16 |
17 | ////////////////////////////////////////////////////////////////////////////////
18 |
19 |
20 | var cli = new ArgumentParser({
21 | prog: 'mincer',
22 | version: require('../package.json').version,
23 | addHelp: true
24 | });
25 |
26 |
27 | cli.addArgument(['files'], {
28 | help: 'Config file(s) to validate',
29 | metavar: 'FILE',
30 | nargs: '+'
31 | });
32 |
33 |
34 | ////////////////////////////////////////////////////////////////////////////////
35 |
36 |
37 | function findDuplicates(arr, key) {
38 | var processed = Object(null);
39 |
40 | // group values by key
41 | arr.forEach(function (o) {
42 | var k = o[key];
43 |
44 | if (!processed[k]) {
45 | processed[k] = [];
46 | }
47 |
48 | processed[k].push(o);
49 | });
50 |
51 | // return only those who have
52 | // more than one element in group
53 | return _.filter(processed, function (o) {
54 | return 1 < o.length;
55 | });
56 | }
57 |
58 |
59 | ////////////////////////////////////////////////////////////////////////////////
60 |
61 |
62 | var has_errors = false;
63 | var found_glyphs = [];
64 | var args = cli.parseArgs();
65 |
66 |
67 | ////////////////////////////////////////////////////////////////////////////////
68 |
69 |
70 | args.files.forEach(function (filename) {
71 | var config;
72 |
73 | try {
74 | config = require(path.resolve(filename));
75 | } catch (err) {
76 | console.log('ERROR: Failed read file ' + filename);
77 | console.log(err.stack || String(err));
78 | process.exit(1);
79 | }
80 |
81 | if (!config.glyphs || !config.glyphs.length) {
82 | console.log('ERROR: No glyphs in config ' + filename);
83 | process.exit(1);
84 | }
85 |
86 | //
87 | // push glyphs into "all known glyphs" list
88 | //
89 |
90 | found_glyphs = found_glyphs.concat(config.glyphs.map(function (glyph) {
91 | return {
92 | uid: glyph.uid,
93 | css: glyph.css,
94 | code: glyph.code,
95 | file: glyph.file,
96 | config_file: filename
97 | };
98 | }));
99 |
100 | //
101 | // check for duplicate uid's
102 | //
103 |
104 | findDuplicates(config.glyphs, 'uid').forEach(function (duplicates) {
105 | console.log('Duplicate uid <' + duplicates[0].uid + '> (in: ' + filename + ')');
106 |
107 | has_errors = true;
108 |
109 | duplicates.forEach(function (g) {
110 | console.log(' - code: ' + '0x' + g.code.toString(16));
111 | console.log(' css: ' + g.css);
112 |
113 | if (g.file) {
114 | console.log(' file: ' + g.file);
115 | }
116 | });
117 | });
118 |
119 | //
120 | // check for duplicate name's
121 | //
122 |
123 | findDuplicates(config.glyphs, 'css').forEach(function (duplicates) {
124 | console.log('Duplicate css <' + duplicates[0].css + '> (in: ' + filename + ')');
125 |
126 | has_errors = true;
127 |
128 | duplicates.forEach(function (g) {
129 | console.log(' - code: ' + '0x' + g.code.toString(16));
130 | console.log(' css: ' + g.css);
131 |
132 | if (g.file) {
133 | console.log(' file: ' + g.file);
134 | }
135 | });
136 | });
137 | });
138 |
139 | //
140 | // Search for duplicates across multiple configs
141 | //
142 |
143 | if (1 < args.files.length) {
144 |
145 | //
146 | // check for duplicate uid's
147 | //
148 |
149 | findDuplicates(found_glyphs, 'uid').forEach(function (duplicates) {
150 | console.log('Duplicate uid <' + duplicates[0].uid + '>');
151 |
152 | has_errors = true;
153 |
154 | duplicates.forEach(function (g) {
155 | console.log(' - code: ' + '0x' + g.code.toString(16));
156 | console.log(' css: ' + g.css);
157 |
158 | if (g.file) {
159 | console.log(' file: ' + g.file);
160 | }
161 |
162 | console.log(' from: ' + g.config_file);
163 | });
164 | });
165 |
166 | }
167 |
168 |
169 | process.exit(has_errors ? 1 : 0);
170 |
--------------------------------------------------------------------------------
/bin/font-dump.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | 'use strict';
4 |
5 | var fs = require('fs');
6 | var path = require('path');
7 | var childProcess = require('child_process');
8 | var crypto = require('crypto');
9 |
10 | var _ = require('underscore');
11 | var yaml = require('yamljs');
12 | var fstools = require('fs-tools');
13 |
14 | var phantomjs = require('phantomjs');
15 | var binPath = phantomjs.path;
16 |
17 | var svg_template =
18 | '';
24 |
25 | var ArgumentParser = require('argparse').ArgumentParser;
26 |
27 | var parser = new ArgumentParser({
28 | version: '0.0.1',
29 | addHelp: true,
30 | description: 'Dump glyphs from font'
31 | });
32 | parser.addArgument(
33 | ['--hcrop'],
34 | {
35 | help: 'Crop free space from left and right',
36 | action: 'storeTrue'
37 | }
38 | );
39 | parser.addArgument(
40 | ['--vcenter'],
41 | {
42 | help: 'Realign glyphs vertically',
43 | action: 'storeTrue'
44 | }
45 | );
46 | parser.addArgument(
47 | [ '-c', '--config' ],
48 | {
49 | help: 'Font config file'
50 | }
51 | );
52 | parser.addArgument(
53 | [ '-i', '--src_font' ],
54 | {
55 | help: 'Source font path',
56 | required: true
57 | }
58 | );
59 | parser.addArgument(
60 | [ '-o', '--glyphs_dir' ],
61 | {
62 | help: 'Glyphs output folder',
63 | required: true
64 | }
65 | );
66 | parser.addArgument(
67 | [ '-d', '--diff_config' ],
68 | {
69 | help: 'Difference config output file'
70 | }
71 | );
72 | parser.addArgument(
73 | [ '-f', '--force' ],
74 | {
75 | help: 'Force override glyphs from config',
76 | action: 'storeTrue'
77 | }
78 | );
79 |
80 |
81 | var args = parser.parseArgs();
82 |
83 | var params = {
84 | src_font: args.src_font,
85 | glyphs_dir: args.glyphs_dir,
86 | hcrop: args.hcrop,
87 | vcenter: args.vcenter,
88 | };
89 |
90 | //console.log(config.toObject());
91 | //process.exit();
92 |
93 | var tmp_dir = fstools.tmpdir('/tmp/font-dumpXXX');
94 | var tmp_config_file = path.join(tmp_dir, 'config.json');
95 |
96 | fs.mkdirSync(tmp_dir);
97 | fs.writeFileSync(tmp_config_file, JSON.stringify(params));
98 |
99 | var childArgs = [
100 | path.join(__dirname, '../lib/font-dump-phantom.js'),
101 | tmp_config_file
102 | ];
103 |
104 | childProcess.execFile(binPath, childArgs, function (err, stdout, stderr) {
105 | if (err) {
106 | console.log(err);
107 | fstools.removeSync(tmp_dir);
108 | process.exit(1);
109 | }
110 |
111 | console.log(stdout);
112 |
113 | // Load generated glyphs from json output
114 | var glyphs = require(path.join(tmp_dir, 'config.json.out.json'));
115 |
116 | fstools.removeSync(tmp_dir);
117 |
118 | var config
119 | , diff = [];
120 |
121 | if (args.config) {
122 | config = yaml.load(args.config);
123 | }
124 |
125 | console.log('Writing output:\n\n');
126 |
127 | _.each(glyphs, function(glyph) {
128 |
129 | var exists
130 | , glyph_out = {};
131 |
132 | // if got config from existing font, then write only missed files
133 | if (config) {
134 | exists = _.find(config.glyphs, function(element) {
135 | //console.log('---' + element.from + '---' + glyph.unicode)
136 | return (element.from || element.code) == glyph.unicode;
137 | });
138 |
139 | if (exists && !args.force) {
140 | console.log((glyph.unicode.toString(16)) + ' exists, skipping');
141 | return;
142 | }
143 | }
144 |
145 | glyph.svg = svg_template
146 | .replace('${path}', glyph.path)
147 | .replace('${width}', glyph.width)
148 | .replace('${height}', glyph.height);
149 |
150 | if (exists) {
151 | // glyph exists in config, but we forced dump
152 | fs.writeFileSync(path.join(params.glyphs_dir, (exists.file || exists.css) + '.svg'), glyph.svg);
153 | console.log((glyph.unicode.toString(16)) + ' - Found, but override forced');
154 | return;
155 | }
156 |
157 | // Completely new glyph
158 | //
159 | console.log((glyph.unicode.toString(16)) + ' - NEW glyph, writing...');
160 |
161 | glyph_out = {
162 | css: glyph.name,
163 | code: '0x' + glyph.unicode.toString(16),
164 | uid: crypto.randomBytes(16).toString('hex'),
165 | search: []
166 | }
167 |
168 | fs.writeFileSync(path.join(params.glyphs_dir, glyph.name + '.svg'), glyph.svg);
169 |
170 | diff.push(glyph_out);
171 | });
172 |
173 | // Create config template for new glyphs, if option set
174 | if (args.diff_config) {
175 |
176 | if (!diff.length) {
177 | console.log("No new glyphs, skip writing diff");
178 | return;
179 | }
180 |
181 | fs.writeFileSync(args.diff_config, yaml.stringify({glyphs: diff}, 10, 2));
182 | }
183 | });
184 |
--------------------------------------------------------------------------------
/bin/font_merge.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import sys
3 | from sys import stderr
4 | import argparse
5 | import yaml
6 | import fontforge
7 |
8 | parser = argparse.ArgumentParser(description='Merges glyphs from '
9 | 'several fonts, as specified in config.')
10 | parser.add_argument('-c', '--config', type=str, required=False,
11 | help='Config file in json or yml format. If missed, then '
12 | 'loaded from stdin. '
13 | 'example: ../config.json')
14 | parser.add_argument('-o', '--dst_font', type=str, required=True,
15 | help='Output font')
16 |
17 | args = parser.parse_args()
18 |
19 | if args.config is not None:
20 | try:
21 | unparsed_config = open(args.config, 'r')
22 |
23 | except IOError as (errno, strerror):
24 | stderr.write("Cannot open %s: %s\n" % (args.config, strerror))
25 | sys.exit(1)
26 | else:
27 | unparsed_config = sys.stdin
28 |
29 | try:
30 | # yaml parser undestend both formats
31 | config = yaml.load(unparsed_config)
32 |
33 | except yaml.YAMLError, e:
34 |
35 | config_file_name = '' if args.config is None else args.config
36 | if hasattr(e, 'problem_mark'):
37 | mark = e.problem_mark
38 | stderr.write("YAML parser error in config %s at line %d, col %d\n" %
39 | (config_file_name, mark.line + 1, mark.column + 1))
40 | else:
41 | stderr.write("YAML parser error in config %s: %s\n" % (config_file_name, e))
42 | sys.exit(1)
43 |
44 | # init new font
45 | new_font = fontforge.font()
46 | new_font.encoding = 'UnicodeFull'
47 |
48 | # load font properties from config
49 | for key, value in config['font'].iteritems():
50 | setattr(new_font, key, value)
51 |
52 | try:
53 | # read source fonts
54 | src_fonts = {}
55 | for name, path in config['src_fonts'].iteritems():
56 | src_fonts[name] = fontforge.open(path)
57 | except:
58 | stderr.write("Error: fontforge can't open source font from %s" % path)
59 | sys.exit(1)
60 |
61 | # prepare config to view:
62 | # [(from_code1, to_code1, src), (from_code2, to_code2, src), ...]
63 | remap_config = [(glyph.get('from', glyph['code']),
64 | glyph['code'], glyph['src'])
65 | for glyph in config['glyphs']]
66 |
67 |
68 | for from_code, to_code, src in remap_config:
69 | try:
70 | src_fonts[src][from_code]
71 | except TypeError:
72 | stderr.write("Warning: no such glyph in the source font (code=0x%04x)\n" %
73 | from_code)
74 | continue
75 |
76 | src_fonts[src].selection.select(("unicode",), from_code)
77 | src_fonts[src].copy()
78 | new_font.selection.select(("unicode",), to_code)
79 | new_font.paste()
80 |
81 | try:
82 | new_font.generate(args.dst_font)
83 | except:
84 | stderr.write("Cannot write to file %s\n" % args.dst_font)
85 | sys.exit(1)
86 |
--------------------------------------------------------------------------------
/bin/font_mkconfig.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import argparse
4 | import yaml
5 | import fontforge
6 | import string
7 | import random
8 |
9 | parser = argparse.ArgumentParser(description='Font config generation tool')
10 | parser.add_argument('-i', '--src_font', type=str, required=True,
11 | help='Input font')
12 | parser.add_argument('-c', '--config', type=str, required=True,
13 | help='Output config')
14 |
15 | def get_attrs(font, attrs):
16 | result = {}
17 | for a in attrs:
18 | result[a] = getattr(font, a)
19 | return result
20 |
21 | args = parser.parse_args()
22 |
23 | try:
24 | font = fontforge.open(args.src_font)
25 | except:
26 | stderr.write("Error: fontforge can't open source font from %s" % args.src_font)
27 | sys.exit(1)
28 |
29 |
30 | font_attrs = [
31 | "version",
32 | "fontname",
33 | "fullname",
34 | "familyname",
35 | "copyright",
36 | "ascent",
37 | "descent",
38 | "weight"
39 | ]
40 |
41 | # add beginning part
42 | config = """
43 |
44 | meta:
45 | author:
46 | homepage:
47 | email:
48 | licence:
49 | licence_url:
50 |
51 | css_prefix: "icon-"
52 | columns: 4
53 |
54 | transform:
55 | baseline: 0.5
56 | rescale: 1.0
57 | offset: 0.0
58 |
59 | font:
60 | version: "{version}"
61 |
62 | # use !!!small!!! letters a-z, or Opera will fail under OS X
63 | # fontname will be also used as file name.
64 | fontname: {fontname}
65 |
66 | fullname: {fullname}
67 | familyname: {familyname}
68 |
69 | copyright: {copyright}
70 |
71 | ascent: {ascent}
72 | descent: {descent}
73 | weight: {weight}
74 |
75 |
76 | """.format(**get_attrs(font, font_attrs))
77 |
78 | # add glyphs part
79 | config += """glyphs:
80 | """
81 |
82 | glyph_template = """
83 | - css: glyph{i}
84 | code: {code}
85 | uid: {uid}
86 | from: {code}
87 | search:
88 | """
89 |
90 | for i, glyph in enumerate(font.glyphs()):
91 | if glyph.unicode == -1:
92 | continue
93 |
94 | code = '0x%04x' % glyph.unicode
95 |
96 | uid = ''.join(random.choice(string.ascii_lowercase+string.digits)
97 | for x in range(32))
98 |
99 | config += glyph_template.format(i=i, code=code, uid=uid)
100 |
101 | try:
102 | open(args.config, "w").write(config)
103 | except IOError as (errno, strerror):
104 | stderr.write("Cannot write %s: %s\n" % (args.config, strerror))
105 | sys.exit(1)
106 |
107 |
--------------------------------------------------------------------------------
/bin/font_remap.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import sys
4 | from sys import stderr
5 | import argparse
6 | import yaml
7 | import fontforge
8 |
9 | parser = argparse.ArgumentParser(description='Font remap tool')
10 | parser.add_argument('-c', '--config', type=str, required=True,
11 | help='Config example: ../config.yml')
12 | parser.add_argument('-i', '--src_font', type=str, required=True,
13 | help='Input font')
14 | parser.add_argument('-o', '--dst_font', type=str, required=True,
15 | help='Output font')
16 |
17 | args = parser.parse_args()
18 |
19 | try:
20 | config = yaml.load(open(args.config, 'r'))
21 | except IOError as (errno, strerror):
22 | stderr.write("Cannot open %s: %s\n" % (args.config, strerror))
23 | sys.exit(1)
24 | except yaml.YAMLError, e:
25 | if hasattr(e, 'problem_mark'):
26 | mark = e.problem_mark
27 | stderr.write("YAML parser error in file %s at line %d, col %d\n" %
28 | (args.config, mark.line + 1, mark.column + 1))
29 | else:
30 | stderr.write("YAML parser error in file %s: %s\n" % (args.config, e))
31 | sys.exit(1)
32 |
33 | # prepare config to view:
34 | # [(from_code1, to_code1), (from_code2, to_code2), ...]
35 | remap_config = [(glyph.get('from', glyph['code']), glyph['code'])
36 | for glyph in config['glyphs']]
37 |
38 | # validate config: fetcy duplicate 'from' codes
39 | src_codes = zip(*remap_config)[0]
40 | if len(src_codes) != len(set(src_codes)):
41 | stderr.write("Error in file %s: glyph codes aren't unique:\n" % args.config)
42 | for code in set(src_codes):
43 | if src_codes.count(code) > 1:
44 | stderr.write("Duplicate 'from:' 0x%04x\n" % code)
45 | sys.exit(1)
46 |
47 | try:
48 | font = fontforge.open(args.src_font)
49 | # set font encoding so we can select any unicode code point
50 | font.encoding = 'UnicodeFull'
51 | except:
52 | stderr.write("Error: Fontforge can't open source %s" % args.src_font)
53 | sys.exit(1)
54 |
55 | new_font = fontforge.font()
56 | new_font.encoding = 'UnicodeFull'
57 |
58 | # load font properties from config
59 | for key, value in config.get('font', {}).items():
60 | setattr(new_font, key, value)
61 |
62 |
63 | for from_code, to_code in remap_config:
64 | try:
65 | font[from_code]
66 | except TypeError:
67 | stderr.write("Warning: no such glyph in the source font (code=0x%04x)\n" %
68 | from_code)
69 | continue
70 |
71 | font.selection.select(("unicode",), from_code)
72 | font.copy()
73 | new_font.selection.select(("unicode",), to_code)
74 | new_font.paste()
75 |
76 | try:
77 | new_font.generate(args.dst_font)
78 | except:
79 | stderr.write("Cannot write to file %s\n" % args.dst_font)
80 | sys.exit(1)
81 |
--------------------------------------------------------------------------------
/bin/font_transform.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import sys
4 | from sys import stderr
5 | import argparse
6 | import yaml
7 | import fontforge
8 | import psMat
9 |
10 |
11 | def apply_rescale(glyph, origin, scale):
12 | """Rescale glyph"""
13 | # move scale origin point to (0, 0)
14 | sx, sy = origin
15 | translate_matrix = psMat.translate(-sx, -sy)
16 | glyph.transform(translate_matrix)
17 |
18 | # scale around (0, 0)
19 | scale_matrix = psMat.scale(scale)
20 | glyph.transform(scale_matrix)
21 |
22 | # move scale origin point back to its old position
23 | translate_matrix = psMat.translate(origin)
24 | glyph.transform(translate_matrix)
25 |
26 |
27 | parser = argparse.ArgumentParser(description='Font transform tool')
28 | parser.add_argument('-c', '--config', type=str, required=True,
29 | help='Config example: ../config.yml')
30 | parser.add_argument('-i', '--src_font', type=str, required=True,
31 | help='Input font')
32 | parser.add_argument('-o', '--dst_font', type=str, required=True,
33 | help='Output font')
34 |
35 | args = parser.parse_args()
36 |
37 | try:
38 | config = yaml.load(open(args.config, 'r'))
39 | except IOError as (errno, strerror):
40 | stderr.write("Cannot open %s: %s\n" % (args.config, strerror))
41 | sys.exit(1)
42 | except yaml.YAMLError, e:
43 | if hasattr(e, 'problem_mark'):
44 | mark = e.problem_mark
45 | stderr.write("YAML parser error in file %s at line %d, col %d" %
46 | (args.config, mark.line + 1, mark.column + 1))
47 | else:
48 | stderr.write("YAML parser error in file %s: %s" % (args.config, e))
49 | sys.exit(1)
50 |
51 |
52 | # fetch genral rules
53 | base = config.get('transform', {}).items()
54 | # merge local and general rules
55 | merge = lambda glyph: dict(base + glyph.get('transform', {}).items())
56 | # prepare config to view:
57 | # [(code1, {'rescale': 1.0, 'offset': 0.0}), (code2, {}), ...]
58 | transform_config = [(glyph.get('code'), merge(glyph)) for glyph in config['glyphs']]
59 |
60 |
61 | # validate config: fetch duplicate codes
62 | codes = zip(*transform_config)[0]
63 | if len(codes) != len(set(codes)):
64 | stderr.write("Error in file %s: glyph codes aren't unique:\n" % args.config)
65 | for code in set(codes):
66 | if codes.count(code) > 1:
67 | stderr.write("Duplicate 'from:' 0x%04x\n" % code)
68 | sys.exit(1)
69 |
70 | try:
71 | # source font
72 | font = fontforge.open(args.src_font)
73 | # set font encoding so we can select any unicode code point
74 | font.encoding = 'UnicodeFull'
75 | except:
76 | stderr.write("Error: fontforge can't open source font from %s" % args.src_font)
77 | sys.exit(1)
78 |
79 | # set ascent/descent
80 | ascent = config.get('font', {}).get('ascent', font.ascent)
81 | descent = config.get('font', {}).get('descent', font.descent)
82 | font.ascent = ascent
83 | font.descent = descent
84 |
85 | default_baseline = float(descent) / (ascent + descent)
86 |
87 | origin_point = lambda baseline: (0, (ascent + descent) * baseline - descent)
88 | offset_matrix = lambda offset: psMat.translate(0, offset * (ascent + descent))
89 | # apply transformations
90 | for code, transform in transform_config:
91 | try:
92 | glyph = font[code]
93 | except TypeError:
94 | stderr.write("Warning: no such glyph (code=0x%04x)\n" % code)
95 | continue
96 |
97 | if 'rescale' in transform:
98 | baseline = transform.get('baseline', default_baseline)
99 | scale = transform['rescale']
100 | apply_rescale(glyph, origin_point(baseline), scale)
101 |
102 | if 'offset' in transform:
103 | glyph.transform(offset_matrix(transform['offset']))
104 |
105 | if 'rescale_rel' in transform:
106 | baseline = transform.get('baseline_rel', default_baseline)
107 | scale = transform['rescale_rel']
108 | apply_rescale(glyph, origin_point(baseline), scale)
109 |
110 | if 'offset_rel' in transform:
111 | glyph.transform(offset_matrix(transform['offset_rel']))
112 |
113 | try:
114 | font.generate(args.dst_font)
115 | except:
116 | stderr.write("Cannot write to file %s\n" % args.dst_font)
117 | sys.exit(1)
118 |
--------------------------------------------------------------------------------
/bin/fontbuild.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import sys
4 | from sys import stderr
5 | import argparse
6 | import yaml
7 | import fontforge
8 |
9 | KERNING = 0
10 |
11 | parser = argparse.ArgumentParser(description='Font builder tool')
12 | parser.add_argument('-c', '--config', type=str, help='Config example: ../config.yml', required=True)
13 | parser.add_argument('-t', '--sfd_template', type=str, help='SFD template file', required=True)
14 | parser.add_argument('-i', '--svg_dir', type=str, help='Input svg file', required=True)
15 | parser.add_argument('-o', '--ttf_file', type=str, help='Output ttf file', required=True)
16 |
17 | args = parser.parse_args()
18 |
19 | try:
20 | config = yaml.load(open(args.config, 'r'))
21 | except IOError as (errno, strerror):
22 | stderr.write("Cannot open %s: %s\n" % (args.config, strerror))
23 | sys.exit(1)
24 | except yaml.YAMLError, e:
25 | if hasattr(e, 'problem_mark'):
26 | mark = e.problem_mark
27 | stderr.write("YAML parser error in file %s at line %d, col %d\n" %
28 | (args.config, mark.line + 1, mark.column + 1))
29 | else:
30 | stderr.write("YAML parser error in file %s: %s\n" % (args.config, e))
31 | sys.exit(1)
32 |
33 | font = fontforge.font()
34 |
35 | # load font properties from config
36 | for key, value in config['font'].items():
37 | setattr(font, key, value)
38 |
39 | # process glyphs
40 | for glyph in config['glyphs']:
41 | c = font.createChar(int(glyph['code']))
42 |
43 | # css used as file name, if nod redefined
44 | f = glyph.get('file', glyph.get('css'))
45 |
46 | c.importOutlines(args.svg_dir + '/' + f + '.svg')
47 |
48 | # DON'T REMOVE 2 strings below, or it will break
49 | # width of imported glyphs. Just set KEGNING=0 instead.
50 | c.left_side_bearing = KERNING
51 | c.right_side_bearing = KERNING
52 |
53 | # small optimization, should not affect quality
54 | c.simplify()
55 | c.round()
56 | # c.addExtrema()
57 |
58 | try:
59 | font.generate(args.ttf_file)
60 | except:
61 | stderr.write("Cannot write to file %s\n" % args.ttf_file)
62 | sys.exit(1)
63 |
--------------------------------------------------------------------------------
/bin/fontconvert.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import sys
4 | from sys import stderr
5 | import argparse
6 | import fontforge
7 | import os
8 |
9 | parser = argparse.ArgumentParser(description='Font convertor tool')
10 | parser.add_argument('-i', '--src_font', type=str,
11 | help='Input font file', required=True)
12 | parser.add_argument('-o', '--fonts_dir', type=str,
13 | help='Output fonts folder', required=True)
14 |
15 | args = parser.parse_args()
16 |
17 | font_name = os.path.basename(args.src_font)[:-4]
18 | font_name_template = args.fonts_dir + '/' + font_name
19 |
20 | try:
21 | font = fontforge.open(args.src_font)
22 | except:
23 | stderr.write("Error: Fontforge can't open source %s" % args.src_font)
24 | sys.exit(1)
25 |
26 | try:
27 | font.generate(font_name_template + '.woff')
28 | font.generate(font_name_template + '.svg')
29 |
30 | # Fix SVG header, to make webkit work
31 | source_text = '''