netsurf/tools/convert_font.c

1216 lines
25 KiB
C

/*
* Copyright 2014 Michael Drake <tlsa@netsurf-browser.org>
* Copyright 2014 Vincent Sanders <vince@netsurf-browser.org>
*
* This file is part of the convert_font tool used to convert font
* glyph data into a compilable representation.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* * The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <getopt.h>
#define GLYPH_LEN 16
#define BUCKETS 512
#define CHUNK_SIZE (64 * 1024)
#define HEADER_MAX 2000
#define SECTION_SIZE (sizeof(uint16_t) * 256)
const char *labels[4] = {
" Regular",
" Italic",
" Bold",
"Bold & Italic"
};
const char *var_lables[4] = {
"fb_regular",
"fb_italic",
"fb_bold",
"fb_bold_italic"
};
const char *short_labels[4] = {
" ",
" i",
"b ",
"bi"
};
enum font_style {
REGULAR = 0,
ITALIC = (1 << 0),
BOLD = (1 << 1),
ITALIC_BOLD = (1 << 2)
};
enum log_level {
LOG_DEBUG,
LOG_INFO,
LOG_RESULT,
LOG_WARNING,
LOG_ERROR
};
enum log_level level;
typedef struct glyph_entry {
union {
uint32_t u32[GLYPH_LEN / 4];
uint8_t u8[GLYPH_LEN];
} data;
uint32_t index;
struct glyph_entry *next;
} glyph_entry;
/** Scratch glyph for generated code points */
uint8_t code_point[GLYPH_LEN];
/** Hash table */
glyph_entry *ht[BUCKETS];
#define LOG(lev, fmt, ...) \
if (lev >= level) \
printf(fmt, ##__VA_ARGS__);
/**
* Get hash for glyph data
* \param g Glyph data (GLYPH_LEN bytes)
* \return glyph's hash
*/
static inline uint32_t glyph_hash(const uint8_t *g)
{
uint32_t hash = 0x811c9dc5;
unsigned int len = GLYPH_LEN;
while (len > 0) {
hash *= 0x01000193;
hash ^= *g++;
len--;
}
return hash;
}
/**
* Check whether glyphs are identical (compares glyph data)
*
* \param g1 First glyph's data (GLYPH_LEN bytes)
* \param g2 Second glyph's data (GLYPH_LEN bytes)
* \return true iff both glyphs are identical, else false
*/
static inline bool glyphs_match(const uint8_t *g1, const uint8_t *g2)
{
return (memcmp(g1, g2, GLYPH_LEN) == 0);
}
/**
* Add a glyph to a hash chain (or free, and return pointer to existing glyph)
*
* Note that if new glyph already exists in chain, it is freed and a pointer to
* the existing glyph is returned. If the glyph does not exist in the chain
* it is added and its pointer is returned.
*
* \param head Head of hash chain
* \param new New glyph to add (may be freed)
* \return pointer to glyph in hash chain
*/
static glyph_entry * glyph_add_to_chain(glyph_entry **head, glyph_entry *new)
{
glyph_entry *e = *head;
if (*head == NULL) {
new->next = NULL;
*head = new;
return new;
}
do {
if (glyphs_match(new->data.u8, e->data.u8)) {
free(new);
return e;
}
if (e->next == NULL)
break;
e = e->next;
} while (1);
new->next = e->next;
e->next = new;
return new;
}
/**
* Free a glyph entry chain
*
* \param head Head of hash chain
*/
static void free_chain(glyph_entry *head)
{
glyph_entry *e = head;
if (head == NULL)
return;
while (e != NULL) {
head = e->next;
free(e);
e = head;
};
}
/**
* Add new glyph to hash table (or free, and return pointer to existing glyph)
*
* Note that if new glyph already exists in table, it is freed and a pointer to
* the existing glyph is returned. If the glyph does not exist in the table
* it is added and its pointer is returned.
*
* \param new New glyph to add (may be freed)
* \return pointer to glyph in hash table
*/
static glyph_entry * glyph_add_to_table(glyph_entry *new)
{
uint32_t hash = glyph_hash(new->data.u8);
return glyph_add_to_chain(&ht[hash % BUCKETS], new);
}
/**
* Free glyph table.
*/
static void free_table(void)
{
int i;
for (i = 0; i < BUCKETS; i++) {
free_chain(ht[i]);
}
}
struct parse_context {
enum {
START,
IN_HEADER,
BEFORE_ID,
GLYPH_ID,
BEFORE_GLYPH_DATA,
IN_GLYPH_DATA
} state; /**< Current parser state */
union {
struct {
bool new_line;
} in_header;
struct {
bool new_line;
bool u;
} before_id;
struct {
int c;
} g_id;
struct {
bool new_line;
bool prev_h;
bool prev_s;
int c;
} before_gd;
struct {
int line;
int pos;
int styles;
int line_styles;
glyph_entry *e[4];
} in_gd;
} data; /**< The state specific data */
int id; /**< Current ID */
int codepoints; /**< Glyphs containing codepoints */
int count[4]; /**< Count of glyphs in file */
};
struct font_data {
char header[HEADER_MAX];
int header_len;
uint8_t section_table[4][256];
uint8_t sec_count[4];
uint16_t *sections[4];
glyph_entry *e[0xffff];
int glyphs;
};
static bool generate_font_header(const char *path, struct font_data *data)
{
FILE *fp;
int s;
fp = fopen(path, "wb");
if (fp == NULL) {
LOG(LOG_ERROR, "Couldn't open header file \"%s\"\n", path);
return false;
}
fprintf(fp, "/*\n");
fwrite(data->header, 1, data->header_len, fp);
fprintf(fp, " */\n\n");
fprintf(fp, "/* Don't edit this file, it was generated from the "
"plain text source data. */\n\n");
for (s = 0; s < 4; s++) {
fprintf(fp, "extern const uint8_t *%s_section_table;\n",
var_lables[s]);
fprintf(fp, "extern const uint16_t *%s_sections;\n",
var_lables[s]);
}
fprintf(fp, "extern const uint8_t *font_glyph_data;\n");
fprintf(fp, "\n\n");
fclose(fp);
return true;
}
static bool generate_font_source(const char *path, struct font_data *data)
{
int s, i, y;
int limit;
FILE *fp;
fp = fopen(path, "wb");
if (fp == NULL) {
LOG(LOG_ERROR, "Couldn't open output file \"%s\"\n", path);
return false;
}
fprintf(fp, "/*\n");
fwrite(data->header, 1, data->header_len, fp);
fprintf(fp, " */\n\n");
fprintf(fp, "/* Don't edit this file, it was generated from the "
"plain text source data. */\n\n");
fprintf(fp, "#include <stdint.h>\n");
fprintf(fp, "\n");
for (s = 0; s < 4; s++) {
fprintf(fp, "static const uint8_t %s_section_table_c[256] = {\n",
var_lables[s]);
for (i = 0; i < 256; i++) {
if (i == 255)
fprintf(fp, "0x%.2X\n",
data->section_table[s][i]);
else if (i % 8 == 7)
fprintf(fp, "0x%.2X,\n",
data->section_table[s][i]);
else if (i % 8 == 0)
fprintf(fp, "\t0x%.2X, ",
data->section_table[s][i]);
else
fprintf(fp, "0x%.2X, ",
data->section_table[s][i]);
}
fprintf(fp, "};\nconst uint8_t *%s_section_table = &%s_section_table_c[0];\n\n",
var_lables[s], var_lables[s]);
fprintf(fp, "static const uint16_t %s_sections_c[%i] = {\n",
var_lables[s], data->sec_count[s] * 256);
limit = data->sec_count[s] * 256;
for (i = 0; i < limit; i++) {
uint16_t offset = data->sections[s][i];
if (i == limit - 1)
fprintf(fp, "0x%.4X\n", offset);
else if (i % 4 == 3)
fprintf(fp, "0x%.4X,\n", offset);
else if (i % 4 == 0)
fprintf(fp, "\t0x%.4X, ", offset);
else
fprintf(fp, "0x%.4X, ", offset);
}
fprintf(fp, "};\nconst uint16_t *%s_sections = &%s_sections_c[0];\n\n", var_lables[s], var_lables[s]);
}
fprintf(fp, "static const uint8_t font_glyph_data_c[%i] = {\n",
(data->glyphs + 1) * 16);
fprintf(fp, "\t0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n"
"\t0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n");
limit = data->glyphs;
for (i = 0; i < limit; i++) {
glyph_entry *e = data->e[i];
for (y = 0; y < 16; y++) {
if (i == limit - 1 && y == 15)
fprintf(fp, "0x%.2X\n", e->data.u8[y]);
else if (y % 8 == 7)
fprintf(fp, "0x%.2X,\n", e->data.u8[y]);
else if (y % 8 == 0)
fprintf(fp, "\t0x%.2X, ", e->data.u8[y]);
else
fprintf(fp, "0x%.2X, ", e->data.u8[y]);
}
}
fprintf(fp, "};\n");
fprintf(fp, "const uint8_t *font_glyph_data = &font_glyph_data_c[0];\n\n");
fclose(fp);
return true;
}
static bool add_glyph_to_data(glyph_entry *add, int id, int style,
struct font_data *d)
{
glyph_entry *e;
int offset;
int s;
/* Find out if 'add' is unique, and get its unique table entry */
e = glyph_add_to_table(add);
if (e == add) {
/* Unique glyph */
d->e[d->glyphs++] = e;
e->index = d->glyphs;
if (d->glyphs >= 0xfffd) {
LOG(LOG_ERROR, " Too many glyphs for internal data "
"representation\n");
return false;
}
} else {
/* Duplicate glyph */
LOG(LOG_DEBUG, " U+%.4X (%s) is duplicate\n",
id, short_labels[style]);
}
/* Find glyph's section */
s = id / 256;
/* Allocate section if needed */
if ((s == 0 && d->sections[style] == NULL) ||
(s != 0 && d->section_table[style][s] == 0)) {
size_t size = (d->sec_count[style] + 1) * SECTION_SIZE;
uint16_t *temp = realloc(d->sections[style], size);
if (temp == NULL) {
LOG(LOG_ERROR, " Couldn't increase sections "
"allocation\n");
return false;
}
memset(temp + d->sec_count[style] * 256, 0,
SECTION_SIZE);
d->section_table[style][s] = d->sec_count[style];
d->sections[style] = temp;
d->sec_count[style]++;
}
offset = d->section_table[style][s] * 256 + (id & 0xff);
d->sections[style][offset] = e->index;
return true;
}
static bool check_glyph_data_valid(int pos, char c)
{
int offset = pos % 11;
if (pos == 44) {
if (c != '\n') {
LOG(LOG_ERROR, " Invalid glyph data: "
"expecting '\\n', got '%c' (%i)\n",
c, c);
return false;
} else {
return true;
}
} else if (pos < 3) {
if (c != ' ') {
LOG(LOG_ERROR, " Invalid glyph data: "
"expecting ' ', got '%c' (%i)\n",
c, c);
return false;
} else {
return true;
}
} else if (offset == 0) {
if (c != '\n' && c != ' ') {
LOG(LOG_ERROR, " Invalid glyph data: "
"expecting '\\n' or ' ', "
"got '%c' (%i)\n",
c, c);
return false;
} else {
return true;
}
} else if (offset < 3) {
if (c != ' ') {
LOG(LOG_ERROR, " Invalid glyph data: "
"expecting ' ', got '%c' (%i)\n",
c, c);
return false;
} else {
return true;
}
} else if (offset >= 3 && pos < 11) {
if (c != '.' && c != '#') {
LOG(LOG_ERROR, " Invalid glyph data: "
"expecting '.' or '#', "
"got '%c' (%i)\n",
c, c);
return false;
} else {
return true;
}
}
/* offset must be >=3 */
if (c != '.' && c != '#' && c != ' ') {
LOG(LOG_ERROR, " Invalid glyph data: "
"expecting '.', '#', or ' ', "
"got '%c' (%i)\n",
c, c);
return false;
}
return true;
}
#define SEVEN_SET ((1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | \
(1 << 4) | (1 << 5) | (1 << 6))
#define THREE_SSS ((1 << 0) | (1 << 1) | (1 << 2))
#define THREE_S_S ((1 << 0) | (1 << 2))
#define THREE__SS ((1 << 0) | (1 << 1) )
#define THREE_SS_ ( (1 << 1) | (1 << 2))
#define THREE_S__ (1 << 2)
#define THREE__S_ (1 << 1)
#define THREE___S (1 << 0)
uint8_t frag[16][5] = {
{ THREE_SSS,
THREE_S_S,
THREE_S_S,
THREE_S_S,
THREE_SSS },
{ THREE__S_,
THREE_SS_,
THREE__S_,
THREE__S_,
THREE_SSS },
{ THREE_SS_,
THREE___S,
THREE__S_,
THREE_S__,
THREE_SSS },
{ THREE_SS_,
THREE___S,
THREE_SS_,
THREE___S,
THREE_SS_ },
{ THREE_S_S,
THREE_S_S,
THREE_SSS,
THREE___S,
THREE___S },
{ THREE_SSS,
THREE_S__,
THREE_SSS,
THREE___S,
THREE_SSS },
{ THREE__SS,
THREE_S__,
THREE_SSS,
THREE_S_S,
THREE_SSS },
{ THREE_SSS,
THREE___S,
THREE__S_,
THREE__S_,
THREE__S_ },
{ THREE_SSS,
THREE_S_S,
THREE_SSS,
THREE_S_S,
THREE_SSS },
{ THREE_SSS,
THREE_S_S,
THREE_SSS,
THREE___S,
THREE___S },
{ THREE__S_,
THREE_S_S,
THREE_SSS,
THREE_S_S,
THREE_S_S },
{ THREE_SS_,
THREE_S_S,
THREE_SS_,
THREE_S_S,
THREE_SS_ },
{ THREE__S_,
THREE_S_S,
THREE_S__,
THREE_S_S,
THREE__S_ },
{ THREE_SS_,
THREE_S_S,
THREE_S_S,
THREE_S_S,
THREE_SS_ },
{ THREE_SSS,
THREE_S__,
THREE_SS_,
THREE_S__,
THREE_SSS },
{ THREE_SSS,
THREE_S__,
THREE_SS_,
THREE_S__,
THREE_S__ }
};
static void build_codepoint(int id, bool italic, uint8_t *code_point)
{
int shift = 0;
int l;
int r;
if (!italic)
shift = 1;
l = (id >> 12);
r = 0xf & (id >> 8);
code_point[ 0] = 0;
code_point[ 1] = SEVEN_SET << shift;
code_point[ 2] = 0;
code_point[ 3] = (frag[l][0] << (4 + shift)) | (frag[r][0] << shift);
code_point[ 4] = (frag[l][1] << (4 + shift)) | (frag[r][1] << shift);
code_point[ 5] = (frag[l][2] << (4 + shift)) | (frag[r][2] << shift);
code_point[ 6] = (frag[l][3] << (4 + shift)) | (frag[r][3] << shift);
code_point[ 7] = (frag[l][4] << (4 + shift)) | (frag[r][4] << shift);
code_point[ 8] = 0;
shift = 1;
l = 0xf & (id >> 4);
r = 0xf & id ;
code_point[ 9] = (frag[l][0] << (4 + shift)) | (frag[r][0] << shift);
code_point[10] = (frag[l][1] << (4 + shift)) | (frag[r][1] << shift);
code_point[11] = (frag[l][2] << (4 + shift)) | (frag[r][2] << shift);
code_point[12] = (frag[l][3] << (4 + shift)) | (frag[r][3] << shift);
code_point[13] = (frag[l][4] << (4 + shift)) | (frag[r][4] << shift);
code_point[14] = 0;
code_point[15] = SEVEN_SET << shift;
}
#undef SEVEN_SET
#undef THREE_SSS
#undef THREE_S_S
#undef THREE__SS
#undef THREE_SS_
#undef THREE_S__
#undef THREE__S_
#undef THREE___S
static bool glyph_is_codepoint(const glyph_entry *e, int id, int style)
{
bool italic = false;
if (style == 1 || style == 3) {
italic = true;
}
build_codepoint(id, italic, code_point);
return glyphs_match(code_point, e->data.u8);
}
static bool parse_glyph_data(struct parse_context *ctx, char c,
struct font_data *d)
{
int glyph = ctx->data.in_gd.pos / 11;
int g_pos = ctx->data.in_gd.pos % 11 - 3;
uint8_t *row;
bool ok;
int i;
/* Check that character is valid */
if (check_glyph_data_valid(ctx->data.in_gd.pos, c) == false) {
LOG(LOG_ERROR, " Error in U+%.4X data: "
"glyph line: %i, pos: %i\n",
ctx->id,
ctx->data.in_gd.line,
ctx->data.in_gd.pos);
goto error;
}
/* Allocate glyph data if needed */
if (ctx->data.in_gd.line == 0 &&
(c == '.' || c == '#')) {
if (ctx->data.in_gd.e[glyph] == NULL) {
ctx->data.in_gd.e[glyph] =
calloc(sizeof(struct glyph_entry), 1);
if (ctx->data.in_gd.e[glyph] == NULL) {
LOG(LOG_ERROR, " Couldn't allocate memory for "
"glyph entry\n");
goto error;
}
ctx->data.in_gd.styles |= 1 << glyph;
}
}
/* Build glyph data */
if (c == '#') {
row = &ctx->data.in_gd.e[glyph]->data.u8[ctx->data.in_gd.line];
*row += 1 << (7 - g_pos);
ctx->data.in_gd.line_styles |= 1 << glyph;
} else if (c == '.') {
ctx->data.in_gd.line_styles |= 1 << glyph;
}
/* Deal with current position */
if (c == '\n') {
if (ctx->data.in_gd.line == 0) {
if (ctx->data.in_gd.e[0] == NULL) {
LOG(LOG_ERROR, " Error in U+%.4X data: "
"\"Regular\" glyph style must "
"be present\n", ctx->id);
goto error;
}
} else if (ctx->data.in_gd.styles !=
ctx->data.in_gd.line_styles) {
LOG(LOG_ERROR, " Error in U+%.4X data: "
"glyph line: %i "
"styles don't match first line\n",
ctx->id,
ctx->data.in_gd.line);
goto error;
}
ctx->data.in_gd.pos = 0;
ctx->data.in_gd.line++;
ctx->data.in_gd.line_styles = 0;
} else {
ctx->data.in_gd.pos++;
}
/* If we've got all the glyph data, tidy up and advance state */
if (ctx->data.in_gd.line == 16) {
for (i = 0; i < 4; i++) {
if (ctx->data.in_gd.e[i] != NULL) {
ctx->count[i] += 1;
if (glyph_is_codepoint(ctx->data.in_gd.e[i],
ctx->id, i)) {
LOG(LOG_DEBUG, " U+%.4X (%s) is "
"codepoint\n",
ctx->id,
short_labels[i]);
ctx->codepoints += 1;
free(ctx->data.in_gd.e[i]);
ctx->data.in_gd.e[i] = NULL;
continue;
}
ok = add_glyph_to_data(ctx->data.in_gd.e[i],
ctx->id, i, d);
if (!ok) {
goto error;
}
}
}
ctx->data.before_id.new_line = false;
ctx->data.before_id.u = false;
ctx->state = BEFORE_ID;
}
return true;
error:
for (i = 0; i < 4; i++) {
free(ctx->data.in_gd.e[i]);
}
return false;
}
static void parse_init(struct parse_context *ctx)
{
memset(ctx, 0, sizeof(struct parse_context));
}
static bool get_hex_digit_value(char c, int *v)
{
if (c >= '0' && c <= '9')
*v = (c - '0');
else if (c >= 'A' && c <= 'F')
*v = (10 + c - 'A');
else {
LOG(LOG_ERROR, "Invalid hex digit '%c' (%i)\n", c, c);
return false;
}
return true;
}
static bool assemble_codepoint(const char* c, int n, int *id)
{
bool ok;
int v;
ok = get_hex_digit_value(*c, &v);
if (!ok) {
return false;
}
*id += v << (4 * (3 - n));
return true;
}
static bool parse_chunk(struct parse_context *ctx, const char *buf, size_t len,
struct font_data *d)
{
int i;
bool ok;
int count[4];
const char *pos = buf;
const char *end = buf + len;
for (i = 0; i < 4; i++) {
count[i] = ctx->count[i];
}
while (pos < end) {
if (*pos == '\r') {
LOG(LOG_ERROR, "Detected \'\\r\': Bad line ending\n");
return false;
}
switch (ctx->state) {
case START:
if (*pos != '*') {
LOG(LOG_ERROR, "First character must be '*'\n");
printf("Got: %c (%i)\n", *pos, *pos);
return false;
}
d->header_len = 0;
ctx->data.in_header.new_line = true;
ctx->state = IN_HEADER;
/* Fall through */
case IN_HEADER:
if (ctx->data.in_header.new_line == true) {
if (*pos != '*') {
LOG(LOG_INFO, " Got header "
"(%i bytes)\n",
d->header_len);
LOG(LOG_DEBUG, " Header:\n\n%.*s\n",
d->header_len,
d->header);
ctx->data.before_id.new_line = false;
ctx->data.before_id.u = false;
ctx->state = BEFORE_ID;
continue;
} else if (*pos == '*') {
d->header[d->header_len++] = ' ';
}
ctx->data.in_header.new_line = false;
} else if (*pos == '\n') {
ctx->data.in_header.new_line = true;
}
if (d->header_len == HEADER_MAX) {
LOG(LOG_ERROR, " Header too long "
"(>%i bytes)\n",
d->header_len);
return false;
}
d->header[d->header_len++] = *pos;
break;
case BEFORE_ID:
if (*pos == '+' &&
ctx->data.before_id.new_line == true &&
ctx->data.before_id.u == true) {
ctx->data.g_id.c = 0;
ctx->id = 0;
ctx->state = GLYPH_ID;
break;
} else if (*pos == 'U' &&
ctx->data.before_id.new_line == true) {
ctx->data.before_id.u = true;
} else if (*pos == '\n') {
ctx->data.before_id.new_line = true;
ctx->data.before_id.u = false;
} else {
ctx->data.before_id.new_line = false;
ctx->data.before_id.u = false;
}
break;
case GLYPH_ID:
ok = assemble_codepoint(pos, ctx->data.g_id.c++,
&ctx->id);
if (!ok) {
LOG(LOG_ERROR, " Invalid glyph ID\n");
return false;
}
if (ctx->data.g_id.c == 4) {
ctx->data.before_gd.new_line = false;
ctx->data.before_gd.prev_h = false;
ctx->data.before_gd.prev_s = false;
ctx->data.before_gd.c = 0;
ctx->state = BEFORE_GLYPH_DATA;
break;
}
break;
case BEFORE_GLYPH_DATA:
/* Skip until end of dashed line */
if (*pos == '\n' && ctx->data.before_gd.c == 53) {
ctx->state = IN_GLYPH_DATA;
ctx->data.in_gd.e[0] = NULL;
ctx->data.in_gd.e[1] = NULL;
ctx->data.in_gd.e[2] = NULL;
ctx->data.in_gd.e[3] = NULL;
ctx->data.in_gd.line = 0;
ctx->data.in_gd.pos = 0;
ctx->data.in_gd.line_styles = 0;
ctx->data.in_gd.styles = 0;
break;
} else if (*pos == '\n') {
ctx->data.before_gd.new_line = true;
ctx->data.before_gd.prev_h = false;
ctx->data.before_gd.prev_s = false;
ctx->data.before_gd.c = 0;
} else if (*pos == '-' &&
ctx->data.before_gd.new_line == true) {
assert(ctx->data.before_gd.c == 0);
ctx->data.before_gd.new_line = false;
ctx->data.before_gd.c++;
ctx->data.before_gd.prev_h = true;
} else if (*pos == ' ' &&
ctx->data.before_gd.prev_h == true) {
assert(ctx->data.before_gd.prev_s == false);
ctx->data.before_gd.c++;
ctx->data.before_gd.prev_h = false;
ctx->data.before_gd.prev_s = true;
} else if (*pos == '-' &&
ctx->data.before_gd.prev_s == true) {
assert(ctx->data.before_gd.prev_h == false);
ctx->data.before_gd.c++;
ctx->data.before_gd.prev_h = true;
ctx->data.before_gd.prev_s = false;
} else {
ctx->data.before_gd.new_line = false;
ctx->data.before_gd.prev_h = false;
ctx->data.before_gd.prev_s = false;
ctx->data.before_gd.c = 0;
}
break;
case IN_GLYPH_DATA:
ok = parse_glyph_data(ctx, *pos, d);
if (!ok) {
return false;
}
break;
}
pos++;
}
for (i = 0; i < 4; i++) {
LOG(LOG_DEBUG, " %s: %i gylphs\n", labels[i],
ctx->count[i] - count[i]);
}
return true;
}
static bool load_font(const char *path, struct font_data **data)
{
struct parse_context ctx;
struct font_data *d;
size_t file_len;
size_t done;
size_t len;
int count;
char *buf;
FILE *fp;
bool ok;
int i;
*data = NULL;
fp = fopen(path, "rb");
if (fp == NULL) {
LOG(LOG_ERROR, "Couldn't open font data file\n");
return false;
}
d = calloc(sizeof(struct font_data), 1);
if (d == NULL) {
LOG(LOG_ERROR, "Couldn't allocate memory for font data\n");
fclose(fp);
return false;
}
/* Find filesize */
fseek(fp, 0L, SEEK_END);
file_len = ftell(fp);
if ((long)file_len == -1) {
LOG(LOG_ERROR, "Could not size input file\n");
free(d);
fclose(fp);
return false;
}
fseek(fp, 0L, SEEK_SET);
LOG(LOG_DEBUG, "Input size: %zu bytes\n", file_len);
/* Allocate buffer for data chunks */
buf = malloc(CHUNK_SIZE);
if (buf == NULL) {
LOG(LOG_ERROR, "Couldn't allocate memory for input buffer\n");
free(d);
fclose(fp);
return false;
}
/* Initialise parser */
parse_init(&ctx);
LOG(LOG_DEBUG, "Using chunk size of %i bytes\n", CHUNK_SIZE);
/* Parse the input file in chunks */
for (done = 0; done < file_len; done += CHUNK_SIZE) {
LOG(LOG_INFO, "Parsing input chunk %zu\n", done / CHUNK_SIZE);
/* Read chunk */
len = fread(buf, 1, CHUNK_SIZE, fp);
if (file_len - done < CHUNK_SIZE &&
len != file_len - done) {
LOG(LOG_WARNING, "Last chunk has suspicious size\n");
} else if (file_len - done >= CHUNK_SIZE &&
len != CHUNK_SIZE) {
LOG(LOG_ERROR, "Problem reading file\n");
free(buf);
free(d);
fclose(fp);
return false;
}
/* Parse chunk */
ok = parse_chunk(&ctx, buf, len, d);
if (!ok) {
free(buf);
free(d);
fclose(fp);
return false;
}
LOG(LOG_DEBUG, "Parsed %zu bytes\n", done + len);
}
fclose(fp);
if (ctx.state != BEFORE_ID) {
LOG(LOG_ERROR, "Unexpected end of file\n");
free(buf);
free(d);
return false;
}
LOG(LOG_INFO, "Parsing complete:\n");
count = 0;
for (i = 0; i < 4; i++) {
LOG(LOG_INFO, " %s: %i gylphs\n", labels[i], ctx.count[i]);
count += ctx.count[i];
}
LOG(LOG_RESULT, " Total %i gylphs "
"(of which %i unique, %i codepoints, %i duplicates)\n",
count, d->glyphs, ctx.codepoints,
count - d->glyphs - ctx.codepoints);
free(buf);
*data = d;
return true;
}
static void log_usage(const char *argv0)
{
level = LOG_INFO;
LOG(LOG_INFO,
"Usage:\n"
"\t%s [options] <in_file> <out_file>\n"
"\n"
"Options:\n"
"\t--help -h Display this text\n"
"\t--quiet -q Don't show warnings\n"
"\t--verbose -v Verbose output\n"
"\t--debug -d Full debug output\n",
argv0);
}
int main(int argc, char** argv)
{
const char *in_path = NULL;
const char *out_path = NULL;
char *header_path = NULL;
struct font_data *data;
bool ok;
int i;
int opt;
level = LOG_RESULT;
/* Handle program arguments */
struct option long_options[] = {
{ "help", no_argument, NULL, 'h' },
{ "quiet", no_argument, NULL, 'q' },
{ "verbose", no_argument, NULL, 'v' },
{ "debug", no_argument, NULL, 'd' },
{ "header", required_argument, NULL, 'H' },
};
while ((opt = getopt_long(argc, argv, "hqvdH:", long_options, NULL)) != -1) {
switch (opt) {
case 'q':
level = LOG_WARNING;
break;
case 'v':
level = LOG_INFO;
break;
case 'd':
level = LOG_DEBUG;
break;
case 'H':
header_path = strdup(optarg);
break;
case 'h':
log_usage(argv[0]);
free(header_path);
return EXIT_SUCCESS;
default:
log_usage(argv[0]);
free(header_path);
return EXIT_FAILURE;
}
}
if ((argc - optind) < 2) {
log_usage(argv[0]);
free(header_path);
return EXIT_FAILURE;
}
in_path = argv[optind];
out_path = argv[optind + 1];
LOG(LOG_DEBUG, "Using input path: \"%s\"\n", in_path);
LOG(LOG_DEBUG, "Using output path: \"%s\"\n", out_path);
ok = load_font(in_path, &data);
if (!ok) {
free_table();
free(header_path);
return EXIT_FAILURE;
}
ok = generate_font_source(out_path, data);
if (ok && (header_path != NULL)) {
ok = generate_font_header(header_path, data);
}
free(header_path);
free_table();
for (i = 0; i < 4; i++) {
free(data->sections[i]);
}
free(data);
if (!ok) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}