blob: 9bd06ed371769abd66c9c6e1b56619cae8439512 [file] [log] [blame]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright 2015 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Converts a font in bdf format into C source code."""
from __future__ import print_function
import re
import sys
class GlyphSet(object):
"""Collects glyph bitmap data and outputs it into C source code"""
def __init__(self, width, height):
self.glyph_map = {}
self.width = width
self.height = height
self.bits_per_pixel = 1
self.bytes_per_row = (self.bits_per_pixel * self.width + 7) // 8
self.glyph_size = self.bytes_per_row * self.height
def AddGlyph(self, code_point, data):
"""Adds a bitmap associated with the glyph identified by the code point.
A glyph can be added at most once.
Args:
code_point: a 32-bit unsigned integer identifying the code point of the
glyph bitmap
data: an array of unsigned bytes with a length of exactly self.glyph_size
Raises:
Exception: the bitmap data is the wrong size or the code point was added
once before
"""
if code_point in self.glyph_map:
raise Exception('code point %s already added' % code_point)
if len(data) != self.glyph_size:
raise Exception('given glyph is the wrong size, expected %s, got %s' %
(self.glyph_size, len(data)))
self.glyph_map[code_point] = data
def ToCSource(self, out_file):
"""Writes this GlyphSet's data into a C source file.
The data written includes:
- the global dimensions of the glyphs
- the glyph bitmaps, stored in an array
- a function to convert code points to the index of the glyph in the
bitmap array
The C source file outputs static data and methods and is intended to be
#include'd by a compilation unit.
Args:
out_file: the file to write the GlyphSet to
"""
glyph_properties = {
'width': self.width,
'height': self.height,
'bpp': self.bytes_per_row
}
out_file.write("""/* This code is generated. Do not edit. */
#define GLYPH_WIDTH %(width)s
#define GLYPH_HEIGHT %(height)s
#define GLYPH_BYTES_PER_ROW %(bpp)s
""" % glyph_properties)
sorted_glyphs = sorted(self.glyph_map.items())
breaks = []
last_code_point = None
for glyph_index, (code_point, _) in enumerate(sorted_glyphs):
if last_code_point is None or (last_code_point + 1) != code_point:
breaks.append((code_point, glyph_index))
last_code_point = code_point
breaks.append((None, len(sorted_glyphs)))
out_file.write('static int32_t code_point_to_glyph_index(uint32_t cp)\n{\n')
for break_idx, (this_break_code_point,
this_break_glyph_index) in enumerate(breaks[:-1]):
this_break_code_point, this_break_glyph_index = breaks[break_idx]
next_break_glyph_index = breaks[break_idx + 1][1]
this_break_range = next_break_glyph_index - this_break_glyph_index
out_file.write(' if (cp < %s) {\n' %
(this_break_code_point + this_break_range))
if this_break_range == 1:
out_file.write(' if (cp == %s)\n' % this_break_code_point)
out_file.write(' return %s;\n' % this_break_glyph_index)
else:
out_file.write(' if (cp >= %s)\n' % this_break_code_point)
out_file.write(' return cp - %s;\n' %
(this_break_code_point - this_break_glyph_index))
out_file.write(' else\n')
out_file.write(' return -1;\n')
out_file.write(' }\n')
out_file.write('\n')
out_file.write(' return -1;\n')
out_file.write('}\n\n')
out_file.write('static const uint8_t glyphs[%s][%s] = {\n' %
(len(self.glyph_map), self.glyph_size))
for code_point, data in sorted_glyphs:
out_file.write(' {')
for data_idx in range(self.glyph_size):
out_file.write('0x{:02x}, '.format(data[data_idx]))
out_file.write('},\n')
out_file.write('};\n')
class BdfState(object):
"""Holds the state and output of the bdf parser.
This parses the input file on init. This BDF parser is very simple. It
basically does the minimum work to extract all the glyphs in the input file.
The only validation done is to check that each glyph has exactly the right
amount of data. This has only ever been tested on the normal Terminus font,
sizes 16 and 32.
"""
def __init__(self, in_file):
# Simple algorithm, try to match each line of input against each regex.
# The first match that works gets passed to the handler in the tuples
# below. If there was no match, the line is dropped.
self.patterns = [
(re.compile(r'FONTBOUNDINGBOX +(\d+) +(\d+) +([+-]?\d+) +([+-]?\d+)$'),
self.HandleFONTBOUNDINGBOX),
(re.compile(r'ENCODING +(\d+)$'), self.HandleENCODING),
(re.compile(r'BITMAP$'), self.HandleBITMAP),
(re.compile(r'ENDCHAR$'), self.HandleENDCHAR),
(re.compile(r'([0-9a-fA-F]{2})+$'), self.HandleDataBITMAP),
]
self.out_glyph_set = None
self.current_code_point = None
self.current_glyph_data = None
self.current_glyph_data_index = None
for line in in_file:
self.HandleLine(line)
def HandleLine(self, line):
"""Handles a single line of bdf file input.
The line is matched to a pattern and that match is passed to the
corresponding handler.
"""
line = line.strip()
for pattern, handler in self.patterns:
match = pattern.match(line)
if match is not None:
handler(match)
break
def HandleFONTBOUNDINGBOX(self, match):
"""Constructs a GlyphSet with the given bitmap size."""
self.out_glyph_set = GlyphSet(int(match.group(1)), int(match.group(2)))
def HandleENCODING(self, match):
"""Remembers the code point for a later call to AddGlyph"""
self.current_code_point = int(match.group(1))
def HandleBITMAP(self, _match):
"""Construct a blank pre-sized list of bitmap data."""
self.current_glyph_data = [0] * self.out_glyph_set.glyph_size
self.current_glyph_data_index = 0
def HandleDataBITMAP(self, match):
"""Adds data to the bitmap data."""
row = match.group(0)
for c_idx in range(len(row) // 2):
c = row[c_idx * 2:(c_idx + 1) * 2]
if self.current_glyph_data_index >= len(self.current_glyph_data):
raise Exception('too much glyph data, expected %s' %
len(self.current_glyph_data))
self.current_glyph_data[self.current_glyph_data_index] = int(c, base=16)
self.current_glyph_data_index += 1
def HandleENDCHAR(self, _match):
"""Uses the most recent glyph data and adds it to the GlyphSet"""
if self.current_glyph_data_index != len(self.current_glyph_data):
raise Exception('too little glyph data, expected %s, got %s' %
(len(self.current_glyph_data),
self.current_glyph_data_index))
self.out_glyph_set.AddGlyph(self.current_code_point,
self.current_glyph_data)
self.current_code_point = None
self.current_glyph_data = None
self.current_glyph_data_index = None
def main(args):
if len(args) != 2:
print('Usage: %s [INPUT BDF PATH] [OUTPUT C PATH]' % sys.argv[0])
sys.exit(1)
gs = BdfState(open(args[0], 'r')).out_glyph_set
gs.ToCSource(open(args[1], 'w'))
if __name__ == '__main__':
main(sys.argv[1:])