portal64-still-alive/tools/font_converter.js
2023-10-28 12:31:30 -06:00

186 lines
5.2 KiB
JavaScript

// this tool takes a the json output from https://github.com/andryblack/fontbuilder and create a font usabe in portal64
// usage
// generate multiple font files with each image not being larger than 4kb
// then name the files font_file_0.json, font_file_1.json, font_file_2.json
// you also need a json file with all the characters in a single file called font_file_all.json
// this all file is only needed to extract the kerning
// once you have that use this as follows
//
// node tools/font_converter.js FontName /path/to/font_file /path/to/output.c
const fs = require('fs');
const name = process.argv[2];
const filePrefix = process.argv[3];
const joinedSymbols = [];
let index = 0;
while (index < 100) {
const filename = `${filePrefix}_${index}.json`;
if (!fs.existsSync(filename)) {
break;
}
const singleInput = JSON.parse(fs.readFileSync(filename));
singleInput.symbols.forEach((symbol) => {
joinedSymbols.push({
...symbol,
textureIndex: index,
});
});
++index;
}
const allSymbols = JSON.parse(fs.readFileSync(`${filePrefix}_all.json`));
const input = {
kerning: allSymbols.kerning,
config: allSymbols.config,
symbols: joinedSymbols,
};
function kerningHashFunction(kerning, multiplier, arraySize) {
return ((kerning.first * multiplier) + kerning.second) % arraySize;
}
function symbolHashFunction(symbol, multiplier, arraySize) {
return (symbol.id * multiplier) % arraySize;
}
function checkForCollisions(list, hashFunction, multiplier, arraySize, emptyObj) {
const sparseArray = [];
sparseArray.length = arraySize;
if (arraySize < list.length) {
return null;
}
let maxCollisions = 0;
let averageCollisions = 0;
for (const element of list) {
let index = hashFunction(element, multiplier, arraySize);
let currentCollisions = 0;
while (sparseArray[index]) {
index = (index + 1) % arraySize;
currentCollisions = currentCollisions + 1;
}
maxCollisions = Math.max(maxCollisions, currentCollisions);
averageCollisions += currentCollisions;
sparseArray[index] = element;
}
averageCollisions /= list.length;
for (let i = 0; i < arraySize; ++i) {
if (!sparseArray[i]) {
sparseArray[i] = emptyObj;
}
}
return {sparseArray, maxCollisions, averageCollisions};
}
function searchForBestHashTable(list, hashFunction, emptyObj) {
let result;
let multiplier;
let arraySize = 1;
let mask = 1;
while (arraySize < list.length) {
arraySize *= 2;
mask <<= 1;
}
arraySize *= 2;
mask <<= 1;
for (let i = 1; i < 0x10000; ++i) {
const check = checkForCollisions(list, hashFunction, i, arraySize, emptyObj);
if (!result || check.maxCollisions < result.maxCollisions) {
result = check
multiplier = i;
}
}
mask -= 1;
return {result: result.sparseArray, multiplier: multiplier, mask: mask, maxCollisions: result.maxCollisions, averageCollisions: result.averageCollisions};
}
function buildKerning(kerningList) {
return `struct FontKerning g${name}Kerning[] = {
${kerningList.map(kerning => ` {.amount = ${kerning.amount}, .first = ${kerning.first}, .second = ${kerning.second}},`).join('\n')}
};
`
}
function buildFont(kerningResult, symbolResult) {
return `struct Font g${name}Font = {
.kerning = &g${name}Kerning[0],
.symbols = &g${name}Symbols[0],
.base = ${input.config.base},
.charHeight = ${input.config.charHeight},
.symbolMultiplier = ${symbolResult.multiplier},
.symbolMask = 0x${symbolResult.mask.toString(16)},
.symbolMaxCollisions = ${symbolResult.maxCollisions},
.kerningMultiplier = ${kerningResult.multiplier},
.kerningMask = 0x${kerningResult.mask.toString(16)},
.kerningMaxCollisions = ${kerningResult.maxCollisions},
};
`
}
function buildSymbol(symbol) {
return ` {
.id = ${symbol.id},
.x = ${symbol.x}, .y = ${symbol.y},
.width = ${symbol.width}, .height = ${symbol.height},
.xoffset = ${symbol.xoffset}, .yoffset = ${symbol.yoffset},
.xadvance = ${symbol.xadvance},
.textureIndex = ${symbol.textureIndex},
},`
}
const kerningResult = searchForBestHashTable(
input.kerning,
kerningHashFunction,
{amount: 0, first: 0, second: 0}
);
const symbolResult = searchForBestHashTable(
input.symbols,
symbolHashFunction,
{id: 0, x: 0, y: 0, width: 0, height: 0, xoffset: 0, yoffset: 0, xadvance: 0, textureIndex: -1}
);
console.log(`symbolLength = ${input.symbols.length}/${symbolResult.result.length}`);
console.log(`symbolMaxCollisions = ${symbolResult.maxCollisions}`);
console.log(`symbolAverageCollisions = ${symbolResult.averageCollisions}`);
console.log(`kerningLength = ${input.kerning.length}/${kerningResult.result.length}`);
console.log(`maxKerningCollisions = ${kerningResult.maxCollisions}`);
console.log(`kerningAverageCollisions = ${kerningResult.averageCollisions}`);
fs.writeFileSync(process.argv[4], `
#include "font.h"
${buildKerning(kerningResult.result)}
struct FontSymbol g${name}Symbols[] = {
${symbolResult.result.map(buildSymbol).join('\n')}
};
${buildFont(kerningResult, symbolResult)}
`);