Landfall

Annotated tools/readfont.py

129:152c4fc0a967
2021-01-16 Paul Boddie Added missing Makefiles.
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