First jameswitzema.net commit
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
#
|
||||
# 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)
|
||||
Reference in New Issue
Block a user