mirror of
https://github.com/mwpenny/portal64-still-alive.git
synced 2024-10-19 22:27:36 -04:00
186 lines
5.2 KiB
JavaScript
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)}
|
|
`); |