294 lines
7.7 KiB
JavaScript
294 lines
7.7 KiB
JavaScript
/*
|
|
Copyright (C) 2017-2019 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 bdfexp = require('./bdfexp.js');
|
|
|
|
// -- Params --
|
|
class Params extends fncli.Params {
|
|
constructor() {
|
|
super();
|
|
this.version = -1;
|
|
this.exchange = -1;
|
|
this.output = null;
|
|
}
|
|
}
|
|
|
|
// -- Options --
|
|
const HELP = ('' +
|
|
'usage: bdftopsf [-1|-2|-r] [-g|-G] [-o OUTPUT] [INPUT.bdf] [TABLE...]\n' +
|
|
'Convert a BDF font to PC Screen Font or raw font\n' +
|
|
'\n' +
|
|
' -1, -2 write a PSF version 1 or 2 font (default = 1 if possible)\n' +
|
|
' -r, --raw write a RAW font\n' +
|
|
' -g, --vga exchange the characters at positions 0...31 with these at\n' +
|
|
' 192...223 (default for VGA text mode compliant PSF fonts\n' +
|
|
' with 224 to 512 characters starting with unicode 00A3)\n' +
|
|
' -G do not exchange characters 0...31 and 192...223\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 monospaced unicode-encoded BDF 2.1 font.\n' +
|
|
'\n' +
|
|
'The tables are text files with two or more hexadecimal unicodes per line:\n' +
|
|
'a character code from the BDF, and extra code(s) for it. All extra codes\n' +
|
|
'are stored sequentially in the PSF unicode table for their character.\n' +
|
|
'<ss> is always specified as FFFE, although it is stored as FE in PSF2.\n');
|
|
|
|
const VERSION = 'bdftopsf 1.58, Copyright (C) 2017-2019 Dimitar Toshkov Zhekov\n\n' + fnutil.GPL2PLUS_VERSION;
|
|
|
|
class Options extends fncli.Options {
|
|
constructor() {
|
|
super(['-o'], HELP, VERSION);
|
|
}
|
|
|
|
parse(name, value, params) {
|
|
switch (name) {
|
|
case '-1':
|
|
case '-2':
|
|
params.version = parseInt(name[1]);
|
|
break;
|
|
case '-r':
|
|
case '--raw':
|
|
params.version = 0;
|
|
break;
|
|
case '-g':
|
|
case '--vga':
|
|
params.exchange = true;
|
|
break;
|
|
case '-G':
|
|
params.exchange = false;
|
|
break;
|
|
case '-o':
|
|
params.output = value;
|
|
break;
|
|
default:
|
|
this.fallback(name, params);
|
|
}
|
|
}
|
|
}
|
|
|
|
// -- Main --
|
|
function mainProgram(nonopt, parsed) {
|
|
const bdfile = nonopt.length > 0 && nonopt[0].toLowerCase().endsWith('.bdf');
|
|
let version = parsed.version;
|
|
let exchange = parsed.exchange;
|
|
let ver1Unicodes = true;
|
|
|
|
// READ INPUT
|
|
let ifs = new fnio.InputFileStream(bdfile ? nonopt[0] : null);
|
|
|
|
try {
|
|
var font = bdfexp.Font.read(ifs);
|
|
|
|
ifs.close();
|
|
font.chars.forEach(char => {
|
|
const prefix = `char ${char.code}: `;
|
|
|
|
if (char.bbx.width !== font.bbx.width) {
|
|
throw new Error(prefix + 'output width not equal to maximum output width');
|
|
}
|
|
if (char.code === 65534) {
|
|
throw new Error(prefix + 'not a character, use 65535 for empty position');
|
|
}
|
|
if (char.code >= 65536) {
|
|
if (version === 1) {
|
|
throw new Error(prefix + '-1 requires unicodes <= 65535');
|
|
}
|
|
ver1Unicodes = false;
|
|
}
|
|
});
|
|
|
|
// VERSION
|
|
var ver1NumChars = (font.chars.length === 256 || font.chars.length === 512);
|
|
|
|
if (version === 1) {
|
|
if (!ver1NumChars) {
|
|
throw new Error('-1 requires a font with 256 or 512 characters');
|
|
}
|
|
if (font.bbx.width !== 8) {
|
|
throw new Error('-1 requires a font with width 8');
|
|
}
|
|
}
|
|
|
|
// EXCHANGE
|
|
var vgaNumChars = font.chars.length >= 224 && font.chars.length <= 512;
|
|
var vgaTextSize = font.bbx.width === 8 && [8, 14, 16].indexOf(font.bbx.height) !== -1;
|
|
|
|
if (exchange === true) {
|
|
if (!vgaNumChars) {
|
|
throw new Error('-g/--vga requires a font with 224...512 characters');
|
|
}
|
|
if (!vgaTextSize) {
|
|
throw new Error('-g/--vga requires an 8x8, 8x14 or 8x16 font');
|
|
}
|
|
}
|
|
} catch (e) {
|
|
e.message = ifs.location() + e.message;
|
|
throw e;
|
|
}
|
|
|
|
// READ TABLES
|
|
let tables = [];
|
|
|
|
function loadExtra(line) {
|
|
const words = line.split(/\s+/);
|
|
|
|
if (words.length < 2) {
|
|
throw new Error('invalid format');
|
|
}
|
|
|
|
const uni = fnutil.parseHex('unicode', words[0]);
|
|
let table = tables[uni];
|
|
|
|
if (uni === 0xFFFE) {
|
|
throw new Error('FFFE is not a character');
|
|
}
|
|
|
|
if (font.chars.findIndex(char => char.code === uni) !== -1) {
|
|
if (uni > fnutil.UNICODE_BMP_MAX) {
|
|
ver1Unicodes = false;
|
|
}
|
|
if (table == null) {
|
|
table = tables[uni] = [];
|
|
}
|
|
|
|
words.slice(1).forEach(word => {
|
|
const dup = fnutil.parseHex('extra code', word);
|
|
|
|
if (dup === 0xFFFF) {
|
|
throw new Error('FFFF is not a character');
|
|
}
|
|
if (dup > fnutil.UNICODE_BMP_MAX) {
|
|
ver1Unicodes = false;
|
|
}
|
|
if (table.indexOf(dup) === -1 || table.indexOf(0xFFFE) !== -1) {
|
|
table.push(dup);
|
|
}
|
|
});
|
|
if (version === 1 && !ver1Unicodes) {
|
|
throw new Error('-1 requires unicodes <= ' + fnutil.UNICODE_BMP_MAX.toString(16));
|
|
}
|
|
}
|
|
}
|
|
|
|
nonopt.slice(Number(bdfile)).forEach(name => {
|
|
ifs = new fnio.InputFileStream(name);
|
|
|
|
try {
|
|
ifs.readLines(loadExtra);
|
|
ifs.close();
|
|
} catch (e) {
|
|
e.message = ifs.location() + e.message;
|
|
throw e;
|
|
}
|
|
});
|
|
|
|
// VERSION
|
|
if (version === -1) {
|
|
version = ver1NumChars && ver1Unicodes && font.bbx.width === 8 ? 1 : 2;
|
|
}
|
|
|
|
// EXCHANGE
|
|
if (exchange === -1) {
|
|
exchange = vgaTextSize && version >= 1 && vgaNumChars && font.chars[0].code === 0x00A3;
|
|
}
|
|
|
|
if (exchange) {
|
|
const control = font.chars.splice(0, 32, ...font.chars.splice(192, 32));
|
|
font.chars.splice(192, 0, ...control);
|
|
}
|
|
|
|
// WRITE
|
|
let ofs = new fnio.OutputFileStream(parsed.output, null);
|
|
|
|
try {
|
|
// HEADER
|
|
if (version === 1) {
|
|
ofs.write8(0x36);
|
|
ofs.write8(0x04);
|
|
ofs.write8((font.chars.length >> 8) + 1);
|
|
ofs.write8(font.bbx.height);
|
|
} else if (version === 2) {
|
|
ofs.write32(0x864AB572);
|
|
ofs.write32(0x00000000);
|
|
ofs.write32(0x00000020);
|
|
ofs.write32(0x00000001);
|
|
ofs.write32(font.chars.length);
|
|
ofs.write32(font.chars[0].data.length);
|
|
ofs.write32(font.bbx.height);
|
|
ofs.write32(font.bbx.width);
|
|
}
|
|
|
|
// GLYPHS
|
|
font.chars.forEach(char => ofs.write(char.data));
|
|
|
|
// UNICODES
|
|
if (version > 0) {
|
|
const writeUnicode = function(code) {
|
|
if (version === 1) {
|
|
ofs.write16(code);
|
|
} else if (code <= 0x7F) {
|
|
ofs.write8(code);
|
|
} else if (code === 0xFFFE || code === 0xFFFF) {
|
|
ofs.write8(code & 0xFF);
|
|
} else {
|
|
if (code <= 0x7FF) {
|
|
ofs.write8(0xC0 + (code >> 6));
|
|
} else {
|
|
if (code <= 0xFFFF) {
|
|
ofs.write8(0xE0 + (code >> 12));
|
|
} else {
|
|
ofs.write8(0xF0 + (code >> 18));
|
|
ofs.write8(0x80 + ((code >> 12) & 0x3F));
|
|
}
|
|
ofs.write8(0x80 + ((code >> 6) & 0x3F));
|
|
}
|
|
ofs.write8(0x80 + (code & 0x3F));
|
|
}
|
|
};
|
|
|
|
font.chars.forEach(char => {
|
|
if (char.code !== 0xFFFF) {
|
|
writeUnicode(char.code);
|
|
}
|
|
if (tables[char.code] != null) {
|
|
tables[char.code].forEach(extra => writeUnicode(extra));
|
|
}
|
|
writeUnicode(0xFFFF);
|
|
});
|
|
}
|
|
|
|
// FINISH
|
|
ofs.close();
|
|
} catch (e) {
|
|
e.message = ofs.location() + e.message + ofs.destroy();
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
if (require.main === module) {
|
|
fncli.start('bdftopsf.js', new Options(), new Params(), mainProgram);
|
|
}
|