#include #include #include #include #include #include extern const char *__progname; #include "font.h" #include "istack.h" #include "text-flags.h" #define N_CHARS 256 typedef struct charinfo CHARINFO; typedef struct pbmops PBMOPS; struct charinfo { int enc; int adv; int bbx; int bby; int bbw; int bbh; int stride; unsigned char *bits; } ; struct font { int ascent; int descent; int defchar; int minbbx; int minbby; int maxbbx; int maxbby; CHARINFO chars[N_CHARS]; } ; struct pbmops { void *(*init)(FILE *); int (*getx)(void *); int (*gety)(void *); int (*nextbit)(void *); void (*nextline)(void *); void (*done)(void *); } ; extern char builtin_font_bdf[]; static __inline__ int cdiv(int a, int b) { return((a+(b-1))/b); } static __inline__ int imin(int a, int b) { return((ab)?a:b); } static char *getline(FILE *f) { static char *lb = 0; static int la = 0; int ln; int c; void savec(int c) { if (ln >= la) lb = realloc(lb,la=ln+16); lb[ln++] = c; } ln = 0; while (1) { c = getc(f); switch (c) { case EOF: if (ln == 0) return(0); savec('\0'); return(lb); break; case '\r': c = getc(f); if (c == '\n') { case '\n': savec('\0'); return(lb); } ungetc(c,f); c = '\r'; break; } savec(c); } } static void font_bb(FONT *font, CHARINFO *ci, int *firstp) { if ((ci->bbw == 0) || (ci->bbh == 0)) return; if (*firstp) { *firstp = 0; font->minbbx = ci->bbx; font->minbby = ci->bby; font->maxbbx = ci->bbx + ci->bbw; font->maxbby = ci->bby + ci->bbh; } else { font->minbbx = imin(font->minbbx,ci->bbx); font->minbby = imin(font->minbby,ci->bby); font->maxbbx = imax(font->maxbbx,ci->bbx+ci->bbw); font->maxbby = imax(font->maxbby,ci->bby+ci->bbh); } } static void save_bdf_bits(const char *hex, unsigned char *bits, int nbytes) { int v; for (;nbytes>0;nbytes--) { sscanf(hex,"%2x",&v); *bits = v; hex += 2; bits ++; } } static void cibitclr(CHARINFO *ci, int x, int y) { if ((x < 0) || (x >= ci->bbw) || (y < 0) || (y >= ci->bbh)) abort(); ci->bits[(x>>3)+(y*ci->stride)] &= ~(0x80 >> (x&7)); } static void cibitset(CHARINFO *ci, int x, int y) { if ((x < 0) || (x >= ci->bbw) || (y < 0) || (y >= ci->bbh)) abort(); ci->bits[(x>>3)+(y*ci->stride)] |= 0x80 >> (x&7); } static unsigned int cibit(CHARINFO *ci, int x, int y) { if ((x < 0) || (x >= ci->bbw) || (y < 0) || (y >= ci->bbh)) abort(); return((ci->bits[(x>>3)+(y*ci->stride)]>>(7-(x&7)))&1); } static int bbox_cleanup(CHARINFO *ci) { int i; int j; int x; int y; int w; int h; CHARINFO ci2; if ((ci->bbw == 0) || (ci->bbh == 0)) return(0); do <"found"> { for (i=0;ibbw;i++) { for (j=ci->bbh-1;j>=0;j--) { if (cibit(ci,i,j)) break <"found">; } } free(ci->bits); ci->bbw = 0; ci->bbh = 0; ci->bits = 0; return(1); } while (0); x = i; do <"found"> { for (j=0;jbbh;j++) { for (i=ci->bbw-1;i>=0;i--) { if (cibit(ci,i,j)) break <"found">; } } abort(); } while (0); y = j; do <"found"> { for (i=ci->bbw-1;i>=0;i--) { for (j=ci->bbh-1;j>=0;j--) { if (cibit(ci,i,j)) break <"found">; } } abort(); } while (0); w = i+1 - x; do <"found"> { for (j=ci->bbh-1;j>=0;j--) { for (i=ci->bbw-1;i>=0;i--) { if (cibit(ci,i,j)) break <"found">; } } abort(); } while (0); h = j+1 - y; if ((w == ci->bbw) && (h == ci->bbh)) return(0); ci2 = *ci; ci->bbx += x; ci->bby += ci2.bbh - (y+h); ci->bbw = w; ci->bbh = h; ci->stride = cdiv(w,8); ci->bits = malloc(ci->stride*ci->bbh); for (j=h-1;j>=0;j--) { for (i=w-1;i>=0;i--) { if (cibit(&ci2,i+x,j+y)) { cibitset(ci,i,j); } else { cibitclr(ci,i,j); } } } free(ci2.bits); return(1); } static int font_load_bdf(FONT *font, FILE *f) { __label__ ret0; char *sp; int nprop; int nchars; enum { STARTCHAR_OR_ENDFONT = 1, ENCODING, SWIDTH, DWIDTH, BBX, BITMAP_OR_ATTR, BITS_OR_ENDCHAR } expected; int bbw; int bbh; int bbx; int bby; int rowbytes; int bitrow; int enc; int enc2; int adv; CHARINFO *ci; char *l; int bbfirst; void fail(const char *fmt, ...) { FILE *f; va_list ap; f = istack_err_open(); va_start(ap,fmt); fprintf(f,"BDF font load: "); vfprintf(f,fmt,ap); va_end(ap); fclose(f); goto ret0; } nprop = -1; nchars = -1; bbfirst = 1; while (1) { l = getline(f); if (! l) fail("unterminated file"); for (sp=l;(*sp)&&(*sp!=' ');sp++) ; if ((nprop < 0) && (sp-l == 15) && !bcmp(l,"STARTPROPERTIES",15)) { nprop = atoi(l+15); continue; } if (nprop > 0) { if ((sp-l == 11) && !bcmp(l,"FONT_ASCENT",11)) { font->ascent = atoi(l+11); } if ((sp-l == 12) && !bcmp(l,"FONT_DESCENT",12)) { font->descent = atoi(l+12); } if ((sp-l == 12) && !bcmp(l,"DEFAULT_CHAR",12)) { font->defchar = atoi(l+12); } nprop --; continue; } if ((sp-l == 5) && !bcmp(l,"CHARS",5)) { nchars = atoi(l+5); expected = STARTCHAR_OR_ENDFONT; continue; } if (nchars < 0) continue; switch (expected) { case STARTCHAR_OR_ENDFONT: if (nchars < 1) { if ((sp-l != 7) || bcmp(l,"ENDFONT",7)) fail("expected ENDFONT, found %.*s",(int)(sp-l),l); return(1); } else { if ((sp-l != 9) || bcmp(l,"STARTCHAR",9)) fail("expected STARTCHAR, found %.*s",(int)(sp-l),l); expected = ENCODING; } break; case ENCODING: if ((sp-l != 8) || bcmp(l,"ENCODING",8)) fail("expected ENCODING, found %.*s",(int)(sp-l),l); switch (sscanf(l+8,"%d%d",&enc,&enc2)) { case 1: break; case 2: if (enc != -1) fail("invalid ENCODING: %s",l+8); enc = enc2; break; } if ((enc < 0) || (enc >= N_CHARS)) enc = -1; expected = SWIDTH; break; case SWIDTH: if ((sp-l != 6) || bcmp(l,"SWIDTH",6)) fail("expected SWIDTH, found %.*s",(int)(sp-l),l); expected = DWIDTH; break; case DWIDTH: if ((sp-l != 6) || bcmp(l,"DWIDTH",6)) fail("expected DWIDTH, found %.*s",(int)(sp-l),l); if (sscanf(l+6,"%d",&adv) != 1) return(0); expected = BBX; break; case BBX: if ((sp-l != 3) || bcmp(l,"BBX",3)) fail("expected BBX, found %.*s",(int)(sp-l),l); if (sscanf(l+3,"%d%d%d%d",&bbw,&bbh,&bbx,&bby) != 4) fail("invalid BBX, can't read numbers"); rowbytes = cdiv(bbw,8); bitrow = 0; if (enc < 0) { ci = 0; } else { ci = &font->chars[enc]; ci->adv = adv; ci->bbx = bbx; ci->bby = bby; ci->bbw = bbw; ci->bbh = bbh; ci->stride = rowbytes; ci->bits = (bbh && rowbytes) ? malloc(bbh*rowbytes) : 0; font_bb(font,ci,&bbfirst); } expected = BITMAP_OR_ATTR; break; case BITMAP_OR_ATTR: if ((sp-l == 10) || !bcmp(l,"ATTRIBUTES",10)) continue; if ((sp-l != 6) || bcmp(l,"BITMAP",6)) fail("expected ATTRIBUTES or BITMAP, found %.*s",(int)(sp-l),l); expected = BITS_OR_ENDCHAR; break; case BITS_OR_ENDCHAR: if (bitrow >= bbh) { if ((sp-l != 7) || bcmp(l,"ENDCHAR",7)) fail("expected ENDCHAR, found %.*s",(int)(sp-l),l); if (ci && bbox_cleanup(ci)) { fprintf(stderr,"%s: warning: BDF character %d bounding-box is too loose (fixed)\n",__progname,ci->enc); } nchars --; expected = STARTCHAR_OR_ENDFONT; } else { if (sp-l != 2*rowbytes) fail("bitmap length wrong"); if (ci) save_bdf_bits(l,ci->bits+(bitrow*rowbytes),rowbytes); bitrow ++; } break; default: abort(); break; } } ret0:; return(0); } static void init_font(FONT *f) { int i; CHARINFO ci; f->ascent = 0; f->descent = 0; f->defchar = 0; ci.adv = 0; ci.bbx = 0; ci.bby = 0; ci.bbw = 0; ci.bbh = 0; ci.bits = 0; for (i=N_CHARS-1;i>=0;i--) { ci.enc = i; f->chars[i] = ci; } } static void font_free(FONT *f) { int i; for (i=N_CHARS-1;i>=0;i--) free(f->chars[i].bits); free(f); } static int font_load_pbm(FONT *font, FILE *f, PBMOPS *ops) { __label__ ret0; void *priv; int w; int h; char *bits; #define B(x,y) (bits[(x)+((y)*w)]) int x; int y; int bg; int basey; int i; int j; int k; int sepx[N_CHARS+1]; int orgx; CHARINFO *ci; int bbfirst; void fail(const char *fmt, ...) { FILE *f; va_list ap; if (priv) (*ops->done)(priv); if (bits) free(bits); f = istack_err_open(); va_start(ap,fmt); fprintf(f,"PBM font load: "); vfprintf(f,fmt,ap); va_end(ap); fclose(f); goto ret0; } bits = 0; priv = (*ops->init)(f); if (! priv) return(0); w = (*ops->getx)(priv); h = (*ops->gety)(priv); if (w < 768) fail("X size impossibly small"); if (h < 4) fail("Y size impossibly small"); bits = malloc(w*h); for (y=0;ynextbit)(priv); } (*ops->nextline)(priv); } (*ops->done)(priv); priv = 0; bg = bits[0]; basey = -1; for (y=h-1;y>=0;y--) { if (B(0,y) != bg) { if (basey >= 0) fail("multiple baseline markers"); basey = y; } } if (basey < 0) fail("no baseline marker"); sepx[0] = 1; i = 1; for (x=1;x= N_CHARS) fail("too many separator bars"); sepx[i++] = x; } } if (i != N_CHARS) fail("too few separator bars"); sepx[N_CHARS] = w; bbfirst = 1; for (i=N_CHARS-1;i>=0;i--) { ci = &font->chars[i]; j = -1; k = -1; for (x=sepx[i+1]-1;x>sepx[i];x--) { if (B(x,0) != bg) { if (j >= 0) fail("character %d has multiple origin points",i); j = x; } if (B(x,h-1) != bg) { if (k >= 0) fail("character %d has multiple advance points",i); k = x; } } if (j < 0) fail("character %d has no origin point",i); if (k < 0) fail("character %d has no advance point",i); orgx = j - (sepx[i] + 1); ci->adv = (k - (sepx[i] + 1)) - orgx; do <"bbox"> { for (x=sepx[i+1]-1;x>sepx[i];x--) { for (y=h-2;y>=1;y--) { if (B(x,y) != bg) { int l; int r; int t; int b; r = x; for <"l"> (x=sepx[i]+1;;x++) { for (y=h-2;y>=1;y--) { if (B(x,y) != bg) { l = x; break <"l">; } } } for <"t"> (y=1;;y++) { for (x=r;x>=l;x--) { if (B(x,y) != bg) { t = y; break <"t">; } } } for <"b"> (y=h-2;;y--) { for (x=r;x>=l;x--) { if (B(x,y) != bg) { b = y; break <"b">; } } } if ((l < sepx[i]+2) || (r > sepx[i+1]-2) || (t < 2) || (b > h-3)) fail("character %d doesn't have blank surround",i); ci->bbx = l - orgx; ci->bby = basey - b; ci->bbw = r + 1 - l; ci->bbh = b + 1 - t; ci->stride = cdiv(ci->bbw,8); ci->bits = malloc(ci->bbh*ci->stride); for (y=t;y<=b;y++) { for (x=l;x<=r;x++) { if (B(x,y) == bg) cibitclr(ci,x-l,y-t); else cibitset(ci,x-l,y-t); } } break <"bbox">; } } } ci->bbx = 0; ci->bby = 0; ci->bbw = 0; ci->bbh = 0; ci->stride = 1; ci->bits = 0; } while (0); font_bb(font,ci,&bbfirst); } return(1); ret0:; return(0); } static int pbm_getc(FILE *f) { int c; while (1) { c = getc(f); switch (c) { case ' ': case '\t': case '\r': case '\n': return(' '); break; case '#': while <"comment"> (1) { c = getc(f); switch (c) { case EOF: return(EOF); break; case '\n': break <"comment">; } } break; default: return(c); break; } } } static int digitvalue(int c) { switch (c) { case '0': return(0); break; case '1': return(1); break; case '2': return(2); break; case '3': return(3); break; case '4': return(4); break; case '5': return(5); break; case '6': return(6); break; case '7': return(7); break; case '8': return(8); break; case '9': return(9); break; } return(-1); } static int Px_getsize(FILE *f, int *xp, int *yp) { int c; enum { X, Y } which; int in_num; int n; int d; which = X; in_num = 0; while (1) { c = pbm_getc(f); if (! in_num) { switch (c) { case ' ': break; default: n = digitvalue(c); if (n < 0) return(0); in_num = 1; break; } } else { if (c == ' ') { switch (which) { case X: *xp = n; which = Y; in_num = 0; break; case Y: *yp = n; return(1); break; default: abort(); break; } } d = digitvalue(c); if (d < 0) return(0); n = (n * 10) + d; if (n < 0) return(0); } } } typedef struct p1priv P1PRIV; struct p1priv { FILE *f; int x; int y; } ; static void *P1_init(FILE *f) { P1PRIV *p; p = malloc(sizeof(P1PRIV)); p->f = f; if (! Px_getsize(f,&p->x,&p->y)) { free(p); return(0); } return(p); } static int P1_getx(void *pv) { return(((P1PRIV *)pv)->x); } static int P1_gety(void *pv) { return(((P1PRIV *)pv)->y); } static int P1_nextbit(void *pv) { P1PRIV *p; int c; p = pv; while (1) { c = pbm_getc(p->f); switch (c) { case ' ': break; case '0': return(0); break; case '1': return(1); break; default: return(-1); break; } } } static void P1_nextline(void *pv __attribute__((__unused__))) { } static void P1_done(void *pv) { free(pv); } static PBMOPS pbmget_P1 = { &P1_init, &P1_getx, &P1_gety, &P1_nextbit, &P1_nextline, &P1_done }; typedef struct p4priv P4PRIV; struct p4priv { FILE *f; int x; int y; unsigned char buf; int n; } ; static void *P4_init(FILE *f) { P4PRIV *p; p = malloc(sizeof(P4PRIV)); p->f = f; if (! Px_getsize(f,&p->x,&p->y)) { free(p); return(0); } p->n = 0; return(p); } static int P4_getx(void *pv) { return(((P4PRIV *)pv)->x); } static int P4_gety(void *pv) { return(((P4PRIV *)pv)->y); } static int P4_nextbit(void *pv) { P4PRIV *p; int c; p = pv; if (p->n < 1) { c = getc(p->f); if (c == EOF) return(-1); p->buf = c; p->n = 8; } p->n --; return((p->buf>>p->n)&1); } static void P4_nextline(void *pv) { ((P4PRIV *)pv)->n = 0; } static void P4_done(void *pv) { free(pv); } static PBMOPS pbmget_P4 = { &P4_init, &P4_getx, &P4_gety, &P4_nextbit, &P4_nextline, &P4_done }; static FONT *load_stdio(FILE *f) { FONT *font; int c; int ok; char *l; font = malloc(sizeof(FONT)); init_font(font); c = getc(f); switch (c) { default: ok = 0; break; case 'P': c = getc(f); switch (c) { default: ok = 0; break; case '1': ok = font_load_pbm(font,f,&pbmget_P1); break; case '4': ok = font_load_pbm(font,f,&pbmget_P4); break; } break; case 'S': ungetc(c,f); l = getline(f); if (!strcmp(l,"STARTFONT 2.1")) { ok = font_load_bdf(font,f); } else { ok = 0; } break; } if (! ok) { font_free(font); return(0); } return(font); } FONT *font_builtin(void) { int x; int sz; FILE *f; FONT *rv; int rd(void *cookie __attribute__((__unused__)), char *buf, int len) { if ((x < 0) || (x > sz) || (len < 0)) return(0); if (len > sz) len = sz; if (x+len > sz) len = sz - x; if (len < 1) return(0); bcopy(&builtin_font_bdf[x],buf,len); x += len; return(len); } sz = strlen(&builtin_font_bdf[0]); x = 0; f = fropen(0,&rd); rv = load_stdio(f); fclose(f); if (! rv) abort(); return(rv); } FONT *font_load(const char *fn) { FONT *rv; FILE *f; if (! fn) return(font_builtin()); f = fopen(fn,"r"); if (f == 0) { fprintf(stderr,"%s: can't open font file %s: %s\n",__progname,fn,strerror(errno)); return(0); } rv = load_stdio(f); fclose(f); return(rv); } void font_unload(FONT *f) { font_free(f); } static void render_glyph(CHARINFO *ci, int atx, int aty, unsigned char *into, int width, int height) { int i; int j; int y; int x; x = atx + ci->bbx; if ((x < 0) || (x+ci->bbw > width)) abort(); y = aty - (ci->bby + ci->bbh); if ((y < 0) || (y+ci->bbh > height)) abort(); y += ci->bbh - 1; for (j=ci->bbh-1;j>=0;j--,y--) { for (i=ci->bbw-1;i>=0;i--) { if (cibit(ci,i,j)) into[(y*width)+x+i] = 1; } } } /* * font_render handles the JUST, X, Y, and BBOXMARGIN fields of the * flags, leaving RECTBG, XF, and NO_AUTO_SWAP to other code. */ void font_render( FONT *font, const char *txt, unsigned int flags, unsigned char **bitsp, int *wp, int *hp, int *xp, int *yp ) { int lines; int i; unsigned char c; int lastbl; CHARINFO *ci; int linelen; int x; int y; int w; int h; int lastadv; int linefirst; int linebbfirst; int line_l; int line_t; int line_r; int line_b; int firstline; int all_mino; int all_maxa; int all_l; int all_t; int all_r; int all_b; unsigned char *bits; int cx; int cy; int cl; for (lines=1,i=0;txt[i];i++) if (txt[i] == '\n') lines ++; { int offsets[lines]; void init_line(void) { x = 0; linelen = 0; linefirst = 1; linebbfirst = 1; } void finish_line(void) { lastbl = y; if (! linefirst) { switch (flags & TEXTFLAG_JUST) { case TEXTFLAG_JUST_L: offsets[y] = 0; break; case TEXTFLAG_JUST_C: offsets[y] = lastadv / 2; break; case TEXTFLAG_JUST_R: offsets[y] = lastadv; break; default: abort(); break; } line_l -= offsets[y]; line_r -= offsets[y]; if (! linebbfirst) { if (firstline) { if (y) abort(); firstline = 0; all_l = line_l; all_t = line_t; all_r = line_r; all_b = line_b; all_mino = - offsets[y]; all_maxa = lastadv - offsets[y]; } else { all_l = imin(all_l,line_l); all_t = imin(all_t,line_t+(y*(font->ascent+font->descent))); all_r = imax(all_r,line_r); all_b = imax(all_b,line_b+(y*(font->ascent+font->descent))); all_mino = imin(all_mino,-offsets[y]); all_maxa = imax(all_maxa,lastadv-offsets[y]); } } } } y = 0; firstline = 1; init_line(); for (i=0;(c=txt[i]);i++) { if (c >= N_CHARS) continue; if (c == '\n') { finish_line(); y ++; init_line(); continue; } ci = &font->chars[c]; if (ci->bbw || ci->bbh) { if (linebbfirst) { linebbfirst = 0; line_l = x + ci->bbx; line_t = - (ci->bby + ci->bbh); line_r = x + ci->bbx + ci->bbw; line_b = - ci->bby; } else { line_l = imin(line_l,x+ci->bbx); line_t = imin(line_t,-(ci->bby+ci->bbh)); line_r = imax(line_r,x+ci->bbx+ci->bbw); line_b = imax(line_b,-ci->bby); } } linefirst = 0; lastadv = x += ci->adv; } if (firstline && linefirst) { *wp = 0; *hp = 0; *xp = 0; *yp = 0; *bitsp = 0; return; } finish_line(); lastbl *= font->ascent + font->descent; w = all_r - all_l; h = all_b - all_t; /* all_[rlbt] are the bounding box of the pixels; all_mino is the minimum origin X coordinate and all_maxa is the maximum advance point X coordinate. These are in a coordinate system where x=0 is the point justification lines up on (first origin, last advance, or midpoint) and y=0 is the baseline if the first line. */ /* Set x and y to the coordinates of the anchor point relative to the upper-left corner of the bounding box. */ switch (flags & (TEXTFLAG_X|TEXTFLAG_BBOXMARGIN)) { case TEXTFLAG_X_L: x = all_mino - all_l; break; case TEXTFLAG_X_C: x = ((all_mino + all_maxa) / 2) - all_l; break; case TEXTFLAG_X_R: x = all_maxa - all_l; break; case TEXTFLAG_X_L | TEXTFLAG_BBOXMARGIN: x = 0; break; case TEXTFLAG_X_C | TEXTFLAG_BBOXMARGIN: x = w / 2; break; case TEXTFLAG_X_R | TEXTFLAG_BBOXMARGIN: x = w; break; default: abort(); break; } switch (flags & (TEXTFLAG_Y|TEXTFLAG_BBOXMARGIN)) { case TEXTFLAG_Y_TOP: y = - font->ascent - all_t; break; case TEXTFLAG_Y_CENTRE: y = ((lastbl + font->descent - font->ascent) / 2) - all_t; break; case TEXTFLAG_Y_BOTTOM: y = lastbl + font->descent - all_t; break; case TEXTFLAG_Y_TOP | TEXTFLAG_BBOXMARGIN: y = 0; break; case TEXTFLAG_Y_CENTRE | TEXTFLAG_BBOXMARGIN: y = h / 2; break; case TEXTFLAG_Y_BOTTOM | TEXTFLAG_BBOXMARGIN: y = h; break; case TEXTFLAG_Y_BASE_FIRST: case TEXTFLAG_Y_BASE_FIRST | TEXTFLAG_BBOXMARGIN: y = - all_t; break; case TEXTFLAG_Y_BASE_LAST | TEXTFLAG_BBOXMARGIN: case TEXTFLAG_Y_BASE_LAST: y = lastbl - all_t; break; case TEXTFLAG_Y_CENTREBASE | TEXTFLAG_BBOXMARGIN: case TEXTFLAG_Y_CENTREBASE: y = (lastbl / 2) - all_t; break; default: abort(); break; } *wp = w; *hp = h; *xp = x; *yp = y; bits = malloc(w*h); *bitsp = bits; bzero(bits,w*h); cl = 0; cx = offsets[0] - all_l; cy = - all_t; for (i=0;(c=txt[i]);i++) { if (c >= N_CHARS) continue; if (c == '\n') { cl ++; cx = offsets[cl] - all_l; cy += font->ascent + font->descent; continue; } ci = &font->chars[c]; if (ci->bbw || ci->bbh) render_glyph(ci,cx,cy,bits,w,h); cx += ci->adv; } } }