Files
jameswitzeman.net/fonts/terminus-font/bin/bdfexp.js
T
2026-05-29 11:30:10 -07:00

288 lines
7.2 KiB
JavaScript

/*
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);
}