Files
jameswitzeman.net/fonts/terminus-font/bin/bdfexp.py
T
2026-05-29 11:30:10 -07:00

246 lines
6.6 KiB
Python

#
# 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 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.
#
from collections import OrderedDict
import fnutil
import fncli
import fnio
import bdf
# -- Font --
class Font(bdf.Font):
def __init__(self):
bdf.Font.__init__(self)
self.min_width = 0 # used in proportional()
self.avg_width = 0
def _expand(self, char):
if char.dwidth.x >= 0:
if char.bbx.xoff >= 0:
width = max(char.bbx.xoff + char.bbx.width, char.dwidth.x)
dst_xoff = char.bbx.xoff
exp_xoff = 0
else:
width = max(char.bbx.width, char.dwidth.x - char.bbx.xoff)
dst_xoff = 0
exp_xoff = char.bbx.xoff
else:
rev_xoff = char.bbx.xoff + char.bbx.width
if rev_xoff <= 0:
width = -min(char.dwidth.x, char.bbx.xoff)
dst_xoff = width + char.bbx.xoff
exp_xoff = -width
else:
width = max(char.bbx.width, rev_xoff - char.dwidth.x)
dst_xoff = width - char.bbx.width
exp_xoff = rev_xoff - width
height = self.bbx.height
if width == char.bbx.width and height == char.bbx.height:
return
src_row_size = char.bbx.row_size()
dst_row_size = (width + 7) >> 3
dst_ymax = self.px_ascender - char.bbx.yoff
dst_ymin = dst_ymax - char.bbx.height
copy_row = (dst_xoff & 7) == 0
dst_data = bytearray(dst_row_size * height)
for dst_y in range(dst_ymin, dst_ymax):
src_byte_no = (dst_y - dst_ymin) * src_row_size
dst_byte_no = dst_y * dst_row_size + (dst_xoff >> 3)
if copy_row:
dst_data[dst_byte_no : dst_byte_no + src_row_size] = \
char.data[src_byte_no : src_byte_no + src_row_size]
else:
src_bit_no = 7
dst_bit_no = 7 - (dst_xoff & 7)
for _ in range(0, char.bbx.width):
if char.data[src_byte_no] & (1 << src_bit_no):
dst_data[dst_byte_no] |= (1 << dst_bit_no)
if src_bit_no > 0:
src_bit_no -= 1
else:
src_bit_no = 7
src_byte_no += 1
if dst_bit_no > 0:
dst_bit_no -= 1
else:
dst_bit_no = 7
dst_byte_no += 1
char.bbx = bdf.BBX(width, height, exp_xoff, self.bbx.yoff)
char.props.set('BBX', char.bbx)
char.data = dst_data
def expand(self):
# PREXPAND / VERTICAL
ascent = self.props.get('FONT_ASCENT')
descent = self.props.get('FONT_DESCENT')
px_ascent = 0 if ascent is None else fnutil.parse_dec('FONT_ASCENT', ascent, 0, bdf.DPARSE_LIMIT)
px_descent = 0 if descent is None else fnutil.parse_dec('FONT_DESCENT', descent, 0, bdf.DPARSE_LIMIT)
for char in self.chars:
px_ascent = max(px_ascent, char.bbx.height + char.bbx.yoff)
px_descent = max(px_descent, -char.bbx.yoff)
self.bbx.height = px_ascent + px_descent
self.bbx.yoff = -px_descent
# EXPAND / HORIZONTAL
total_width = 0
self.min_width = self.chars[0].bbx.width
for char in self.chars:
self._expand(char)
self.min_width = min(self.min_width, char.bbx.width)
self.bbx.width = max(self.bbx.width, char.bbx.width)
self.bbx.xoff = min(self.bbx.xoff, char.bbx.xoff)
total_width += char.bbx.width
self.avg_width = round(total_width / len(self.chars))
self.props.set('FONTBOUNDINGBOX', self.bbx)
def expand_x(self):
for char in self.chars:
if char.dwidth.x != char.bbx.width:
char.swidth.x = round(char.bbx.width * 1000 / self.bbx.height)
char.props.set('SWIDTH', char.swidth)
char.dwidth.x = char.bbx.width
char.props.set('DWIDTH', char.dwidth)
char.bbx.xoff = 0
char.props.set('BBX', char.bbx)
self.bbx.xoff = 0
self.props.set('FONTBOUNDINGBOX', self.bbx)
def expand_y(self):
props = OrderedDict((
('FONT_ASCENT', self.px_ascender),
('FONT_DESCENT', -self.px_descender),
('PIXEL_SIZE', self.bbx.height)
))
for [name, value] in props.items():
if self.props.get(name) is not None:
self.props.set(name, value)
self.xlfd[bdf.XLFD.PIXEL_SIZE] = bytes(str(self.bbx.height), 'ascii')
self.props.set('FONT', b'-'.join(self.xlfd))
@property
def proportional(self):
return self.bbx.width > self.min_width or bdf.Font.proportional.fget(self) # pylint: disable=no-member
@property
def px_ascender(self):
return self.bbx.height + self.bbx.yoff
@property
def px_descender(self):
return self.bbx.yoff
def _read(self, input):
bdf.Font._read(self, input)
self.expand()
return self
@staticmethod
def read(input):
return Font()._read(input) # pylint: disable=protected-access
# -- Params --
class Params(fncli.Params):
def __init__(self):
fncli.Params.__init__(self)
self.expand_x = False
self.expand_y = False
self.output_name = None
# -- Options --
HELP = ('' +
'usage: bdfexp [-X] [-Y] [-o OUTPUT] [INPUT]\n' +
'Expand BDF font bitmaps\n' +
'\n' +
' -X zero xoffs, set character S/DWIDTH.X from the output\n' +
' BBX.width if needed\n' +
' -Y enlarge FONT_ASCENT, FONT_DESCENT and PIXEL_SIZE to\n' +
' cover the font bounding box, if needed\n' +
' -o OUTPUT output file (default = stdout)\n' +
' --help display this help and exit\n' +
' --version display the program version and license, and exit\n' +
' --excstk display the exception stack on error\n' +
'\n' +
'The input must be a BDF 2.1 font with unicode encoding.\n')
VERSION = 'bdfexp 1.62, Copyright (C) 2017-2020 Dimitar Toshkov Zhekov\n\n' + fnutil.GPL2PLUS_LICENSE
class Options(fncli.Options):
def __init__(self):
fncli.Options.__init__(self, ['-o'], HELP, VERSION)
def parse(self, name, value, params):
if name == '-X':
params.expand_x = True
elif name == '-Y':
params.expand_y = True
elif name == '-o':
params.output_name = value
else:
self.fallback(name, params)
# -- Main --
def main_program(nonopt, parsed):
if len(nonopt) > 1:
raise Exception('invalid number of arguments, try --help')
# READ INPUT
font = fnio.read_file(nonopt[0] if nonopt else None, Font.read)
# EXTRA ACTIONS
if parsed.expand_x:
font.expand_x()
if parsed.expand_y:
font.expand_y()
# WRITE OUTPUT
fnio.write_file(parsed.output_name, lambda ofs: font.write(ofs))
if __name__ == '__main__':
fncli.start('bdfexp.py', Options(), Params(), main_program)