246 lines
6.6 KiB
Python
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)
|