Po-Hsien Wang | 0041ad0 | 2020-02-24 13:10:42 -0800 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # -*- coding: utf-8 -*- |
Zach Reizner | a752c10 | 2015-04-15 15:03:11 -0700 | [diff] [blame] | 3 | # Copyright 2015 The Chromium OS Authors. All rights reserved. |
| 4 | # Use of this source code is governed by a BSD-style license that can be |
| 5 | # found in the LICENSE file. |
| 6 | |
| 7 | """Converts a font in bdf format into C source code.""" |
| 8 | |
| 9 | from __future__ import print_function |
| 10 | |
| 11 | import re |
| 12 | import sys |
| 13 | |
| 14 | |
| 15 | class GlyphSet(object): |
| 16 | """Collects glyph bitmap data and outputs it into C source code""" |
| 17 | def __init__(self, width, height): |
| 18 | self.glyph_map = {} |
| 19 | self.width = width |
| 20 | self.height = height |
| 21 | self.bits_per_pixel = 1 |
| 22 | self.bytes_per_row = (self.bits_per_pixel * self.width + 7) // 8 |
| 23 | self.glyph_size = self.bytes_per_row * self.height |
| 24 | |
| 25 | def AddGlyph(self, code_point, data): |
| 26 | """Adds a bitmap associated with the glyph identified by the code point. |
| 27 | |
| 28 | A glyph can be added at most once. |
| 29 | |
| 30 | Args: |
| 31 | code_point: a 32-bit unsigned integer identifying the code point of the |
| 32 | glyph bitmap |
| 33 | data: an array of unsigned bytes with a length of exactly self.glyph_size |
| 34 | |
| 35 | Raises: |
| 36 | Exception: the bitmap data is the wrong size or the code point was added |
| 37 | once before |
| 38 | """ |
| 39 | if code_point in self.glyph_map: |
| 40 | raise Exception('code point %s already added' % code_point) |
| 41 | |
| 42 | if len(data) != self.glyph_size: |
| 43 | raise Exception('given glyph is the wrong size, expected %s, got %s' % |
| 44 | (self.glyph_size, len(data))) |
| 45 | |
| 46 | self.glyph_map[code_point] = data |
| 47 | |
| 48 | def ToCSource(self, out_file): |
| 49 | """Writes this GlyphSet's data into a C source file. |
| 50 | |
| 51 | The data written includes: |
| 52 | - the global dimensions of the glyphs |
| 53 | - the glyph bitmaps, stored in an array |
| 54 | - a function to convert code points to the index of the glyph in the |
| 55 | bitmap array |
| 56 | |
| 57 | The C source file outputs static data and methods and is intended to be |
| 58 | #include'd by a compilation unit. |
| 59 | |
| 60 | Args: |
| 61 | out_file: the file to write the GlyphSet to |
| 62 | """ |
| 63 | glyph_properties = { |
| 64 | 'width': self.width, |
| 65 | 'height': self.height, |
| 66 | 'bpp': self.bytes_per_row |
| 67 | } |
Po-Hsien Wang | 0041ad0 | 2020-02-24 13:10:42 -0800 | [diff] [blame] | 68 | out_file.write("""/* This code is generated. Do not edit. */ |
Zach Reizner | a752c10 | 2015-04-15 15:03:11 -0700 | [diff] [blame] | 69 | #define GLYPH_WIDTH %(width)s |
| 70 | #define GLYPH_HEIGHT %(height)s |
| 71 | #define GLYPH_BYTES_PER_ROW %(bpp)s |
| 72 | |
Po-Hsien Wang | 0041ad0 | 2020-02-24 13:10:42 -0800 | [diff] [blame] | 73 | """ % glyph_properties) |
Zach Reizner | a752c10 | 2015-04-15 15:03:11 -0700 | [diff] [blame] | 74 | |
| 75 | sorted_glyphs = sorted(self.glyph_map.items()) |
| 76 | |
| 77 | breaks = [] |
| 78 | last_code_point = None |
| 79 | for glyph_index, (code_point, _) in enumerate(sorted_glyphs): |
| 80 | if last_code_point is None or (last_code_point + 1) != code_point: |
| 81 | breaks.append((code_point, glyph_index)) |
| 82 | |
| 83 | last_code_point = code_point |
| 84 | |
| 85 | breaks.append((None, len(sorted_glyphs))) |
| 86 | |
Haixia Shi | 7795552 | 2015-04-22 15:21:03 -0700 | [diff] [blame] | 87 | out_file.write('static int32_t code_point_to_glyph_index(uint32_t cp)\n{\n') |
Zach Reizner | a752c10 | 2015-04-15 15:03:11 -0700 | [diff] [blame] | 88 | for break_idx, (this_break_code_point, |
| 89 | this_break_glyph_index) in enumerate(breaks[:-1]): |
| 90 | this_break_code_point, this_break_glyph_index = breaks[break_idx] |
| 91 | next_break_glyph_index = breaks[break_idx + 1][1] |
| 92 | this_break_range = next_break_glyph_index - this_break_glyph_index |
| 93 | out_file.write(' if (cp < %s) {\n' % |
| 94 | (this_break_code_point + this_break_range)) |
| 95 | if this_break_range == 1: |
| 96 | out_file.write(' if (cp == %s)\n' % this_break_code_point) |
| 97 | out_file.write(' return %s;\n' % this_break_glyph_index) |
| 98 | else: |
| 99 | out_file.write(' if (cp >= %s)\n' % this_break_code_point) |
| 100 | out_file.write(' return cp - %s;\n' % |
| 101 | (this_break_code_point - this_break_glyph_index)) |
| 102 | out_file.write(' else\n') |
| 103 | out_file.write(' return -1;\n') |
| 104 | out_file.write(' }\n') |
| 105 | out_file.write('\n') |
| 106 | out_file.write(' return -1;\n') |
| 107 | out_file.write('}\n\n') |
| 108 | |
| 109 | out_file.write('static const uint8_t glyphs[%s][%s] = {\n' % |
| 110 | (len(self.glyph_map), self.glyph_size)) |
| 111 | for code_point, data in sorted_glyphs: |
| 112 | out_file.write(' {') |
| 113 | for data_idx in range(self.glyph_size): |
| 114 | out_file.write('0x{:02x}, '.format(data[data_idx])) |
| 115 | out_file.write('},\n') |
| 116 | out_file.write('};\n') |
| 117 | |
| 118 | |
| 119 | class BdfState(object): |
| 120 | """Holds the state and output of the bdf parser. |
| 121 | |
| 122 | This parses the input file on init. This BDF parser is very simple. It |
| 123 | basically does the minimum work to extract all the glyphs in the input file. |
| 124 | The only validation done is to check that each glyph has exactly the right |
| 125 | amount of data. This has only ever been tested on the normal Terminus font, |
| 126 | sizes 16 and 32. |
| 127 | """ |
| 128 | |
| 129 | def __init__(self, in_file): |
| 130 | # Simple algorithm, try to match each line of input against each regex. |
| 131 | # The first match that works gets passed to the handler in the tuples |
| 132 | # below. If there was no match, the line is dropped. |
| 133 | self.patterns = [ |
| 134 | (re.compile(r'FONTBOUNDINGBOX +(\d+) +(\d+) +([+-]?\d+) +([+-]?\d+)$'), |
| 135 | self.HandleFONTBOUNDINGBOX), |
| 136 | (re.compile(r'ENCODING +(\d+)$'), self.HandleENCODING), |
| 137 | (re.compile(r'BITMAP$'), self.HandleBITMAP), |
| 138 | (re.compile(r'ENDCHAR$'), self.HandleENDCHAR), |
| 139 | (re.compile(r'([0-9a-fA-F]{2})+$'), self.HandleDataBITMAP), |
| 140 | ] |
| 141 | self.out_glyph_set = None |
| 142 | self.current_code_point = None |
| 143 | self.current_glyph_data = None |
| 144 | self.current_glyph_data_index = None |
| 145 | for line in in_file: |
| 146 | self.HandleLine(line) |
| 147 | |
| 148 | def HandleLine(self, line): |
| 149 | """Handles a single line of bdf file input. |
| 150 | |
| 151 | The line is matched to a pattern and that match is passed to the |
| 152 | corresponding handler. |
Zach Reizner | a752c10 | 2015-04-15 15:03:11 -0700 | [diff] [blame] | 153 | """ |
| 154 | line = line.strip() |
| 155 | for pattern, handler in self.patterns: |
| 156 | match = pattern.match(line) |
| 157 | if match is not None: |
| 158 | handler(match) |
| 159 | break |
| 160 | |
| 161 | def HandleFONTBOUNDINGBOX(self, match): |
| 162 | """Constructs a GlyphSet with the given bitmap size.""" |
| 163 | self.out_glyph_set = GlyphSet(int(match.group(1)), int(match.group(2))) |
| 164 | |
| 165 | def HandleENCODING(self, match): |
| 166 | """Remembers the code point for a later call to AddGlyph""" |
| 167 | self.current_code_point = int(match.group(1)) |
| 168 | |
| 169 | def HandleBITMAP(self, _match): |
| 170 | """Construct a blank pre-sized list of bitmap data.""" |
| 171 | self.current_glyph_data = [0] * self.out_glyph_set.glyph_size |
| 172 | self.current_glyph_data_index = 0 |
| 173 | |
| 174 | def HandleDataBITMAP(self, match): |
| 175 | """Adds data to the bitmap data.""" |
| 176 | row = match.group(0) |
Po-Hsien Wang | 0041ad0 | 2020-02-24 13:10:42 -0800 | [diff] [blame] | 177 | for c_idx in range(len(row) // 2): |
Zach Reizner | a752c10 | 2015-04-15 15:03:11 -0700 | [diff] [blame] | 178 | c = row[c_idx * 2:(c_idx + 1) * 2] |
| 179 | if self.current_glyph_data_index >= len(self.current_glyph_data): |
| 180 | raise Exception('too much glyph data, expected %s' % |
| 181 | len(self.current_glyph_data)) |
| 182 | self.current_glyph_data[self.current_glyph_data_index] = int(c, base=16) |
| 183 | self.current_glyph_data_index += 1 |
| 184 | |
| 185 | def HandleENDCHAR(self, _match): |
| 186 | """Uses the most recent glyph data and adds it to the GlyphSet""" |
| 187 | if self.current_glyph_data_index != len(self.current_glyph_data): |
| 188 | raise Exception('too little glyph data, expected %s, got %s' % |
| 189 | (len(self.current_glyph_data), |
| 190 | self.current_glyph_data_index)) |
| 191 | self.out_glyph_set.AddGlyph(self.current_code_point, |
| 192 | self.current_glyph_data) |
| 193 | self.current_code_point = None |
| 194 | self.current_glyph_data = None |
| 195 | self.current_glyph_data_index = None |
| 196 | |
| 197 | |
| 198 | def main(args): |
| 199 | if len(args) != 2: |
| 200 | print('Usage: %s [INPUT BDF PATH] [OUTPUT C PATH]' % sys.argv[0]) |
| 201 | sys.exit(1) |
| 202 | gs = BdfState(open(args[0], 'r')).out_glyph_set |
| 203 | gs.ToCSource(open(args[1], 'w')) |
| 204 | |
| 205 | |
| 206 | if __name__ == '__main__': |
| 207 | main(sys.argv[1:]) |