blob: 9bd06ed371769abd66c9c6e1b56619cae8439512 [file] [log] [blame]
Po-Hsien Wang0041ad02020-02-24 13:10:42 -08001#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
Zach Reiznera752c102015-04-15 15:03:11 -07003# 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
9from __future__ import print_function
10
11import re
12import sys
13
14
15class 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 Wang0041ad02020-02-24 13:10:42 -080068 out_file.write("""/* This code is generated. Do not edit. */
Zach Reiznera752c102015-04-15 15:03:11 -070069#define GLYPH_WIDTH %(width)s
70#define GLYPH_HEIGHT %(height)s
71#define GLYPH_BYTES_PER_ROW %(bpp)s
72
Po-Hsien Wang0041ad02020-02-24 13:10:42 -080073""" % glyph_properties)
Zach Reiznera752c102015-04-15 15:03:11 -070074
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 Shi77955522015-04-22 15:21:03 -070087 out_file.write('static int32_t code_point_to_glyph_index(uint32_t cp)\n{\n')
Zach Reiznera752c102015-04-15 15:03:11 -070088 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
119class 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 Reiznera752c102015-04-15 15:03:11 -0700153 """
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 Wang0041ad02020-02-24 13:10:42 -0800177 for c_idx in range(len(row) // 2):
Zach Reiznera752c102015-04-15 15:03:11 -0700178 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:])