diff options
| author | Toomas Soome <tsoome@FreeBSD.org> | 2022-02-02 12:26:09 +0000 | 
|---|---|---|
| committer | Toomas Soome <tsoome@FreeBSD.org> | 2022-02-02 12:26:09 +0000 | 
| commit | c0f397da86d3d6af94e900630eb1652ba0133e75 (patch) | |
| tree | 9c7755a79124ee44ae890ea34cbea7769c1e744d /bin/bdf.py | |
| parent | cee3932f8c02a220d70e48949c7c5ca6e98dfef4 (diff) | |
Diffstat (limited to 'bin/bdf.py')
| -rw-r--r-- | bin/bdf.py | 224 | 
1 files changed, 89 insertions, 135 deletions
| diff --git a/bin/bdf.py b/bin/bdf.py index 54a40ff02cec..7a6f7d87a2d1 100644 --- a/bin/bdf.py +++ b/bin/bdf.py @@ -1,27 +1,31 @@  # -# Copyright (c) 2018 Dimitar Toshkov Zhekov <dimitar.zhekov@gmail.com> +# Copyright (C) 2017-2020 Dimitar Toshkov Zhekov <dimitar.zhekov@gmail.com>  # -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 2 of -# the License, or (at your option) any later version. +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 2 of the License, or (at your option) +# any later version.  # -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the -# GNU General Public License for more details. +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.  #  import re  import codecs +from collections import OrderedDict  from enum import IntEnum, unique  import fnutil - -WIDTH_MAX = 127 -HEIGHT_MAX = 255 -SWIDTH_MAX = 32000 +# -- Width -- +DPARSE_LIMIT = 512 +SPARSE_LIMIT = 32000  class Width:  	def __init__(self, x, y): @@ -30,25 +34,27 @@ class Width:  	@staticmethod -	def _parse(name, value, limit_x, limit_y): +	def parse(name, value, limit):  		words = fnutil.split_words(name, value, 2) -		return Width(fnutil.parse_dec('width x', words[0], -limit_x, limit_x), -			fnutil.parse_dec('width y', words[1], -limit_y, limit_y)) +		return Width(fnutil.parse_dec(name + '.x', words[0], -limit, limit), +			fnutil.parse_dec(name + '.y', words[1], -limit, limit))  	@staticmethod -	def parse_s(value): -		return Width._parse('SWIDTH', value, SWIDTH_MAX, SWIDTH_MAX) +	def parse_s(name, value): +		return Width.parse(name, value, SPARSE_LIMIT)  	@staticmethod -	def parse_d(value): -		return Width._parse('DWIDTH', value, WIDTH_MAX, HEIGHT_MAX) +	def parse_d(name, value): +		return Width.parse(name, value, DPARSE_LIMIT) -OFFSET_MIN = -128 -OFFSET_MAX = 127 +	def __str__(self): +		return '%d %d' % (self.x, self.y) + +# -- BXX --  class BBX:  	def __init__(self, width, height, xoff, yoff):  		self.width = width @@ -60,10 +66,10 @@ class BBX:  	@staticmethod  	def parse(name, value):  		words = fnutil.split_words(name, value, 4) -		return BBX(fnutil.parse_dec('width', words[0], 1, WIDTH_MAX), -			fnutil.parse_dec('height', words[1], 1, HEIGHT_MAX), -			fnutil.parse_dec('bbxoff', words[2], -WIDTH_MAX, WIDTH_MAX), -			fnutil.parse_dec('bbyoff', words[3], -WIDTH_MAX, WIDTH_MAX)) +		return BBX(fnutil.parse_dec('width', words[0], 1, DPARSE_LIMIT), +			fnutil.parse_dec('height', words[1], 1, DPARSE_LIMIT), +			fnutil.parse_dec('bbxoff', words[2], -DPARSE_LIMIT, DPARSE_LIMIT), +			fnutil.parse_dec('bbyoff', words[3], -DPARSE_LIMIT, DPARSE_LIMIT))  	def row_size(self): @@ -74,48 +80,18 @@ class BBX:  		return '%d %d %d %d' % (self.width, self.height, self.xoff, self.yoff) -class Props: -	def __init__(self): -		self.names = [] -		self.values = [] - - -	def add(self, name, value): -		self.names.append(name) -		self.values.append(value) - - -	def clone(self): -		props = Props() -		props.names = self.names[:] -		props.values = self.values[:] -		return props - - -	def get(self, name): -		try: -			return self.values[self.names.index(name)] -		except ValueError: -			return None - - -	class Iter: -		def __init__(self, props): -			self.index = 0 -			self.props = props - +# -- Props -- +def skip_comments(line): +	return None if line[:7] == b'COMMENT' else line -		def __next__(self): -			if self.index == len(self.props.names): -				raise StopIteration -			result = (self.props.names[self.index], self.props.values[self.index]) -			self.index += 1 -			return result +class Props(OrderedDict): +	def __iter__(self): +		return self.items().__iter__() -	def __iter__(self): -		return Props.Iter(self) +	def read(self, input, name, callback=None): +		return self.parse(input.read_lines(skip_comments), name, callback)  	def parse(self, line, name, callback=None): @@ -123,36 +99,23 @@ class Props:  			raise Exception(name + ' expected')  		value = line[len(name):].lstrip() -		self.add(name, value) +		self[name] = value  		return value if callback is None else callback(name, value)  	def set(self, name, value): -		try: -			self.values[self.names.index(name)] = value -		except ValueError: -			self.add(name, value) +		self[name] = value if isinstance(value, (bytes, bytearray)) else bytes(str(value), 'ascii') +# -- Base --  class Base:  	def __init__(self):  		self.props = Props()  		self.bbx = None -		self.finis = [] - - -	def keep_comments(self, line): -		if not line.startswith(b'COMMENT'): -			return line -		self.props.add('', line) -		return None - - -	def keep_finishes(self, line): -		self.finis.append(line) -		return None if line.startswith(b'COMMENT') else line +# -- Char +HEX_BYTES = (48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70)  class Char(Base):  	def __init__(self): @@ -163,55 +126,51 @@ class Char(Base):  		self.data = None -	@staticmethod -	def bitmap(data, row_size): +	def bitmap(self):  		bitmap = '' +		row_size = self.bbx.row_size() -		for index in range(0, len(data), row_size): -			bitmap += data[index : index + row_size].hex() + '\n' +		for index in range(0, len(self.data), row_size): +			bitmap += self.data[index : index + row_size].hex() + '\n'  		return bytes(bitmap, 'ascii').upper()  	def _read(self, input):  		# HEADER -		read_next = lambda: input.read_lines(lambda line: self.keep_comments(line)) -		read_prop = lambda name, callback=None: self.props.parse(read_next(), name, callback) - -		read_prop('STARTCHAR') -		self.code = read_prop('ENCODING', fnutil.parse_dec) -		self.swidth = read_prop('SWIDTH', lambda _, value: Width.parse_s(value)) -		self.dwidth = read_prop('DWIDTH', lambda _, value: Width.parse_d(value)) -		self.bbx = read_prop('BBX', BBX.parse) -		line = read_next() +		self.props.read(input, 'STARTCHAR') +		self.code = self.props.read(input, 'ENCODING', fnutil.parse_dec) +		self.swidth = self.props.read(input, 'SWIDTH', Width.parse_s) +		self.dwidth = self.props.read(input, 'DWIDTH', Width.parse_d) +		self.bbx = self.props.read(input, 'BBX', BBX.parse) +		line = input.read_lines(skip_comments)  		if line and line.startswith(b'ATTRIBUTES'):  			self.props.parse(line, 'ATTRIBUTES') -			line = read_next() +			line = input.read_lines(skip_comments)  		# BITMAP -		if self.props.parse(line, 'BITMAP') != b'': +		if self.props.parse(line, 'BITMAP'):  			raise Exception('BITMAP expected')  		row_len = self.bbx.row_size() * 2 -		bitmap = b'' +		self.data = bytearray()  		for _ in range(0, self.bbx.height): -			line = read_next() +			line = input.read_lines(skip_comments)  			if not line:  				raise Exception('bitmap data expected')  			if len(line) == row_len: -				bitmap += line +				self.data += codecs.decode(line, 'hex')  			else:  				raise Exception('invalid bitmap length')  		# FINAL -		if input.read_lines(lambda line: self.keep_finishes(line)) != b'ENDCHAR': +		if input.read_lines(skip_comments) != b'ENDCHAR':  			raise Exception('ENDCHAR expected') -		self.data = codecs.decode(bitmap, 'hex')  # no spaces allowed  		return self @@ -224,9 +183,10 @@ class Char(Base):  		for [name, value] in self.props:  			output.write_prop(name, value) -		output.write_line(Char.bitmap(self.data, self.bbx.row_size()) + b'\n'.join(self.finis)) +		output.write_line(self.bitmap() + b'ENDCHAR') +# -- Font --  @unique  class XLFD(IntEnum):  	FOUNDRY = 1 @@ -254,48 +214,44 @@ class Font(Base):  		self.default_code = -1 -	def get_ascent(self): -		ascent = self.props.get('FONT_ASCENT') - -		if ascent is not None: -			return fnutil.parse_dec('FONT_ASCENT', ascent, -HEIGHT_MAX, HEIGHT_MAX) +	@property +	def bold(self): +		return b'bold' in self.xlfd[XLFD.WEIGHT_NAME].lower() -		return self.bbx.height + self.bbx.yoff +	@property +	def italic(self): +		return self.xlfd[XLFD.SLANT] in [b'I', b'O'] -	def get_bold(self): -		return int(b'bold' in self.xlfd[XLFD.WEIGHT_NAME].lower()) - -	def get_italic(self): -		return int(re.search(b'^[IO]', self.xlfd[XLFD.SLANT]) is not None) +	@property +	def proportional(self): +		return self.xlfd[XLFD.SPACING] == b'P'  	def _read(self, input):  		# HEADER -		read_next = lambda: input.read_lines(lambda line: self.keep_comments(line)) -		read_prop = lambda name, callback=None: self.props.parse(read_next(), name, callback) -		line = input.read_lines(Font.skip_empty) +		line = input.read_line()  		if self.props.parse(line, 'STARTFONT') != b'2.1':  			raise Exception('STARTFONT 2.1 expected') -		self.xlfd = read_prop('FONT', lambda name, value: value.split(b'-', 15)) +		self.xlfd = self.props.read(input, 'FONT', lambda name, value: value.split(b'-', 15))  		if len(self.xlfd) != 15 or self.xlfd[0] != b'':  			raise Exception('non-XLFD font names are not supported') -		read_prop('SIZE') -		self.bbx = read_prop('FONTBOUNDINGBOX', BBX.parse) -		line = read_next() +		self.props.read(input, 'SIZE') +		self.bbx = self.props.read(input, 'FONTBOUNDINGBOX', BBX.parse) +		line = input.read_lines(skip_comments)  		if line and line.startswith(b'STARTPROPERTIES'):  			num_props = self.props.parse(line, 'STARTPROPERTIES', fnutil.parse_dec)  			for _ in range(0, num_props): -				line = read_next() +				line = input.read_lines(skip_comments) -				if not line: +				if line is None:  					raise Exception('property expected')  				match = re.fullmatch(br'(\w+)\s+([-\d"].*)', line) @@ -306,18 +262,21 @@ class Font(Base):  				name = str(match.group(1), 'ascii')  				value = match.group(2) +				if self.props.get(name) is not None: +					raise Exception('duplicate property') +  				if name == 'DEFAULT_CHAR':  					self.default_code = fnutil.parse_dec(name, value) -				self.props.add(name, value) +				self.props[name] = value -			if read_prop('ENDPROPERTIES') != b'': +			if self.props.read(input, 'ENDPROPERTIES') != b'':  				raise Exception('ENDPROPERTIES expected') -			line = read_next() +			line = input.read_lines(skip_comments)  		# GLYPHS -		num_chars = self.props.parse(line, 'CHARS', lambda name, value: fnutil.parse_dec(name, value, 1, CHARS_MAX)) +		num_chars = fnutil.parse_dec('CHARS', self.props.parse(line, 'CHARS'), 1, CHARS_MAX)  		for _ in range(0, num_chars):  			self.chars.append(Char.read(input)) @@ -326,10 +285,10 @@ class Font(Base):  			raise Exception('invalid DEFAULT_CHAR')  		# FINAL -		if input.read_lines(lambda line: self.keep_finishes(line)) != b'ENDFONT': +		if input.read_lines(skip_comments) != b'ENDFONT':  			raise Exception('ENDFONT expected') -		if input.read_lines(Font.skip_empty): +		if input.read_line() is not None:  			raise Exception('garbage after ENDFONT')  		return self @@ -340,11 +299,6 @@ class Font(Base):  		return Font()._read(input)  # pylint: disable=protected-access -	@staticmethod -	def skip_empty(line): -		return line if line else None - -  	def write(self, output):  		for [name, value] in self.props:  			output.write_prop(name, value) @@ -352,4 +306,4 @@ class Font(Base):  		for char in self.chars:  			char.write(output) -		output.write_line(b'\n'.join(self.finis)) +		output.write_line(b'ENDFONT') | 
