aboutsummaryrefslogtreecommitdiff
path: root/bin/bdfexp.js
diff options
context:
space:
mode:
Diffstat (limited to 'bin/bdfexp.js')
-rw-r--r--bin/bdfexp.js287
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);
+}