197 lines
4.9 KiB
C++
197 lines
4.9 KiB
C++
#include <cstdio>
|
|
#include <cstring>
|
|
#include <cerrno>
|
|
#include <cstdint>
|
|
#include <vector>
|
|
#include <array>
|
|
#include <algorithm>
|
|
|
|
#include <arpa/inet.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include <png.h>
|
|
|
|
#include "common.hh"
|
|
|
|
struct Image {
|
|
unsigned height, width;
|
|
std::vector<uint8_t> data;
|
|
};
|
|
|
|
struct Font {
|
|
unsigned numChars;
|
|
unsigned charHeight;
|
|
unsigned maxCharWidth;
|
|
uint8_t bgColor;
|
|
std::vector<Image> chars;
|
|
};
|
|
|
|
struct FontHeader { // little endian
|
|
uint32_t unknown1; // (always)? "1.\0\0"
|
|
uint32_t entries;
|
|
uint32_t charHeight;
|
|
uint8_t bgColor;
|
|
char unknown3[3];
|
|
} __attribute__((__packed__));
|
|
|
|
Font parseFont(FILE* file, size_t length)
|
|
{
|
|
if (length < sizeof(FontHeader))
|
|
throw FormatException{"File smaller than header"};
|
|
|
|
FontHeader header;
|
|
if (fread(&header, sizeof(FontHeader), 1, file) != 1)
|
|
throw POSIXException{errno, "Could not read"};
|
|
|
|
if (header.unknown1 != 0x00002E31)
|
|
throw FormatException{"Not a font?"};
|
|
|
|
const unsigned index_start = sizeof(FontHeader);
|
|
const unsigned index_end = index_start+4*header.entries;
|
|
|
|
if (index_end > length)
|
|
throw Exception{"Index table exceeds file length"};
|
|
|
|
std::vector<uint32_t> indices;
|
|
for (unsigned i = index_start;i < index_end;i+=4) {
|
|
uint32_t data;
|
|
if (fread(&data, 4, 1, file) != 1)
|
|
throw POSIXException{errno, "Could not read"};
|
|
indices.push_back(data);
|
|
}
|
|
|
|
auto minmax = std::minmax_element(indices.begin(), indices.end());
|
|
printf("Index range %u .. %u\n",
|
|
*minmax.first, *minmax.second);
|
|
|
|
if (*minmax.second > length)
|
|
throw FormatException{"Index exceeds file length"};
|
|
|
|
Font ret;
|
|
ret.numChars = header.entries;
|
|
ret.charHeight = header.charHeight;
|
|
ret.maxCharWidth = 0;
|
|
ret.bgColor = header.bgColor;
|
|
for (auto const& index : indices) {
|
|
uint32_t len;
|
|
if (fseeko(file, index, SEEK_SET) != 0)
|
|
throw POSIXException(errno, "Could not seek");
|
|
|
|
if (fread(&len, 4, 1, file) != 1)
|
|
throw POSIXException{errno, "Could not read"};
|
|
|
|
if (!len) {
|
|
ret.chars.push_back(Image{0, 0, {}});
|
|
continue;
|
|
}
|
|
|
|
if (index+4+len*header.charHeight > length)
|
|
throw FormatException{"Glyph data exceeds file length"};
|
|
|
|
Image chr;
|
|
chr.height = header.charHeight;
|
|
chr.width = len;
|
|
chr.data.resize(chr.height*chr.width);
|
|
|
|
if (chr.width > ret.maxCharWidth)
|
|
ret.maxCharWidth = chr.width;
|
|
|
|
if (fread(chr.data.data(), chr.height*chr.width, 1, file) != 1)
|
|
throw POSIXException{errno, "Could not read"};
|
|
|
|
ret.chars.push_back(chr);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void saveImage(std::string const& name, Image const& img)
|
|
{
|
|
FILEUPtr file{fopen(name.c_str(), "wb")};
|
|
if (!file)
|
|
throw POSIXException{errno, "Could not open " + name};
|
|
|
|
png_image pngImage;
|
|
pngImage.version = PNG_IMAGE_VERSION;
|
|
pngImage.width = img.width;
|
|
pngImage.height = img.height;
|
|
pngImage.format = PNG_FORMAT_GRAY;
|
|
pngImage.flags = 0;
|
|
pngImage.opaque = nullptr;
|
|
pngImage.colormap_entries = 0;
|
|
pngImage.warning_or_error = 0;
|
|
memset(pngImage.message, '\0', 64);
|
|
|
|
if (!png_image_write_to_stdio(&pngImage, file.get(), false, img.data.data(),
|
|
img.width, nullptr))
|
|
throw Exception{"PNG write failed: " + pngImage.message};
|
|
}
|
|
|
|
Image makeFontImage(Font const& font)
|
|
{
|
|
const unsigned charsPerLine = 16;
|
|
|
|
Image ret;
|
|
ret.width = font.maxCharWidth*charsPerLine;
|
|
unsigned numCharLines = font.numChars/charsPerLine;
|
|
if ((font.numChars%charsPerLine) != 0)
|
|
++numCharLines;
|
|
ret.height = font.charHeight*numCharLines;
|
|
|
|
ret.data.reserve(ret.width*ret.height);
|
|
|
|
for (unsigned int c_y = 0;c_y < numCharLines;++c_y) {
|
|
for (unsigned int y = 0;y < font.charHeight;++y) {
|
|
for (unsigned int c_x = 0;c_x < charsPerLine;++c_x) {
|
|
unsigned charNo = c_y*charsPerLine+c_x;
|
|
if (charNo > font.numChars) {
|
|
for (unsigned x = 0;x < font.maxCharWidth;++x)
|
|
ret.data.push_back(font.bgColor);
|
|
} else {
|
|
for (unsigned x = 0;x < font.chars[charNo].width;++x)
|
|
ret.data.push_back(font.chars[charNo].data[y*font.chars[charNo].width+x]);
|
|
for (unsigned x = font.chars[charNo].width; x < font.maxCharWidth;++x)
|
|
ret.data.push_back(font.bgColor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
if (argc != 2) {
|
|
fprintf(stderr, "Usage: %s font-file\n", argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
try {
|
|
FILEUPtr fontFile{fopen(argv[1], "rb")};
|
|
if (!fontFile) {
|
|
throw POSIXException{errno, "Could not open "s+ argv[1]};
|
|
}
|
|
|
|
struct stat statBuf;
|
|
|
|
if (fstat(fileno(fontFile.get()), &statBuf) != 0) {
|
|
throw POSIXException(errno, "Could not stat");
|
|
}
|
|
|
|
auto font = parseFont(fontFile.get(), statBuf.st_size);
|
|
saveImage(argv[1] + ".png", makeFontImage(font));
|
|
|
|
} catch (POSIXException &ex) {
|
|
fprintf(stderr, "%s\n", ex.toString().c_str());
|
|
return 2;
|
|
} catch (Exception &ex) {
|
|
fprintf(stderr, "%s\n", ex.toString().c_str());
|
|
return 3;
|
|
}
|
|
|
|
return 0;
|
|
}
|