blob: 7c46d433d329cd3301bf22f657e5c382c37c0dae [file] [log] [blame]
Zach Reiznera752c102015-04-15 15:03:11 -07001#!/usr/bin/python2
2# Copyright 2015 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Converts a font in bdf format into C source code."""
7
8from __future__ import print_function
9
10import re
11import sys
12
13
14class GlyphSet(object):
15 """Collects glyph bitmap data and outputs it into C source code"""
16 def __init__(self, width, height):
17 self.glyph_map = {}
18 self.width = width
19 self.height = height
20 self.bits_per_pixel = 1
21 self.bytes_per_row = (self.bits_per_pixel * self.width + 7) // 8
22 self.glyph_size = self.bytes_per_row * self.height
23
24 def AddGlyph(self, code_point, data):
25 """Adds a bitmap associated with the glyph identified by the code point.
26
27 A glyph can be added at most once.
28
29 Args:
30 code_point: a 32-bit unsigned integer identifying the code point of the
31 glyph bitmap
32 data: an array of unsigned bytes with a length of exactly self.glyph_size
33
34 Raises:
35 Exception: the bitmap data is the wrong size or the code point was added
36 once before
37 """
38 if code_point in self.glyph_map:
39 raise Exception('code point %s already added' % code_point)
40
41 if len(data) != self.glyph_size:
42 raise Exception('given glyph is the wrong size, expected %s, got %s' %
43 (self.glyph_size, len(data)))
44
45 self.glyph_map[code_point] = data
46
47 def ToCSource(self, out_file):
48 """Writes this GlyphSet's data into a C source file.
49
50 The data written includes:
51 - the global dimensions of the glyphs
52 - the glyph bitmaps, stored in an array
53 - a function to convert code points to the index of the glyph in the
54 bitmap array
55
56 The C source file outputs static data and methods and is intended to be
57 #include'd by a compilation unit.
58
59 Args:
60 out_file: the file to write the GlyphSet to
61 """
62 glyph_properties = {
63 'width': self.width,
64 'height': self.height,
65 'bpp': self.bytes_per_row
66 }
67 out_file.write('''/* This code is generated. Do not edit. */
68#define GLYPH_WIDTH %(width)s
69#define GLYPH_HEIGHT %(height)s
70#define GLYPH_BYTES_PER_ROW %(bpp)s
71
72''' % glyph_properties)
73
74 sorted_glyphs = sorted(self.glyph_map.items())
75
76 breaks = []
77 last_code_point = None
78 for glyph_index, (code_point, _) in enumerate(sorted_glyphs):
79 if last_code_point is None or (last_code_point + 1) != code_point:
80 breaks.append((code_point, glyph_index))
81
82 last_code_point = code_point
83
84 breaks.append((None, len(sorted_glyphs)))
85
Haixia Shi77955522015-04-22 15:21:03 -070086 out_file.write('static int32_t code_point_to_glyph_index(uint32_t cp)\n{\n')
Zach Reiznera752c102015-04-15 15:03:11 -070087 for break_idx, (this_break_code_point,
88 this_break_glyph_index) in enumerate(breaks[:-1]):
89 this_break_code_point, this_break_glyph_index = breaks[break_idx]
90 next_break_glyph_index = breaks[break_idx + 1][1]
91 this_break_range = next_break_glyph_index - this_break_glyph_index
92 out_file.write(' if (cp < %s) {\n' %
93 (this_break_code_point + this_break_range))
94 if this_break_range == 1:
95 out_file.write(' if (cp == %s)\n' % this_break_code_point)
96 out_file.write(' return %s;\n' % this_break_glyph_index)
97 else:
98 out_file.write(' if (cp >= %s)\n' % this_break_code_point)
99 out_file.write(' return cp - %s;\n' %
100 (this_break_code_point - this_break_glyph_index))
101 out_file.write(' else\n')
102 out_file.write(' return -1;\n')
103 out_file.write(' }\n')
104 out_file.write('\n')
105 out_file.write(' return -1;\n')
106 out_file.write('}\n\n')
107
108 out_file.write('static const uint8_t glyphs[%s][%s] = {\n' %
109 (len(self.glyph_map), self.glyph_size))
110 for code_point, data in sorted_glyphs:
111 out_file.write(' {')
112 for data_idx in range(self.glyph_size):
113 out_file.write('0x{:02x}, '.format(data[data_idx]))
114 out_file.write('},\n')
115 out_file.write('};\n')
116
117
118class BdfState(object):
119 """Holds the state and output of the bdf parser.
120
121 This parses the input file on init. This BDF parser is very simple. It
122 basically does the minimum work to extract all the glyphs in the input file.
123 The only validation done is to check that each glyph has exactly the right
124 amount of data. This has only ever been tested on the normal Terminus font,
125 sizes 16 and 32.
126 """
127
128 def __init__(self, in_file):
129 # Simple algorithm, try to match each line of input against each regex.
130 # The first match that works gets passed to the handler in the tuples
131 # below. If there was no match, the line is dropped.
132 self.patterns = [
133 (re.compile(r'FONTBOUNDINGBOX +(\d+) +(\d+) +([+-]?\d+) +([+-]?\d+)$'),
134 self.HandleFONTBOUNDINGBOX),
135 (re.compile(r'ENCODING +(\d+)$'), self.HandleENCODING),
136 (re.compile(r'BITMAP$'), self.HandleBITMAP),
137 (re.compile(r'ENDCHAR$'), self.HandleENDCHAR),
138 (re.compile(r'([0-9a-fA-F]{2})+$'), self.HandleDataBITMAP),
139 ]
140 self.out_glyph_set = None
141 self.current_code_point = None
142 self.current_glyph_data = None
143 self.current_glyph_data_index = None
144 for line in in_file:
145 self.HandleLine(line)
146
147 def HandleLine(self, line):
148 """Handles a single line of bdf file input.
149
150 The line is matched to a pattern and that match is passed to the
151 corresponding handler.
152
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)
177 for c_idx in range(len(row) / 2):
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
198def 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
206if __name__ == '__main__':
207 main(sys.argv[1:])