ref: 28f4567ba615c4036c079021392825757ef2a008
dir: /sys/src/cmd/abaco/util.c/
#include <u.h> #include <libc.h> #include <bio.h> #include <draw.h> #include <memdraw.h> #include <thread.h> #include <cursor.h> #include <mouse.h> #include <keyboard.h> #include <frame.h> #include <plumb.h> #include <html.h> #include <regexp.h> #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; j<n; j+=w){ if(*q < Runeself){ w = 1; *s = *q++; }else{ w = chartorune(s, (char*)q); q += w; } if(*s) s++; else if(nulls) *nulls = TRUE; } *nb = (char*)q-p; *nr = s-r; } void bytetorunestr(char *s, Runestr *rs) { Rune *r; int nb, nr; nb = strlen(s); r = runemalloc(nb+1); cvttorunes(s, nb, r, &nb, &nr, nil); r[nr] = '\0'; rs->nr = 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<n; i++){ spec[i] = dimenspec(d[i]); if(spec[i] < 0) spec[i] = 0; kind[i] = dimenkind(d[i]); switch(kind[i]){ case Dpixels: totpix += spec[i]; break; case Dpercent: totpcnt += spec[i]; break; case Drelative: totrel += spec[i]; break; case Dnone: totrel++; break; } } spix = spcnt = 1.0; minrelu = 0; if(totrel > 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; i<n-1; i++){ vd = (double)spec[i]; switch(kind[i]){ case Dpixels: vd = vd*spix; break; case Dpercent: vd = vd*(double)t*spcnt/100.0; break; case Drelative: vd = vd*relu; break; case Dnone: vd = relu; break; } x[i] = (int)(vd+.5); tt += x[i]; } x[n - 1] = t - tt; *ret = x; free(spec); free(kind); } Image * getbg(Page *p) { Docinfo *d; Cimage *ci; Image *bg; d = p->doc; 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 *deffontpaths[] = { #include "fonts.h" }; static char *fontpaths[NumFnt]; static Font *fonts[NumFnt]; void initfontpaths(void) { Biobufhdr *bp; char buf[128]; char *s; int i; /* we don't care if getenv(2) fails */ s = getenv("home"); snprint(buf, sizeof(buf)-1, "%s/lib/abaco.fonts", s); free(s); if((bp=Bopen(buf, OREAD)) == nil) goto Default; for(i=0; i<NumFnt; i++) if((fontpaths[i]=Brdstr(bp, '\n', 1)) == nil) goto Error; Bterm(bp); return; Error: fprint(2, "abaco: not enough fontpaths in '%s'\n", buf); Bterm(bp); for(i--; i>=0; i--) free(fontpaths[i]); Default: for(i=0; i<NumFnt; i++) fontpaths[i] = deffontpaths[i]; } Font * getfont(int i) { if(fonts[i] == nil){ 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; } int hexdigit(int v) { if(0<=v && v<=9) return '0' + v; else return 'A' + v - 10; } static int inclass(char 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; i<n; i++){ if(cl[i]=='-' && i>0 && i<n-1){ if(c>=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; } Rune* ucvt(Rune* s) { Rune* u; char *t; int i, c, n, j, len; t = smprint("%S", s); n = strlen(t); len = 0; for(i=0; i<n; i++){ c = t[i]; if(inclass(c, L"- /$_@.!*'(),a-zA-Z0-9")) len++; else len += 3; } u = runemalloc(len+1); j = 0; for(i=0; i<n; i++){ c = t[i]; if(inclass(c, L"-/$_@.!*'(),a-zA-Z0-9")) u[j++] = c; else if(c == ' ') u[j++] = '+'; else { u[j++] = '%'; u[j++] = hexdigit((c >> 4)&15); u[j++] = hexdigit(c&15); } } u[j] = 0; free(t); return u; } void reverseimages(Iimage **head) { Iimage *r, *c, *n; r = nil; for(c=*head; c!=nil; c=n){ n = c->nextimage; c->nextimage = r; r = c; } *head = r; } char urlexpr[] = "^(https?|ftp|file|gopher|mailto|news|nntp|telnet|wais|" "prospero)://[^/]+"; 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; } static 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]); if(e->q[0]){ 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"); } int pipeline(int fd, char *cmd, ...) { Exec *e; va_list a; e = emalloc(sizeof(Exec)); if(pipe(e->p)<0 || pipe(e->q)<0) error("can't create pipe"); close(e->p[0]); e->p[0] = fd; va_start(a, cmd); e->cmd = vsmprint(cmd, a); va_end(a); e->sync = chancreate(sizeof(ulong), 0); if(e->sync == nil) error("can't create channel"); proccreate(execproc, e, STACK); recvul(e->sync); chanfree(e->sync); free(e->cmd); close(e->p[0]); close(e->p[1]); close(e->q[1]); fd = e->q[0]; free(e); return fd; } static int isspace(char c) { return c==' ' || c== '\t' || c=='\r' || c=='\n'; } int findctype(char *b, int l, char *keyword, char *s) { char *p, *e, c; 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; switch (c = *p){ case '"': case '\'': p++; e = strchr(p, c); if(!e) return -1; break; default: for(e = p; *e < 127 && *e > ' ' ; e++) ; } i = e-p; if(i < 1) return -1; snprint(b, l, "%.*s", i, p); return 0; } 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.y<p->bot.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<nbits; y+=2) bits[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"); qlock(&refreshlock); if(p->status){ free(p->status); p->status = nil; } p->status = s; 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; i<c->nw; 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; }