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