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

257 lines
7.3 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');
const bdfexp = require('./bdfexp.js');
// -- Params --
class Params extends fncli.Params {
constructor() {
super();
this.charSet = -1;
this.minChar = -1;
this.fntFamily = 0;
this.output = null;
}
}
// -- Options --
const HELP = ('' +
'usage: bdftofnt [-c CHARSET] [-m MINCHAR] [-f FAMILY] [-o OUTPUT] [INPUT]\n' +
'Convert a BDF font to Windows FNT\n' +
'\n' +
' -c CHARSET fnt character set (default = 0, see wingdi.h ..._CHARSET)\n' +
' -m MINCHAR fnt minimum character code (8-bit CP decimal, not unicode)\n' +
' -f FAMILY fnt family: DontCare, Roman, Swiss, Modern or Decorative\n' +
' -o OUTPUT output file (default = stdout, may not be a terminal)\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 = 'bdftofnt 1.60, Copyright (C) 2017-2020 Dimitar Toshkov Zhekov\n\n' + fnutil.GPL2PLUS_LICENSE;
const FNT_FAMILIES = [ 'DontCare', 'Roman', 'Swiss', 'Modern', 'Decorative' ];
class Options extends fncli.Options {
constructor() {
super(['-c', '-m', '-f', '-o'], HELP, VERSION);
}
parse(name, value, params) {
switch (name) {
case '-c':
params.charSet = fnutil.parseDec('CHARSET', value, 0, 255);
break;
case '-m':
params.minChar = fnutil.parseDec('MINCHAR', value, 0, 255);
break;
case '-f':
params.fntFamily = FNT_FAMILIES.indexOf(value);
if (params.fntFamily === -1) {
throw new Error('invalid FAMILY');
}
break;
case '-o':
params.output = value;
break;
default:
this.fallback(name, params);
}
}
}
// -- Main --
const FNT_HEADER_SIZE = 118;
const FNT_CHARSETS = [238, 204, 0, 161, 162, 177, 178, 186, 163];
function mainProgram(nonopt, parsed) {
if (nonopt.length > 1) {
throw new Error('invalid number of arguments, try --help');
}
let charSet = parsed.charSet;
let minChar = parsed.minChar;
// READ INPUT
let ifs = new fnio.InputFileStream(nonopt[0]);
try {
var font = bdfexp.Font.read(ifs);
ifs.close();
} catch (e) {
e.message = ifs.location() + e.message;
throw e;
}
// COMPUTE
if (charSet === -1) {
const encoding = font.xlfd[bdf.XLFD.CHARSET_ENCODING];
if (encoding.toLowerCase().match(/^(cp)?125[0-8]$/)) {
charSet = FNT_CHARSETS[parseInt(encoding.substring(encoding.length - 1), 10)];
} else {
charSet = 255;
}
}
try {
const numChars = font.chars.length;
if (numChars > 256) {
throw new Error('too many characters, the maximum is 256');
}
if (minChar === -1) {
if (numChars === 192 || numChars === 256) {
minChar = 256 - numChars;
} else {
minChar = font.chars[0].code;
}
}
var maxChar = minChar + numChars - 1;
if (maxChar >= 256) {
throw new Error('the maximum character code is too big, (re)specify -m');
}
// HEADER
var vtell = FNT_HEADER_SIZE + (numChars + 1) * 4;
var bitsOffset = vtell;
var ctable = [];
var widthBytes = 0;
// CTABLE/GLYPHS
font.chars.forEach(char => {
const rowSize = char.bbx.rowSize();
ctable.push(char.bbx.width);
ctable.push(vtell);
vtell += rowSize * font.bbx.height;
widthBytes += rowSize;
});
if (vtell > 0xFFFF) {
throw new Error('too much character data');
}
// SENTINEL
var sentinel = 2 - widthBytes % 2;
ctable.push(sentinel * 8);
ctable.push(vtell);
vtell += sentinel * font.bbx.height;
widthBytes += sentinel;
if (widthBytes > 0xFFFF) {
throw new Error('the total character width is too big');
}
} catch (e) {
e.message = ifs.location() + e.message;
throw e;
}
// WRITE
let ofs = new fnio.OutputFileStream(parsed.output, null);
try {
// HEADER
const family = font.xlfd[bdf.XLFD.FAMILY_NAME];
let copyright = font.props.get('COPYRIGHT');
copyright = (copyright != null) ? fnutil.unquote(copyright).substring(0, 60) : '';
ofs.write16(0x0200); // font version
ofs.write32(vtell + family.length + 1); // total size
ofs.writeZStr(copyright, 60 - copyright.length);
ofs.write16(0); // gdi, device type
ofs.write16(fnutil.round(font.bbx.height * 72 / 96));
ofs.write16(96); // vertical resolution
ofs.write16(96); // horizontal resolution
ofs.write16(font.pxAscender); // base line
ofs.write16(0); // internal leading
ofs.write16(0); // external leading
ofs.write8(Number(font.italic));
ofs.write8(0); // underline
ofs.write8(0); // strikeout
ofs.write16(font.bold ? 700 : 400);
ofs.write8(charSet);
ofs.write16(font.proportional ? 0 : font.bbx.width);
ofs.write16(font.bbx.height);
ofs.write8((parsed.fntFamily << 4) + Number(font.proportional));
ofs.write16(font.avgWidth);
ofs.write16(font.bbx.width);
ofs.write8(minChar);
ofs.write8(maxChar);
let defaultIndex = maxChar - minChar;
let breakIndex = 0;
if (font.defaultCode !== -1) {
defaultIndex = font.chars.findIndex(char => char.code === font.defaultCode);
}
if (minChar <= 0x20 && maxChar >= 0x20) {
breakIndex = 0x20 - minChar;
}
ofs.write8(defaultIndex);
ofs.write8(breakIndex);
ofs.write16(widthBytes);
ofs.write32(0); // device name
ofs.write32(vtell);
ofs.write32(0); // gdi bits pointer
ofs.write32(bitsOffset);
ofs.write8(0); // reserved
// CTABLE
ctable.forEach(value => ofs.write16(value));
// GLYPHS
const data = Buffer.alloc(font.bbx.height * font.bbx.rowSize());
font.chars.forEach(char => {
const rowSize = char.bbx.rowSize();
let counter = 0;
// MS coordinates
for (let n = 0; n < rowSize; n++) {
for (let y = 0; y < font.bbx.height; y++) {
data[counter++] = char.data[rowSize * y + n];
}
}
ofs.write(data.slice(0, counter));
});
ofs.write(Buffer.alloc(sentinel * font.bbx.height));
// FAMILY
ofs.writeZStr(family, 1);
ofs.close();
} catch (e) {
e.message = ofs.location() + e.message + ofs.destroy();
throw e;
}
}
if (require.main === module) {
fncli.start('bdftofnt.js', new Options(), new Params(), mainProgram);
}