#include #include #include #include #include /* 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(char *base, Rune r1, Rune r2, struct RawSubfont *s) { 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("naive", 0, nglyphs, s); 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("noholes", 0, n, s); 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); } 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, err; 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();*/ }