#include #include #include #include #include #include #include #include #include #include #include #include #include #include "dat.h" #include "fns.h" static Point prevmouse; static Window *mousew; int min(int a, int b) { if(a < b) return a; return b; } int max(int a, int b) { if(a > b) return a; return b; } void cvttorunes(char *p, int n, Rune *r, int *nb, int *nr, int *nulls) { uchar *q; Rune *s; int j, w; /* * Always guaranteed that n bytes may be interpreted * without worrying about partial runes. This may mean * reading up to UTFmax-1 more bytes than n; the caller * knows this. If n is a firm limit, the caller should * set p[n] = 0. */ q = (uchar*)p; s = r; for(j=0; jnr = nr; rs->r = r; } void error(char *s) { fprint(2, "abaco: %s: %r\n", s); abort(); threadexitsall(s); } void* emalloc(ulong n) { void *p; p = malloc(n); if(p == nil) error("malloc failed"); setmalloctag(p, getcallerpc(&n)); memset(p, 0, n); return p; } void* erealloc(void *p, ulong n) { p = realloc(p, n); if(p == nil) error("realloc failed"); setmalloctag(p, getcallerpc(&n)); return p; } Rune* erunestrdup(Rune *r) { void *p; if(r == nil) return nil; p = runestrdup(r); if(p == nil) error("runestrdup failed"); setmalloctag(p, getcallerpc(&r)); return p; } char* estrdup(char *s) { char *t; t = strdup(s); if(t == nil) error("strdup failed"); setmalloctag(t, getcallerpc(&s)); return t; } int runestreq(Runestr a, Runestr b) { return runeeq(a.r, a.nr, b.r, b.nr); } int runeeq(Rune *s1, uint n1, Rune *s2, uint n2) { if(n1 != n2) return FALSE; return memcmp(s1, s2, n1*sizeof(Rune)) == 0; } void closerunestr(Runestr *rs) { rs->nr = 0; if(rs->r) free(rs->r); rs->r = nil; } void copyrunestr(Runestr *a, Runestr *b) { closerunestr(a); a->r = runemalloc(b->nr+1); runemove(a->r, b->r, b->nr); a->r[b->nr] = 0; a->nr = b->nr; } int isalnum(Rune c) { /* * Hard to get absolutely right. Use what we know about ASCII * and assume anything above the Latin control characters is * potentially an alphanumeric. */ if(c <= ' ') return FALSE; if(0x7F<=c && c<=0xA0) return FALSE; if(utfrune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c)) return FALSE; return TRUE; } Rune* skipbl(Rune *r, int n, int *np) { while(n>0 && (*r==' ' || *r=='\t' || *r=='\n')){ --n; r++; } *np = n; return r; } Rune* findbl(Rune *r, int n, int *np) { while(n>0 && *r!=' ' && *r!='\t' && *r!='\n'){ --n; r++; } *np = n; return r; } int istextfield(Item *i) { Formfield *ff; ff = ((Iformfield *)i)->formfield; if(ff->ftype==Ftext || ff->ftype==Ftextarea || ff->ftype==Fpassword) return TRUE; return FALSE; } int forceitem(Item *i) { if(i->state&IFwrap && i->tag!=Iruletag && i->tag!=Itabletag) return FALSE; return TRUE; } int dimwidth(Dimen d, int w) { int s, k; k = dimenkind(d); if(k == Dnone) return w; s = dimenspec(d); if(k == Dpixels) w = s; else if(k==Dpercent && s<100) w = s*w/100; return w; } void frdims(Dimen *d, int n, int t, int **ret) { int totpix, totpcnt, totrel; double spix, spcnt, relu, vd; int tt, trest, totpixrel, minrelu, i; int *x, *spec, *kind; if(n == 1){ *ret = x = emalloc(sizeof(int)); x[0] = t; return; } totpix = totpcnt = totrel = 0; spec = emalloc(n*sizeof(int)); kind = emalloc(n*sizeof(int)); for(i=0; i 0) minrelu = Scrollsize+Scrollgap; relu = (double)minrelu; tt = totpix + t*totpcnt/100 + totrel*minrelu; if(tt < t){ if(totrel == 0){ if(totpcnt != 0) spcnt = (double)((t-totpix)*100)/(double)(t*totpcnt); else spix = (double)t/(double)totpix; }else relu += (double)(t-tt)/(double)totrel; }else{ totpixrel = totpix + totrel*minrelu; if(totpixrel < t) spcnt = (double)((t-totpixrel)*100)/(double)(t*totpcnt); else{ trest = t - totrel*minrelu; if(trest > 0) spcnt = (double)trest/(double)(totpix + (t*totpcnt/100)); else{ spcnt = (double)t/(double)tt; relu = 0.0; } spix = spcnt; } } x = emalloc(n * sizeof(int)); tt = 0; for(i=0; idoc; if(d->backgrounditem){ if(d->backgrounditem->aux){ ci = d->backgrounditem->aux; if(ci->mi) getimage(ci, d->backgrounditem->altrep); bg = ci->i; }else bg = display->white; }else bg = getcolor(d->background.color); return bg; } Rune * getbase(Page *p) { if(p->doc) return p->doc->base; if(p->url->act.r) return p->url->act.r; return p->url->src.r; } Image * eallocimage(Display *d, Rectangle r, ulong chan, int repl, int col) { Image *i; i = allocimage(d, r, chan, repl, col); if(i == nil) error("allocimage failed"); return i; } void rect3d(Image *im, Rectangle r, int i, Image **c, Point sp) { Point p[6]; if(i < 0){ r = insetrect(r, i); sp = addpt(sp, Pt(i,i)); i = -i; } draw(im, Rect(r.min.x+i, r.min.y+i, r.max.x-i, r.max.y-i), c[2], nil, sp); p[0] = r.min; p[1] = Pt(r.min.x, r.max.y); p[2] = Pt(r.min.x+i, r.max.y-i); p[3] = Pt(r.min.x+i, r.min.y+i); p[4] = Pt(r.max.x-i, r.min.y+i); p[5] = Pt(r.max.x, r.min.y); fillpoly(im, p, 6, 0, c[0], sp); p[0] = r.max; p[1] = Pt(r.min.x, r.max.y); p[2] = Pt(r.min.x+i, r.max.y-i); p[3] = Pt(r.max.x-i, r.max.y-i); p[4] = Pt(r.max.x-i, r.min.y+i); p[5] = Pt(r.max.x, r.min.y); fillpoly(im, p, 6, 0, c[1], sp); } void ellipse3d(Image *im, Point p, int rad, int i, Image **c, Point sp) { fillarc(im, p, rad, rad, c[0], sp, 45, 180); fillarc(im, p, rad, rad, c[1], sp, 45, -180); fillellipse(im, p, rad-i, rad-i, c[2], sp); } void colarray(Image **c, Image *c0, Image *c1, Image *c2, int checked) { if(checked){ c[0] = c0; c[1] = c1; }else{ c[0] = c1; c[1] = c0; } c[2] = c2; } static char *fontpaths[NumFnt]; static Font *fonts[NumFnt]; static char seq[NumFnt*2] = "trsrnrlrvrtisinilivitbsbnblbvbttstntltvt"; void initfontpaths(void) { Biobufhdr *b; char *f[3], *s, *p; int i; p = getenv("htmlfont"); if(p == nil) sysfatal("abaco: $htmlfont unset"); if((b = Bopen(p, OREAD)) == nil) sysfatal("abaco: no html fonts"); free(p); for(; s = Brdstr(b, '\n', 1); free(s)){ if(p = strchr(s, '#')) *p = 0; if(tokenize(s, f, nelem(f)) != 2 || strlen(f[0]) != 2) continue; f[0][0] = tolower(f[0][0]); f[0][1] = tolower(f[0][1]); p = strstr(seq, f[0]); i = p - seq; if(p == nil || i & 1) continue; free(fontpaths[i/2]); fontpaths[i/2] = strdup(f[1]); } Bterm(b); for(i = 0; i < NumFnt; i++) if(fontpaths[i] == nil || access(fontpaths[i], AREAD)) sysfatal("abaco: bad font %#.2s %s\n", seq+i*2, fontpaths[i]); // else // fprint(2, "font%d: %#.2s %s\n", i, seq+i*2, fontpaths[i]); } Font * getfont(int i) { if(fonts[i] == nil){ if(fontpaths[i] == nil) sysfatal("fontpaths[%d] == nil\n", i); fonts[i] = openfont(display, fontpaths[i]); if(fonts[i] == nil) error("can't open font file"); } return fonts[i]; } typedef struct Color Color; struct Color { int rgb; Image *i; Color *next; }; enum { NHASH = 19, }; static Color *colortab[NHASH]; Image * getcolor(int rgb) { Color *c; int h; if(rgb == 0xFFFFFF) return display->white; else if(rgb == 0x000000) return display->black; h = rgb%NHASH; for(c=colortab[h]; c!=nil; c=c->next) if(c->rgb == rgb){ flushimage(display, 0); /* BUG? */ return c->i; } c = emalloc(sizeof(Color)); c->i = eallocimage(display, Rect(0,0,1,1), screen->chan, 1, (rgb<<8)|0xFF); c->rgb = rgb; c->next = colortab[h]; colortab[h] = c; return c->i; } int plumbrunestr(Runestr *rs, char *attr) { Plumbmsg *m; int i; i = -1; if(plumbsendfd >= 0){ m = emalloc(sizeof(Plumbmsg)); m->src = estrdup("abaco"); m->dst = nil; m->wdir = estrdup("/tmp"); m->type = estrdup("text"); if(attr) m->attr = plumbunpackattr(attr); else m->attr = nil; m->data = smprint("%.*S", rs->nr, rs->r); m->ndata = -1; i = plumbsend(plumbsendfd, m); plumbfree(m); } return i; } static int inclass(Rune c, Rune* cl) { int n, ans, negate, i; n = runestrlen(cl); if(n == 0) return 0; ans = 0; negate = 0; if(cl[0] == '^'){ negate = 1; cl++; n--; } for(i=0; i0 && i=cl[i - 1] && c<=cl[i+1]){ ans = 1; break; } i++; } else if(c == cl[i]){ ans = 1; break; } } if(negate) ans = !ans; return ans; } char* ucvt(Rune* s) { char* u, buf[16]; int i, c, n, j, k, l, len; n = runestrlen(s); len = 0; for(i=0; inextimage; c->nextimage = r; r = c; } *head = r; } char urlexpr[] = "^(https?|ftp|file|gopher|mailto|news|nntp|telnet|wais|prospero)://([a-zA-Z0-9_@\\-]+([.:][a-zA-Z0-9_@\\-]+)*)"; Reprog *urlprog; int validurl(Rune *r) { Resub rs[10]; if(urlprog == nil){ urlprog = regcomp(urlexpr); if(urlprog == nil) error("regcomp"); } memset(rs, 0, sizeof rs); if(rregexec(urlprog, r, rs, nelem(rs)) == 0) return FALSE; return TRUE; } void execproc(void *v) { Exec *e; threadsetname("execproc"); e = v; rfork(RFFDG); dup(e->p[0], 0); close(e->p[0]); close(e->p[1]); dup(e->q[1], 1); close(e->q[0]); close(e->q[1]); if(!procstderr) close(2); procexecl(e->sync, "/bin/rc", "rc", "-c", e->cmd, nil); error("can't exec"); } typedef struct Warg Warg; struct Warg { Channel *sync; int fd; char *s; uvlong np; }; static void writeproc(void *v) { uvlong i, n; Warg *w; threadsetname("writeproc"); w = v; for(i=0; inp; i+=n){ n = w->np-i; if(n > BUFSIZE) n = BUFSIZE; if(write(w->fd, w->s+i, n) != n) break; } close(w->fd); sendul(w->sync, i); } struct { char *mime; char *tcs; } tcstab[] = { #include "tcs.h" /* not generated by the script */ "euc_jp", "jis", "euc_kr", " euc-k", "windows-874", "tis", }; char * tcs(char *cs, char *s, long *np) { char *t, buf[BUFSIZE], cmd[50]; long i, n; Exec e; Warg w; if(s==nil || *s=='\0' || *np==0){ werrstr("tcs failed: no data"); return s; } if(cs == nil){ werrstr("tcs failed: no charset"); return s; } if(cistrncmp(cs, "utf-8", 5)==0 || cistrncmp(cs, "utf8", 4)==0) return s; for(i=0; i < nelem(tcstab); i++) if(cistrncmp(cs, tcstab[i].mime, strlen(tcstab[i].mime)) == 0) break; if(i == nelem(tcstab)){ fprint(2, "abaco: charset: %s not supported\n", cs); goto latin1; } if(cistrcmp(tcstab[i].tcs, "8859-1")==0 || cistrcmp(tcstab[i].tcs, "ascii")==0){ latin1: return s; } memset(&e, 0, sizeof e); if(pipe(e.p)<0 || pipe(e.q)<0) error("can't create pipe"); e.sync = chancreate(sizeof(ulong), 0); if(e.sync == nil) error("can't create channel"); snprint(cmd, sizeof cmd, "tcs -f %s", tcstab[i].tcs); e.cmd = cmd; proccreate(execproc, &e, STACK); recvul(e.sync); chanfree(e.sync); close(e.p[0]); close(e.q[1]); /* in case tcs fails */ t = s; memset(&w, 0, sizeof w); w.sync = chancreate(sizeof(ulong), 0); if(w.sync == nil) error("can't create channel"); w.fd = e.p[1]; w.s = s; w.np = *np; proccreate(writeproc, &w, STACK); s = nil; while((n = read(e.q[0], buf, sizeof(buf))) > 0){ s = erealloc(s, i+n+1); memmove(s+i, buf, n); i += n; s[i] = '\0'; } n = recvul(w.sync); if(n != *np) fprint(2, "tcs: did not write %ld; wrote %lud\n", *np, n); *np = i; chanfree(w.sync); close(e.q[0]); if(s == nil){ fprint(2, "tcs failed: can't convert charset=%s to %s\n", cs, tcstab[i].tcs); return t; } free(t); return s; } static int isspace(char c) { return c==' ' || c== '\t' || c=='\r' || c=='\n'; } static int findctype(char *b, int l, char *keyword, char *s) { char *p, *e; int i; p = cistrstr(s, keyword); if(!p) return -1; p += strlen(keyword); while(*p && isspace(*p)) p++; if(*p != '=') return -1; p++; while(*p && isspace(*p)) p++; if(!*p) return -1; if(*p == '"'){ p++; e = strchr(p, '"'); if(!e) return -1; }else for(e = p; *e < 127 && *e > ' ' ; e++) ; i = e-p; if(i < 1) return -1; snprint(b, l, "%.*s", i, p); return 0; } static int finddocctype(char *b, int l, char *s) { char *p, *e; p = cistrstr(s, "'); if(!e) return -1; snprint(b, l, "%.*s", (int)(e-p), p); return 0; } static int findxmltype(char *b, int l, char *s) { char *p, *e; p = cistrstr(s, ""); if(!e) return -1; snprint(b, l, "%.*s", (int)(e-p), p); return 0; } /* * servers can lie about lie about the charset, * so we use the charset based on the priority. */ char* convert(Runestr ctype, char *s, long *np) { char t[25], buf[256]; *t = '\0'; if(ctype.nr){ snprint(buf, sizeof(buf), "%.*S", ctype.nr, ctype.r); findctype(t, sizeof(t), "charset", buf); } if(findxmltype(buf, sizeof(buf), s)==0) findctype(t, sizeof(t), "encoding", buf); if(finddocctype(buf, sizeof(buf), s) == 0) findctype(t, sizeof(t), "charset", buf); if(*t == '\0') strcpy(t, charset); return tcs(t, s, np); } int xtofchar(Rune *s, Font *f, long p) { Rune *r; int q; if(p == 0) return 0; q = 0; for(r=s; *r!=L'\0'; r++){ p -= runestringnwidth(f, r, 1); if(p < 0) break; q++; } return q; } int istextsel(Page *p, Rectangle r, int *q0, int *q1, Rune *s, Font *f) { int topinr, botinr; *q0 = *q1 = 0; topinr= ptinrect(p->top, r); if(topinr || (r.min.y>p->top.y && r.max.ybot.y)) p->selecting = TRUE; botinr = ptinrect(p->bot, r); if(botinr || r.min.y>p->bot.y) p->selecting = FALSE; if(topinr || botinr){ if(topinr) *q0 = xtofchar(s, f, p->top.x-r.min.x); if(botinr) *q1 = xtofchar(s, f, p->bot.x-r.min.x); if(*q0!=0 || *q1!=0) return TRUE; } return p->selecting; } Point getpt(Page *p, Point xy) { xy.x = xy.x-p->r.min.x+p->pos.x; xy.y = xy.y-p->r.min.y+p->pos.y; return xy; } void getimage(Cimage *ci, Rune *altr) { Rectangle r; Memimage *mi; Image *i, *i2; char buf[128]; uchar *bits; int nbits; mi = ci->mi; if(mi == nil){ snprint(buf, sizeof(buf), "[%S]", altr ? altr : L"IMG"); r.min = Pt(0, 0); r.max.x = 2*Space + stringwidth(font, buf); r.max.y = 2*Space + font->height; ci->i = eallocimage(display, r, GREY1, 1, DBlack); r.min.x += Space; r.min.y += Space; string(ci->i, r.min, display->white, ZP, font, buf); return; } nbits = bytesperline(mi->r, mi->depth)*Dy(mi->r); bits = emalloc(nbits); unloadmemimage(mi, mi->r, bits, nbits); /* /* get rid of alpha channel from transparent gif * / if(mi->depth == 16){ for(y=1; y>1] = bits[y]; } */ i = eallocimage(display, mi->r, mi->chan, 0, DNofill); loadimage(i, i->r, bits, nbits); i2 = eallocimage(display, i->r, RGB24, 1, DNofill); draw(i2, i2->r, display->black, nil, ZP); draw(i2, i2->r, i, nil, i->r.min); free(bits); freememimage(mi); freeimage(i); ci->i = i2; ci->mi = nil; } static void fixtext1(Item **list) { Itext *text, *ntext; Item *it, *prev; Rune *s, *s1, *s2; int n; if(*list == nil) return; prev = nil; for(it=*list; it!=nil; it=prev->next){ if(it->tag!=Itexttag || forceitem(it)) goto Continue; text = (Itext *)it; s = text->s; while(*s && isspacerune(*s)) s++; if(!*s){ if(prev == nil) prev = *list = it->next; else prev->next = it->next; it->next = nil; freeitems(it); if(prev == nil) return; continue; } n = 0; while(s[n] && !isspacerune(s[n])) n++; if(!s[n]) goto Continue; s1 = runemalloc(n+1); s1 = runemove(s1, s, n); s1[n] = L'\0'; s += n; while(*s && isspacerune(*s)) s++; if(*s){ n = runestrlen(s); s2 = runemalloc(n+1); runemove(s2, s, n); s2[n] = L'\0'; ntext = emalloc(sizeof(Itext)); ntext->s = s2; ntext->ascent = text->ascent; ntext->anchorid = text->anchorid; ntext->state = text->state&~(IFbrk|IFbrksp|IFnobrk|IFcleft|IFcright); ntext->tag = text->tag; ntext->fnt = text->fnt; ntext->fg = text->fg; ntext->ul = text->ul; ntext->next = (Item *)text->next; text->next = (Item *)ntext; } free(text->s); text->s = s1; Continue: prev = it; } } void fixtext(Page *p) { Tablecell *c; Table *t; fixtext1(&p->items); for(t=p->doc->tables; t!=nil; t=t->next) for(c=t->cells; c!=nil; c=c->next) fixtext1(&c->content); } typedef struct Refresh Refresh; struct Refresh { Page *p; Refresh *next; }; static Refresh *refreshs = nil; static QLock refreshlock; void addrefresh(Page *p, char *fmt, ...) { Refresh *r; Rune *s; va_list arg; if(p->aborting) return; va_start(arg, fmt); s = runevsmprint(fmt, arg); va_end(arg); if(s == nil) error("runevsmprint failed"); if(p->status){ free(p->status); p->status = nil; } p->status = s; qlock(&refreshlock); for(r=refreshs; r!=nil; r=r->next) if(r->p == p) goto Return; incref(p->w); /* flushrefresh will decref */ r = emalloc(sizeof(Refresh)); r->p = p; r->next = refreshs; refreshs = r; Return: nbsendp(crefresh, nil); qunlock(&refreshlock); } /* called while row is locked */ void flushrefresh(void) { Refresh *r, *next; Page *p; qlock(&refreshlock); for(r=refreshs; r!=nil; r=next){ p = r->p; if(p->changed==TRUE && p->aborting==FALSE){ p->changed = FALSE; if(p->parent==nil || p->loading==FALSE) pagerender(p); if(!p->refresh.t) pagesetrefresh(p); } if(p->status){ winsetstatus(p->w, p->status); free(p->status); p->status = nil; } winseturl(p->w); winsettag(p->w); decref(p->w); next = r->next; free(r); } refreshs = nil; qunlock(&refreshlock); } void savemouse(Window *w) { prevmouse = mouse->xy; mousew = w; } void restoremouse(Window *w) { if(mousew!=nil && mousew==w) moveto(mousectl, prevmouse); mousew = nil; } void clearmouse() { mousew = nil; } /* * Heuristic city. */ Window* makenewwindow(Page *p) { Column *c; Window *w, *bigw, *emptyw; Page *emptyp; int i, y, el; if(activecol) c = activecol; else if(selpage && selpage->col) c = selpage->col; else if(p && p->col) c = p->col; else{ if(row.ncol==0 && rowadd(&row, nil, -1)==nil) error("can't make column"); c = row.col[row.ncol-1]; } activecol = c; if(p==nil || p->w==nil || c->nw==0) return coladd(c, nil, nil, -1); /* find biggest window and biggest blank spot */ emptyw = c->w[0]; bigw = emptyw; for(i=1; inw; i++){ w = c->w[i]; /* use >= to choose one near bottom of screen */ if(Dy(w->page.all) >= Dy(bigw->page.all)) bigw = w; if(w->page.lay==nil && Dy(w->page.all) >= Dy(emptyw->page.all)) emptyw = w; } emptyp = &emptyw->page; el = Dy(emptyp->all); /* if empty space is big, use it */ if(el>15 || (el>3 && el>(Dy(bigw->page.all)-1)/2)) y = emptyp->all.max.y; else{ /* if this window is in column and isn't much smaller, split it */ if(p->col==c && Dy(p->w->r)>2*Dy(bigw->r)/3) bigw = p->w; y = (bigw->r.min.y + bigw->r.max.y)/2; } w = coladd(c, nil, nil, y); colgrow(w->col, w, 1); return w; }