#include <u.h>
#include <libc.h>
#include <bio.h>
/* entry has 1 byte in widtab + 8 bytes per tile */
#define ufxlen(n) ((n)*(n)*8)
#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))
#define warn(fmt, ...) fprint(2, "%s: " fmt "\n", argv0, __VA_ARGS__)
enum{
MR_BLANK,
MR_SINGLE,
MR_RANGE
};
struct MapRule{
struct MapRule *next;
uchar ty;
uint start, end;
};
struct Mapping{
struct Mapping *next;
uint linum;
Rune start;
union {Rune end; uint idx;};
struct MapRule *rules;
};
struct MapFile{
struct MapFile *next;
char *file;
struct Mapping *mappings;
};
struct GlyphInfo{
uchar top, bottom, left, width;
};
uint fwidth, nglyphs, glyphlen, glyphedge, baseline;
uchar setconv, donaive, doholes;
struct MapFile *mapfiles, *lastmf;
uchar wasmferr, wasreaderr;
struct GlyphInfo *glyphs;
void
bioerr(char *s)
{
wasreaderr = 1;
wasmferr = 1;
fprint(2, "%s: %s\n", argv0, s);
}
char *
readtoken(Biobuf *b, char **line, uint *linum)
{
char *ret, *p;
while(*line != nil){
p = *line;
while(p[0] != '\0' && strchr("\t\r ", p[0]) != nil)
p++;
if(strchr("#;\n", p[0]) != nil){
*line = Brdline(b, '\n');
(*linum)++;
continue;
}
ret = p;
while(strchr("\t\r\n ", p[0]) == nil)
p++;
*line = p;
if(p[0] != '\n')
(*line)++;
p[0] = '\0';
return ret;
}
return nil;
}
#pragma varargck argpos mferr 3
void
mferr(char *file, uint linum, char *fmt, ...)
{
char buf[1024];
va_list va;
va_start(va, fmt);
vsnprint(buf, sizeof(buf), fmt, va);
va_end(va);
if(linum != 0)
fprint(2, "%s: %s:%ud: %s\n", argv0, file, linum, buf);
else
fprint(2, "%s: %s: %s\n", argv0, file, buf);
wasmferr = 1;
}
void
_parsemapfile(Biobuf *b, struct MapFile *mf)
{
char *line, *tok, *p;
uint linum = 1;
struct Mapping *mlast = nil;
struct MapRule *rlast;
wasreaderr = 0;
line = Brdline(b, '\n');
if(line == nil){
if(!wasreaderr)
warn("warn: %s: empty file\n", mf->file);
return;
}
tok = readtoken(b, &line, &linum);
while(tok != nil){
if(strcmp(tok, "mapping") == 0){
if(mlast == nil)
mf->mappings = mlast = malloc(sizeof(struct Mapping));
else
mlast = mlast->next = malloc(sizeof(struct Mapping));
mlast->linum = linum;
mlast->rules = nil;
mlast->next = nil;
tok = readtoken(b, &line, &linum);
if(tok == nil){
mferr(mf->file, linum, "unexpected eof");
return;
}
mlast->start = strtoul(tok, &p, 0);
if(tok == p){
mferr(mf->file, linum, "empty lower rune bound");
return;
}
tok = p;
if(tok[0] == '\0'){ /* 1:1 mapping */
tok = readtoken(b, &line, &linum);
if(tok == nil){
mferr(mf->file, linum, "unexpected eof");
return;
}
mlast->idx = strtoul(tok, &p, 0);
if(tok == p || p[0] != '\0'){
mferr(mf->file, linum, "bad glyph index");
return;
}
tok = readtoken(b, &line, &linum);
continue;
}else if(tok[0] != '-'){
mferr(mf->file, linum, "bad rune range");
return;
}
tok++;
/* many:many mapping */
mlast->end = strtoul(tok, &p, 0);
if(tok == p){
mferr(mf->file, linum, "empty upper rune bound");
return;
}else if(p[0] != '\0'){
mferr(mf->file, linum, "bad upper rune bound");
return;
}
rlast = nil;
while((tok = readtoken(b, &line, &linum)) != nil){
if(rlast == nil)
mlast->rules = rlast = malloc(sizeof(struct MapRule));
else
rlast = rlast->next = malloc(sizeof(struct MapRule));
rlast->next = nil;
if(strcmp(tok, "blank") == 0){
rlast->ty = MR_BLANK;
continue;
}else if(tok[0] == '-'){ /* e.g. -0x4f */
tok++;
rlast->ty = MR_RANGE;
rlast->start = 0;
rlast->end = strtoul(tok, &p, 0);
if(tok == p || p[0] != '\0'){
mferr(mf->file, linum, "bad upper glyph bound");
return;
}
continue;
}
rlast->start = strtoul(tok, &p, 0);
if(tok == p)
break; /* return to toplevel parser */
else if(p[0] == '\0'){
rlast->ty = MR_SINGLE;
continue;
}else if(p[0] != '-'){
mferr(mf->file, linum, "bad lower glyph bound");
return;
}
rlast->ty = MR_RANGE;
tok = p+1;
if(tok[0] == '\0'){ /* e.g. 0x80- */
rlast->end = nglyphs - 1;
continue;
}
rlast->end = strtoul(tok, &p, 0);
if(tok == p || p[0] != '\0'){
mferr(mf->file, linum, "bad upper glyph bound");
return;
}
} /* end while */
if(rlast == nil){
mferr(mf->file, linum, "unexpected eof");
return;
}
}else{
mferr(mf->file, linum, "unknown verb %s", tok);
return;
}
} /* end while */
}
void
parsemapfile(struct MapFile *mf)
{
Biobuf *b;
b = Bopen(mf->file, OREAD);
if(b == nil){
fprint(2, "%s: %r\n", argv0);
wasmferr = 1;
return;
}
Blethal(b, bioerr);
_parsemapfile(b, mf);
Bterm(b);
}
void
printmapfile(struct MapFile *mf)
{
struct Mapping *mp;
struct MapRule *rp;
print("mapfile: %s\n", mf->file);
for(mp = mf->mappings; mp != nil; mp = mp->next){
if(mp->rules == nil)
print("\t%ud: %ux => %ux\n", mp->linum, mp->start, mp->idx);
else{
print("\t%ud: %ux - %ux =>\n", mp->linum, mp->start, mp->end);
for(rp = mp->rules; rp != nil; rp = rp->next){
switch(rp->ty){
case MR_SINGLE:
print("\t\t%ux\n", rp->start);
break;
case MR_RANGE:
print("\t\t%ux - %ux\n", rp->start, rp->end);
break;
case MR_BLANK:
print("\t\tblank\n");
break;
}
}
}
}
}
void
addmapfile(char *file)
{
if(lastmf == nil)
mapfiles = lastmf = malloc(sizeof(struct MapFile));
else
lastmf = lastmf->next = malloc(sizeof(struct MapFile));
lastmf->file = file;
lastmf->next = nil;
lastmf->mappings = nil;
parsemapfile(lastmf);
}
void
checkmapfiles(void)
{
if(wasmferr)
exits("mapfile");
}
void
guesswidth(char *file)
{
uint len;
char c;
len = strlen(file);
c = file[len-1];
if('1' <= c && c <= '8' && strncmp(file+len-4, ".uf", 3) == 0){
fwidth = c - '0';
}
}
void
guesslength(char *file, Dir *d)
{
if(nglyphs == 0){
if(d->length % (1+glyphlen) != 0)
sysfatal("%s: can't determine file dimensions", file);
nglyphs = d->length / (1+glyphlen);
}else if(nglyphs * (1+glyphlen) > d->length)
sysfatal("%s: file too short", file);
}
void
guessdims(char *file)
{
Dir *d;
if(fwidth < 1)
guesswidth(file);
if(fwidth < 1)
sysfatal("%s: can't determine glyph width", file);
glyphlen = ufxlen(fwidth);
glyphedge = fwidth * 8;
d = dirstat(file);
if(d == nil)
sysfatal("%r");
guesslength(file, d);
free(d);
}
void
loadwidths(Biobuf *b, char *file)
{
uint i;
int n;
for(i = 0; i < nglyphs; i++){
n = Bgetc(b);
if(n < 0)
sysfatal("%r");
if(n > glyphedge){
warn("%s: 0x%02x: bad width %d, truncating\n", file, i, n);
n = glyphedge;
}
glyphs[i].width = n;
}
}
uchar
testscanline(uchar *bitmap, int rowlen)
{
uchar mask, x;
for(x = 0; x <= (rowlen-1)/8; x++){
/* ignore the right edge of the last byte */
mask = 0xff << (8-min(8, rowlen-x*8));
if((bitmap[x*glyphedge] & mask) != 0)
return 1;
}
return 0;
}
void
parseglyph(uchar *bitmap, struct GlyphInfo *ginfo, uint *botfreqs)
{
uint top, bot;
int rowlen = ginfo->width;
for(top = 0; top < glyphedge; top++)
if(testscanline(bitmap+top, rowlen))
break;
for(bot = glyphedge-1; bot >= top; bot--)
if(testscanline(bitmap+bot, rowlen))
break;
if(botfreqs != nil && top <= bot) /* don't count empty glyphs */
botfreqs[bot]++;
ginfo->top = top; ginfo->bottom = bot;
}
void
readglyphs(Biobuf *b, uint *botfreqs)
{
uint i;
uchar *bitmap;
long n;
bitmap = malloc(glyphlen);
for(i = 0; i < nglyphs; i++){
n = Bread(b, bitmap, glyphlen);
if(n < 0)
sysfatal("%r");
if(n != glyphlen)
sysfatal("unexpected eof");
parseglyph(bitmap, glyphs+i, botfreqs);
}
free(bitmap);
}
void
loadglyphs(Biobuf *b)
{
uint i, *botfreqs, bestfreq;
if(baseline < 1){
botfreqs = calloc(glyphedge, sizeof(uint));
readglyphs(b, botfreqs);
bestfreq = 0;
for(i = 0; i < glyphedge; i++){
if(botfreqs[i] > bestfreq){
baseline = i;
bestfreq = botfreqs[i];
}
}
}else
readglyphs(b, nil);
}
void
loadufx(char *infile)
{
Biobuf *b;
glyphs = malloc(nglyphs * sizeof(struct GlyphInfo));
b = Bopen(infile, OREAD);
loadwidths(b, infile);
loadglyphs(b);
Bterm(b);
}
void
usage(void)
{
fprint(2,
"usage: %s [-nh] [-b baseline] [-m mapfile]"
"[-l length] [-x width] in.ufx out/\n",
argv0);
exits("usage");
}
void
main(int argc, char **argv)
{
char *infile, *outdir;
ARGBEGIN{
case 'l':
nglyphs = atoi(EARGF(usage()));
break;
case 'x':
fwidth = atoi(EARGF(usage()));
if(1 > fwidth || fwidth > 8)
sysfatal("-x: width must be in range 1-8");
break;
case 'n':
setconv = 1;
donaive = 1;
break;
case 'h':
setconv = 1;
doholes = 1;
break;
case 'm':
setconv = 1;
addmapfile(EARGF(usage()));
break;
case 'b':
baseline = atoi(EARGF(usage()));
if(baseline < 1)
sysfatal("-b: baseline must be in range 1-width*8");
break;
default:
usage();
}ARGEND;
checkmapfiles();
if(argv0 == nil)
argv0 = "ufx2font";
if(argc != 2)
usage();
infile = argv[0];
outdir = argv[1];
guessdims(infile);
if(baseline >= glyphedge)
sysfatal("-b: baseline must be in range 1-width*8");
loadufx(infile);
}