aboutsummaryrefslogtreecommitdiff
path: root/bin/bdfexp.py
diff options
context:
space:
mode:
Diffstat (limited to 'bin/bdfexp.py')
-rw-r--r--bin/bdfexp.py245
1 files changed, 245 insertions, 0 deletions
diff --git a/bin/bdfexp.py b/bin/bdfexp.py
new file mode 100644
index 000000000000..8409935ee01b
--- /dev/null
+++ b/bin/bdfexp.py
@@ -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)