1# 2# Copyright (c) 2018 Dimitar Toshkov Zhekov <dimitar.zhekov@gmail.com> 3# 4# This program is free software; you can redistribute it and/or 5# modify it under the terms of the GNU General Public License as 6# published by the Free Software Foundation; either version 2 of 7# the License, or (at your option) any later version. 8# 9# This program is distributed in the hope that it will be useful, 10# but WITHOUT ANY WARRANTY; without even the implied warranty of 11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12# GNU General Public License for more details. 13# 14 15import bdf 16 17 18class Char: 19 def __init__(self, code, name, width, data): 20 self.code = code 21 self.name = name 22 self.width = width 23 self.data = data 24 25 26 @staticmethod 27 def from_bdf(char, fbbox): 28 delta_yoff = char.bbx.yoff - fbbox.yoff # ~DSB 29 30 if delta_yoff < 0: 31 raise Exception('char %d: BBX yoff < FONTBOUNDINGBOX yoff' % char.code) 32 33 if char.dwidth.x >= 0: 34 if char.bbx.xoff >= 0: 35 width = max(char.bbx.width + char.bbx.xoff, char.dwidth.x) 36 dst_xoff = char.bbx.xoff 37 else: 38 width = max(char.bbx.width, char.dwidth.x - char.bbx.xoff) 39 dst_xoff = 0 40 else: 41 dst_xoff = max(char.bbx.xoff - char.dwidth.x, 0) 42 width = char.bbx.width + dst_xoff 43 44 if width > bdf.WIDTH_MAX: 45 raise Exception('char %d: output width > %d' % (char.code, bdf.WIDTH_MAX)) 46 47 height = fbbox.height 48 src_row_size = char.bbx.row_size() 49 dst_row_size = (width + 7) >> 3 50 dst_ymax = height - delta_yoff 51 dst_ymin = dst_ymax - char.bbx.height 52 compat_row = dst_xoff == 0 and width >= char.bbx.width 53 54 if compat_row and src_row_size == dst_row_size and dst_ymin == 0 and dst_ymax == height: 55 data = char.data 56 elif dst_ymin < 0: 57 raise Exception('char %d: start row %d' % (char.code, dst_ymin)) 58 elif compat_row: 59 src_byte_no = 0 60 data = bytearray(dst_ymin * dst_row_size) 61 line_fill = bytes(dst_row_size - src_row_size) 62 63 for dst_y in range(dst_ymin, dst_ymax): 64 data += char.data[src_byte_no : src_byte_no + src_row_size] + line_fill 65 src_byte_no += src_row_size 66 67 data += bytes(delta_yoff * dst_row_size) 68 else: 69 data = bytearray(dst_row_size * height) 70 71 for dst_y in range(dst_ymin, dst_ymax): 72 src_byte_no = (dst_y - dst_ymin) * src_row_size 73 dst_byte_no = dst_y * dst_row_size + (dst_xoff >> 3) 74 75 src_bit_no = 7 76 dst_bit_no = 7 - (dst_xoff & 7) 77 78 for _ in range(0, char.bbx.width): 79 if char.data[src_byte_no] & (1 << src_bit_no): 80 data[dst_byte_no] |= (1 << dst_bit_no) 81 82 if src_bit_no > 0: 83 src_bit_no -= 1 84 else: 85 src_bit_no = 7 86 src_byte_no += 1 87 88 if dst_bit_no > 0: 89 dst_bit_no -= 1 90 else: 91 dst_bit_no = 7 92 dst_byte_no += 1 93 94 return Char(char.code, char.props.get('STARTCHAR'), width, data) 95 96 97 def row_size(self): 98 return (self.width + 7) >> 3 99 100 101 def write(self, output, max_width, yoffset): 102 output.write_line(b'STARTCHAR %s\nENCODING %d' % (self.name, self.code)) 103 output.write_line(b'SWIDTH %d 0\nDWIDTH %d 0' % (round(self.width * 1000 / max_width), self.width)) 104 output.write_line(b'BBX %d %d 0 %d' % (self.width, len(self.data) / self.row_size(), yoffset)) 105 output.write_line(b'BITMAP\n' + bdf.Char.bitmap(self.data, self.row_size()) + b'ENDCHAR') 106 107 108class Font(bdf.Font): 109 def __init__(self): 110 bdf.Font.__init__(self) 111 self.min_width = bdf.WIDTH_MAX 112 self.avg_width = 0 113 114 115 def _read(self, input): 116 bdf.Font._read(self, input) 117 self.chars = [Char.from_bdf(char, self.bbx) for char in self.chars] 118 self.bbx.xoff = 0 119 total_width = 0 120 121 for char in self.chars: 122 self.min_width = min(self.min_width, char.width) 123 self.bbx.width = max(self.bbx.width, char.width) 124 total_width += char.width 125 126 self.avg_width = round(total_width / len(self.chars)) 127 self.props.set('FONTBOUNDINGBOX', bytes(str(self.bbx), 'ascii')) 128 return self 129 130 131 @staticmethod 132 def read(input): 133 return Font()._read(input) # pylint: disable=protected-access 134 135 136 def get_proportional(self): 137 return int(self.bbx.width > self.min_width) 138 139 140 def write(self, output): 141 for [name, value] in self.props: 142 output.write_prop(name, value) 143 144 for char in self.chars: 145 char.write(output, self.bbx.width, self.bbx.yoff) 146 147 output.write_line(b'ENDFONT') 148