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