1 #!/usr/bin/env python 2 3 """ 4 Convert GNU Unifont format definitions into the .tff font format used by L4Re. 5 Note that this .tff format is *not* .ttf, the latter being TrueType format. 6 Searching the Internet for .tff will yield lots of .ttf nonsense. 7 8 Copyright (C) 2017, 2018 Paul Boddie <paul@boddie.org.uk> 9 10 This program is free software; you can redistribute it and/or modify it under 11 the terms of the GNU General Public License as published by the Free Software 12 Foundation; either version 3 of the License, or (at your option) any later 13 version. 14 15 This program is distributed in the hope that it will be useful, but WITHOUT ANY 16 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 17 PARTICULAR PURPOSE. See the GNU General Public License for more details. 18 19 You should have received a copy of the GNU General Public License along 20 with this program. If not, see <http://www.gnu.org/licenses/>. 21 """ 22 23 from os.path import isfile, split 24 import sys 25 26 def convert_font(fin, fout, points, missing=32): 27 28 """ 29 Convert the font obtained via 'fin' to an assembly language representation, 30 writing to 'fout' the characters for the chosen sequence of 'points'. 31 32 A table of offsets is written to permit each character to be found in the 33 generated data. 34 35 If 'missing' is specified, the character of the given value will be used 36 for missing character data. 37 """ 38 39 points.sort() 40 base = points[0] 41 limit = points[-1] 42 43 # Store the offset of each chosen character. 44 45 table = [] 46 47 # Search for each character until no more remain to be found. 48 49 index = 0 50 end = len(points) 51 52 # Record character widths and bitmaps. 53 54 widths = [] 55 bitmaps = [] 56 height = 16 57 58 line = fin.readline() 59 60 while line and index < end: 61 point, width, bytes = read_entry(line, height) 62 63 # Add a null offset for unselected characters. 64 65 if point < points[index]: 66 if point >= base: 67 widths.append(None) 68 bitmaps.append(None) 69 line = fin.readline() 70 continue 71 72 bitmaps.append(bytes) 73 widths.append(width) 74 75 index += 1 76 line = fin.readline() 77 78 # Generate the offsets. 79 80 line_step = 0 81 82 for width in widths: 83 if width is None: 84 width = widths[missing - base] 85 table.append(line_step) 86 line_step += width 87 88 # Write the offset table. 89 90 for offset in table: 91 if offset is None: 92 offset = table[missing - base] 93 fout.write(uint32_out(offset)) 94 95 # Write the widths table. 96 97 for width in widths: 98 if width is None: 99 width = widths[missing - base] 100 fout.write(uint32_out(width)) 101 102 # Write the line data step size and common height. 103 104 fout.write(uint32_out(line_step)) 105 fout.write(uint32_out(height)) 106 107 # Write the bitmaps. 108 109 line = 0 110 while line < height: 111 for width, bitmap in zip(widths, bitmaps): 112 if bitmap is None: 113 bitmap = bitmaps[missing - base] 114 115 bytes_per_line = width / 8 116 start = line * bytes_per_line 117 118 data = bitmap[start:start+bytes_per_line] 119 fout.write(bits_out(data)) 120 121 line += 1 122 123 def read_entry(line, height): 124 125 # Obtain the code point and data. 126 127 point, data = line.rstrip().split(":") 128 point = int(point, 16) 129 130 # Each data digit represents four bits. 131 132 width = (len(data) * 4) / height 133 134 # Obtain the byte values from the data. 135 136 bytes = [] 137 i = 0 138 while i < len(data): 139 bytes.append(int(data[i:i+2], 16)) 140 i += 2 141 142 return point, width, bytes 143 144 # Utilities for writing .tff fonts. 145 146 def bits_out(s): 147 result = [] 148 for value in s[::-1]: 149 i = 0 150 while i < 8: 151 result.insert(0, (value & 0x01) and "\xff" or "\x00") 152 value >>= 1 153 i += 1 154 return "".join(result) 155 156 def uint32_out(value): 157 result = [] 158 i = 0 159 while i < 4: 160 result.append(chr(value & 0xff)) 161 value >>= 8 162 i += 1 163 return "".join(result) 164 165 # Utilities for reading .tff fonts. 166 167 def uint32(s): 168 result = 0 169 i = 3 170 while i >= 0: 171 result = (result << 8) + ord(s[i]) 172 i -= 1 173 return result 174 175 def words(s): 176 l = [] 177 i = 0 178 while i < len(s): 179 l.append(s[i:i+4]) 180 i += 4 181 return l 182 183 class Font: 184 185 "An abstraction for the .tff format." 186 187 def __init__(self, filename): 188 f = open(filename) 189 try: 190 self.otab = map(uint32, words(f.read(1024))) 191 self.wtab = map(uint32, words(f.read(1024))) 192 self.w = uint32(f.read(4)) 193 self.h = uint32(f.read(4)) 194 self.img = f.read() 195 finally: 196 f.close() 197 198 def show(self, c): 199 num = ord(c) 200 offset = self.otab[num] 201 row = 0 202 while row < self.h: 203 start = offset + row * self.w 204 print self.img[start:start + self.wtab[num]].replace("\xff", "#").replace("\x00", ".") 205 row += 1 206 207 # Main program. 208 209 if __name__ == "__main__": 210 211 # Test options. 212 213 if "--help" in sys.argv or len(sys.argv) < 3: 214 basename = split(sys.argv[0])[1] 215 print >>sys.stderr, """\ 216 Usage: 217 218 %s <input filename> <output filename> 219 """ % basename 220 sys.exit(1) 221 222 base = 0 223 points = range(base, 256) 224 225 filename_or_option, tff_filename = sys.argv[1:3] 226 227 if filename_or_option == "--show": 228 f = Font(tff_filename) 229 for arg in sys.argv[3:]: 230 for char in arg: 231 f.show(char) 232 233 # Convert from the input to the output file. 234 235 else: 236 if not isfile(filename_or_option): 237 print >>sys.stderr, "Font file not found:", filename_or_option 238 sys.exit(1) 239 240 fin = open(filename_or_option) 241 fout = open(tff_filename, "w") 242 try: 243 convert_font(fin, fout, points, base) 244 finally: 245 fin.close() 246 fout.close() 247 248 # vim: tabstop=4 expandtab shiftwidth=4