plan9fox/sys/src/cmd/abaco/util.c
2019-02-25 04:20:43 +01:00

1091 lines
17 KiB
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 = utfnlen(p, 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;
}