diff options
Diffstat (limited to 'bin/bdfexp.js')
-rw-r--r-- | bin/bdfexp.js | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/bin/bdfexp.js b/bin/bdfexp.js new file mode 100644 index 000000000000..4a08131d0abb --- /dev/null +++ b/bin/bdfexp.js @@ -0,0 +1,287 @@ +/* + 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. +*/ + +'use strict'; + +const fnutil = require('./fnutil.js'); +const fncli = require('./fncli.js'); +const fnio = require('./fnio.js'); +const bdf = require('./bdf.js'); + +// -- Font -- +class Font extends bdf.Font { + constructor() { + super(); + this.minWidth = 0; // used in proportional() + this.avgWidth = 0; + } + + _expand(char) { + if (char.dwidth.x >= 0) { + if (char.bbx.xoff >= 0) { + var width = Math.max(char.bbx.xoff + char.bbx.width, char.dwidth.x); + var dstXOff = char.bbx.xoff; + var expXOff = 0; + } else { + width = Math.max(char.bbx.width, char.dwidth.x - char.bbx.xoff); + dstXOff = 0; + expXOff = char.bbx.xoff; + } + } else { + const revXOff = char.bbx.xoff + char.bbx.width; + + if (revXOff <= 0) { + width = -Math.min(char.dwidth.x, char.bbx.xoff); + dstXOff = width + char.bbx.xoff; + expXOff = -width; + } else { + width = Math.max(char.bbx.width, revXOff - char.dwidth.x); + dstXOff = width - char.bbx.width; + expXOff = revXOff - width; + } + } + + const height = this.bbx.height; + + if (width === char.bbx.width && height === char.bbx.height) { + return; + } + + const srcRowSize = char.bbx.rowSize(); + const dstRowSize = (width + 7) >> 3; + const dstYMax = this.pxAscender - char.bbx.yoff; + const dstYMin = dstYMax - char.bbx.height; + const copyRow = (dstXOff & 7) === 0; + const dstData = Buffer.alloc(dstRowSize * height); + + for (let dstY = dstYMin; dstY < dstYMax; dstY++) { + let srcByteNo = (dstY - dstYMin) * srcRowSize; + let dstByteNo = dstY * dstRowSize + (dstXOff >> 3); + + if (copyRow) { + char.data.copy(dstData, dstByteNo, srcByteNo, srcByteNo + srcRowSize); + } else { + let srcBitNo = 7; + let dstBitNo = 7 - (dstXOff & 7); + + for (let x = 0; x < char.bbx.width; x++) { + if (char.data[srcByteNo] & (1 << srcBitNo)) { + dstData[dstByteNo] |= (1 << dstBitNo); + } + if (--srcBitNo < 0) { + srcBitNo = 7; + srcByteNo++; + } + if (--dstBitNo < 0) { + dstBitNo = 7; + dstByteNo++; + } + } + } + } + + char.bbx = new bdf.BBX(width, height, expXOff, this.bbx.yoff); + char.props.set('BBX', char.bbx); + char.data = dstData; + } + + expand() { + // PREXPAND / VERTICAL + const ascent = this.props.get('FONT_ASCENT'); + const descent = this.props.get('FONT_DESCENT'); + let pxAscent = (ascent == null ? 0 : fnutil.parseDec('FONT_ASCENT', ascent, 0, bdf.DPARSE_LIMIT)); + let pxDescent = (descent == null ? 0 : fnutil.parseDec('FONT_DESCENT', descent, 0, bdf.DPARSE_LIMIT)); + + this.chars.forEach(char => { + pxAscent = Math.max(pxAscent, char.bbx.height + char.bbx.yoff); + pxDescent = Math.max(pxDescent, -char.bbx.yoff); + }); + this.bbx.height = pxAscent + pxDescent; + this.bbx.yoff = -pxDescent; + + // EXPAND / HORIZONTAL + let totalWidth = 0; + + this.minWidth = this.chars[0].bbx.width; + this.chars.forEach(char => { + this._expand(char); + this.minWidth = Math.min(this.minWidth, char.bbx.width); + this.bbx.width = Math.max(this.bbx.width, char.bbx.width); + this.bbx.xoff = Math.min(this.bbx.xoff, char.bbx.xoff); + totalWidth += char.bbx.width; + }); + this.avgWidth = fnutil.round(totalWidth / this.chars.length); + this.props.set('FONTBOUNDINGBOX', this.bbx); + } + + expandX() { + this.chars.forEach(char => { + if (char.dwidth.x !== char.bbx.width) { // preserve SWIDTH if possible + char.swidth.x = fnutil.round(char.bbx.width * 1000 / this.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); + }); + this.bbx.xoff = 0; + this.props.set('FONTBOUNDINGBOX', this.bbx); + } + + expandY() { + const props = new Map([ + [ 'FONT_ASCENT', this.pxAscender ], + [ 'FONT_DESCENT', -this.pxDescender ], + [ 'PIXEL_SIZE', this.bbx.height ] + ]); + + props.forEach((value, name) => { + if (this.props.get(name) != null) { + this.props.set(name, value); + } + }); + + this.xlfd[bdf.XLFD.PIXEL_SIZE] = this.bbx.height.toString(); + this.props.set('FONT', this.xlfd.join('-')); + } + + get proportional() { + return this.bbx.width > this.minWidth || super.proportional; + } + + get pxAscender() { + return this.bbx.height + this.bbx.yoff; + } + + get pxDescender() { + return this.bbx.yoff; + } + + _read(input) { + super._read(input); + this.expand(); + return this; + } + + static read(input) { + return (new Font())._read(input); + } + + _updateProp(name, value) { + if (this.props.get(name) != null) { + this.props.set(name, value); + } + } +} + +// -- Export -- +module.exports = Object.freeze({ + Font +}); + +// -- Params -- +class Params extends fncli.Params { + constructor() { + super(); + this.expandX = false; + this.expandY = false; + this.output = null; + } +} + +// -- Options -- +const 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'); + +const VERSION = 'bdfexp 1.60, Copyright (C) 2017-2020 Dimitar Toshkov Zhekov\n\n' + fnutil.GPL2PLUS_LICENSE; + +class Options extends fncli.Options { + constructor() { + super(['-o'], HELP, VERSION); + } + + parse(name, value, params) { + switch (name) { + case '-X': + params.expandX = true; + break; + case '-Y': + params.expandY = true; + break; + case '-o': + params.output = value; + break; + default: + this.fallback(name, params); + } + } +} + +// -- Main -- +function mainProgram(nonopt, parsed) { + if (nonopt.length > 1) { + throw new Error('invalid number of arguments, try --help'); + } + + // READ INPUT + let ifs = new fnio.InputFileStream(nonopt[0]); + + try { + var font = Font.read(ifs); + ifs.close(); + } catch (e) { + e.message = ifs.location() + e.message; + throw e; + } + + // EXTRA ACTIONS + if (parsed.expandX) { + font.expandX(); + } + if (parsed.expandY) { + font.expandY(); + } + + // WRITE OUTPUT + let ofs = new fnio.OutputFileStream(parsed.output); + + try { + font.write(ofs); + ofs.close(); + } catch (e) { + e.message = ofs.location() + e.message() + ofs.destroy(); + throw e; + } +} + +if (require.main === module) { + fncli.start('bdfexp.js', new Options(), new Params(), mainProgram); +} |