forked from Tblue/mkttf
-
Notifications
You must be signed in to change notification settings - Fork 0
/
mkttf.py
executable file
·309 lines (265 loc) · 11 KB
/
mkttf.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
#!/usr/bin/env python
#
# This Python script uses FontForge to convert a set of BDF files into a
# TrueType font (TTF) and an SFD file.
#
# Copyright (c) 2013-2016 by Tilman Blumenbach <tilman [AT] ax86 [DOT] net>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of the author nor the names of its contributors
# may be used to endorse or promote products derived from this
# software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import print_function
import argparse
import fontforge
import sys
from itertools import dropwhile
# Maps argument names to their font attribute names.
_argNameFontAttrMap = {
'name': 'fontname',
'family': 'familyname',
'display_name': 'fullname',
'weight': 'weight',
'copyright': 'copyright',
'font_version': 'version',
}
# Determines which fsSelection and macStyle bits in the OS/2 table get set
# when a certain font weight is specified and OS/2 table tweaks are enabled.
#
# Use lowercase font weights here. The "italic" font weight is special:
# If the font weight is "medium" and the font name ends with "italic"
# (case-insensitive), then "italic" is used when looking up values in this
# dictionary instead of "medium".
#
# The first value of each tuple contains the bits to set in the fsSelection
# field.
#
# The second value of each tuple contains the bits to set in the macStyle
# field in the OS/2 table.
#
# See https://www.microsoft.com/typography/otspec/os2.htm#fss for details.
_weightToStyleMap = {
# fsSelection: Set bit 6 ("REGULAR").
'normal': (0x40, 0),
# fsSelection: Set bit 6 ("REGULAR").
'medium': (0x40, 0),
# fsSelection: Set bits 0 ("ITALIC") and 9 ("OBLIQUE").
# macStyle: Set bit 1 (which presumably also means "ITALIC").
'italic': (0x201, 0x2),
# fsSelection: Set bit 5 ("BOLD").
# macStyle: Set bit 0 (which presumably also means "BOLD").
'bold': (0x20, 0x1),
# fsSelection: Set bits 0 ("ITALIC"), 9 ("OBLIQUE") and 5 ("BOLD").
# macStyle: Set bits 1 (italic) and 0 (bold).
'bolditalic': (0x221, 0x3),
}
def initArgumentParser():
"""Initialize and return an argparse.ArgumentParser that parses this program's arguments."""
argParser = argparse.ArgumentParser(
description='Convert a set of BDF files into a TrueType font (TTF). '
'The BDF files have to be sorted by font size in ascending order.'
)
# Positional arguments.
argParser.add_argument(
'bdf_file',
nargs='+',
help='BDF file to process.'
)
# Optional arguments.
argParser.add_argument(
'-n',
'--name',
help='Font name to use for generated font (default: taken from first BDF file).'
)
argParser.add_argument(
'-f',
'--family',
help='Font family to use for generated font (default: taken from first BDF file).'
)
argParser.add_argument(
'-N',
'--display-name',
help='Full font name (for display) to use for generated font (default: taken from first BDF file).'
)
argParser.add_argument(
'-w',
'--weight',
help='Weight to use for generated font (default: taken from first BDF file).'
)
argParser.add_argument(
'-c',
'--copyright',
help='Copyright notice to use for generated font (default: taken from first BDF file).'
)
argParser.add_argument(
'-C',
'--append-copyright',
help='Copyright notice to use for generated font (appends to notice taken from first BDF file).'
)
argParser.add_argument(
'-V',
'--font-version',
help='Font version to use for generated font (default: taken from first BDF file).'
)
argParser.add_argument(
'-a',
'--prefer-autotrace',
action='store_true',
help='Prefer AutoTrace over Potrace, if possible (default: %(default)s).'
)
argParser.add_argument(
'-A',
'--tracer-args',
default='',
help='Additional arguments for AutoTrace/Potrace (default: none).'
)
argParser.add_argument(
'-s',
'--visual-studio-fixes',
action='store_true',
help='Make generated font compatible with Visual Studio (default: %(default)s).'
)
argParser.add_argument(
'-O',
'--os2-table-tweaks',
action='store_true',
help='Tweak OS/2 table according to the font weight. This may be needed for some '
'buggy FontForge versions which do not do this by themselves.'
)
return argParser
def setFontAttrsFromArgs(font, args):
"""Set font attributes from arguments.
If an argument is None, that means that no value was given. In that case, the font attribute
is not modified.
args is an argparse.Namespace.
font is a fontforge.font.
"""
for argName in _argNameFontAttrMap:
argValue = getattr(args, argName)
if argValue is not None:
# User gave a new value for this font attribute.
setattr(
font,
_argNameFontAttrMap[argName],
argValue
)
# Parse the command line arguments.
args = initArgumentParser().parse_args()
# Set FontForge options.
fontforge.setPrefs("PreferPotrace", not args.prefer_autotrace)
fontforge.setPrefs("AutotraceArgs", args.tracer_args)
# Good, can we open the base font?
try:
baseFont = fontforge.open(args.bdf_file[0])
except EnvironmentError as e:
sys.exit("Could not open base font `%s'!" % args.bdf_file[0])
# Now import all the bitmaps from the other BDF files into this font.
print('Importing bitmaps from %d additional fonts...' % (len(args.bdf_file) - 1))
for fontFile in args.bdf_file[1:]:
try:
baseFont.importBitmaps(fontFile)
except EnvironmentError as e:
sys.exit("Could not import additional font `%s'!" % fontFile)
# Import the last (biggest) BDF font into the glyph background.
try:
baseFont.importBitmaps(args.bdf_file[-1], True)
except EnvironmentError as e:
sys.exit("Could not import font `%s' into glyph background!" % args.bdf_file[-1])
# Now set font properties.
setFontAttrsFromArgs(baseFont, args)
# Do we want to append to the current copyright notice?
if args.append_copyright is not None:
baseFont.copyright += args.append_copyright
# FontForge won't write the OS/2 table unless we set a vendor and we set it BEFORE modifying
# the OS/2 table in any way (although this is not documented anywhere...).
# "PfEd" is the value FontForge writes when using the GUI.
baseFont.os2_vendor = 'PfEd'
# Newer FontForge releases require us to manually set the macStyle
# and fsSelection (aka "StyleMap") fields in the OS/2 table.
if args.os2_table_tweaks:
if not hasattr(baseFont, "os2_stylemap"):
sys.exit("You requested OS/2 table tweaks, but your FontForge version is too old for these "
"tweaks to work.")
os2_weight = baseFont.weight.lower()
if os2_weight == "medium" and baseFont.fontname.lower().endswith("italic"):
os2_weight = "italic"
elif os2_weight == "bold" and baseFont.fontname.lower().endswith("italic"):
os2_weight = "bolditalic"
try:
styleMap, macStyle = _weightToStyleMap[os2_weight]
except KeyError:
sys.exit("Cannot tweak OS/2 table: No tweaks defined for guessed font weight `%s'!" % os2_weight)
print(
"OS/2 table tweaks: Guessed weight is `%s' -> Adding %#x to StyleMap and %#x to macStyle." % (
os2_weight,
styleMap,
macStyle
)
)
baseFont.os2_stylemap |= styleMap
baseFont.macstyle |= macStyle
# AutoTrace all glyphs, add extrema and simplify.
print('Processing glyphs...')
baseFont.selection.all()
baseFont.autoTrace()
baseFont.addExtrema()
baseFont.simplify()
# Do we need to fixup the font for use with Visual Studio?
# Taken from http://www.electronicdissonance.com/2010/01/raster-fonts-in-visual-studio-2010.html
# Really, it's a MESS that one has to use dirty workarounds like this...
if args.visual_studio_fixes:
print('Applying Visual Studio fixes...')
# Make sure the encoding used for indexing is set to UCS.
baseFont.encoding = 'iso10646-1'
# Need to add CP950 (Traditional Chinese) to OS/2 table.
# According to http://www.microsoft.com/typography/otspec/os2.htm#cpr,
# we need to set bit 20 to enable CP950.
baseFont.os2_codepages = (baseFont.os2_codepages[0] | (1 << 20), baseFont.os2_codepages[1])
# The font needs to include glyphs for certain characters.
# Try to find a fitting glyph to substitute for those glyphs which
# the font does not already contain. U+0000 is the "default character";
# it _should_ be displayed instead of missing characters, so it is a good choice.
# If the font does not contain a glyph for U+0000, try other, less optimal glyphs.
try:
selector = next(dropwhile(lambda x: x not in baseFont, [0, 'question', 'space']))
substGlyph = baseFont[selector]
except StopIteration:
sys.exit(' While applying Visual Studio fixes: Could not find a substitution glyph!')
print(" Chose `%s' as substitution glyph." % substGlyph.glyphname)
baseFont.selection.select(substGlyph)
baseFont.copyReference()
for codePoint in [0x3044, 0x3046, 0x304B, 0x3057, 0x306E, 0x3093]:
if codePoint not in baseFont:
baseFont.selection.select(codePoint)
baseFont.paste()
# Finally, save the files!
basename = baseFont.fontname
if baseFont.version != '':
basename += '-' + baseFont.version
print('Saving TTF file...')
baseFont.generate(basename + '.ttf', 'ttf')
print('Saving SFD file...')
baseFont.save(basename + '.sfd')
print('Done!')