#include <u.h>
#include <libc.h>
#include <bio.h>
#include <draw.h>
#include <memdraw.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;
uint linum;
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;
};
struct RawSubfont{
Memimage *img;
char n[12], height[12], ascent[12];
uchar entries[][6];
};
uint fwidth, nglyphs, glyphlen, glyphedge, baseline;
uchar setconv, donaive, doholes;
struct MapFile *mapfiles, *lastmf;
uchar wasmferr, wasreaderr;
struct GlyphInfo *glyphs;
Memimage *glyphimg;
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;
if(b->aux != nil){
ret = b->aux;
b->aux = nil;
return ret;
}
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;
}
void
unreadtoken(Biobuf *b, char *tok)
{
b->aux = tok;
}
#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;
}
struct MapRule *
parsemaprule(char *file, Biobuf *b, char **line, uint *linum)
{
struct MapRule *ret;
char *tok, *p;
tok = readtoken(b, line, linum);
if(tok == nil)
return nil;
ret = malloc(sizeof(struct MapRule));
ret->next = nil;
ret->linum = *linum;
if(strcmp(tok, "blank") == 0){
ret->ty = MR_BLANK;
return ret;
}else if(tok[0] == '-'){ /* e.g. -0x4f */
tok++;
ret->ty = MR_RANGE;
ret->start = 0;
ret->end = strtoul(tok, &p, 0);
if(tok == p || p[0] != '\0'){
mferr(file, *linum, "bad upper glyph bound");
free(ret);
return nil;
}
return ret;
}
ret->start = strtoul(tok, &p, 0);
if(tok == p){
unreadtoken(b, tok);
free(ret);
return nil;
}else if(p[0] == '\0'){
ret->ty = MR_SINGLE;
return ret;
}else if(p[0] != '-'){
mferr(file, *linum, "bad lower glyph bound");
free(ret);
return nil;
}
ret->ty = MR_RANGE;
tok = p+1;
if(tok[0] == '\0'){ /* e.g. 0x80- */
ret->end = nglyphs - 1;
return ret;
}
ret->end = strtoul(tok, &p, 0);
if(tok == p || p[0] != '\0'){
mferr(file, *linum, "bad upper glyph bound");
free(ret);
return nil;
}
return ret;
}
struct Mapping *
parsemapping(char *file, Biobuf *b, char **line, uint *linum)
{
struct Mapping *ret;
struct MapRule *rlast = nil, *r;
char *tok, *p;
tok = readtoken(b, line, linum);
if(tok == nil){
mferr(file, *linum, "unexpected eof");
return nil;
}
ret = malloc(sizeof(struct Mapping));
ret->linum = *linum;
ret->rules = nil;
ret->next = nil;
ret->start = strtoul(tok, &p, 0);
if(tok == p){
mferr(file, *linum, "empty lower rune bound");
free(ret);
return nil;
}
tok = p;
if(tok[0] == '\0'){ /* 1:1 mapping */
tok = readtoken(b, line, linum);
if(tok == nil){
mferr(file, *linum, "unexpected eof");
free(ret);
return nil;
}
ret->idx = strtoul(tok, &p, 0);
if(tok == p || p[0] != '\0'){
mferr(file, *linum, "bad glyph index");
free(ret);
return nil;
}
return ret;
}else if(tok[0] != '-'){
mferr(file, *linum, "bad rune range");
free(ret);
return nil;
}
tok++;
/* many:many mapping */
ret->end = strtoul(tok, &p, 0);
if(tok == p){
mferr(file, *linum, "empty upper rune bound");
free(ret);
return nil;
}else if(p[0] != '\0'){
mferr(file, *linum, "bad upper rune bound");
free(ret);
return nil;
}
while((r = parsemaprule(file, b, line, linum)) != nil){
if(rlast == nil)
ret->rules = rlast = r;
else
rlast = rlast->next = r;
}
if(rlast == nil){
mferr(file, *linum, "unexpected eof");
free(ret);
return nil;
}
return ret;
}
void
_parsemapfile(Biobuf *b, struct MapFile *mf)
{
char *line, *tok;
uint linum = 1;
struct Mapping *mlast = nil, *m;
wasreaderr = 0;
line = Brdline(b, '\n');
if(line == nil){
if(!wasreaderr)
warn("warn: %s: empty file\n", mf->file);
return;
}
while((tok = readtoken(b, &line, &linum)) != nil){
if(strcmp(tok, "mapping") == 0){
m = parsemapping(mf->file, b, &line, &linum);
if(m == nil)
continue;
if(mlast == nil)
mf->mappings = mlast = m;
else
mlast = mlast->next = m;
}else{
mferr(mf->file, linum, "unknown verb %s", tok);
return;
}
}
}
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
testmappingoverlap(char *file, struct Mapping *a, struct Mapping *b)
{
uchar res = 0;
struct Mapping *tmp;
if(a->rules == nil){
tmp = a; a = b; b = tmp;
}
if(a->rules == nil){
if(a->start == b->start)
res = 1;
}else if(b->rules == nil){
if(a->start <= b->start && b->start <= a->end)
res = 1;
}else{
if(a->start > b->start){
tmp = a; a = b; b = tmp;
}
if(a->end >= b->start)
res = 1;
}
if(res)
mferr(file, a->linum, "overlaps mapping on line %ud", b->linum);
}
void
testmappingrange(char *file, struct Mapping *m)
{
struct MapRule *r;
int nrunes, n = 0;
for(r = m->rules; r != nil; r = r->next){
if((r->ty != MR_BLANK && r->start >= nglyphs) ||
(r->ty == MR_RANGE && r->end >= nglyphs))
mferr(file, r->linum, "glyphs out of range");
if(r->ty == MR_RANGE)
n += r->end - r->start;
n++;
}
nrunes = (m->end-m->start+1);
if(nrunes != n)
mferr(file, m->linum, "# runes != # glyphs (%d != %d)", nrunes, n);
}
void
checkmapfiles(void)
{
struct MapFile *mf;
struct Mapping *p1, *p2;
if(wasmferr)
exits("mapfile");
for(mf = mapfiles; mf != nil; mf = mf->next){
for(p1 = mf->mappings; p1 != nil; p1 = p1->next){
if(p1->rules != nil && p1->start > p1->end)
mferr(mf->file, p1->linum, "rune start > rune end");
if(p1->rules != nil)
testmappingrange(mf->file, p1);
else if(p1->idx >= nglyphs)
mferr(mf->file, p1->linum, "glyph out of range");
for(p2 = p1->next; p2 != nil; p2 = p2->next)
testmappingoverlap(mf->file, p1, p2);
}
}
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;
}
}
void
allocglyphimg(void)
{
Rectangle dims = Rect(0,0, nglyphs*fwidth*8,glyphedge);
glyphimg = allocmemimage(dims, strtochan("k1"));
if(glyphimg == nil)
sysfatal("%r");
}
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;
bot++;
if(botfreqs != nil && top <= bot) /* don't count empty glyphs */
botfreqs[bot]++;
ginfo->top = top; ginfo->bottom = bot;
}
void
storeglyph(uint i, uchar *bitmap)
{
uint y, x;
ulong srcidx;
Point dst;
for(x = 0; x < fwidth; x++){
for(y = 0; y < glyphedge; y++){
srcidx = y+(x*glyphedge);
dst = Pt((i*fwidth+x)*8, y);
*byteaddr(glyphimg, dst) = bitmap[srcidx];
}
}
}
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);
storeglyph(i, bitmap);
}
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);
allocglyphimg();
loadwidths(b, infile);
loadglyphs(b);
Bterm(b);
}
struct RawSubfont *
allocrawsubfont(uint n, uint width)
{
char buf[13];
struct RawSubfont *ret;
ret = malloc(sizeof(struct RawSubfont)+n*6+6);
ret->img = allocmemimage(Rect(0,0, width,glyphedge), strtochan("k1"));
if(ret->img == nil)
sysfatal("%r");
snprint(buf, 13, "%11ud ", n);
memcpy(ret->n, buf, 12);
snprint(buf, 13, "%11ud ", glyphedge);
memcpy(ret->height, buf, 12);
snprint(buf, 13, "%11ud ", baseline);
memcpy(ret->ascent, buf, 12);
return ret;
}
void
setsubfontparams(struct RawSubfont *s, uint x, uint src, uint dst)
{
s->entries[dst][0] = x &0xff;
s->entries[dst][1] = (x>>8)&0xff;
if(glyphs[src].top >= glyphs[src].bottom){
s->entries[dst][2] = 0;
s->entries[dst][3] = glyphedge;
}else{
s->entries[dst][2] = glyphs[src].top;
s->entries[dst][3] = glyphs[src].bottom;
}
s->entries[dst][4] = 0;
s->entries[dst][5] = glyphs[src].width;
}
void
copyglyph(uint i, uint *x, Memimage *img)
{
Point dstcorner, srccorner, offset;
Rectangle dst;
offset = Pt(glyphs[i].width,glyphedge);
dstcorner = Pt(*x,0);
srccorner = Pt(i*glyphedge,0);
dst = Rpt(dstcorner, addpt(dstcorner, offset));
memimagedraw(img, dst, glyphimg, srccorner, nil, Pt(0,0), SoverD);
*x += glyphs[i].width;
}
char *
saverawsubfont(struct RawSubfont *s, char *base, Rune r1, Rune r2)
{
char *file;
uchar *sfdata;
ulong sfdlen;
uint n;
int fd;
n = r2 - r1;
sfdata = (uchar *)s + sizeof(Memimage *);
sfdlen = sizeof(struct RawSubfont)-sizeof(Memimage *)+n*6+6;
file = smprint("%s.%04uX-%04uX", base, r1, r2-1);
fd = create(file, OWRITE, 0644);
if(fd < 0)
sysfatal("%r");
if(writememimage(fd, s->img) != 0)
sysfatal("memimage");
if(write(fd, sfdata, sfdlen) != sfdlen)
sysfatal("%r");
close(fd);
return file;
}
void
freerawsubfont(struct RawSubfont *s)
{
freememimage(s->img);
free(s);
}
int
createfont(char *file)
{
int fd, err;
fd = create(file, OWRITE, 0644);
if(fd < 0)
sysfatal("%r");
err = fprint(fd, "%ud %ud\n", glyphedge, baseline);
if(err < 0)
sysfatal("%r");
return fd;
}
void
appendfont(int fd, Rune r1, Rune r2, uint off, char *file)
{
int err;
if(off > 0)
err = fprint(fd, "0x%04ux\t0x%04ux\t0x%04ux\t%s\n", r1, r2, off, file);
else
err = fprint(fd, "0x%04ux\t0x%04ux\t%s\n", r1, r2, file);
if(err < 0)
sysfatal("%r");
}
void
savenaivefont(char *file)
{
int fd;
fd = createfont("naive.font");
appendfont(fd, 0, nglyphs-1, 0, file);
close(fd);
}
void
makenaive(void)
{
uint width = 0, x = 0, i;
struct RawSubfont *s;
char *file;
for(i = 0; i < nglyphs; i++)
width += glyphs[i].width;
s = allocrawsubfont(nglyphs, width);
for(i = 0; i < nglyphs; i++){
setsubfontparams(s, x, i, i);
copyglyph(i, &x, s->img);
}
setsubfontparams(s, x, 0, i);
file = saverawsubfont(s, "naive", 0, nglyphs);
savenaivefont(file);
freerawsubfont(s);
free(file);
}
char *
makenoholessubfont(void)
{
uint width = 0, x = 0, n = 0, i, j;
struct RawSubfont *s;
char *file;
for(i = 0; i < nglyphs; i++){
width += glyphs[i].width;
n++;
}
s = allocrawsubfont(n, width);
j = 0;
for(i = 0; i < nglyphs; i++){
if(glyphs[i].width == 0)
continue;
setsubfontparams(s, x, i, j);
copyglyph(i, &x, s->img);
j++;
}
setsubfontparams(s, x, 0, i);
file = saverawsubfont(s, "noholes", 0, n);
freerawsubfont(s);
return file;
}
void
makenoholes(void)
{
char *subfont;
int fd;
uint i, j = 0, n = 0;
subfont = makenoholessubfont();
fd = createfont("noholes.font");
for(i = 0; i < nglyphs; i++){
if(glyphs[i].width == 0){
if(n != 0){
appendfont(fd, i-n, i-1, j, subfont);
j += n;
n = 0;
}
continue;
}
n++;
}
if(n != 0)
appendfont(fd, i-n, i-1, j, subfont);
free(subfont);
close(fd);
}
uint
mappingwidth(struct Mapping *m)
{
struct MapRule *r;
uint ret = 0, i;
for(r = m->rules; r != nil; r = r->next){
switch(r->ty){
case MR_SINGLE:
ret += glyphs[r->start].width;
break;
case MR_RANGE:
for(i = r->start; i <= r->end; i++)
ret += glyphs[i].width;
break;
}
}
return ret;
}
void
makemultimapping(struct Mapping *m, int fd, char *base)
{
struct MapRule *r;
struct RawSubfont *s;
char *file;
uint n, width, i, j = 0, x = 0;
n = m->end-m->start+1;
width = mappingwidth(m);
s = allocrawsubfont(n, width);
for(r = m->rules; r != nil; r = r->next){
switch(r->ty){
case MR_BLANK:
s->entries[j][0] = x &0xff;
s->entries[j][1] = (x>>8)&0xff;
s->entries[j][2] = 0;
s->entries[j][3] = glyphedge;
s->entries[j][4] = 0;
s->entries[j][5] = 0;
j++;
break;
case MR_SINGLE:
setsubfontparams(s, x, r->start, j++);
copyglyph(r->start, &x, s->img);
break;
case MR_RANGE:
for(i = r->start; i <= r->end; i++){
setsubfontparams(s, x, i, j++);
copyglyph(i, &x, s->img);
}
break;
}
}
setsubfontparams(s, x, 0, j);
file = saverawsubfont(s, base, m->start, m->end+1);
appendfont(fd, m->start, m->end, 0, file);
freerawsubfont(s);
free(file);
}
void
makesinglemapping(struct Mapping *m, int fd, char *base)
{
struct RawSubfont *s;
uint x = 0;
char *file;
s = allocrawsubfont(1, glyphs[m->idx].width);
setsubfontparams(s, 0, m->idx, 0);
copyglyph(m->idx, &x, s->img);
setsubfontparams(s, x, 0, 1);
file = saverawsubfont(s, base, m->start, m->start);
appendfont(fd, m->start, m->start, 0, file);
freerawsubfont(s);
free(file);
}
void
makeonemapped(struct MapFile *mf)
{
struct Mapping *m;
char *base, fontfile[64], *p;
int fd;
base = strrchr(mf->file, '/');
if(base == nil)
base = mf->file;
p = seprint(fontfile, fontfile+sizeof(fontfile)-5, "%s", base);
strcpy(p, ".font");
fd = createfont(fontfile);
for(m = mf->mappings; m != nil; m = m->next){
if(m->rules != nil)
makemultimapping(m, fd, base);
else
makesinglemapping(m, fd, base);
}
close(fd);
}
void
makemapped(void)
{
struct MapFile *mf;
for(mf = mapfiles; mf != nil; mf = mf->next)
makeonemapped(mf);
}
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;
int fd;
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;
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");
checkmapfiles();
if(memimageinit() != 0)
sysfatal("%r");
loadufx(infile);
fd = create(outdir, OWRITE, 0777|DMDIR);
if(fd < 0)
sysfatal("%r");
close(fd);
if(chdir(outdir) != 0)
sysfatal("%r");
if(!setconv || donaive)
makenaive();
if(!setconv || doholes)
makenoholes();
makemapped();
}