#include "sanstop.h"
#include "utf8.h"
int main(int argc, char** argv) {
Config c = (Config) {NULL, NULL, NULL, -1, HMARGIN, VMARGIN, 0, 0};
Glyph* glyphs = NULL;
FT_Library ft = NULL;
FT_Face face = NULL;
Buffer b = (Buffer) {NULL, -1, 1, IMAGE_DIM - c.vMargin - c.vShift};
int stat = readConfig(&c, argc, argv);
if (stat == 0) {
size_t glyphCap = 256;
size_t glyphCount = 0;
glyphs = malloc(glyphCap * sizeof(Glyph));
if (initializeBuffer(&b) != 0) {
fprintf(stderr, "Could not allocate memory for buffer.\n");
fprintf(stderr, "Maybe you don't have enough?\n");
stat = -2;
goto end;
}
if (FT_Init_FreeType(&ft) != 0) {
fprintf(stderr, "Could not init FreeType library\n");
stat = -2;
goto end;
}
if (FT_New_Face(ft, c.fontFileName, 0, &face) != 0) {
fprintf(stderr, "Could not load font face %s\n", c.fontFileName);
stat = -1;
goto end;
}
stat = FT_Select_Charmap(face, FT_ENCODING_UNICODE);
if (stat != 0) {
fprintf(stderr, "This font doesn't have a Unicode charmap.\n");
goto end;
}
FT_Set_Pixel_Sizes(face, 0, c.size);
char* here = c.targets;
int codepoint;
// Load and blit glyphs
while (*here != '\0') {
int ustat = utf8Next(&here, &codepoint);
//printf("[0x%x]\n", codepoint);
if (ustat != 0) {
fprintf(stderr, "Malformatted UTF-8.\n");
int byte = ustat & 0xFF;
switch (utf8DecodeErrorClass(ustat)) {
case ERRC_UNEXPECTED_CONTINUATION:
fprintf(stderr, "Unexpected continuation byte 0x%x.\n", byte);
break;
case ERRC_CONTINUATION_EXPECTED:
fprintf(stderr, "Expected continuation byte, but got 0x%x instead.\n", byte);
break;
case ERRC_INVALID_UTF8_BYTE:
fprintf(stderr, "Invalid byte 0x%x.\n", byte);
}
stat = -1;
goto end;
}
// printf("[%x] ", codepoint);
if (codepoint < 32 || codepoint == 0xfeff || codepoint == 0xfffe) continue;
if (FT_Load_Char(face, codepoint, FT_LOAD_RENDER) != 0) {
fprintf(stderr, "Warn: glyph %lc [0x%x] does not exist in this font.\n", codepoint, codepoint);
} else {
// printf("[%x] ", codepoint);
glyphs[glyphCount].id = codepoint;
stat = blitAndAdvance(&b, face, glyphs + glyphCount, &c);
if (stat != 0) goto end;
++glyphCount;
if (glyphCount >= glyphCap) {
glyphCap <<= 1;
glyphs = realloc(glyphs, glyphCap * sizeof(Glyph));
}
}
}
// Write what's Left
writePage(&b, &c);
// Write XML file
int len = strlen(c.fontPrefix) + 5;
char* fname = malloc(sizeof(char) * len);
snprintf(fname, len, "%s.xml", c.fontPrefix);
FILE* f = fopen(fname, "w");
if (f == NULL) {
fprintf(stderr, "Cannot write to file %s!", fname);
free(fname);
stat = -1;
goto end;
}
writeXMLHeader(f, face, &c);
writeXMLPageData(f, &c, &b);
writeXMLGlyphData(f, glyphCount, glyphs, &c);
writeXMLFooter(f);
fclose(f);
free(fname);
}
end:
cleanConfig(&c);
if (glyphs != NULL) free(glyphs);
if (face != NULL) FT_Done_Face(face);
if (ft != NULL) FT_Done_FreeType(ft);
if (b.data != NULL) free(b.data);
return stat;
}
int initializeBuffer(Buffer* b) {
b->page = 0;
uint8_t* data = malloc(IMAGE_PIXELS * sizeof(uint8_t));
if (data == NULL) return -1;
b->data = data;
return 0;
}
void flipPage(Buffer* b, Config* c) {
b->page++;
memset(b->data, 0, IMAGE_PIXELS * sizeof(uint8_t));
b->x = 1;
b->y = IMAGE_DIM - c->vMargin - c->vShift;
}
int shouldGoUp(Buffer* b, int width) {
return IMAGE_DIM - b->x < width;
}
int shouldFlip(Buffer* b, Config* c, int height) {
return b->y < height - c->vShift;
}
void goUp(Buffer* b, int height) {
b->y -= height;
b->x = 1;
}
void advance(Buffer* b, int width) {
b->x += width;
}
#include "dds.h"
int writePage(Buffer* b, Config* c) {
size_t len = snprintf(NULL, 0, "%s_%d.dds", c->fontPrefix, b->page) + 1;
char* fname = malloc(sizeof(char) * len);
snprintf(fname, len, "%s_%d.dds", c->fontPrefix, b->page);
FILE* f = fopen(fname, "wb");
if (f == NULL) {
fprintf(stderr, "File %s is unwritable\n", fname);
free(fname);
return -1;
}
fwrite(DDS_HEADER, DDS_HEADER_SIZE, 1, f);
fwrite(b->data, IMAGE_PIXELS * sizeof(uint8_t), 1, f);
free(fname);
fclose(f);
return 0;
}
void blit(Buffer* b, FT_Face face, Glyph* gp, int tw, int th, int size) {
Glyph g = *gp;
g.page = b->page;
g.left = b->x;
g.bottom = b->y;
g.right = b->x + tw;
g.top = b->y - th;
int sx = g.left + face->glyph->bitmap_left;
int base = g.bottom + size * face->descender / face->units_per_EM;
int sy = base - face->glyph->bitmap_top;
int w = face->glyph->bitmap.width;
int h = face->glyph->bitmap.rows;
uint8_t* iwl = b->data + sx + sy * IMAGE_DIM;
uint8_t* grl = face->glyph->bitmap.buffer;
// This is hardly the fastest way, but let's focus on being correct first.
for (int i = 0; i < h; ++i) {
memcpy(iwl, grl, w);
iwl += IMAGE_DIM;
grl += w;
}
*gp = g;
}
int blitAndAdvance(Buffer* b, FT_Face face, Glyph* gp, Config* c) {
int tw = (face->glyph->advance.x >> 6) + c->hMargin;
int th = c->vMargin + c->size * face->height / face->units_per_EM;
if (shouldGoUp(b, tw))
goUp(b, th);
if (shouldFlip(b, c, th)) {
int stat = writePage(b, c);
if (stat != 0) return stat;
flipPage(b, c);
}
blit(b, face, gp, tw, th, c->size);
advance(b, tw);
return 0;
}
const char* xmlFontPropertyFormatter =
"\n"
;
void writeXMLHeader(FILE* f, FT_Face face, Config* c) {
fprintf(f, "\n");
fprintf(f, xmlFontPropertyFormatter,
face->family_name,
c->size * 5,
700,
!!(face->style_flags & FT_STYLE_FLAG_BOLD),
!!(face->style_flags & FT_STYLE_FLAG_ITALIC)
);
}
void writeXMLPageData(FILE* f, Config* c, Buffer* b) {
int totalPages = b->page + 1;
fprintf(f, "\t\t\n", totalPages);
char* proper = strrchr(c->fontPrefix, '/');
if (proper == NULL) proper = c->fontPrefix;
else ++proper;
for (int i = 0; i < totalPages; ++i) {
fprintf(f, "\t\t\t\t\n",
proper, i, IMAGE_DIM, IMAGE_DIM);
}
fprintf(f, "\t\t\n");
}
const char* xmlGlyphEntryFormatter =
"\t\t\t\t"
"\n"
;
#include
#include
void writeXMLGlyphData(FILE* f, int glyphCount, Glyph* glyphs, Config* c) {
srand(time(NULL));
fprintf(f, "\t\t\n", glyphCount);
for (int i = 0; i < glyphCount; ++i) {
Glyph g = glyphs[i];
fprintf(f, xmlGlyphEntryFormatter,
g.id,
0,
g.right - g.left - 2,
0,
g.page,
g.top + c->vShift,
g.bottom + c->vShift,
g.left + c->hShift,
g.right - c->hMargin + c->hShift
);
}
fprintf(f, "\t\t\n");
}
void writeXMLFooter(FILE* f) {
fprintf(f, "\n");
}
int readConfig(Config* c, int argc, char** argv) {
int pos = 0;
int ignoreOpts = 0;
int status = 0;
for (int i = 1; i < argc; ++i) {
if (argv[i][0] == '-' && !ignoreOpts) {
if (argv[i][1] == '-') {
char* opt = argv[i] + 2;
if (*opt == '\0') ignoreOpts = 1;
else if (!strcmp(opt, "help")) {
printHelp();
status = 1;
break;
} else if (!strcmp(opt, "horizontal-margin")) {
if (i >= argc - 1) {
fprintf(stderr, "--horizontal-margin requires a parameter.\n");
status = -1;
break;
}
c->hMargin = atoi(argv[++i]);
} else if (!strcmp(opt, "vertical-margin")) {
if (i >= argc - 1) {
fprintf(stderr, "--vertical-margin requires a parameter.\n");
status = -1;
break;
}
c->vMargin = atoi(argv[++i]);
} else if (!strcmp(opt, "horizontal-shift")) {
if (i >= argc - 1) {
fprintf(stderr, "--horizontal-shift requires a parameter.\n");
status = -1;
break;
}
c->hShift = atoi(argv[++i]);
} else if (!strcmp(opt, "vertical-shift")) {
if (i >= argc - 1) {
fprintf(stderr, "--vertical-shift requires a parameter.\n");
status = -1;
break;
}
c->vShift = atoi(argv[++i]);
} else {
fprintf(stderr, "Invalid option %s.\n", argv[i]);
status = -1;
break;
}
} else {
for (char* f = argv[i] + 1; *f != '\0'; ++f) {
switch (*f) {
case 'h':
printHelp();
status = 1;
goto end;
default:
fprintf(stderr, "Invalid option -%c.\n", *f);
status = -1;
goto end;
}
}
}
}
else {
switch (pos) {
case 0:
c->fontFileName = argv[i];
break;
case 1:
c->fontPrefix = argv[i];
break;
case 2: {
FILE* f = fopen(argv[i], "r");
if (f == NULL) {
fprintf(stderr, "File %s is missing or unreadable\n", argv[i]);
status = -1;
goto end;
}
// Slurp the source
// Thanks http://stackoverflow.com/questions/14002954/c-programming-how-to-read-the-whole-file-contents-into-a-buffer
fseek(f, 0, SEEK_END);
long fsize = ftell(f);
fseek(f, 0, SEEK_SET);
char* string = (char*) malloc(fsize + 1);
size_t br = fread(string, fsize, 1, f);
if (br < 1) {
status = -2;
goto end;
}
fclose(f);
c->targets = string;
break;
}
case 3:
c->size = atoi(argv[i]);
if (c->size <= 0) {
fprintf(stderr, "Size too small!\n");
status = -1;
goto end;
}
if (c->size > 144) {
fprintf(stderr, "Size too big (max is 144)!\n");
status = -1;
goto end;
}
break;
default:
status = -1;
goto end;
}
++pos;
}
}
end:
if (status == 0 && (c->fontFileName == NULL || c->fontPrefix == NULL ||
c->targets == NULL || c->size <= 0))
status = -1;
if (c->vShift > c->vMargin || c->vShift < -c->vMargin ||
c->hShift < 0 || c->hShift > c->hMargin)
fprintf(stderr, "Warn: shift exceeds margin bounds.\n");
if (status == -1) printUsage();
if (status == -2) fprintf(stderr, "Unexpected error; please tell kyarei.\n");
return status;
}
void cleanConfig(Config* c) {
free(c->targets);
}
static const char* DESCRIPTION =
"sanstop - generates xml and dds files from font files for Wizard101.\n"
"Version 0.1.\n"
;
static const char* USAGE =
"Usage:\n"
" sanstop [OPTIONS] \n"
" font-file: the source font file (ttf / otf)\n"
" font-prefix: the prefix used for output files\n"
" targets: file containing list of characters to be used\n"
" size: the height to use for glyphs\n"
" OPTIONS -\n"
" -h --help display this help message\n"
" -- don't treat future arguments starting with - as flags\n"
" --horizontal-margin specify how much extra space to leave\n"
" between glyphs (default: 4). Useful for dealing with\n"
" ill-behaved fonts.\n"
" --vertical-margin specify how much extra space to leave\n"
" between rows (default: 8). Useful for dealing with\n"
" ill-behaved fonts.\n"
" --horizontal-shift specify how much to the left (-) or\n"
" right (+) the bounding boxes should be adjusted (default: 0).\n"
" Useful for dealing with ill-behaved fonts.\n"
" --vertical-shift specify how much up (-) or down (+)\n"
" the bounding boxes should be adjusted (default: 0).\n"
" Useful for dealing with ill-behaved fonts.\n"
"Return codes:\n"
" 0: everything is fine\n"
" 1: help was displayed\n"
" -1: usage error\n"
" -2: please tell kyarei!\n"
;
void printUsage() {
fprintf(stderr, USAGE);
}
void printHelp() {
fprintf(stderr, DESCRIPTION);
printUsage();
}