1065 lines
39 KiB
Python
Executable file
1065 lines
39 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""
|
|
C64 Character Set to TrueType Converter
|
|
Version 1.4
|
|
|
|
Copyright (c) 2013-2020, A.T.Brask (atbrask[at]gmail[dot]com)
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
|
|
1. Redistributions of source code must retain the above copyright notice, this
|
|
list of conditions and the following disclaimer.
|
|
2. 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.
|
|
|
|
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.
|
|
"""
|
|
|
|
import argparse
|
|
from datetime import date
|
|
import getpass
|
|
import math
|
|
import time
|
|
import array
|
|
import os
|
|
|
|
from fontTools.ttLib import TTFont, newTable
|
|
from fontTools.ttLib.tables import ttProgram
|
|
from fontTools.ttLib.tables._c_m_a_p import cmap_format_4, cmap_format_0
|
|
from fontTools.ttLib.tables._h_e_a_d import mac_epoch_diff
|
|
from fontTools.ttLib.tables._g_l_y_f import Glyph, GlyphCoordinates
|
|
from fontTools.ttLib.tables.O_S_2f_2 import Panose
|
|
from fontTools.ttLib.tables._n_a_m_e import NameRecord
|
|
|
|
# DATA SECTION
|
|
|
|
# Map from a subset of C64 PETSCII to ASCII as well as a few Unicode points.
|
|
# Not all PETSCII characters can be mapped into Unicode.
|
|
# Each tuple is [C64 character generator index, AGL name, [unicode codes]]
|
|
# The rest of the C64 glyphs can be included at 0xEE00...0xEFFF by using the
|
|
# command line argument --add-all
|
|
# The chars are in the C64 character generator order.
|
|
|
|
CHAR_HI = [[0, "at", [0x40]],
|
|
[1, "A", [0x41]],
|
|
[2, "B", [0x42]],
|
|
[3, "C", [0x43]],
|
|
[4, "D", [0x44]],
|
|
[5, "E", [0x45]],
|
|
[6, "F", [0x46]],
|
|
[7, "G", [0x47]],
|
|
[8, "H", [0x48]],
|
|
[9, "I", [0x49]],
|
|
[10, "J", [0x4a]],
|
|
[11, "K", [0x4b]],
|
|
[12, "L", [0x4c]],
|
|
[13, "M", [0x4d]],
|
|
[14, "N", [0x4e]],
|
|
[15, "O", [0x4f]],
|
|
[16, "P", [0x50]],
|
|
[17, "Q", [0x51]],
|
|
[18, "R", [0x52]],
|
|
[19, "S", [0x53]],
|
|
[20, "T", [0x54]],
|
|
[21, "U", [0x55]],
|
|
[22, "V", [0x56]],
|
|
[23, "W", [0x57]],
|
|
[24, "X", [0x58]],
|
|
[25, "Y", [0x59]],
|
|
[26, "Z", [0x5a]],
|
|
[27, "bracketleft", [0x5b]],
|
|
[28, "sterling", [0xa3]],
|
|
[29, "bracketright", [0x5d]],
|
|
[30, "arrowup", [0x2191]],
|
|
[31, "arrowleft", [0x2190]],
|
|
[32, "space", [0x20, 0xa0]],
|
|
[33, "exclam", [0x21]],
|
|
[34, "quotedblright", [0x22, 0x201c, 0x201d]],
|
|
[35, "numbersign", [0x23]],
|
|
[36, "dollar", [0x24]],
|
|
[37, "percent", [0x25]],
|
|
[38, "ampersand", [0x26]],
|
|
[39, "quoteright", [0x27, 0x92, 0x2019]],
|
|
[40, "parenleft", [0x28]],
|
|
[41, "parenright", [0x29]],
|
|
[42, "asterisk", [0x2a]],
|
|
[43, "plus", [0x2b]],
|
|
[44, "comma", [0x2c]],
|
|
[45, "hyphen", [0x2d]],
|
|
[46, "period", [0x2e]],
|
|
[47, "slash", [0x2f]],
|
|
[48, "zero", [0x30]],
|
|
[49, "one", [0x31]],
|
|
[50, "two", [0x32]],
|
|
[51, "three", [0x33]],
|
|
[52, "four", [0x34]],
|
|
[53, "five", [0x35]],
|
|
[54, "six", [0x36]],
|
|
[55, "seven", [0x37]],
|
|
[56, "eight", [0x38]],
|
|
[57, "nine", [0x39]],
|
|
[58, "colon", [0x3a]],
|
|
[59, "semicolon", [0x3b]],
|
|
[60, "less", [0x3c]],
|
|
[61, "equal", [0x3d]],
|
|
[62, "greater", [0x3e]],
|
|
[63, "question", [0x3f]],
|
|
[64, "SF100000", [0x2500, 0x2501]],
|
|
[65, "spade", [0x2660]],
|
|
[66, "SF110000", [0x2502, 0x2503]],
|
|
[73, "uni256e", [0x256e]],
|
|
[74, "uni2570", [0x2570]],
|
|
[75, "uni256F", [0x256f]],
|
|
[77, "uni2572", [0x2572]],
|
|
[78, "uni2571", [0x2571]],
|
|
[81, "periodcentered", [0xb7, 0x2022, 0x2219, 0x25cf]],
|
|
[83, "heart", [0x2665]],
|
|
[85, "uni256D", [0x256d]],
|
|
[86, "uni2573", [0x2573]],
|
|
[87, "circle", [0x25cb]],
|
|
[88, "club", [0x2663]],
|
|
[90, "diamond", [0x25c6, 0x2666]],
|
|
[91, "SF050000", [0x253c, 0x254b]],
|
|
[94, "pi", [0x3c0]],
|
|
[95, "uni25e5", [0x25e5]],
|
|
[97, "lfblock", [0x258c]],
|
|
[98, "dnblock", [0x2584]],
|
|
[99, "uni2594", [0x2594]],
|
|
[100, "uni2581", [0x2581]],
|
|
[101, "uni258E", [0x258e]],
|
|
[102, "shade", [0x2592]],
|
|
[105, "uni25E4", [0x25e4]],
|
|
[107, "SF080000", [0x251c, 0x2523]],
|
|
[108, "uni2597", [0x2597]],
|
|
[109, "SF020000", [0x2514, 0x2517]],
|
|
[110, "SF030000", [0x2510, 0x2513]],
|
|
[111, "uni2582", [0x2582]],
|
|
[112, "SF010000", [0x250c, 0x250f]],
|
|
[113, "SF070000", [0x2534, 0x253b]],
|
|
[114, "SF060000", [0x252c, 0x2533]],
|
|
[115, "SF090000", [0x2524, 0x252b]],
|
|
[117, "uni258D", [0x258d]],
|
|
[121, "uni2583", [0x2583]],
|
|
[123, "uni2596", [0x2596]],
|
|
[124, "uni259D", [0x259d]],
|
|
[125, "SF040000", [0x2518, 0x251b]],
|
|
[126, "uni2598", [0x2598]],
|
|
[127, "uni259A", [0x259a]],
|
|
[160, "uni2588", [0x2588]],
|
|
[223, "uni25E3", [0x25e3]],
|
|
[225, "uni2590", [0x2590]],
|
|
[226, "uni2580", [0x2580]],
|
|
[227, "uni2587", [0x2587]],
|
|
[231, "uni258A", [0x258a]],
|
|
[233, "uni25E2", [0x25e2]],
|
|
[236, "uni259B", [0x259b]],
|
|
[246, "uni258B", [0x258b]],
|
|
[247, "uni2586", [0x2586]],
|
|
[248, "uni2585", [0x2585]],
|
|
[251, "uni259C", [0x259c]],
|
|
[252, "uni2599", [0x2599]],
|
|
[254, "uni259F", [0x259f]],
|
|
[255, "uni259E", [0x259e]]]
|
|
|
|
CHAR_LO = [[0, "at", [0x40]],
|
|
[1, "a", [0x61]],
|
|
[2, "b", [0x62]],
|
|
[3, "c", [0x63]],
|
|
[4, "d", [0x64]],
|
|
[5, "e", [0x65]],
|
|
[6, "f", [0x66]],
|
|
[7, "g", [0x67]],
|
|
[8, "h", [0x68]],
|
|
[9, "i", [0x69]],
|
|
[10, "j", [0x6a]],
|
|
[11, "k", [0x6b]],
|
|
[12, "l", [0x6c]],
|
|
[13, "m", [0x6d]],
|
|
[14, "n", [0x6e]],
|
|
[15, "o", [0x6f]],
|
|
[16, "p", [0x70]],
|
|
[17, "q", [0x71]],
|
|
[18, "r", [0x72]],
|
|
[19, "s", [0x73]],
|
|
[20, "t", [0x74]],
|
|
[21, "u", [0x75]],
|
|
[22, "v", [0x76]],
|
|
[23, "w", [0x77]],
|
|
[24, "x", [0x78]],
|
|
[25, "y", [0x79]],
|
|
[26, "z", [0x7a]],
|
|
[27, "bracketleft", [0x5b]],
|
|
[28, "sterling", [0xa3]],
|
|
[29, "bracketright", [0x5d]],
|
|
[30, "arrowup", [0x2191]],
|
|
[31, "arrowleft", [0x2190]],
|
|
[32, "space", [0x20, 0xa0]],
|
|
[33, "exclam", [0x21]],
|
|
[34, "quotedblright", [0x22, 0x201c, 0x201d]],
|
|
[35, "numbersign", [0x23]],
|
|
[36, "dollar", [0x24]],
|
|
[37, "percent", [0x25]],
|
|
[38, "ampersand", [0x26]],
|
|
[39, "quoteright", [0x27, 0x92, 0x2019]],
|
|
[40, "parenleft", [0x28]],
|
|
[41, "parenright", [0x29]],
|
|
[42, "asterisk", [0x2a]],
|
|
[43, "plus", [0x2b]],
|
|
[44, "comma", [0x2c]],
|
|
[45, "hyphen", [0x2d]],
|
|
[46, "period", [0x2e]],
|
|
[47, "slash", [0x2f]],
|
|
[48, "zero", [0x30]],
|
|
[49, "one", [0x31]],
|
|
[50, "two", [0x32]],
|
|
[51, "three", [0x33]],
|
|
[52, "four", [0x34]],
|
|
[53, "five", [0x35]],
|
|
[54, "six", [0x36]],
|
|
[55, "seven", [0x37]],
|
|
[56, "eight", [0x38]],
|
|
[57, "nine", [0x39]],
|
|
[58, "colon", [0x3a]],
|
|
[59, "semicolon", [0x3b]],
|
|
[60, "less", [0x3c]],
|
|
[61, "equal", [0x3d]],
|
|
[62, "greater", [0x3e]],
|
|
[63, "question", [0x3f]],
|
|
[64, "SF100000", [0x2500, 0x2501]],
|
|
[65, "A", [0x41]],
|
|
[66, "B", [0x42]],
|
|
[67, "C", [0x43]],
|
|
[68, "D", [0x44]],
|
|
[69, "E", [0x45]],
|
|
[70, "F", [0x46]],
|
|
[71, "G", [0x47]],
|
|
[72, "H", [0x48]],
|
|
[73, "I", [0x49]],
|
|
[74, "J", [0x4a]],
|
|
[75, "K", [0x4b]],
|
|
[76, "L", [0x4c]],
|
|
[77, "M", [0x4d]],
|
|
[78, "N", [0x4e]],
|
|
[79, "O", [0x4f]],
|
|
[80, "P", [0x50]],
|
|
[81, "Q", [0x51]],
|
|
[82, "R", [0x52]],
|
|
[83, "S", [0x53]],
|
|
[84, "T", [0x54]],
|
|
[85, "U", [0x55]],
|
|
[86, "V", [0x56]],
|
|
[87, "W", [0x57]],
|
|
[88, "X", [0x58]],
|
|
[89, "Y", [0x59]],
|
|
[90, "Z", [0x5a]],
|
|
[91, "SF050000", [0x253c, 0x254b]],
|
|
[93, "SF110000", [0x2502, 0x2503]],
|
|
[97, "lfblock", [0x258c]],
|
|
[98, "dnblock", [0x2584]],
|
|
[99, "uni2594", [0x2594]],
|
|
[100, "uni2581", [0x2581]],
|
|
[101, "uni258E", [0x258e]],
|
|
[102, "shade", [0x2592]],
|
|
[107, "SF080000", [0x251c, 0x2523]],
|
|
[108, "uni2597", [0x2597]],
|
|
[109, "SF020000", [0x2514, 0x2517]],
|
|
[110, "SF030000", [0x2510, 0x2513]],
|
|
[111, "uni2582", [0x2582]],
|
|
[112, "SF010000", [0x250c, 0x250f]],
|
|
[113, "SF070000", [0x2534, 0x253b]],
|
|
[114, "SF060000", [0x252c, 0x2533]],
|
|
[115, "SF090000", [0x2524, 0x252b]],
|
|
[117, "uni258D", [0x258d]],
|
|
[121, "uni2583", [0x2583]],
|
|
[122, "uni2713", [0x2713]],
|
|
[123, "uni2596", [0x2596]],
|
|
[124, "uni259D", [0x259d]],
|
|
[125, "SF040000", [0x2518, 0x251b]],
|
|
[126, "uni2598", [0x2598]],
|
|
[127, "uni259A", [0x259a]],
|
|
[160, "uni2588", [0x2588]],
|
|
[225, "uni2590", [0x2590]],
|
|
[226, "uni2580", [0x2580]],
|
|
[227, "uni2587", [0x2587]],
|
|
[231, "uni258A", [0x258a]],
|
|
[236, "uni259B", [0x259b]],
|
|
[246, "uni258B", [0x258b]],
|
|
[247, "uni2586", [0x2586]],
|
|
[248, "uni2585", [0x2585]],
|
|
[251, "uni259C", [0x259c]],
|
|
[252, "uni2599", [0x2599]],
|
|
[254, "uni259F", [0x259f]],
|
|
[255, "uni259E", [0x259e]]]
|
|
|
|
# The Mac OS Roman mapping is an 8-bit encoding including 7-bit ASCII and a few
|
|
# bits and pieces. The format is [mac roman code, glyph name]
|
|
# Indices not in this table will be mapped to ".notdef"
|
|
|
|
CMAP_MACROMAN = [[0x0, ".null"],
|
|
[0x8, ".null"],
|
|
[0x9, "nonmarkingreturn"],
|
|
[0xd, "nonmarkingreturn"],
|
|
[0x1d, ".null"],
|
|
[0x20, "space"],
|
|
[0x21, "exclam"],
|
|
[0x23, "numbersign"],
|
|
[0x24, "dollar"],
|
|
[0x25, "percent"],
|
|
[0x26, "ampersand"],
|
|
[0x28, "parenleft"],
|
|
[0x29, "parenright"],
|
|
[0x2a, "asterisk"],
|
|
[0x2b, "plus"],
|
|
[0x2c, "comma"],
|
|
[0x2d, "hyphen"],
|
|
[0x2e, "period"],
|
|
[0x2f, "slash"],
|
|
[0x30, "zero"],
|
|
[0x31, "one"],
|
|
[0x32, "two"],
|
|
[0x33, "three"],
|
|
[0x34, "four"],
|
|
[0x35, "five"],
|
|
[0x36, "six"],
|
|
[0x37, "seven"],
|
|
[0x38, "eight"],
|
|
[0x39, "nine"],
|
|
[0x3a, "colon"],
|
|
[0x3b, "semicolon"],
|
|
[0x3c, "less"],
|
|
[0x3d, "equal"],
|
|
[0x3e, "greater"],
|
|
[0x3f, "question"],
|
|
[0x40, "at"],
|
|
[0x41, "A"],
|
|
[0x42, "B"],
|
|
[0x43, "C"],
|
|
[0x44, "D"],
|
|
[0x45, "E"],
|
|
[0x46, "F"],
|
|
[0x47, "G"],
|
|
[0x48, "H"],
|
|
[0x49, "I"],
|
|
[0x4a, "J"],
|
|
[0x4b, "K"],
|
|
[0x4c, "L"],
|
|
[0x4d, "M"],
|
|
[0x4e, "N"],
|
|
[0x4f, "O"],
|
|
[0x50, "P"],
|
|
[0x51, "Q"],
|
|
[0x52, "R"],
|
|
[0x53, "S"],
|
|
[0x54, "T"],
|
|
[0x55, "U"],
|
|
[0x56, "V"],
|
|
[0x57, "W"],
|
|
[0x58, "X"],
|
|
[0x59, "Y"],
|
|
[0x5a, "Z"],
|
|
[0x5b, "bracketleft"],
|
|
[0x5c, "backslash"],
|
|
[0x5d, "bracketright"],
|
|
[0x5e, "asciicircum"],
|
|
[0x5f, "underscore"],
|
|
[0x60, "grave"],
|
|
[0x61, "a"],
|
|
[0x62, "b"],
|
|
[0x63, "c"],
|
|
[0x64, "d"],
|
|
[0x65, "e"],
|
|
[0x66, "f"],
|
|
[0x67, "g"],
|
|
[0x68, "h"],
|
|
[0x69, "i"],
|
|
[0x6a, "j"],
|
|
[0x6b, "k"],
|
|
[0x6c, "l"],
|
|
[0x6d, "m"],
|
|
[0x6e, "n"],
|
|
[0x6f, "o"],
|
|
[0x70, "p"],
|
|
[0x71, "q"],
|
|
[0x72, "r"],
|
|
[0x73, "s"],
|
|
[0x74, "t"],
|
|
[0x75, "u"],
|
|
[0x76, "v"],
|
|
[0x77, "w"],
|
|
[0x78, "x"],
|
|
[0x79, "y"],
|
|
[0x7a, "z"],
|
|
[0x7b, "braceleft"],
|
|
[0x7c, "bar"],
|
|
[0x7d, "braceright"],
|
|
[0x7e, "asciitilde"],
|
|
[0x81, "Aring"],
|
|
[0x8c, "aring"],
|
|
[0xa3, "sterling"],
|
|
[0xae, "AE"],
|
|
[0xaf, "Oslash"],
|
|
[0xb9, "pi"],
|
|
[0xbe, "ae"],
|
|
[0xbf, "oslash"],
|
|
[0xca, "space"],
|
|
[0xd3, "quotedblright"],
|
|
[0xd5, "quoteright"]]
|
|
|
|
def makeEmptyGlyphs():
|
|
bitmap = dict()
|
|
|
|
bitmap[".notdef"] = [[0b00000000,
|
|
0b01111110,
|
|
0b01000010,
|
|
0b01000010,
|
|
0b01000010,
|
|
0b01000010,
|
|
0b01111110,
|
|
0b00000000], []]
|
|
|
|
bitmap[".null"] = [[],[]]
|
|
|
|
bitmap["nonmarkingreturn"] = [[],[]]
|
|
|
|
return bitmap
|
|
|
|
def makeMissingDanishChars():
|
|
print("Adding Danish characters...")
|
|
bitmap = dict()
|
|
bitmap["ae"] = [[0b00000000,
|
|
0b00000000,
|
|
0b01110110,
|
|
0b00011011,
|
|
0b01111111,
|
|
0b11011000,
|
|
0b01111110,
|
|
0b00000000], [0xe6]]
|
|
|
|
bitmap["oslash"] = [[0b00000000,
|
|
0b00000000,
|
|
0b00111011,
|
|
0b01101110,
|
|
0b01111110,
|
|
0b01110110,
|
|
0b11011100,
|
|
0b00000000], [0xf8]]
|
|
|
|
bitmap["aring"] = [[0b00011000,
|
|
0b00000000,
|
|
0b00111100,
|
|
0b00000110,
|
|
0b00111110,
|
|
0b01100110,
|
|
0b00111110,
|
|
0b00000000], [0xe5]]
|
|
|
|
bitmap["AE"] = [[0b00011111,
|
|
0b00111100,
|
|
0b01101100,
|
|
0b01111111,
|
|
0b01101100,
|
|
0b01101100,
|
|
0b01101111,
|
|
0b00000000], [0xc6]]
|
|
|
|
bitmap["Oslash"] = [[0b00111011,
|
|
0b01101110,
|
|
0b01101110,
|
|
0b01111110,
|
|
0b01110110,
|
|
0b01110110,
|
|
0b11011100,
|
|
0b00000000], [0xd8]]
|
|
|
|
bitmap["Aring"] = [[0b00011000,
|
|
0b00000000,
|
|
0b00111100,
|
|
0b01100110,
|
|
0b01111110,
|
|
0b01100110,
|
|
0b01100110,
|
|
0b00000000], [0xc5]]
|
|
return bitmap
|
|
|
|
def makeMissingASCII():
|
|
print("Adding the 8 missing ASCII characters in C64 PETSCII...")
|
|
bitmap = dict()
|
|
|
|
# Grave accent
|
|
bitmap["grave"] = [[0b00100000,
|
|
0b00010000,
|
|
0b00001000,
|
|
0b00000000,
|
|
0b00000000,
|
|
0b00000000,
|
|
0b00000000,
|
|
0b00000000], [0x60]]
|
|
|
|
# Curly left brace
|
|
bitmap["braceleft"] = [[0b00001100,
|
|
0b00011000,
|
|
0b00011000,
|
|
0b00110000,
|
|
0b00011000,
|
|
0b00011000,
|
|
0b00001100,
|
|
0b00000000], [0x7b]]
|
|
|
|
# Curly right brace
|
|
bitmap["braceright"] = [[0b00110000,
|
|
0b00011000,
|
|
0b00011000,
|
|
0b00001100,
|
|
0b00011000,
|
|
0b00011000,
|
|
0b00110000,
|
|
0b00000000], [0x7d]]
|
|
|
|
# Vertical bar
|
|
bitmap["bar"] = [[0b00011000,
|
|
0b00011000,
|
|
0b00011000,
|
|
0b00011000,
|
|
0b00011000,
|
|
0b00011000,
|
|
0b00011000,
|
|
0b00011000], [0x7c]]
|
|
|
|
# Tilde
|
|
bitmap["asciitilde"] = [[0b00000000,
|
|
0b00000000,
|
|
0b00000000,
|
|
0b00111001,
|
|
0b01001110,
|
|
0b00000000,
|
|
0b00000000,
|
|
0b00000000], [0x7e]]
|
|
|
|
# Caret
|
|
bitmap["asciicircum"] = [[0b00001000,
|
|
0b00011100,
|
|
0b00110110,
|
|
0b01100011,
|
|
0b01000001,
|
|
0b00000000,
|
|
0b00000000,
|
|
0b00000000], [0x5e]]
|
|
|
|
# Backslash
|
|
bitmap["backslash"] = [[0b00000000,
|
|
0b01100000,
|
|
0b00110000,
|
|
0b00011000,
|
|
0b00001100,
|
|
0b00000110,
|
|
0b00000011,
|
|
0b00000000], [0x5c]]
|
|
|
|
# Underscore
|
|
bitmap["underscore"] = [[0b00000000,
|
|
0b00000000,
|
|
0b00000000,
|
|
0b00000000,
|
|
0b00000000,
|
|
0b00000000,
|
|
0b00000000,
|
|
0b11111111], [0x5f]]
|
|
|
|
return bitmap
|
|
|
|
# THE VECTORIZATION ALGORITHM
|
|
|
|
def vectorizeGlyph(glyphData, pixelSize, descent):
|
|
if glyphData is None or len(glyphData) == 0:
|
|
return []
|
|
|
|
bitmap = unpackChar(glyphData)
|
|
edges = generateEdges(bitmap)
|
|
scaledEdges = scaleEdges(edges, pixelSize, descent)
|
|
return mergeContours(scaledEdges)
|
|
|
|
# 1) First, we need to unpack the bits in the bitmap and reverse the order so
|
|
# that [0,0] is the lower left corner. This will make the next step easier
|
|
# to understand.
|
|
def unpackChar(glyphData):
|
|
return [[((row >> bit) & 1) == 1 for bit in range(7, -1, -1)] for row in reversed(glyphData)]
|
|
|
|
# 2) A simple way of vectorizing a b/w bitmap is to simply generate (up to)
|
|
# four edges going clockwise around each opaque pixel.
|
|
def generateEdges(bitmap):
|
|
edges = []
|
|
height = len(bitmap)
|
|
width = len(bitmap[0])
|
|
for y in range(height):
|
|
for x in range(width):
|
|
if bitmap[y][x]:
|
|
# Insert edge to the left of the pixel?
|
|
if x == 0 or not bitmap[y][x - 1]:
|
|
edges.append([[x, y], [x, y + 1]])
|
|
|
|
# Insert edge above the pixel?
|
|
if y == height - 1 or not bitmap[y + 1][x]:
|
|
edges.append([[x, y + 1], [x + 1, y + 1]])
|
|
|
|
# Insert edge to the right of the pixel?
|
|
if x == width - 1 or not bitmap[y][x + 1]:
|
|
edges.append([[x + 1, y + 1], [x + 1, y]])
|
|
|
|
# Insert edge below the pixel?
|
|
if y == 0 or not bitmap[y - 1][x]:
|
|
edges.append([[x + 1, y], [x, y]])
|
|
return edges
|
|
|
|
# 3) Scaling and translation of the vectorized pixels.
|
|
def scaleEdges(edges, pixelSize, descent):
|
|
return [[[point[0] * pixelSize, (point[1] - descent) * pixelSize] for point in edge] for edge in edges]
|
|
|
|
# 4) In a TTF file we have to provide contours rather than just a bunch of
|
|
# unordered edges. If possible, consecutive edges are merged.
|
|
def mergeContours(edges):
|
|
edgeList = sorted(edges)
|
|
contours = []
|
|
|
|
while len(edgeList) > 1:
|
|
start = edgeList[0]
|
|
edgeList.remove(start)
|
|
current = start
|
|
contour = [start[0], start[1]]
|
|
|
|
while current[1] != start[0]:
|
|
next = [edge for edge in edgeList if edge[0] == current[1]][0]
|
|
if next[1][0] == current[0][0] or next[1][1] == current[0][1]:
|
|
contour[-1] = next[1]
|
|
else:
|
|
contour.append(next[1])
|
|
current = next
|
|
edgeList.remove(current)
|
|
|
|
# We don't need to include the last edge as it's implicit.
|
|
contours.append(contour[:-1])
|
|
|
|
return contours
|
|
|
|
# TRUETYPE FONT HANDLING
|
|
|
|
def saveFont(glyphs, outputFileName, asXML, pixelSize, descent, fontName, copyrightYear, creator, version):
|
|
f = TTFont()
|
|
|
|
vectorizedGlyphs = {glyph : [vectorizeGlyph(glyphs[glyph][0], pixelSize, descent), glyphs[glyph][1]] for glyph in glyphs}
|
|
unicodes = [code for glyph in glyphs for code in glyphs[glyph][1]]
|
|
|
|
# Populate basic tables (there are a few dependencies so order matters)
|
|
makeTable_glyf(f, vectorizedGlyphs)
|
|
makeTable_maxp(f)
|
|
makeTable_loca(f)
|
|
makeTable_head(f)
|
|
makeTable_hmtx(f)
|
|
makeTable_hhea(f, pixelSize, descent)
|
|
makeTable_OS2(f, pixelSize, descent, min(unicodes), max(unicodes))
|
|
makeTable_cmap(f, glyphs)
|
|
makeTable_name(f, fontName, "Regular", copyrightYear, creator, version)
|
|
makeTable_post(f, pixelSize, descent)
|
|
|
|
if asXML:
|
|
# We have to compile the TTFont manually when saving as TTX
|
|
# (to auto-calculate stuff here and there)
|
|
f["glyf"].compile(f)
|
|
f["maxp"].compile(f)
|
|
f["loca"].compile(f)
|
|
f["head"].compile(f)
|
|
f["hmtx"].compile(f)
|
|
f["hhea"].compile(f)
|
|
f["OS/2"].compile(f)
|
|
f["cmap"].compile(f)
|
|
f["name"].compile(f)
|
|
f["post"].compile(f)
|
|
print("PLEASE NOTE: When exporting directly to XML, the checkSumAdjustment value in the head table will be 0.")
|
|
f.saveXML(outputFileName)
|
|
else:
|
|
f.save(outputFileName)
|
|
|
|
# glyf - Glyph Data
|
|
def makeTable_glyf(ttf, glyphs):
|
|
|
|
glyf = newTable("glyf")
|
|
|
|
glyf.glyphs = {glyph: makeTTFGlyph(glyphs[glyph][0]) for glyph in glyphs}
|
|
|
|
# We need to sort the glyphs in a specific way (so all basic glyphs are below index 256) to work around a MacRoman related quirk.
|
|
glyf.glyphOrder = sorted([key for key in glyf.glyphs.keys() if not key.startswith('uni')])
|
|
glyf.glyphOrder += sorted([key for key in glyf.glyphs.keys() if key.startswith('uni')])
|
|
|
|
ttf["glyf"] = glyf
|
|
ttf.glyphOrder = glyf.glyphOrder
|
|
|
|
def makeTTFGlyph(polygons):
|
|
result = Glyph()
|
|
result.numberOfContours = len(polygons)
|
|
result.coordinates = GlyphCoordinates([coordinate for polygon in polygons for coordinate in polygon])
|
|
result.flags = array.array("B", [1] * len(result.coordinates))
|
|
result.endPtsOfContours = [sum(len(polygon) for polygon in polygons[:idx + 1]) - 1 for idx in range(len(polygons))]
|
|
result.program = ttProgram.Program()
|
|
result.program.assembly = []
|
|
return result
|
|
|
|
# maxp - Maximum Profile
|
|
def makeTable_maxp(ttf):
|
|
maxp = newTable("maxp")
|
|
|
|
maxp.tableVersion = 0x00010000
|
|
maxp.numGlyphs = 0 # Auto-calculated by maxp.compile()
|
|
maxp.maxPoints = 0 # Auto-calculated by maxp.compile()
|
|
maxp.maxContours = 0 # Auto-calculated by maxp.compile()
|
|
maxp.maxCompositePoints = 0 # Auto-calculated by maxp.compile()
|
|
maxp.maxCompositeContours = 0 # Auto-calculated by maxp.compile()
|
|
maxp.maxZones = 2
|
|
maxp.maxTwilightPoints = 0
|
|
maxp.maxStorage = 0
|
|
maxp.maxFunctionDefs = 0
|
|
maxp.maxInstructionDefs = 0
|
|
maxp.maxStackElements = 0
|
|
maxp.maxSizeOfInstructions = 0
|
|
maxp.maxComponentElements = 0
|
|
maxp.maxComponentDepth = 0 # Auto-calculated by maxp.compile()
|
|
|
|
ttf["maxp"] = maxp
|
|
|
|
# loca - Index to Location
|
|
def makeTable_loca(ttf):
|
|
# Nothing to do here... Locations are auto-calculated by glyf.compile()
|
|
ttf["loca"] = newTable("loca")
|
|
|
|
# head - Font Header
|
|
def makeTable_head(ttf):
|
|
head = newTable("head")
|
|
|
|
head.tableVersion = 1.0
|
|
head.fontRevision = 1.0
|
|
head.checkSumAdjustment = 0 # Auto-calculated when writing the TTF.
|
|
head.magicNumber = 0x5F0F3CF5
|
|
head.flags = 11 # bits 0, 1, and 3 = 1 + 2 + 8 = 11
|
|
head.unitsPerEm = 2048
|
|
head.created = int(time.time() - mac_epoch_diff)
|
|
head.modified = int(time.time() - mac_epoch_diff)
|
|
head.xMin = 0 # Auto-calculated by maxp.compile()
|
|
head.xMax = 0 # Auto-calculated by maxp.compile()
|
|
head.yMin = 0 # Auto-calculated by maxp.compile()
|
|
head.yMax = 0 # Auto-calculated by maxp.compile()
|
|
head.macStyle = 0
|
|
head.lowestRecPPEM = 8
|
|
head.fontDirectionHint = 0
|
|
head.indexToLocFormat = 0
|
|
head.glyphDataFormat = 0
|
|
|
|
ttf["head"] = head
|
|
|
|
# hmtx - Horizontal Metrics
|
|
def makeTable_hmtx(ttf):
|
|
hmtx = newTable("hmtx")
|
|
hmtx.metrics = dict()
|
|
|
|
for glyphName in ttf["glyf"].keys():
|
|
if glyphName == ".null":
|
|
hmtx[".null"] = (0, 0)
|
|
else:
|
|
glyph = ttf["glyf"].glyphs[glyphName]
|
|
lsb = 0
|
|
if hasattr(glyph, "coordinates") and len(glyph.coordinates) > 0:
|
|
lsb = min([coord[0] for coord in glyph.coordinates])
|
|
hmtx[glyphName] = (2048, lsb)
|
|
|
|
ttf["hmtx"] = hmtx
|
|
|
|
# hhea - Horizontal Header
|
|
def makeTable_hhea(ttf, pixelSize, descent):
|
|
hhea = newTable("hhea")
|
|
|
|
hhea.tableVersion = 1.0
|
|
hhea.ascent = (8 - descent) * pixelSize
|
|
hhea.descent = -descent * pixelSize
|
|
hhea.lineGap = 0
|
|
hhea.advanceWidthMax = 0 # Auto-calculated by hhea.compile()
|
|
hhea.minLeftSideBearing = 0 # Auto-calculated by hhea.compile()
|
|
hhea.minRightSideBearing = 0 # Auto-calculated by hhea.compile()
|
|
hhea.xMaxExtent = 0 # Auto-calculated by hhea.compile()
|
|
hhea.caretSlopeRise = 1
|
|
hhea.caretSlopeRun = 0
|
|
hhea.caretOffset = 0
|
|
hhea.reserved0 = 0
|
|
hhea.reserved1 = 0
|
|
hhea.reserved2 = 0
|
|
hhea.reserved3 = 0
|
|
hhea.metricDataFormat = 0
|
|
hhea.numOfLongHorMetrics = 0 # Auto-calculated by hmtx.compile()
|
|
|
|
ttf["hhea"] = hhea
|
|
|
|
# OS/2 - OS/2 and Windows Specific Metrics
|
|
def makeTable_OS2(ttf, pixelSize, descentPixels, minUnicode, maxUnicode):
|
|
size = 8 * pixelSize
|
|
descent = pixelSize * descentPixels
|
|
|
|
os_2 = newTable("OS/2")
|
|
|
|
os_2.version = 4
|
|
os_2.xAvgCharWidth = size
|
|
os_2.usWeightClass = 400 # Meaning "Normal (Regular)"
|
|
os_2.usWidthClass = 5 # Meaing "Medium (normal)"
|
|
os_2.fsType = 0 # Windows-only licensing bits...
|
|
os_2.ySubscriptXSize = size
|
|
os_2.ySubscriptYSize = size >> 1
|
|
os_2.ySubscriptXOffset = 0
|
|
os_2.ySubscriptYOffset = descent
|
|
os_2.ySuperscriptXSize = size >> 1
|
|
os_2.ySuperscriptYSize = size >> 1
|
|
os_2.ySuperscriptXOffset = 0
|
|
os_2.ySuperscriptYOffset = size >> 1
|
|
os_2.yStrikeoutSize = pixelSize
|
|
os_2.yStrikeoutPosition = (size >> 1) - descent
|
|
os_2.sFamilyClass = 0x080a # Class ID = 8 (Sans Serif), Subclass ID = 10 (Matrix)
|
|
panose = Panose()
|
|
panose.bFamilyType = 2 # Text and Display
|
|
panose.bSerifStyle = 1 # No Fit
|
|
panose.bWeight = 6 # Medium
|
|
panose.bProportion = 9 # Monospaced
|
|
panose.bContrast = 6 # Medium
|
|
panose.bStrokeVariation = 2 # Gradual/Diagonal
|
|
panose.bArmStyle = 2 # Straight Arms/Horizontal
|
|
panose.bLetterForm = 8 # Normal/Square
|
|
panose.bMidline = 1 # No Fit
|
|
panose.bXHeight = 1 # No Fit
|
|
os_2.panose = panose
|
|
os_2.ulUnicodeRange1 = 0b10000000000000000000000010000011 # Basic Latin + Latin-1 supplement + Greek and Coptic + General punctuation
|
|
os_2.ulUnicodeRange2 = 0b00010000000000001111100001100000 # Arrows + Mathematical operators + Box drawing + Block elements + Geometric shapes + Misc. symbols + Dingbats + Private use area
|
|
os_2.ulUnicodeRange3 = 0b00000000000000000000000000000000 # n/a
|
|
os_2.ulUnicodeRange4 = 0b00000000000000000000000000000000 # n/a
|
|
os_2.achVendID = "C=64" # :-)
|
|
os_2.fsSelection = 64 # Regular
|
|
os_2.fsFirstCharIndex = minUnicode
|
|
os_2.fsLastCharIndex = maxUnicode
|
|
os_2.sTypoAscender = size - descent
|
|
os_2.sTypoDescender = 0 - descent
|
|
os_2.sTypoLineGap = 0
|
|
os_2.usWinAscent = size - descent
|
|
os_2.usWinDescent = descent
|
|
os_2.ulCodePageRange1 = 0b00000000000000000000000000000001 # Latin 1 (Code page 1252)
|
|
os_2.ulCodePageRange2 = 0b11000000000000000000000000000000 # WE/Latin 1 (Code page 850) + US (Code page 437)
|
|
os_2.sxHeight = 6 * pixelSize - descent # Guess (we don't always have a lower-case "x" at 0x78 to measure (as the standard suggests))
|
|
os_2.sCapHeight = size - descent
|
|
os_2.usDefaultChar = 0
|
|
os_2.usBreakChar = 32
|
|
os_2.usMaxContex = 0
|
|
|
|
ttf["OS/2"] = os_2
|
|
|
|
# cmap - Character to Glyph Mapping
|
|
def makeTable_cmap(ttf, glyphs):
|
|
unicodeCMAP = {index: glyph for glyph in glyphs if glyph in ttf["glyf"].glyphs for index in glyphs[glyph][1]}
|
|
macRoman = dict(CMAP_MACROMAN)
|
|
macRomanCMAP = {index: macRoman[index] if index in macRoman and macRoman[index] in ttf["glyf"].glyphs else '.notdef' for index in range(256)}
|
|
|
|
# Unicode
|
|
cmap4_0_3 = cmap_format_4(4)
|
|
cmap4_0_3.platformID = 0
|
|
cmap4_0_3.platEncID = 3
|
|
cmap4_0_3.language = 0
|
|
cmap4_0_3.cmap = unicodeCMAP
|
|
|
|
# Mac Roman
|
|
cmap0_1_0 = cmap_format_0(0)
|
|
cmap0_1_0.platformID = 1
|
|
cmap0_1_0.platEncID = 0
|
|
cmap0_1_0.language = 0
|
|
cmap0_1_0.cmap = macRomanCMAP
|
|
|
|
# Windows
|
|
cmap4_3_1 = cmap_format_4(4)
|
|
cmap4_3_1.platformID = 3
|
|
cmap4_3_1.platEncID = 1
|
|
cmap4_3_1.language = 0
|
|
cmap4_3_1.cmap = unicodeCMAP
|
|
|
|
cmap = newTable("cmap")
|
|
cmap.tableVersion = 0
|
|
cmap.tables = [cmap4_0_3, cmap0_1_0, cmap4_3_1]
|
|
ttf["cmap"] = cmap
|
|
|
|
# name - Naming Table
|
|
def makeTable_name(ttf, fontName, subFamily, copyrightYear, creator, version):
|
|
copyright = "Copyright {0} {1}".format(copyrightYear, creator)
|
|
fullName = "{0} {1}".format(fontName, subFamily)
|
|
uniqueID = "{0} {1}".format(creator, fullName)
|
|
versionText = "Version {0}".format(version)
|
|
psFontName = "".join([b for b in "{0}-{1}".format(fontName, creator) if 32 < ord(b) < 127 and b not in '[](){}<>/%'])[:63]
|
|
nameEntries = [copyright, fontName, subFamily, uniqueID, fullName, versionText, psFontName]
|
|
|
|
unicodeEnc = [0, 3, 0, "utf_16_be"]
|
|
macintoshEnc = [1, 0, 0, "latin1"]
|
|
microsoftEnc = [3, 1, 0x409, "utf_16_be"]
|
|
encodings = [unicodeEnc, macintoshEnc, microsoftEnc]
|
|
|
|
name = newTable("name")
|
|
name.names = [makeNameRecord(idx, entry, *conf) for idx, entry in enumerate(nameEntries) for conf in encodings]
|
|
|
|
ttf["name"] = name
|
|
|
|
def makeNameRecord(nameID, string, platformID, platEncID, langID, encoding):
|
|
rec = NameRecord()
|
|
rec.nameID = nameID
|
|
rec.platformID = platformID
|
|
rec.platEncID = platEncID
|
|
rec.langID = langID
|
|
rec.string = str(string).encode(encoding)
|
|
return rec
|
|
|
|
# post - Postscript Information
|
|
def makeTable_post(ttf, pixelSize, descent):
|
|
post = newTable("post")
|
|
|
|
post.glyphOrder = []
|
|
post.extraNames = []
|
|
post.mapping = dict()
|
|
|
|
post.formatType = 2
|
|
post.italicAngle = 0
|
|
post.underlinePosition = descent
|
|
post.underlineThickness = pixelSize
|
|
post.isFixedPitch = 1
|
|
post.minMemType42 = 0
|
|
post.maxMemType42 = 0
|
|
post.minMemType1 = 0
|
|
post.maxMemType1 = 0
|
|
|
|
ttf["post"] = post
|
|
|
|
# MAIN METHODS
|
|
|
|
def readCharBitmaps(fileName):
|
|
if fileName is None:
|
|
return []
|
|
|
|
print("Processing input file {0}...".format(fileName))
|
|
data = open(fileName, "rb").read()
|
|
|
|
# Shave off magic bytes and append zeroes so the length of the remaining
|
|
# data is an integer multiple of 8.
|
|
#data = data[2:]
|
|
while len(data) % 8 != 0:
|
|
data += bytes([0])
|
|
|
|
if len(data) == 0:
|
|
print("No data found. ")
|
|
return []
|
|
elif len(data) > 2048:
|
|
print("More than 256 chars detected. Are you sure this is a C64 character set???")
|
|
return []
|
|
else:
|
|
print("{0} glyphs loaded...".format(int(len(data) / 8)))
|
|
return [data[idx:idx + 8] for idx in range(0, len(data), 8)]
|
|
|
|
def mapGlyphs(glyphData, charset):
|
|
return {char[1] : [glyphData[char[0]], char[2]] for char in charset if char[0] < len(glyphData)}
|
|
|
|
def mapAllGlyphs(existingGlyphs, newGlyphBitmaps, unicodeOffset):
|
|
newGlyphs = {"uni{0}".format(hex(unicodeOffset + index).upper()[2:]) : [data, [unicodeOffset + index]] for index, data in enumerate(newGlyphBitmaps)}
|
|
updates = {}
|
|
|
|
for newGlyph in newGlyphs:
|
|
newBitmap = newGlyphs[newGlyph][0]
|
|
newUnicodes = newGlyphs[newGlyph][1]
|
|
foundGlyph = False
|
|
for oldGlyph in existingGlyphs:
|
|
oldBitmap = existingGlyphs[oldGlyph][0]
|
|
oldUnicodes = existingGlyphs[oldGlyph][1]
|
|
if oldBitmap == newBitmap:
|
|
if oldGlyph in updates:
|
|
updates[oldGlyph] = [oldBitmap, list(set(updates[oldGlyph][1] + newUnicodes))]
|
|
else:
|
|
updates[oldGlyph] = [oldBitmap, list(set(oldUnicodes + newUnicodes))]
|
|
foundGlyph = True
|
|
break
|
|
if not foundGlyph:
|
|
updates[newGlyph] = newGlyphs[newGlyph]
|
|
|
|
return updates
|
|
|
|
def processCharFiles(lowercaseInputFileName, uppercaseInputFileName, outputFileName, asXML, addMissingASCII, addMissingDanish, pixelSize, descent, addAll, fontName, copyrightYear, creator, version):
|
|
glyphs = makeEmptyGlyphs()
|
|
lowercaseBitmaps = []
|
|
uppercaseBitmaps = []
|
|
|
|
if uppercaseInputFileName is not None:
|
|
uppercaseBitmaps = readCharBitmaps(uppercaseInputFileName)
|
|
glyphs.update(mapGlyphs(uppercaseBitmaps, CHAR_HI))
|
|
|
|
if lowercaseInputFileName is not None:
|
|
lowercaseBitmaps = readCharBitmaps(lowercaseInputFileName)
|
|
glyphs.update(mapGlyphs(lowercaseBitmaps, CHAR_LO))
|
|
|
|
if addMissingASCII:
|
|
glyphs.update(makeMissingASCII())
|
|
|
|
if addMissingDanish:
|
|
glyphs.update(makeMissingDanishChars())
|
|
|
|
if addAll:
|
|
glyphs.update(mapAllGlyphs(glyphs, uppercaseBitmaps, 0xee00))
|
|
glyphs.update(mapAllGlyphs(glyphs, lowercaseBitmaps, 0xef00))
|
|
|
|
saveFont(glyphs, outputFileName, asXML, pixelSize, descent, fontName, copyrightYear, creator, version)
|
|
|
|
# "static void main()"
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description="c64ttf.py v1.4 - C64 Character Set to TrueType Converter (c) 2013-20 atbrask")
|
|
|
|
# Files
|
|
parser.add_argument("-l", "--lowercase", help="Input 64C file with lowercase and uppercase characters.")
|
|
parser.add_argument("-u", "--uppercase", help="Input 64C file with uppercase and graphics characters.")
|
|
parser.add_argument("-o", "--output", help="Output filename (default is font name + '.TTF' or '.TTX')")
|
|
parser.add_argument("-x", "--xml", help="Enable XML output (for debugging purposes)", action="store_true")
|
|
parser.add_argument("-m", "--add-missing-ascii", help="Add non-PETSCII characters for ASCII compatibility (ie. grave accent, curly braces, vertical bar, tilde, caret, backslash, and underscore)", action="store_true")
|
|
parser.add_argument("-i", "--add-missing-danish", help="Add special Danish characters. Needed for proper compatibility with the Danish version of MAC OSX.", action="store_true")
|
|
|
|
# Vectorization
|
|
parser.add_argument("-p", "--pixelsize", help="Pixel size in the resulting TTF file (default is 256)", default=256)
|
|
parser.add_argument("-d", "--descent", help="The descent below baseline in pixels (default is 1)", default=1)
|
|
|
|
# Font stuff
|
|
parser.add_argument("-a", "--add-all", help="Inserts the uppercase character set (if any) at 0xEE00...0xEEFF and the lowercase character set (if any) at 0xEF00...0xEFFF", action="store_true")
|
|
parser.add_argument("-n", "--name", help="Font name (default is C64)")
|
|
parser.add_argument("-y", "--copyrightyear", help="Sets copyright year (default is {0})".format(date.today().year), default=date.today().year)
|
|
parser.add_argument("-c", "--creator", help="Font creator (default is '{0}')".format(getpass.getuser()), default=getpass.getuser())
|
|
parser.add_argument("-v", "--version", help="Sets font version number (default is '1.00')", default="1.00")
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.lowercase is None and args.uppercase is None:
|
|
parser.print_help()
|
|
print("")
|
|
print("No input files! Aborting...")
|
|
exit(1)
|
|
|
|
fontName = args.name
|
|
if fontName is None:
|
|
fontName = "C64"
|
|
|
|
outputFileName = args.output
|
|
if outputFileName is None:
|
|
if args.xml:
|
|
outputFileName = fontName + ".ttx"
|
|
else:
|
|
outputFileName = fontName + ".ttf"
|
|
|
|
processCharFiles(args.lowercase, args.uppercase, outputFileName, args.xml, args.add_missing_ascii, args.add_missing_danish, int(args.pixelsize), int(args.descent), args.add_all, fontName, int(args.copyrightyear), args.creator, args.version)
|