#include "i.h" typedef struct Fontinfo Fontinfo; typedef struct Colornode Colornode; typedef struct Source Source; typedef struct Shtml Shtml; typedef struct Simage Simage; typedef struct Sources Sources; struct Fontinfo { char* name; Font* f; int spw; // width of a space in this font }; // Seems better to use a slightly smaller font in Controls, to match other browsers enum { CtlFnt = FntR*NumSize+Small }; // color stuff. have hash table mapping RGB values to Image for that color struct Colornode { int rgb; Image* im; Colornode* next; }; // Source of info for page (html, image, etc.) struct Source { Source* next; // in list of source for a page ByteSource* bs; int redirects; int tag; // Shtmltag or Simagetag }; struct Shtml { Source* next; // in list of source for a page ByteSource* bs; int redirects; int tag; // Shtmltag ItemSource* itsrc; }; struct Simage { Source* next; // in list of source for a page ByteSource* bs; int redirects; int tag; // Simagetag CImage* ci; Itemlist* itl; ImageSource* imsrc; }; enum { Shtmltag, Simagetag }; struct Sources { Source* srcs; int tot; int done; }; enum { NCOLHASH = 19, // 19 checked for standard colors: only 1 collision TABPIX = 30, // number of pixels in a tab CAPSEP = 5, // number of pixels separating tab from caption SCRBREADTH = 20, // scrollbar breadth (normal) SCRFBREADTH = 14, // scrollbar breadth (inside child frame or select control) FRMARGIN = 10, // default margin around frames RULESP = 7, // extra space before and after rules CBOXWID = 14, // check box width CBOXHT = 12, // check box height ENTVMARGIN = 4, // vertical margin inside entry box ENTHMARGIN = 6, // horizontal margin inside entry box SELMARGIN = 4, // margin inside select control BUTMARGIN = 4, // margin inside button control PBOXWID = 16, // progress box width PBOXHT = 16, // progress box height SELBG = 0x00FFFF, // aqua CTRLMASK = 0x1F, DEL = 0x7F, TAB = '\t', CR = '\n', TABLEMAXTARGET = 2000, // targetwidth to get max width of table cell TABLEFLOATTARGET = 600, // targetwidth for floating tables }; // triangle shapes enum { TRIup, TRIdown, TRIleft, TRIright }; Fontinfo fonts[NumFnt]= { {"/lib/font/bit/lucidasans/unicode.6.font", nil, 0}, {"/lib/font/bit/lucidasans/unicode.7.font", nil, 0}, {"/lib/font/bit/lucidasans/unicode.8.font", nil, 0}, {"/lib/font/bit/lucidasans/unicode.10.font", nil, 0}, {"/lib/font/bit/lucidasans/unicode.13.font", nil, 0}, {"/lib/font/bit/lucidasans/italicunicode.6.font", nil, 0}, {"/lib/font/bit/lucidasans/italicunicode.7.font", nil, 0}, {"/lib/font/bit/lucidasans/italicunicode.8.font", nil, 0}, {"/lib/font/bit/lucidasans/italicunicode.10.font", nil, 0}, {"/lib/font/bit/lucidasans/italicunicode.13.font", nil, 0}, {"/lib/font/bit/lucidasans/boldunicode.6.font", nil, 0}, {"/lib/font/bit/lucidasans/boldunicode.7.font", nil, 0}, {"/lib/font/bit/lucidasans/boldunicode.8.font", nil, 0}, {"/lib/font/bit/lucidasans/boldunicode.10.font", nil, 0}, {"/lib/font/bit/lucidasans/boldunicode.13.font", nil, 0}, {"/lib/font/bit/lucidasans/typeunicode.6.font", nil, 0}, {"/lib/font/bit/lucidasans/typeunicode.7.font", nil, 0}, {"/lib/font/bit/lucidasans/typeunicode.9.font", nil, 0}, {"/lib/font/bit/lucidasans/typeunicode.12.font", nil, 0}, {"/lib/font/bit/lucidasans/typeunicode.16.font", nil, 0} }; Colornode* colorhashtab[19]; uchar wordchar[0xA0]= { ['0'] 1, ['1'] 1, ['2'] 1, ['3'] 1, ['4'] 1, ['5'] 1, ['6'] 1, ['7'] 1, ['8'] 1, ['9'] 1, ['A'] 1, ['B'] 1, ['C'] 1, ['D'] 1, ['E'] 1, ['F'] 1, ['G'] 1, ['H'] 1, ['I'] 1, ['J'] 1, ['K'] 1, ['L'] 1, ['M'] 1, ['N'] 1, ['O'] 1, ['P'] 1, ['Q'] 1, ['R'] 1, ['S'] 1, ['T'] 1, ['U'] 1, ['V'] 1, ['W'] 1, ['X'] 1, ['Y'] 1, ['Z'] 1, ['a'] 1, ['b'] 1, ['c'] 1, ['d'] 1, ['e'] 1, ['f'] 1, ['g'] 1, ['h'] 1, ['i'] 1, ['j'] 1, ['k'] 1, ['l'] 1, ['m'] 1, ['n'] 1, ['o'] 1, ['p'] 1, ['q'] 1, ['r'] 1, ['s'] 1, ['t'] 1, ['u'] 1, ['v'] 1, ['w'] 1, ['x'] 1, ['y'] 1, ['z'] 1, ['_'] 1, ['\''] 1, ['"'] 1, ['.'] 1, [','] 1, ['('] 1, [')'] 1 }; int dbglay = 0; int dbgtab = 0; static int linespace = 0; static int lineascent = 0; static int charspace = 0; static int spspace = 0; static int ctllinespace = 0; static int ctllineascent = 0; static int ctlcharspace = 0; static int ctlspspace = 0; static int frameid = 0; static int addsubords(Sources* sources, Docinfo* di, Rune* auth); static Sources* newsources(void); static void addsource(Sources* srcs, Source* s); static Source* newshtml(ByteSource* bs, ItemSource* is); static Source* newsimage(ByteSource* bs, CImage* ci, ImageSource* is); static Source* copysource(Source* s); static Source* findbs(Sources* srcs, ByteSource* bs); static void startimreq(Simage* s, Rune* auth); static void createvscroll(Frame* f); static void createhscroll(Frame* f); static void fixframegeom(Frame* f); static void haveimage(Frame* f, CImage* ci, Itemlist* itl); static Lay* sublayout(Frame* f, int targetwidth, uchar just, Background bg, Item* content); static void relayout(Frame* f, Lay* lay, int targetwidth, int just); static void appenditems(Frame* f, Lay* lay, Item* items); static void fixgeom(Frame* f, Lay* lay, Line* l); static void fixlinegeom(Frame* f, Lay* lay, Line* l); static int pastbrk(Lay* lay, int y, int state); static void appendline(Line* lprev, Line* l); static void changelines(Line* l, Line* lend); static Font* getfont(int num); static void measure(Frame* fr, Item* items); static void setimagedims(Iimage* i); static void updatelgeom(int* pH, int* pA, Item* it); static int trybreak(Item* bit, int availw, int* piw, int noneok); static int breakstring(Rune* s, int sw, Font* fnt, int availw, int noneok, Rune** ps1, int* pw1, Rune** ps2, int* pw2); static int wrapstring(Font* fnt, Rune* s, int availw, Rune*** plines, int** plinestarts); static int breakpoint(Rune* s, int slen, int i, int incr); static int tryw(Font* fnt, Rune* s, int i, Rune** pss); static int floatw(int ymin, int ymax, Ifloat* flist, uchar side); static void fixfloatxy(Lay* lay, int y, Ifloat* f); static void fixfloatsafter(Lay* lay, Line* l, List* flist); static int floatclry(Ifloat* flist, uchar side, int y); static void sizetable(Frame* f, Table* tab, int availwidth); static void tableparams(Table* tab, int* hsp, int* vsp, int* pad, int* bd, int* cbd, int* hsep, int* vsep); static int cellwidth(Table* tab, Tablecell* c, int hsep); static int cellheight(Table* tab, Tablecell* c, int vsep); static int widthcalc(Table* tab, int* w, int hsep, int domax); static void layframeset(Frame* f, Kidinfo* ki); static int frdimens(Dimen* dims, int n, int t, int** parr); static Item* lastitem(Item* it); static void checktabsize(Frame* f, Itable* t, int availw); static int widthfromspec(Dimen wspec, int availw); static void checkffsize(Frame* f, Item* i, Formfield* ff); static void drawall(Frame* f); static void drawlay(Frame* f, Lay* lay, Point origin); static void drawline(Frame* f, Point layorigin, Line* l, Lay* lay); static void drawimg(Frame* f, Point iorigin, Iimage* i); static void drawtable(Frame* f, Lay* parentlay, Point torigin, Table* tab); static void markchanges(Loc* loc); static Image* colorimage(int rgb); static Colornode* newcolornode(int rgb, Image* im, Colornode* next); static void fillbg(Frame* f, Rectangle r); static void drawtriangle(Image* im, Rectangle r, int kind, int style); static void flushc(Frame* f); static Loc* framefind(Loc* loc, Frame* f, Point p, Item* it); static Loc* layfind(Loc* loc, Frame* f, Lay* lay, Point origin, Point p, Item* it); static Loc* linefind(Loc* loc, Frame* f, Line* l, Point o, Point p, Item* it); static Loc* tablefind(Loc* loc, Frame* f, Itable* ti, Point torigin, Point p, Item* it); static void entrydelrange(Centry* e, int istart, int iend); static void entrysetfromsnarf(Centry* e); static int entrywrapcalc(Centry* e, int** plinestarts, int* ptopline, int* pcursline); static void entryscroll(Centry* e); static void animproc(void *arg); void layoutinit() { Font* fnt; // make sure default and control fonts are loaded getfont(DefFnt); fnt = fonts[DefFnt].f; linespace = fnt->height; lineascent = fnt->ascent; charspace = runestringwidth(fnt, L"a"); spspace = fonts[DefFnt].spw; getfont(CtlFnt); fnt = fonts[CtlFnt].f; ctllinespace = fnt->height; ctllineascent = fnt->ascent; ctlcharspace = runestringwidth(fnt, L"a"); ctlspspace = fonts[CtlFnt].spw; } // Use bsmain to fill frame f, and free bsmain. // Return buffer containing source in *pbuf and return its length. // // An important invariant is that we keep // track of every ByteSource returned by startreq, and // ensure that we freebs each of them. int layout(Frame* f, ByteSource* bsmain, uchar** pbuf) { Sources* sources; Header* hdr; Rune* auth; uchar* ans; int anslen; Docinfo* di; Lay* l; int anyanim; ItemSource* itsrc; ImageSource* imsrc; CImage* ci; CImage* newci; Item* it; Iimage* ii; Itemlist* itl; Source* news; Simage* sim; Shtml* sh; Source* s; int use; int error; Rune* challenge; ParsedUrl* newurl; int n; int ret; MaskedImage* mim; ByteSource* bs; Source* src; int freeit; void* animargs[1]; dbglay = config.dbg['l']; dbgtab = config.dbg['t']; if(pbuf) *pbuf = nil; if(dbgev) logtime("LAYOUT", 0); sources = newsources(); hdr = bsmain->hdr; auth = bsmain->req->auth; ans = nil; anslen = 0; di = newdocinfo(); resetframe(f); f->doc = di; di->frameid = f->id; di->src = hdr->actual; di->base = hdr->base; di->refresh = hdr->refresh; di->chset = hdr->chset; if(f->framebd != 0) { f->cr = insetrect(f->r, 2); drawborder(f->cim, f->cr, 2, DarkGrey); } fillbg(f, f->cr); flushc(f); if(f->flags&FRvscroll) createvscroll(f); if(f->flags&FRhscroll) createhscroll(f); l = newlay(Dx(f->cr), ALleft, f->marginw, di->background); f->layout = l; anyanim = 0; if(hdr->mtype == TextHtml || hdr->mtype == TextPlain) { itsrc = newitemsource(bsmain, di, hdr->mtype); addsource(sources, newshtml(bsmain, itsrc)); } else { if(!supported(hdr->mtype)) { trace("Need to implement something: source isn't supported image type\n"); return 0; } imsrc = newimagesource(bsmain, 0, 0); ci = newcimage(bsmain->req->url, 0, 0); s = newsimage(bsmain, ci, imsrc); addsource(sources, s); it = newiimage(nil, nil, ALbottom, 0, 0, 0, 0, 0, 0, nil); ii = (Iimage*)it; ii->ci = ci; di->images = ii; appenditems(f, l, it); ((Simage*)s)->itl = newitemlist(it, nil); } while(sources->done < sources->tot) { if(dbgev) logtime("LAYOUT GETSOMETHING", 0); bs = waitreq(); if(bs == nil) break; /* abort condition propagated by netget */ src = findbs(sources, bs); // if(src == nil) // continue; assert(src != nil); freeit = 0; if(bs->err) { if(dbglay) trace("error getting something: %S\n", errphrase(bs->err)); freeit = 1; } else { if(bs->hdr != nil && !bs->seenhdr) { use = hdraction(bs, 0, src->redirects, &error, &challenge, &newurl); if(challenge != nil) { trace("Need to implement authorization credential dialog\n"); error = ERRauthfailed; use = 0; } if(error && dbglay) trace("subordinate error: %S\n", errphrase(error)); if(newurl != nil) { // redirect freeit = 1; switch(src->tag) { case Shtmltag: trace("unexpected redirect of subord\n"); break; case Simagetag: sim = (Simage*)src; newci = newcimage(newurl, sim->ci->width, sim->ci->height); for(itl = sim->itl; itl != nil; itl = itl->next) { it = itl->val; assert(it->tag == Iimagetag); ((Iimage*)it)->ci = newci; } news = newsimage(nil, newci, nil); ((Simage*)news)->itl = sim->itl; addsource(sources, news); startimreq((Simage*)news, auth); break; } } if(!use) freeit = 1; } if(!freeit && bs->edata > bs->lim) { switch(src->tag) { case Shtmltag: sh = (Shtml*)src; it = getitems(sh->itsrc); if(bs == bsmain) { if(di->kidinfo != nil) { if(sh->itsrc->kidstk == nil) { layframeset(f, di->kidinfo); flushimage(display, 1); freeit = 1; } } else { l->background = di->background; anyanim |= addsubords(sources, di, auth); if(it != nil) { appenditems(f, l, it); fixframegeom(f); if(dbgev) logtime("LAYOUT_DRAWALL", 0); drawall(f); } } } else { trace("Need to implement embedded html objects\n"); freeit = 1; } break; case Simagetag: sim = (Simage*)src; ret = getmim(sim->imsrc, &mim); if(ret == Mimerror) { freeit = 1; } else if(ret != Mimnone) { if(sim->ci->mims == nil) { sim->ci->mims = (MaskedImage**)emalloc(sizeof(MaskedImage*)); sim->ci->mimslen = 1; sim->ci->mims[0] = mim; sim->ci->width = sim->imsrc->width; sim->ci->height = sim->imsrc->height; if(ret == Mimdone && config.imagelvl <= ImgNoAnim) freeit = 1; } else { n = sim->ci->mimslen; if(mim != sim->ci->mims[n - 1]) { sim->ci->mims = (MaskedImage**)erealloc(sim->ci->mims, (n+1)*sizeof(MaskedImage*)); sim->ci->mims[sim->ci->mimslen++] = mim; anyanim = 1; } } if(sim->ci->mims[0] == mim) haveimage(f, sim->ci, sim->itl); if(bs->lim == bs->hdr->length) imcacheadd(sim->ci); } break; } } if(!freeit && bs->hdr != nil && bs->lim == bs->hdr->length) freeit = 1; } if(freeit) { if(bs == bsmain) { ans = bs->data; anslen = bs->edata; } freebs(bs); src->bs = nil; sources->done++; } } if(anyanim && config.imagelvl > ImgNoAnim) { animargs[0] = f; proccreate(animproc, animargs, STACKSIZE); } if(dbgev) logtime("LAYOUT_END", 0); if(pbuf) *pbuf = ans; return anslen; } // return value is 1 if found any existing images needed animation static int addsubords(Sources* sources, Docinfo* di, Rune* auth) { int anyanim; List* newsims; int iciw; int icih; Source* s; Simage* sim; CImage* cachedci; Iimage* i; anyanim = 0; if(config.imagelvl == ImgNone) return anyanim; newsims = nil; for(i = di->images; i != nil; i = i->nextimage) { if(i->ci->mims == nil) { cachedci = imcachelook(i->ci); if(cachedci != nil) { i->ci = cachedci; if(i->imwidth == 0) i->imwidth = i->ci->width; if(i->imheight == 0) i->imheight = i->ci->height; anyanim |= (cachedci->mimslen > 1); if(i == di->backgrounditem) di->background.image = i->ci; } else { for(s = sources->srcs; s != nil; s = s->next) { if(s->tag == Simagetag) { sim = (Simage*)s; if(cimagematch(sim->ci, i->ci)) { sim->itl = newitemlist((Item*)i, sim->itl); // want all items on list to share same ci; // want most-specific dimension specs iciw = i->ci->width; icih = i->ci->height; i->ci = sim->ci; if(sim->ci->width == 0 && sim->ci->height == 0) { sim->ci->width = iciw; sim->ci->height = icih; } break; } } } if(s == nil) { // didn't find existing Source for this image s = newsimage(nil, i->ci, nil); ((Simage*)s)->itl = newitemlist((Item*)i, nil); newsims = newlist((int)s, newsims); addsource(sources, s); } } } } for( ; newsims != nil; newsims = newsims->next) { startimreq((Simage*)(newsims->val), auth); } return anyanim; } static void startimreq(Simage* s, Rune* auth) { ByteSource* bs; if(dbgev) logtime("LAYOUT STARTREQ", 0); bs = startreq(newreqinfo(s->ci->src, HGet, nil, 0, auth, FTself)); s->bs = bs; s->imsrc = newimagesource(bs, s->ci->width, s->ci->height); } static void createvscroll(Frame* f) { int breadth; int length; breadth = SCRBREADTH; if(f->parent != nil) breadth = SCRFBREADTH; length = Dy(f->cr); if(f->flags&FRhscroll) length -= breadth; f->vscr = newscroll(f, 1, length, breadth); f->vscr->r = rectaddpt(f->vscr->r, f->cr.min); f->cr.min.x += breadth; if(Dx(f->cr) <= 2*f->marginw) trace("botch: frame too small for layout"); else drawctl(f->vscr, 0); } static void createhscroll(Frame* f) { int breadth; int length; int x; breadth = SCRBREADTH; if(f->parent != nil) breadth = SCRFBREADTH; length = Dx(f->cr); x = f->cr.min.x; f->hscr = newscroll(f, 0, length, breadth); f->hscr->r = rectaddpt(f->hscr->r, Pt(x, f->cr.max.y - breadth)); f->cr.max.y -= breadth; if(Dy(f->cr) <= 2*f->marginh) trace("botch: frame too small for layout"); else drawctl(f->hscr, 0); } // Call after a change to f.layout or f.viewr.min to fix totalr and viewr // (We are to leave viewr.min unchanged, if possible, as // user might be scrolling). static void fixframegeom(Frame* f) { Lay* l; int crwidth; int crheight; int crchanged; int n; l = f->layout; if(dbglay) trace("fixframegeom, layout width=%d, height=%d\n", l->width, l->height); f->totalr.max = Pt(l->width, l->height); crwidth = Dx(f->cr); crheight = Dy(f->cr); crchanged = 0; n = l->height + l->margin - crheight; if(n > 0 && f->vscr == nil && (f->flags&FRvscrollauto)) { createvscroll(f); crchanged = 1; crwidth = Dx(f->cr); } if(f->viewr.min.y > n) f->viewr.min.y = max(0, n); n = l->width + l->margin - crwidth; if(n > 0 && f->hscr == nil && (f->flags&FRhscrollauto)) { createhscroll(f); crchanged = 1; crheight = Dy(f->cr); } if(crchanged) { relayout(f, l, crwidth, l->just); fixframegeom(f); return ; } if(f->viewr.min.x > n) f->viewr.min.x = max(0, n); f->viewr.max.x = min(f->viewr.min.x + crwidth, l->width); f->viewr.max.y = min(f->viewr.min.y + crheight, l->height); if(f->vscr != nil) scrollset(f->vscr, f->viewr.min.y, f->viewr.max.y, f->totalr.max.y, 5); if(f->hscr != nil) scrollset(f->hscr, f->viewr.min.x, f->viewr.max.x, f->totalr.max.x, 5); } // The items its within f are Iimage items, // and its image, ci, now has at least a ci->mims[0], which may be partially // or fully filled. static void haveimage(Frame* f, CImage* ci, Itemlist* itl) { int dorelayout; Image* im; Ifloat* fit; Item* locit; int k; Loc* loc; Iimage* i; Item* it; if(dbgev) logtime("HAVEIMAGE", 0); if(dbglay) trace("\nHAVEIMAGE src=%S w=%d h=%d\n", ci->src->url, ci->width, ci->height); for(dorelayout = 0; itl != nil; itl = itl->next) { it = itl->val; switch(it->tag) { case Iimagetag: i = (Iimage*)it; if(it == (Item*)f->doc->backgrounditem && f->doc->background.image == nil) { im = ci->mims[0]->im; im->repl = 1; im->clipr = Rect(-0x3FFFFFFF, 0x3FFFFFFF, -0x3FFFFFFF, 0x3FFFFFFF); f->doc->background.image = i->ci; } else { // If i->imwidth and i->imheight are not both 0, the HTML specified the dimens. // If one of them is 0, the other is to be scaled by the same factor; // we have to relay the line in that case too. if(i->imwidth == 0 || i->imheight == 0) { i->imwidth = ci->width; i->imheight = ci->height; setimagedims(i); loc = findloc(f, ZP, it); // sometimes the image was added to doc image list, but // never made it to layout (e.g., because html bug prevented // a table from being added). // also, script-created images won't have items if(loc != nil) { markchanges(loc); dorelayout = 1; // Floats are assumed to be premeasured, so if there // are any floats in the loc list, remeasure them for(k = loc->n - 1; k > 0; k--) { if(loc->le[k].kind == LEitem) { locit = loc->le[k].item; switch(locit->tag) { case Ifloattag: fit = (Ifloat*)locit; switch(fit->item->tag) { case Iimagetag: fit->height = fit->item->height; break; case Itabletag: checktabsize(f, (Itable*)fit->item, TABLEFLOATTARGET); break; } break; } } } } } } if(dbg > 1) trace("\nhaveimage item: %I", it); break; } } if(dorelayout) { relayout(f, f->layout, f->layout->targetwidth, f->layout->just); fixframegeom(f); } drawall(f); if(dbgev) logtime("HAVEIMAGE_END", 0); } // For first layout of subelements, such as table cells. // After this, content items will be dispersed throughout resulting lay. // Return the new layout. static Lay* sublayout(Frame* f, int targetwidth, uchar just, Background bg, Item* content) { Lay* l; if(dbglay) trace("sublayout, targetwidth=%d\n", targetwidth); l = newlay(targetwidth, just, 0, bg); appenditems(f, l, content); l->flags &= ~Lchanged; if(dbglay) trace("after sublayout, width=%d\n", l->width); return l; } // Relayout of lay, given a new target width or if something changed inside // or if the global justification for the layout changed. // Floats are hard: for now, just relay everything with floats temporarily // moved way down, if there are any floats. static void relayout(Frame* f, Lay* lay, int targetwidth, int just) { int changeall; Ifloat* ff; if(dbglay) trace("relayout, targetwidth=%d, old target=%d, changed=%d\n", targetwidth, lay->targetwidth, (lay->flags&Lchanged) != (uchar)0); changeall = (lay->targetwidth != targetwidth || lay->just != just); if(!changeall && !(lay->flags&Lchanged)) return ; if(lay->floats != nil) { for(ff = lay->floats; ff != nil; ff = ff->nextfloat) ff->y = 0x6fffffff; changeall = 1; } lay->targetwidth = targetwidth; lay->just = just; lay->width = 0; if(changeall) changelines(lay->start->next, lay->end); fixgeom(f, lay, lay->start->next); lay->flags &= ~Lchanged; if(dbglay) trace("after relayout, width=%d\n", lay->width); } // Measure and append the items to the end of layout lay, // and fix the geometry. static void appenditems(Frame* f, Lay* lay, Item* items) { Item* it; Line* lprev; Line* l; Item* lit; Item* nexti; measure(f, items); if(dbglay) printitems(items, "appenditems, after measturetexts"); it = items; if(it == nil) return ; lprev = lay->end->prev; lit = lastitem(lprev->items); if(lit == nil || (it->state&IFbrk)) { // start a new line after existing last line l = newline(); appendline(lprev, l); l->items = it; } else { // start appending items to existing last line l = lprev; lit->next = it; } l->flags |= Lchanged; while(it != nil) { nexti = it->next; if(nexti == nil || (nexti->state&IFbrk)) { it->next = nil; fixgeom(f, lay, l); if(nexti == nil) break; // now there may be multiple lines containing the // items from l, but the one after the last is lay.end l = newline(); appendline(lay->end->prev, l); l->flags |= Lchanged; it = nexti; l->items = it; } else it = nexti; } } // Fix up the geometry of line l and successors. // Assume geometry of previous line is correct. static void fixgeom(Frame* f, Lay* lay, Line* l) { while(l != nil) { fixlinegeom(f, lay, l); l = l->next; } lay->height = lay->end->pos.y; } // Fix geom for one line. // This may change the overall lay->width, if there is no way // to fit the line into the target width. static void fixlinegeom(Frame* f, Lay* lay, Line* l) { Line* lprev; int y; Item* it; int state; int lineh; int lfloatw; int rfloatw; int wrapping; int hang; int linehang; int hangtogo; int indent; int just; int right; int lwid; int w; int linea; Item* lastit; List* nextfloats; int anystuff; Item* rest; int x; int xright; int n; int justw; Item* spaceit; int newlfloatw; int newrfloatw; int kindspec; int avail; Ifloat* ifloat; Itable* itable; Irule* irule; Itext* itext; int oldy; int takeit; int noneok; int iw; Line* nextl; Item* nit; int checkw; Item* qi; Item* li; Item* i; lprev = l->prev; y = lprev->pos.y + lprev->height; it = l->items; state = it->state; if(dbglay > 1) { trace("\nfixlinegeom start, y=prev.y+prev.height=%d+%d=%d, changed=%d\n", l->prev->pos.y, lprev->height, y, l->flags&Lchanged); if(dbglay > 2) printitems(it, "items"); else trace("first item: %I", it); } if(state&IFbrk) { y = pastbrk(lay, y, state); if(dbglay > 1 && y != lprev->pos.y + lprev->height) trace("after pastbrk, line y is now %d\n", y); } l->pos.y = y; lineh = max(l->height, linespace); lfloatw = floatw(y, y + lineh, lay->floats, ALleft); rfloatw = floatw(y, y + lineh, lay->floats, ALright); if(!(l->flags&Lchanged)) { // possibly adjust lay.width n = (lay->width - rfloatw) - (l->pos.x - lay->margin + l->width); if(n < 0) lay->width += -n; return ; } wrapping = state&IFwrap; hang = (state&IFhangmask)*TABPIX/10; linehang = hang; hangtogo = hang; indent = ((state&IFindentmask) >> IFindentshift)*TABPIX; just = (state&(IFcjust|IFrjust)); if(just == 0 && lay->just != ALleft) { if(lay->just == ALcenter) just = IFcjust; else if(lay->just == ALright) just = IFrjust; } right = max(lay->targetwidth, lay->width); lwid = right - (lfloatw + rfloatw + indent); if(lwid < 0) { lay->width = lfloatw + rfloatw + indent; right = lay->width; lwid = 0; } lwid += hang; if(dbglay > 1) { trace("fixlinegeom, now y=%d, lfloatw=%d, rfloatw=%d, indent=%d, hang=%d, lwid=%d\n", y, lfloatw, rfloatw, indent, hang, lwid); } l->pos.y = y; w = 0; lineh = 0; linea = 0; lastit = nil; nextfloats = nil; anystuff = 0; while(it != nil) { if(dbglay > 2) trace("fixlinegeom loop head, w=%d, loop item: %I\n", w, it); state = it->state; if(anystuff && (state&IFbrk)) break; checkw = 1; if(hang && !(state&IFhangmask)) { lwid -= hang; hang = 0; if(hangtogo > 0) { // insert a null spacer item spaceit = newispacer(ISPgeneral); spaceit->width = hangtogo; if(lastit != nil) { spaceit->state = lastit->state&~(IFbrk|IFbrksp|IFnobrk|IFcleft|IFcright); lastit->next = spaceit; } else lastit = spaceit; spaceit->next = it; } } switch(it->tag) { case Ifloattag: ifloat = (Ifloat*)it; if(anystuff) // float will go after this line nextfloats = newlist((int)ifloat, nextfloats); else { // add float beside current line, adjust widths fixfloatxy(lay, y, ifloat); // TODO: only do following if y and/or height changed changelines(l->next, lay->end); newlfloatw = floatw(y, y + 1, lay->floats, ALleft); newrfloatw = floatw(y, y + 1, lay->floats, ALright); lwid -= (newlfloatw - lfloatw) + (newrfloatw - rfloatw); if(lwid < 0) { lay->width += -lwid; right = lay->width; lwid = 0; } lfloatw = newlfloatw; rfloatw = newrfloatw; if(dbglay > 1) trace("float added to current line, i->y=%d, side=%d, h=%d, lfloatw=%d, rfloatw=%d, lwid=%d, lay.width=%d, right=%d\n", ifloat->y, ifloat->side, ifloat->item->height, lfloatw, rfloatw, lwid, lay->width, right); } checkw = 0; break; case Itabletag: // When just doing layout for cell dimensions, don't // want a "100%" spec to make the table really wide itable = (Itable*)it; kindspec = 0; if(lay->targetwidth == TABLEMAXTARGET && dimenkind(itable->table->width) == Dpercent) { kindspec = itable->table->width.kindspec; itable->table->width = makedimen(Dnone, 0); } checktabsize(f, itable, lwid - w); if(kindspec != 0) itable->table->width.kindspec = kindspec; break; case Iruletag: irule = (Irule*)it; avail = lwid - w; // When just doing layout for cell dimensions, don't // want a "100%" spec to make the rule really wide if(lay->targetwidth == TABLEMAXTARGET) avail = min(10, avail); irule->width = widthfromspec(irule->wspec, avail); break; case Iformfieldtag: checkffsize(f, it, ((Iformfield*)it)->formfield); break; } if(checkw) { iw = it->width; if(wrapping && w + iw > lwid) { // it doesn't fit; see if it can be broken // noneok = (anystuff || lfloatw != 0 || rfloatw != 0) && !(state&IFnobrk); noneok = anystuff && !(state&IFnobrk); takeit = trybreak(it, lwid - w, &iw, noneok); if(!takeit) { if(lastit == nil) { // Nothing added because one of the float widths // is nonzero, and not enough room for anything else. // Move y down until there's more room and try again. assert(lfloatw != 0 || rfloatw != 0); oldy = y; y = pastbrk(lay, y, IFcleft|IFcright); if(dbglay > 1) trace("moved y past %d, now y=%d\n", oldy, y); assert(y > oldy); // Do the move down by artificially increasing the // height of the previous line lprev->height += y - oldy; fixlinegeom(f, lay, l); return ; } break; } } w += iw; if(hang) hangtogo -= w; updatelgeom(&lineh, &linea, it); if(!anystuff) { anystuff = 1; // don't count an ordinary space as 'stuff' if wrapping if(it->tag == Itexttag && wrapping && ((Itext*)it)->s[0] == ' ') anystuff = 0; } } lastit = it; it = it->next; if(it == nil) { // perhaps next lines items can now fit on this line nextl = l->next; nit = nextl->items; if(nextl != lay->end && !(nit->state&IFbrk)) { lastit->next = nit; // remove nextl l->next = nextl->next; l->next->prev = l; it = nit; } } } // line is complete, next line will start with it (or it is nil) rest = it; assert(lastit != nil); lastit->next = nil; l->width = w; x = lfloatw + indent - linehang; i = l->items; switch(i->tag) { case Itexttag: itext = (Itext*)i; if(itext->s[0] == ' ') x -= fonts[itext->fnt].spw; break; case Iruletag: irule = (Irule*)i; if(irule->align == ALcenter) just = IFcjust; else if(irule->align == ALright) just = IFrjust; break; case Ifloattag: ifloat = (Ifloat*)i; if(ifloat->next != nil) { qi = ifloat->next; if(qi->tag == Itexttag) { itext = (Itext*)qi; if(itext->s[0] == ' ') x -= fonts[itext->fnt].spw; } } break; } xright = x + w; n = (lay->width - rfloatw) - xright; if(n < 0) { lay->width += -n; } // except when doing the maximum needed cell width, justification // is supposed to be within max(lay.width, targetwidth), // but also, if this is lay for frame itself, justify within f.targetwidth to match other browsers. if(lay == f->layout) justw = lay->targetwidth; else if(lay->targetwidth == TABLEMAXTARGET) justw = lay->width; else justw = max(lay->width, lay->targetwidth); n = (justw - rfloatw) - xright; if(n > 0 && just) { if(just&IFcjust) x += n/2; else x += n; if(x + w + rfloatw > lay->width) lay->width = x + w + rfloatw; } if(dbglay > 1) { trace("line geometry fixed, (x,y)=(%d,%d), w=%d, h=%d, a=%d, lfloatw=%d, rfloatw=%d, lay.width=%d\n", x, l->pos.y, w, lineh, linea, lfloatw, rfloatw, lay->width); if(dbglay > 2) printitems(l->items, "final line items"); } l->pos.x = x + lay->margin; l->height = lineh; l->ascent = linea; l->flags &= ~Lchanged; if(nextfloats != nil) fixfloatsafter(lay, l, nextfloats); if(rest != nil) { nextl = l->next; if(nextl == lay->end || (nextl->items->state&IFbrk)) { nextl = newline(); appendline(l, nextl); } li = lastitem(rest); li->next = nextl->items; nextl->items = rest; nextl->flags |= Lchanged; } } // Return y coord after y due to a break. static int pastbrk(Lay* lay, int y, int state) { int nextralines; int ynext; nextralines = 0; if(state&IFbrksp) nextralines = 1; ynext = y; if(state&IFcleft) ynext = floatclry(lay->floats, ALleft, ynext); if(state&IFcright) ynext = max(ynext, floatclry(lay->floats, ALright, ynext)); ynext += nextralines*linespace; return ynext; } // Add line l after lprev (and before lprev's current successor) static void appendline(Line* lprev, Line* l) { l->next = lprev->next; l->prev = lprev; l->next->prev = l; lprev->next = l; } // Mark lines l up to but not including lend as changed static void changelines(Line* l, Line* lend) { for(; l != lend; l = l->next) l->flags |= Lchanged; } // Return a Font* for font number num = (style*NumSize + size) static Font* getfont(int num) { Font* f; f = fonts[num].f; if(f == nil) { f = openfont(display, fonts[num].name); if(f == nil) { if(num == DefFnt) { trace("can't open default font\n"); assert(0); exits("font problem"); } else { if(warn) trace("warning: substituting default for font %s\n", fonts[num].name); f = fonts[DefFnt].f; } } fonts[num].f = f; fonts[num].spw = runestringwidth(f, L" "); } return f; } // Set the width, height and ascent fields of all items, getting any necessary fonts. // Some widths and heights depend on the available width on the line, and may be // wrong until checked during fixlinegeom. // Don't do tables here at all (except floating tables). // Configure Controls for form fields. static void measure(Frame* fr, Item* items) { Item* it; Font* f; int a; int h; Control* c; Item* i; Itext* t; Irule* r; Iformfield* ff; Ifloat* fl; for(it = items; it != nil; it = it->next) { switch(it->tag) { case Itexttag: t = (Itext*)it; f = getfont(t->fnt); it->width = runestringwidth(f, t->s); a = f->ascent; h = f->height; if(t->voff != Voffbias) { a -= (t->voff) - Voffbias; if(a > h) h = a; } it->height = h; it->ascent = a; break; case Iruletag: r = (Irule*)it; it->height = r->size + 2*RULESP; it->ascent = r->size + RULESP; break; case Iimagetag: setimagedims((Iimage*)it); break; case Iformfieldtag: ff = (Iformfield*)it; c = newff(fr, ff->formfield); if(c != nil) { ff->formfield->ctlid = addcontrol(fr, c); it->width = Dx(c->r); it->height = Dy(c->r); it->ascent = it->height; switch(c->tag) { case Centrytag: it->ascent = lineascent + ENTVMARGIN; break; case Cselecttag: it->ascent = lineascent + SELMARGIN; break; case Cbuttontag: if(((Cbutton*)c)->dorelief) it->ascent -= BUTMARGIN; break; } } break; case Ifloattag: // Leave w at zero, so it doesn't contribute to line width in normal way // (Can find its width in t.item.width). fl = (Ifloat*)it; i = fl->item; switch(i->tag) { case Iimagetag: setimagedims((Iimage*)i); it->height = i->height; break; case Itabletag: checktabsize(fr,(Itable*)i, TABLEFLOATTARGET); break; default: assert(0); break; } it->ascent = it->height; break; case Ispacertag: switch(((Ispacer*)it)->spkind) { case ISPvline: it->height = linespace; it->ascent = lineascent; break; case ISPhspace: it->width = spspace; break; } break; } } } // Set the dimensions of an image item static void setimagedims(Iimage* i) { Font* f; i->width = i->imwidth + 2*(i->hspace + i->border); i->height = i->imheight + 2*(i->vspace + i->border); i->ascent = i->height - (i->vspace + i->border); if(config.imagelvl == ImgNone && i->altrep != nil) { f = fonts[DefFnt].f; i->width = max(i->width, runestringwidth(f, i->altrep)); i->height = max(i->height, f->height); i->ascent = f->ascent; } } // Line geometry function: // Given current line height (*pH) and ascent (distance from top to baseline) (*pA), // and an item, see if that item changes height and ascent. // Return (H', A') in *pH and *pA, the updated line height and ascent. static void updatelgeom(int* pH, int* pA, Item* it) { int h; int a; uchar atype; int d; int Hnew; int Anew; int hhalf; int H; int A; H = *pH; A = *pA; h = it->height; a = it->ascent; atype = ALbaseline; switch(it->tag) { case Iimagetag: atype = ((Iimage*)it)->align; break; case Itabletag: atype = ALtop; break; case Ifloattag: return; } d = h - a; Hnew = H; Anew = A; switch(atype) { case ALbaseline: case ALbottom: if(a > A) { Anew = a; Hnew += (Anew - A); } if(d > Hnew - Anew) Hnew = Anew + d; break; case ALtop: if(h > H) Hnew = h; break; case ALmiddle: case ALcenter: hhalf = h/2; if(hhalf > A) Anew = hhalf; if(hhalf > H - Anew) Hnew = Anew + hhalf; break; } *pH = Hnew; *pA = Anew; } // Try breaking item bit to make it fit in availw. // If that is possible, change bit to be the part that fits // and insert the rest between bit and bit.next. // *piw is the current width of bit. // If noneok is 0, break off the minimum size word // even if it exceeds availw. // Return 1 if supposed to take bit, and iw' = new width of bit in *piw static int trybreak(Item* bit, int availw, int* piw, int noneok) { Item* itn; Itext* t; int iw; Rune* s1; Rune* s2; int w1; int w2; iw = *piw; if(iw <= 0) return 1; if(availw < 0) { if(noneok) return 0; else availw = 0; } if(bit->tag == Itexttag) { t = (Itext*)bit; assert(t->s != nil); if(breakstring(t->s, iw, fonts[t->fnt].f, availw, noneok, &s1, &w1, &s2, &w2) < 0) return !noneok; if(w1 == 0) return 0; // here we split bit into two pieces at i assert(s1 != nil && s2 != nil); itn = newitext(s2, t->fnt, t->fg, t->voff, t->ul); itn->width = w2; itn->height = t->height; itn->ascent = t->ascent; itn->anchorid = t->anchorid; itn->state = t->state&~(IFbrk|IFbrksp|IFnobrk|IFcleft|IFcright); itn->next = t->next; t->next = itn; t->s = s1; t->width = w1; *piw = w1; return 1; } return !noneok; } // s has width sw when drawn in fnt. // Break s into s1 and s2 so that s1 fits in availw. // If noneok is true, it is ok for s1 to be nil, otherwise might // have to return an s1 that overflows availw somewhat. // Return vals in *ps1, *pw1, *ps2, *pw2 where w1 and w2 // are widths of s1 and s2. // Also, return -1 if no break is possible within s, // so the caller needs to take all or nothing. // Assume caller has already checked that sw > availw. static int breakstring(Rune* s, int sw, Font* fnt, int availw, int noneok, Rune** ps1, int* pw1, Rune** ps2, int* pw2) { int slen; int i; int oldi; int ww; int oldww; Rune* ss; Rune* oldss; slen = Strlen(s); if(slen < 2) return -1; // Use linear interpolation to guess break point. // We know avail < iw by conditions of trybreak call. i = slen*availw/sw - 1; if(i < 0) i = 0; i = breakpoint(s, slen, i, -1); ww = tryw(fnt, s, i, &ss); if(ww > availw) { while(ww > availw) { i = breakpoint(s, slen, i - 1, -1); if(i <= 0) break; ww = tryw(fnt, s, i, &ss); } } else { oldi = i; oldss = ss; oldww = ww; while(ww < availw) { oldi = i; oldss = ss; oldww = ww; i = breakpoint(s, slen, i + 1, 1); if(i >= slen) break; ww = tryw(fnt, s, i, &ss); } i = oldi; ss = oldss; ww = oldww; } if(i <= 0 || i >= slen) { if(noneok) { *ps1 = nil; *pw1 = 0; *ps2 = s; *pw2 = sw; return 0; } i = breakpoint(s, slen, 1, 1); if(i >= slen) return -1; ww = tryw(fnt, s, i, &ss); } *ps1 = ss; *pw1 = ww; *ps2 = Strndup(s+i, slen-i); *pw2 = sw-ww; return 1; } // If can break between s[i-1] and s[i], return i. // Else move i in direction incr until this is true. // (Might end up returning 0 or len s). static int breakpoint(Rune* s, int slen, int i, int incr) { int ans; int ci; int di; ans = 0; while(i > 0 && i < slen) { ci = s[i]; di = s[i - 1]; if((ci >= 0xA0 || wordchar[ci]) && (di >= 0xA0 || wordchar[di])) i += incr; else { ans = i; break; } } if(i == slen) ans = slen; return ans; } // Return (s[0:i], width of that slice in font fnt) static int tryw(Font* fnt, Rune* s, int i, Rune** pss) { if(i == 0) { *pss = nil; return 0; } *pss = Strndup(s, i); return runestringwidth(fnt, *pss); } // Return max width of a float that overlaps [ymin, ymax) on given side. // Floats are in reverse order of addition, so each float's y is <= that of // preceding floats in list. Floats from both sides are intermixed. static int floatw(int ymin, int ymax, Ifloat* flist, uchar side) { int ans; int w; Ifloat* fl; int fymin; int fymax; ans = 0; for(fl = flist; fl != nil; fl = fl->nextfloat) { if(fl->side != side) continue; fymin = fl->y; fymax = fymin + fl->item->height; if((fymin <= ymin && ymin < fymax) || (ymin <= fymin && fymin < ymax)) { w = fl->x; if(side == ALleft) w += fl->item->width; if(ans < w) ans = w; } } return ans; } // Float f is to be at vertical position >= y. // Fix its (x,y) pos and add it to lay.floats, if not already there. static void fixfloatxy(Lay* lay, int y, Ifloat* f) { Ifloat* flist; Ifloat* x; f->y = y; flist = lay->floats; if(!f->infloats) { // only take previous floats into account for width while(flist != nil) { x = flist; flist = flist->nextfloat; if(x == f) break; } } f->x = floatw(y, y + f->item->height, flist, f->side); // for right-side floats, x is distance from right edge, // which includes f's width if(f->side == ALright) f->x += f->item->width; if(dbglay > 1) trace("fixfloatxy, now float is: %I\n", (Item*)f); if(!f->infloats) { f->nextfloat = lay->floats; lay->floats = f; f->infloats = 1; } } // Floats in flist are to go after line l. static void fixfloatsafter(Lay* lay, Line* l, List* flist) { int y; List* itl; Ifloat* ifloat; y = l->pos.y + l->height; for(itl = revlist(flist); flist != nil; flist = flist->next) { ifloat = (Ifloat*)(itl->val); assert(ifloat->tag == Ifloattag); fixfloatxy(lay, y, ifloat); } // TODO: only change if y and/or height changed changelines(l->next, lay->end); } // If there's a float on given side that starts on or before y and // ends after y, return ending y of that float, else return original y. // Assume float list is bottom up. static int floatclry(Ifloat* fl, uchar side, int y) { int flymax; for(; fl != nil; fl = fl->nextfloat) { if(fl->side == side) { if(fl->y <= y) { flymax = fl->y + fl->item->height; if(flymax <= y) return y; else return flymax; } } } return y; } // Do preliminaries to laying out table tab in target width linewidth, // setting total height and width. static void sizetable(Frame* f, Table* tab, int availwidth) { int hsp; int vsp; int pad; int bd; int cbd; int hsep; int vsep; int totw; int* colmaxw; int maxw; int ci; int ri; int toth; int tw; Tablecell* c; Lay* clay; int d; int wd; int* colminw; int minw; int w; uchar al; Tablerow* row; int h; int a; int n; int spanht; int i; int ht; Lay* caplay; if(dbgtab) trace("sizetable %d, availwidth=%d, nrow=%d, ncol=%d, changed=%x, tab.availw=%d\n", tab->tableid, availwidth, tab->nrow, tab->ncol, tab->flags&Lchanged, tab->availw); if(tab->ncol == 0 || tab->nrow == 0) return ; if(tab->availw == availwidth && !(tab->flags&Lchanged)) return ; tableparams(tab, &hsp, &vsp, &pad, &bd, &cbd, &hsep, &vsep); totw = widthfromspec(tab->width, availwidth); // reduce totw by spacing, padding, and rule widths // to leave amount left for contents totw -= (tab->ncol - 1)*hsep + 2*(hsp + bd + pad + cbd); if(totw <= 0) totw = 1; if(dbgtab) trace("\nsizetable %d, totw=%d, hsp=%d, vsp=%d, pad=%d, bd=%d, cbd=%d, hsep=%d, vsep=%d\n", tab->tableid, totw, hsp, vsp, pad, bd, cbd, hsep, vsep); for(c = tab->cells; c != nil; c = c->next) { clay = c->lay; if(clay == nil || !(clay->flags&Lchanged)) { c->minw = -1; tw = TABLEMAXTARGET; if(dimenkind(c->wspec) != Dnone) tw = widthfromspec(c->wspec, totw); // When finding max widths, want to lay out using ALleft alignment, // because we don't yet know final width for proper justification. // If the max widths are accepted, we'll redo those needing other justification. if(clay == nil) { if(dbglay) trace("Initial layout for cell %d.%d\n", tab->tableid, c->cellid); clay = c->lay = sublayout(f, tw, ALleft, c->background, c->content); c->content = nil; } else { if(dbglay) trace("Relayout (for max) for cell %d.%d\n", tab->tableid, c->cellid); relayout(f, clay, tw, ALleft); } clay->flags |= Lchanged; // for min test, below c->maxw = clay->width; if(dbgtab) trace("sizetable %d for cell %d max layout done, targw=%d, c.maxw=%d\n", tab->tableid, c->cellid, tw, c->maxw); if(dimenkind(c->wspec) == Dpixels) { // Other browsers don't make the following adjustment for // percentage and relative widths if(c->maxw <= tw) c->maxw = tw; if(dbgtab) trace("after spec adjustment, c.maxw=%d\n", c->maxw); } } } // calc max column widths colmaxw = (int*)emallocz(tab->ncol * sizeof(int)); maxw = widthcalc(tab, colmaxw, hsep, 1); if(dbgtab) trace("sizetable %d maxw=%d, totw=%d\n", tab->tableid, maxw, totw); if(maxw <= totw) { // trial layouts are fine, // but if table width was specified, add more space d = 0; if(totw > maxw && dimenkind(tab->width) != Dnone) d = (totw - maxw)/tab->ncol; for(ci = 0; ci < tab->ncol; ci++) { tab->cols[ci].width = colmaxw[ci] + d; if(dbgtab) trace("sizetable %d tab.cols[%d].width = %d\n", tab->tableid, ci, colmaxw[ci]); } } else { // calc min column widths and apportion out // differences if(dbgtab) trace("sizetable %d, availwidth %d, need min widths too\n", tab->tableid, availwidth); for(c = tab->cells; c != nil; c = c->next) { clay = c->lay; assert(clay != nil); if(c->minw == -1 || !(clay->flags&Lchanged)) { if(dbglay) trace("Relayout (for min) for cell %d.%d\n", tab->tableid, c->cellid); relayout(f, clay, 1, ALleft); c->minw = clay->width; if(dbgtab) trace("sizetable %d for cell %d min layout done, c.min=%d\n", tab->tableid, c->cellid, clay->width); } } colminw = (int*)emallocz(tab->ncol * sizeof(int)); minw = widthcalc(tab, colminw, hsep, 0); w = totw - minw; d = maxw - minw; if(dbgtab) trace("sizetable %d minw=%d, w=%d, d=%d\n", tab->tableid, minw, w, d); for(ci = 0; ci < tab->ncol; ci++) { if(w < 0 || d < 0) wd = colminw[ci]; else wd = colminw[ci] + (colmaxw[ci] - colminw[ci])*w/d; if(dbgtab) trace("sizetable %d col[%d].width = %d\n", tab->tableid, ci, wd); tab->cols[ci].width = wd; } if(dbgtab) trace("sizetable %d, availwidth %d, doing final layouts\n", tab->tableid, availwidth); } // now have col widths; set actual cell dimensions // and relayout (note: relayout will do no work if the target width // and just haven't changed from last layout) for(c = tab->cells; c != nil; c = c->next) { clay = c->lay; wd = cellwidth(tab, c, hsep); if(dbgtab) trace("sizetable %d for cell %d, clay.width=%d, cellwidth=%d\n", tab->tableid, c->cellid, clay->width, wd); if(dbglay) trace("Relayout (final) for cell %d.%d\n", tab->tableid, c->cellid); relayout(f, clay, wd, c->align.halign); if(dbgtab) trace("sizetable %d for cell %d, final width %d, got width %d, height %d\n", tab->tableid, c->cellid, wd, clay->width, clay->height); } // set row heights and ascents // first pass: ignore cells with rowspan > 1 for(ri = 0; ri < tab->nrow; ri++) { row = &tab->rows[ri]; h = 0; a = 0; for(c = row->cells; c != nil; c = c->nextinrow) { clay = c->lay; if(c->rowspan > 1 || clay == nil) continue; al = c->align.valign; if(al == ALnone) al = tab->rows[c->row].align.valign; if(al == ALbaseline) { n = c->ascent; if(n > a) { h += (n - a); a = n; } n = clay->height - c->ascent; if(n > h - a) h = a + n; } else { n = clay->height; if(n > h) h = n; } } row->height = h; row->ascent = a; } // second pass: take care of rowspan > 1 // (this algorithm isn't quite right -- it might add more space // than is needed in the presence of multiple overlapping rowspans) for(c = tab->cells; c != nil; c = c->next) { if(c->rowspan > 1) { spanht = 0; for(i = 0; i < c->rowspan && c->row + i < tab->nrow; i++) spanht += tab->rows[c->row + i].height; clay = c->lay; if(clay == nil) continue; ht = clay->height - (c->rowspan - 1)*vsep; if(ht > spanht) { // add extra space to last spanned row i = c->row + c->rowspan - 1; if(i >= tab->nrow) i = tab->nrow - 1; tab->rows[i].height += ht - spanht; if(dbgtab) trace("sizetable %d, row %d height %d\n", tab->tableid, i, tab->rows[i].height); } } } // get total width, heights, and col x / row y positions totw = bd + hsp + cbd + pad; for(ci = 0; ci < tab->ncol; ci++) { tab->cols[ci].pos.x = totw; if(dbgtab) trace("sizetable %d, col %d at x=%d\n", tab->tableid, ci, totw); totw += tab->cols[ci].width + hsep; } totw = totw - (cbd + pad) + bd; toth = bd + vsp + cbd + pad; // first time: move tab.caption items into layout if(tab->caption != nil) { // lay caption with ALleft; drawing will center it over the table width tab->caption_lay = sublayout(f, availwidth, ALleft, f->layout->background, tab->caption); caplay = tab->caption_lay; tab->caph = caplay->height + CAPSEP; tab->caption = nil; } else if(tab->caption_lay != nil) { caplay = tab->caption_lay; if(tab->availw != availwidth || (caplay->flags&Lchanged) != (uchar)0) { relayout(f, caplay, availwidth, ALleft); tab->caph = caplay->height + CAPSEP; } } if(tab->caption_place == ALtop) toth += tab->caph; for(ri = 0; ri < tab->nrow; ri++) { tab->rows[ri].pos.y = toth; if(dbgtab) trace("sizetable %d, row %d at y=%d\n", tab->tableid, ri, toth); toth += tab->rows[ri].height + vsep; } toth = toth - (cbd + pad) + bd; if(tab->caption_place == ALbottom) toth += tab->caph; tab->totw = totw; tab->toth = toth; tab->availw = availwidth; tab->flags &= ~Lchanged; if(dbgtab) trace("\ndone sizetable %d, availwidth %d, totw=%d, toth=%d\n\n", tab->tableid, availwidth, totw, toth); } // Calculate various table spacing parameters static void tableparams(Table* tab, int* hsp, int* vsp, int* pad, int* bd, int* cbd, int* hsep, int* vsep) { *bd = tab->border; *hsp = tab->cellspacing; *vsp = *hsp; *pad = tab->cellpadding; *cbd = (tab->border)? 1 : 0; *hsep = 2*(*cbd + *pad) + *hsp; *vsep = 2*(*cbd + *pad) + *vsp; } // return cell width, taking multicol spanning into account static int cellwidth(Table* tab, Tablecell* c, int hsep) { int wd; int i; if(c->colspan == 1) return tab->cols[c->col].width; wd = (c->colspan - 1)*hsep; for(i = 0; i < c->colspan && c->col + i < tab->ncol; i++) wd += tab->cols[c->col + i].width; return wd; } // return cell height, taking multirow spanning into account static int cellheight(Table* tab, Tablecell* c, int vsep) { int ht; int i; if(c->rowspan == 1) return tab->rows[c->row].height; ht = (c->rowspan - 1)*vsep; for(i = 0; i < c->rowspan && c->row + i < tab->nrow; i++) ht += tab->rows[c->row + i].height; return ht; } // Calculate the column widths w as the max of the cells // maxw or minw (as domax is 1 or 0). // Return the total of all w. // (hseps were accounted for by the adjustment that got // totw from availwidth). // hsep is amount of free space available between columns // where there is multicolumn spanning. // This is a two-pass algorithm. The first pass ignores // cells that span multiple columns. The second pass // sees if those multispanners need still more space, and // if so, apportions the space out. static int widthcalc(Table* tab, int* w, int hsep, int domax) { int anyspan; int totw; int pass; int curw; int iend; int i; int diff; Tablecell* c; int cwd; int ri; int ci; anyspan = 0; totw = 0; for(pass = 1; pass <= 2; pass++) { if(pass == 2 && !anyspan) break; totw = 0; for(ci = 0; ci < tab->ncol; ci++) { for(ri = 0; ri < tab->nrow; ri++) { c = tab->grid[ri][ci]; if(c == nil) continue; cwd = domax? c->maxw : c->minw; if(pass == 1) { if(c->colspan > 1) { anyspan = 1; continue; } if(cwd > w[ci]) w[ci] = cwd; } else { if(c->colspan == 1 || !(ci == c->col && ri == c->row)) continue; curw = 0; iend = ci + c->colspan; if(iend > tab->ncol) iend = tab->ncol; for(i = ci; i < iend; i++) curw += w[i]; // padding between spanned cols is free cwd -= hsep*(c->colspan - 1); diff = cwd - curw; if(diff <= 0) continue; for(i = ci; i < iend; i++) { if(curw == 0) w[i] = diff/c->colspan; else w[i] += diff*w[i]/curw; } } } totw += w[ci]; } } return totw; } static void layframeset(Frame* f, Kidinfo* ki) { int fwid; int fht; int nrow; int* rowh; int ncol; int* colw; Kidinfo* l; int y; int i; Framelist* al; Rectangle r; Kidinfo* kidki; Frame* kidf; int x; int j; fwid = Dx(f->cr); fht = Dy(f->cr); if(dbglay) trace("layframeset, configuring frame %d wide by %d high\n", fwid, fht); nrow = frdimens(ki->rows, ki->nrows, fht, &rowh); ncol = frdimens(ki->cols, ki->ncols, fwid, &colw); l = ki->kidinfos; y = f->cr.min.y; for(i = 0; i < nrow; i++) { x = f->cr.min.x; for(j = 0; j < ncol; j++) { if(l == nil) return; r = Rect(x, y, x + colw[j], y + rowh[i]); if(dbglay) trace("kid gets rect (%d,%d)(%d,%d)\n", r.min.x, r.min.y, r.max.x, r.max.y); kidki = l; l = l->next; kidf = newkid(f, kidki, r); if(!kidki->isframeset) f->kids = newframelist(kidf, f->kids); if(kidf->framebd != 0) { kidf->cr = insetrect(kidf->r, 2); drawborder(kidf->cim, kidf->cr, 2, DarkGrey); } if(kidki->isframeset) { layframeset(kidf, kidki); for(al = kidf->kids; al != nil; al = al->next) f->kids = newframelist(al->val, f->kids); } x += colw[j]; } y += rowh[i]; } } // Use the dimension specs in dims to allocate total space t. // Return number of dimens, and put an array of allocated space in *parr. static int frdimens(Dimen* dims, int n, int t, int** parr) { int totpix; int totpcnt; int totrel; int i; double spix; double spcnt; int min_relu; double relu; int tt; int* x; int v; int kind; int trest; int totpixrel; double vr; if(n == 1) { *parr = x = (int*)emalloc(sizeof(int)); x[0] = t; return 1; } totpix = 0; totpcnt = 0; totrel = 0; for(i = 0; i < n; i++) { v = dimenspec(dims[i]); kind = dimenkind(dims[i]); if(v < 0) { v = 0; dims[i] = makedimen(kind, v); } switch(kind) { case Dpixels: totpix += v; break; case Dpercent: totpcnt += v; break; case Drelative: totrel += v; break; case Dnone: totrel++; break; } } spix = 1.0; spcnt = 1.0; min_relu = 0; if(totrel > 0) min_relu = 30; // allow for scrollbar (14) and a bit relu = (double)min_relu; tt = totpix + (t*totpcnt/100) + totrel*min_relu; // want // t == totpix*spix + (totpcnt/100)*spcnt*t + totrel*relu if(tt < t) { if(totrel == 0) { if(totpcnt != 0) // spix==1.0, relu==0, solve for spcnt spcnt = (double)((t - totpix)*100)/(double)(t*totpcnt); else // relu==0, totpcnt==0, solve for spix spix = (double)t/(double)totpix; } else // spix=1.0, spcnt=1.0, solve for relu relu += (double)(t - tt)/(double)totrel; } else { // need to contract one or more of spix, spcnt, and have relu==min_relu totpixrel = totpix + totrel*min_relu; if(totpixrel < t) { // spix==1.0, solve for spcnt spcnt = (double)((t - totpixrel)*100)/(double)(t*totpcnt); } else { // let spix==spcnt, solve trest = t - totrel*min_relu; if(trest > 0) { spcnt = (double)trest/(double)(totpix + (t*totpcnt/100)); } else { spcnt = (double)t/(double)tt; relu = 0.0; } spix = spcnt; } } x = (int*)emalloc(n * sizeof(int)); tt = 0; for(i = 0; i < n - 1; i++) { vr = (double)dimenspec(dims[i]); switch(dimenkind(dims[i])) { case Dpixels: vr = vr*spix; break; case Dpercent: vr = vr*(double)t*spcnt/100.0; break; case Drelative: vr = vr*relu; break; case Dnone: vr = relu; break; } x[i] = (int)(vr+.5); tt += x[i]; } x[n - 1] = t - tt; *parr = x; return n; } // Return last item of list of items, or nil if no items static Item* lastitem(Item* it) { Item* ans; ans = it; for(; it != nil; it = it->next) ans = it; return ans; } // Lay out table if availw changed or tab changed static void checktabsize(Frame* f, Itable* t, int availw) { Table* tab; tab = t->table; if(availw != tab->availw || (tab->flags&Lchanged)) { sizetable(f, tab, availw); t->width = tab->totw + 2*tab->border; t->height = tab->toth + 2*tab->border; t->ascent = t->height; } } static int widthfromspec(Dimen wspec, int availw) { int w; int spec; w = availw; spec = dimenspec(wspec); switch(dimenkind(wspec)) { case Dpixels: w = spec; break; case Dpercent: w = spec*w/100; break; } return w; } // An image may have arrived for an image input field static void checkffsize(Frame* f, Item* i, Formfield* ff) { int w; int h; Control* c; Cbutton* b; Iimage* imi; if(ff->ftype == Fimage) { assert(ff->image->tag == Iimagetag); imi = (Iimage*)ff->image; if(imi->ci->mims != nil && ff->ctlid >= 0) { c = f->controls[ff->ctlid]; if(c->tag == Cbuttontag) { b = (Cbutton*)c; if(b->pic == nil) { b->pic = imi->ci->mims[0]->im; b->picmask = imi->ci->mims[0]->mask; w = Dx(b->pic->r); h = Dy(b->pic->r); b->r.max.x = b->r.min.x + w; b->r.max.y = b->r.min.y + h; i->width = w; i->height = h; i->ascent = h; } } } } } static void drawall(Frame* f) { Point origin; pushclipr(f->cr); fillbg(f, f->cr); origin = lptosp(f, Pt(0, 0)); if(dbglay > 1) trace("drawall, cr=(%d,%d,%d,%d), viewr=(%d,%d,%d,%d), origin=(%d,%d)\n", f->cr.min.x, f->cr.min.y, f->cr.max.x, f->cr.max.y, f->viewr.min.x, f->viewr.min.y, f->viewr.max.x, f->viewr.max.y, origin.x, origin.y); if(f->layout != nil) drawlay(f, f->layout, origin); popclipr(); flushc(f); } static void drawlay(Frame* f, Lay* lay, Point origin) { Line* l; for(l = lay->start->next; l != lay->end; l = l->next) drawline(f, origin, l, lay); } // Draw line l in frame f, assuming that content's (0,0) // aligns with layorigin in f->cim. static void drawline(Frame* f, Point layorigin, Line* l, Lay* lay) { Image* im; Point o; int x; int y; Item* it; Font* fnt; int yy; Image* fgi; Control* ctl; Point dims; Point p; Formfield* ff; int xx; Itext* i; Irule* ir; Iimage* ii; Ifloat* ifl; im = f->cim; o = addpt(layorigin, l->pos); x = o.x; y = o.y; for(it = l->items; it != nil; it = it->next) { switch(it->tag) { case Itexttag: i = (Itext*)it; fnt = fonts[i->fnt].f; yy = y + l->ascent - fnt->ascent + (i->voff) - Voffbias; fgi = colorimage(i->fg); runestring(im, Pt(x, yy), fgi, ZP, fnt, i->s); if(i->ul != ULnone) { if(i->ul == ULmid) yy += 2*i->ascent/3; else yy += i->height - 1; draw(im, Rect(x, yy, x + i->width, yy + 1), fgi, nil, ZP); } break; case Iruletag: ir = (Irule*)it; yy = y + RULESP; draw(im, Rect(x, yy, x + ir->width, yy + ir->size), display->black, nil, ZP); break; case Iimagetag: ii = (Iimage*)it; yy = y; if(ii->align == ALbottom) yy += l->ascent - ii->imheight; else if(ii->align == ALmiddle) yy += l->ascent - (ii->imheight/2); drawimg(f, Pt(x, yy), ii); break; case Iformfieldtag: ff = ((Iformfield*)it)->formfield; if(ff->ctlid >= 0) { ctl = f->controls[ff->ctlid]; dims = subpt(ctl->r.max, ctl->r.min); yy = y + l->ascent - it->ascent; p = Pt(x, yy); ctl->r = Rpt(p, addpt(p, dims)); drawctl(ctl, 0); } break; case Itabletag: drawtable(f, lay, Pt(x, y), ((Itable*)it)->table); break; case Ifloattag: ifl = (Ifloat*)it; xx = layorigin.x + lay->margin; if(ifl->side == ALright) { xx -= ifl->x; // for main layout of frame, floats hug // right edge of frame, not layout // (other browsers do that) if(f->layout == lay) xx += lay->targetwidth; else xx += lay->width; } else xx += ifl->x; switch(ifl->item->tag) { case Iimagetag: ii = (Iimage*)ifl->item; drawimg(f, Pt(xx, layorigin.y + ifl->y + (ii->border + ii->vspace)), ii); break; case Itabletag: drawtable(f, lay, Pt(xx, layorigin.y + ifl->y), ((Itable*)ifl->item)->table); break; } break; } x += it->width; } } static void drawimg(Frame* f, Point iorigin, Iimage* i) { CImage* ci; Image* im; Canimimage* ac; Control* c; Point dims; MaskedImage* mim; int bdcol; Rectangle r; Font* fnt; int yy; int xx; int col; Image* fgi; ci = i->ci; im = f->cim; iorigin.x += i->hspace + i->border; // y coord is already adjusted for border and vspace if(ci->mims != nil) { r = Rpt(iorigin, addpt(iorigin, Pt(i->imwidth, i->imheight))); if(i->ctlid >= 0) { // animated c = f->controls[i->ctlid]; dims = subpt(c->r.max, c->r.min); c->r = Rpt(iorigin, addpt(iorigin, dims)); if(c->tag == Canimimagetag) { ac = (Canimimage*)c; ac->redraw = 1; ac->bg = f->layout->background; } drawctl(c, 0); } else { mim = ci->mims[0]; iorigin = addpt(iorigin, mim->origin); draw(im, r, mim->im, mim->mask, ZP); } if(i->border) { if(i->anchorid != 0) bdcol = f->doc->link; else bdcol = Black; drawborder(im, r, i->border, bdcol); } } else if(config.imagelvl == ImgNone && i->altrep != nil) { fnt = fonts[DefFnt].f; yy = iorigin.y + (i->imheight - fnt->height)/2; xx = iorigin.x + (i->width - runestringwidth(fnt, i->altrep))/2; if(i->anchorid != 0) col = f->doc->link; else col = DarkGrey; fgi = colorimage(col); runestring(im, Pt(xx, yy), fgi, ZP, fnt, i->altrep); } } static void drawtable(Frame* f, Lay* parentlay, Point torigin, Table* tab) { Image* im; int hsp; int vsp; int pad; int bd; int cbd; int hsep; int vsep; int x; int y; int capy; int boxy; Image* bgi; int n; Tablecell* c; Lay* clay; int cx; int cy; int wd; int ht; Lay* caplay; int capx; if(tab->ncol == 0 || tab->nrow == 0) return ; im = f->cim; tableparams(tab, &hsp, &vsp, &pad, &bd, &cbd, &hsep, &vsep); x = torigin.x; y = torigin.y; capy = y; boxy = y; if(tab->caption_place == ALbottom) capy = y + tab->toth - tab->caph + vsp; else boxy = y + tab->caph; if(tab->background.image != parentlay->background.image || tab->background.color != parentlay->background.color) { bgi = colorimage(tab->background.color); draw(im, Rect(x, boxy, x + tab->totw, boxy + tab->toth - tab->caph), bgi, nil, ZP); } if(bd != 0) drawborder(im, Rect(x+bd, boxy+bd, x+tab->totw-bd, boxy+tab->toth-tab->caph-bd), 1, Black); for(c = tab->cells; c != nil; c = c->next) { clay = c->lay; if(clay == nil) continue; cx = x + tab->cols[c->col].pos.x; cy = y + tab->rows[c->row].pos.y; wd = cellwidth(tab, c, hsep); ht = cellheight(tab, c, vsep); if(c->background.color != tab->background.color) { bgi = colorimage(c->background.color); draw(im, Rect(cx-pad, cy-pad, cx+wd+pad, cy+ht+pad), bgi, nil, ZP); } if(bd != 0) drawborder(im, Rect(cx-pad+1, cy-pad+1, cx+wd+pad-1, cy+ht+pad-1), 1, Black); if(c->align.valign != ALtop && c->align.valign != ALbaseline) { n = ht - clay->height; if(c->align.valign == ALmiddle) cy += n/2; else if(c->align.valign == ALbottom) cy += n; } if(dbgtab) trace("drawtable %d cell %d at (%d,%d)\n", tab->tableid, c->cellid, cx, cy); drawlay(f, clay, Pt(cx, cy)); } if(tab->caption_lay != nil) { caplay = tab->caption_lay; capx = x; if(caplay->width < tab->totw) capx += (tab->totw - caplay->width)/2; drawlay(f, caplay, Pt(capx, capy)); } } // Draw border of width n just outside r, using src color void drawborder(Image* im, Rectangle r, int n, int color) { int x; int y; int xr; int ybi; Image* src; x = r.min.x - n; y = r.min.y - n; xr = r.max.x + n; ybi = r.max.y; src = colorimage(color); draw(im, Rect(x, y, xr, y + n), src, nil, ZP); // top draw(im, Rect(x, ybi, xr, ybi + n), src, nil, ZP); // bottom draw(im, Rect(x, y + n, x + n, ybi), src, nil, ZP); // left draw(im, Rect(xr - n, y + n, xr, ybi), src, nil, ZP); // right } // Draw relief border just outside r, width 2 border, // colors white/lightgrey/darkgrey/black // to give raised relief (if raised != 0) or sunken. void drawrelief(Image* im, Rectangle r, int raised) { int x; int x1; int x2; int xr; int xr1; int xr2; int y; int y1; int y2; int yb; int yb1; int yb2; Image* tlo; Image* tli; Image* bro; Image* bri; // ((x,y),(xr,yb)) == r x = r.min.x; x1 = x - 1; x2 = x - 2; xr = r.max.x; xr1 = xr + 1; xr2 = xr + 2; y = r.min.y; y1 = y - 1; y2 = y - 2; yb = r.max.y; yb1 = yb + 1; yb2 = yb + 2; // colors for top/left outside, top/left inside, bottom/right outside, bottom/right inside if(raised) { tlo = colorimage(Grey); tli = colorimage(White); bro = colorimage(Black); bri = colorimage(DarkGrey); } else { tlo = colorimage(DarkGrey); tli = colorimage(Black); bro = colorimage(White); bri = colorimage(Grey); } draw(im, Rect(x2, y2, xr1, y1), tlo, nil, ZP); // top outside draw(im, Rect(x1, y1, xr, y), tli, nil, ZP); // top inside draw(im, Rect(x2, y1, x1, yb1), tlo, nil, ZP); // left outside draw(im, Rect(x1, y, x, yb), tli, nil, ZP); // left inside draw(im, Rect(xr, y1, xr1, yb), bri, nil, ZP); // right inside draw(im, Rect(xr1, y, xr2, yb1), bro, nil, ZP); // right outside draw(im, Rect(x1, yb, xr1, yb1), bri, nil, ZP); // bottom inside draw(im, Rect(x, yb1, xr2, yb2), bro, nil, ZP); // bottom outside } // Fill r with color void drawfill(Image* im, Rectangle r, int color) { draw(im, r, colorimage(color), nil, ZP); } // Draw string in default font at p void drawstring(Image* im, Point p, Rune* s) { runestring(im, p, colorimage(Black), ZP, fonts[DefFnt].f, s); } // Return (width, height) of string in default font Point measurestring(Rune* s) { Font* f; f = fonts[DefFnt].f; return Pt(runestringwidth(f, s), f->height); } // Mark as "changed" everything with change flags on the loc path static void markchanges(Loc* loc) { int i; Item* it; Tablecell* c; Lay* clay; for(i = 0; i < loc->n; i++) { switch(loc->le[i].kind) { case LEframe: loc->le[i].frame->layout->flags |= Lchanged; break; case LEline: loc->le[i].line->flags |= Lchanged; break; case LEitem: it = loc->le[i].item; switch(it->tag) { case Itabletag: ((Itable*)it)->table->flags |= Lchanged; break; case Ifloattag: // whole layout will be redone if layout changes // and there are any floats break; } break; case LEtablecell: c = loc->le[i].tcell; clay = c->lay; if(clay != nil) clay->flags |= Lchanged; break; } } } static Image* colorimage(int rgb) { Image* im; int hv; Colornode* xhd; Colornode* x; if(rgb == Black) return display->black; else if(rgb == White) return display->white; else { hv = rgb%NCOLHASH; xhd = colorhashtab[hv]; x = xhd; while(x != nil && x->rgb != rgb) x = x->next; if(x == nil) { im = allocimage(display, Rect(0,0,1,1), screen->chan, 1, (rgb<<8)|0xFF); if(im == nil) { trace("can't allocate color! %x, chan %d\n", rgb, screen->chan); im = display->black; } x = newcolornode(rgb, im, xhd); colorhashtab[hv] = x; } return x->im; } } static Colornode* newcolornode(int rgb, Image* im, Colornode* next) { Colornode* c; c = (Colornode*)emalloc(sizeof(Colornode)); c->rgb = rgb; c->im = im; c->next = next; return c; } // Use f->background.image (if not nil) or f->background.color to fill r (in cim coord system) // with background color. static void fillbg(Frame* f, Rectangle r) { Image* bgi; CImage* bgci; bgci = f->doc->background.image; bgi = nil; if(bgci != nil && bgci->mims != nil) bgi = bgci->mims[0]->im; if(bgi == nil) bgi = colorimage(f->doc->background.color); draw(f->cim, r, bgi, nil, f->viewr.min); } // Draw triangle pointing in direction given by kind (TRlup, etc.) // Assume r is a square static void drawtriangle(Image* im, Rectangle r, int kind, int style) { int b; int b2; int bm2; Point p[3]; Image* col012; Image* col20; Image* d; Image* l; int i; Image* t; drawfill(im, r, Grey); b = r.max.x - r.min.x; if(b < 4) return ; b2 = b/2; bm2 = b - ReliefBd; d = colorimage(DarkGrey); l = colorimage(White); col012 = d; col20 = l; switch(kind) { case TRIup: p[0] = Pt(b2, ReliefBd); p[1] = Pt(bm2, bm2); p[2] = Pt(ReliefBd, bm2); break; case TRIdown: p[0] = Pt(b2, bm2); p[1] = Pt(ReliefBd, ReliefBd); p[2] = Pt(bm2, ReliefBd); col012 = l; col20 = d; break; case TRIleft: p[0] = Pt(bm2, ReliefBd); p[1] = Pt(bm2, bm2); p[2] = Pt(ReliefBd, b2); break; case TRIright: p[0] = Pt(ReliefBd, bm2); p[1] = Pt(ReliefBd, ReliefBd); p[2] = Pt(bm2, b2); col012 = l; col20 = d; break; } if(style == ReliefSunk) { t = col012; col012 = col20; col20 = t; } for(i = 0; i < 3; i++) p[i] = addpt(p[i], r.min); fillpoly(im, p, 3, ~0, colorimage(Grey), ZP); line(im, p[0], p[1], 0, 0, ReliefBd/2, col012, ZP); line(im, p[1], p[2], 0, 0, ReliefBd/2, col012, ZP); line(im, p[2], p[0], 0, 0, ReliefBd/2, col20, ZP); } static void flushc(Frame* f) { USED(f); flushimage(display, 1); } Frame* newframe() { Frame* f; f = (Frame*)emallocz(sizeof(Frame)); f->parent = nil; f->cim = nil; f->r = Rect(0, 0, 0, 0); resetframe(f); return f; } Framelist* newframelist(Frame* f, Framelist* l) { Framelist* fl; fl = (Framelist*)emalloc(sizeof(Framelist)); fl->val = f; fl->next = l; return fl; } Frame* newkid(Frame* parent, Kidinfo* ki, Rectangle r) { Frame* f; f = (Frame*)emallocz(sizeof(Frame)); f->parent = parent; f->cim = parent->cim; f->r = r; resetframe(f); f->src = ki->src; f->name = ki->name; f->marginw = ki->marginw; f->marginh = ki->marginh; f->framebd = ki->framebd; f->flags = ki->flags; return f; } // Note: f->parent, f->cim and f->r should not be reset // And if f->parent is true, don't reset params set in frameset. void resetframe(Frame* f) { f->id = ++frameid; f->doc = nil; if(f->parent == nil) { f->src = nil; f->name = nil; f->marginw = FRMARGIN; f->marginh = FRMARGIN; f->framebd = 0; f->flags = FRvscroll|FRhscrollauto; } f->layout = nil; f->controls = nil; f->controlslen = 0; f->controlid = 0; f->cr = f->r; f->viewr = Rect(0, 0, 0, 0); f->totalr = f->viewr; f->vscr = nil; f->hscr = nil; f->kids = nil; } int addcontrol(Frame* f, Control* c) { int ans; if(f->controlslen <= f->controlid) { f->controlslen += 30; f->controls = (Control**)erealloc(f->controls, f->controlslen * sizeof(Control*)); } f->controls[f->controlid] = c; ans = f->controlid++; return ans; } void xscroll(Frame* f, int kind, int val) { int newx; newx = f->viewr.min.x; switch(kind) { case CAscrollpage: newx += val*(Dx(f->cr)*8/10); break; case CAscrollline: newx += val*Dx(f->cr)/10; break; case CAscrolldelta: newx += val*f->totalr.max.x/Dx(f->cr); break; case CAscrollabs: newx = val; break; } newx = max(0, min(newx, f->totalr.max.x)); if(newx != f->viewr.min.x) { f->viewr.min.x = newx; fixframegeom(f); drawall(f); } } // Don't actually scroll by "page" and "line", // But rather, 80% and 10%, which give more // context in the first case, and more motion // in the second. void yscroll(Frame* f, int kind, int val) { int newy; newy = f->viewr.min.y; switch(kind) { case CAscrollpage: newy += val*(Dy(f->cr)*8/10); break; case CAscrollline: newy += val*Dy(f->cr)/10; break; case CAscrolldelta: newy += val*f->totalr.max.y/Dy(f->cr); break; case CAscrollabs: newy = val; break; } newy = max(0, min(newy, f->totalr.max.y)); if(newy != f->viewr.min.y) { f->viewr.min.y = newy; fixframegeom(f); drawall(f); } } // Convert layout coords (where (0,0) is top left of layout) // to screen coords (i.e., coord system of mouse, f->cr, etc.) Point sptolp(Frame* f, Point sp) { return addpt(f->viewr.min, subpt(sp, f->cr.min)); } // Reverse translation of sptolp Point lptosp(Frame* f, Point lp) { return addpt(lp, subpt(f->cr.min, f->viewr.min)); } // Return Loc of Item or Scrollbar containing p (p in screen coords) // or item it, if that is not nil. Loc* findloc(Frame* f, Point p, Item* it) { return framefind(newloc(), f, p, it); } // Find it (if non-nil) or place where p is (known to be inside f's layout). static Loc* framefind(Loc* loc, Frame* f, Point p, Item* it) { Frame* kf; Loc* try; Framelist* fl; Lay* lay; addloc(loc, LEframe, f->r.min); loc->le[loc->n - 1].frame = f; if(it == nil) { if(f->vscr != nil && ptinrect(p, f->vscr->r)) { addloc(loc, LEcontrol, f->vscr->r.min); loc->le[loc->n - 1].control = f->vscr; loc->pos = subpt(p, f->vscr->r.min); return loc; } if(f->hscr != nil && ptinrect(p, f->hscr->r)) { addloc(loc, LEcontrol, f->hscr->r.min); loc->le[loc->n - 1].control = f->hscr; loc->pos = subpt(p, f->hscr->r.min); return loc; } } if(it != nil || ptinrect(p, f->cr)) { lay = f->layout; if(f->kids != nil) { for(fl = f->kids; fl != nil; fl = fl->next) { kf = fl->val; try = framefind(loc, kf, p, it); if(try != nil) return try; } } else if(lay != nil) return layfind(loc, f, lay, lptosp(f, ZP), p, it); } return nil; } // Find it (if non-nil) or place where p is (known to be inside f's layout). // p (in screen coords), lay offset by origin also in screen coords static Loc* layfind(Loc* loc, Frame* f, Lay* lay, Point origin, Point p, Item* it) { Ifloat* flist; Line* l; Loc* lloc; Ifloat* fl; int fymin; int fymax; int inside; int xx; Point fp; int match; Point o; for(flist = lay->floats; flist != nil; flist = flist->nextfloat) { fl = flist; fymin = fl->y + origin.y; fymax = fymin + fl->item->height; inside = 0; xx = 0; if(it != nil || (fymin <= p.y && p.y < fymax)) { xx = origin.x + lay->margin; if(fl->side == ALright) { if(lay == f->layout) xx += Dx(f->cr) - fl->x; else xx += lay->width - fl->x; } else xx += fl->x; if(p.x >= xx && p.x < xx + fl->item->width) inside = 1; } fp = Pt(xx, fymin); match = 0; if(it != nil) { switch(fl->item->tag) { case Itabletag: addloc(loc, LEitem, fp); loc->le[loc->n - 1].item = (Item*)fl; loc->pos = subpt(p, fp); lloc = tablefind(loc, f, (Itable*)(fl->item), fp, p, it); if(lloc != nil) return lloc; break; case Iimagetag: match = (it == (Item*)fl || it == fl->item); break; } } if(match || inside) { addloc(loc, LEitem, fp); loc->le[loc->n - 1].item = (Item*)fl; loc->pos = subpt(p, fp); if(it == fl->item) { addloc(loc, LEitem, fp); loc->le[loc->n - 1].item = fl->item; } if(inside) { if(fl->item->tag == Itabletag) loc = tablefind(loc, f, (Itable*)(fl->item), fp, p, it); } return loc; } } for(l = lay->start; l != nil; l = l->next) { o = addpt(origin, l->pos); if(it != nil || (o.y <= p.y && p.y < o.y + l->height)) { lloc = linefind(loc, f, l, o, p, it); if(lloc != nil) return lloc; if(it == nil && o.y + l->height >= p.y) break; } } return nil; } // p (in screen coords), line at o, also in screen coords static Loc* linefind(Loc* loc, Frame* f, Line* l, Point o, Point p, Item* it) { int x; int y; Item* i; Font* fnt; Loc* lloc; Itext* ti; Iimage* ii; Iformfield* fi; int yy; int h; addloc(loc, LEline, o); loc->le[loc->n - 1].line = l; x = o.x; y = o.y; for(i = l->items; i != nil; i = i->next) { if(it != nil || (x <= p.x && p.x < x + i->width)) { yy = y; h = 0; switch(i->tag) { case Itexttag: ti = (Itext*)i; fnt = fonts[ti->fnt].f; yy += l->ascent - fnt->ascent + (ti->voff) - Voffbias; h = fnt->height; break; case Iruletag: h = ((Irule*)i)->size; break; case Iimagetag: ii = (Iimage*)i; yy = y; if(ii->align == ALbottom) yy += l->ascent - ii->imheight; else if(ii->align == ALmiddle) yy += l->ascent - (ii->imheight/2); h = ii->imheight; break; case Iformfieldtag: fi = (Iformfield*)i; h = fi->height; yy += l->ascent - fi->ascent; if(it != nil) { if(it == fi->formfield->image) { addloc(loc, LEitem, Pt(x, yy)); loc->le[loc->n - 1].item = i; addloc(loc, LEitem, Pt(x, yy)); loc->le[loc->n - 1].item = it; loc->pos = ZP; return loc; } } else if(yy < p.y && p.y < yy + h && fi->formfield->ctlid >= 0) { addloc(loc, LEcontrol, Pt(x, yy)); loc->le[loc->n - 1].control = f->controls[fi->formfield->ctlid]; loc->pos = subpt(p, Pt(x, yy)); return loc; } break; case Itabletag: lloc = tablefind(loc, f, (Itable*)i, Pt(x, y), p, it); if(lloc != nil) return lloc; break; } if(it == i || (it == nil && yy <= p.y && p.y < yy + h)) { addloc(loc, LEitem, Pt(x, yy)); loc->le[loc->n - 1].item = i; loc->pos = subpt(p, Pt(x, yy)); return loc; } if(it == nil) return nil; } x += i->width; if(it == nil && x >= p.x) break; } loc->n--; return nil; } static Loc* tablefind(Loc* loc, Frame* f, Itable* ti, Point torigin, Point p, Item* it) { Table* t; int hsp; int vsp; int pad; int bd; int cbd; int hsep; int vsep; Lay* caplay; int capy; Loc* lloc; int n; Tablecell* c; Lay* clay; int cx; int cy; int wd; int ht; addloc(loc, LEitem, torigin); loc->le[loc->n - 1].item = (Item*)ti; t = ti->table; tableparams(t, &hsp, &vsp, &pad, &bd, &cbd, &hsep, &vsep); caplay = t->caption_lay; if(caplay != nil) { capy = torigin.y; if(t->caption_place == ALbottom) capy += t->toth - t->caph + vsp; lloc = layfind(loc, f, caplay, Pt(torigin.x, capy), p, it); if(lloc != nil) return lloc; } for(c = t->cells; c != nil; c = c->next) { clay = c->lay; if(clay == nil) continue; cx = torigin.x + t->cols[c->col].pos.x; cy = torigin.y + t->rows[c->row].pos.y; wd = cellwidth(t, c, hsep); ht = cellheight(t, c, vsep); if(it == nil && !ptinrect(p, Rect(cx, cy, cx + wd, cy + ht))) continue; if(c->align.valign != ALtop && c->align.valign != ALbaseline) { n = ht - clay->height; if(c->align.valign == ALmiddle) cy += n/2; else if(c->align.valign == ALbottom) cy += n; } addloc(loc, LEtablecell, Pt(cx, cy)); loc->le[loc->n - 1].tcell = c; lloc = layfind(loc, f, clay, Pt(cx, cy), p, it); if(lloc != nil) return lloc; loc->n--; if(it == nil) return nil; } loc->n--; return nil; } Control* newff(Frame* f, Formfield* ff) { Control* ans; int nh; int nv; Image* pic; Image* picmask; Rune* lab; Iimage* i; Option* o; Option* ao; int k; int nvis; int n; int linewrap; ans = nil; switch(ff->ftype) { case Ftext: case Fpassword: case Ftextarea: nh = ff->size; nv = 1; linewrap = 0; if(ff->ftype == Ftextarea) { nh = ff->cols; nv = ff->rows; linewrap = 1; } ans = newentry(f, nh, nv, linewrap); entryset(ans, ff->value); break; case Fcheckbox: case Fradio: ans = newcheckbox(f, ff->ftype == Fradio); if(ff->flags&FFchecked) ans->flags |= CFactive; break; case Fsubmit: case Fimage: case Freset: case Fbutton: if(ff->image == nil) ans = newbutton(f, nil, nil, ff->value, nil, 0, 1); else { if(ff->image->tag == Iimagetag) { i = (Iimage*)(ff->image); pic = nil; picmask = nil; if(i->ci->mims != nil) { pic = i->ci->mims[0]->im; picmask = i->ci->mims[0]->mask; } lab = nil; if((config).imagelvl == ImgNone) { lab = i->altrep; i = nil; } ans = newbutton(f, pic, picmask, lab, i, 0, 0); } } break; case Fselect: n = listlen((List*)ff->options); if(n > 0) { ao = (Option*)emalloc(n * sizeof(Option)); o = ff->options; for(k = 0; k < n; k++) { ao[k].selected = o->selected; ao[k].value = o->value; ao[k].display = o->display; o = o->next; } nvis = ff->size; if(nvis == 0) nvis = min(n, 4); ans = newselect(f, nvis, ao, n); } break; case Ffile: if(dbglay) trace("warning: unimplemented file form field\n"); break; } if(ans != nil) ans->ff = ff; return ans; } Control* newscroll(Frame* f, int isvert, int length, int breadth) { Point maxpt; int flags; Cscrollbar *sb; // need room for at least two squares and 2 borders of size 2 if(length < 12) { breadth = 0; length = 0; } else if(breadth*2 + 4 > length) breadth = (length - 4)/2; ; flags = CFenabled; if(isvert) { maxpt = Pt(breadth, length); flags |= CFscrvert; } else maxpt = Pt(length, breadth); sb = (Cscrollbar*)emallocz(sizeof(Cscrollbar)); sb->tag = Cscrollbartag; sb->f = f; sb->r = Rpt(ZP, maxpt); sb->flags = flags; return (Control*)sb; } Control* newentry(Frame* f, int nh, int nv, int linewrap) { int w; int h; Centry* e; w = ctlcharspace*nh + 2*ENTHMARGIN; h = ctllinespace*nv + 2*ENTVMARGIN; e = (Centry*)emallocz(sizeof(Centry)); e->tag = Centrytag; e->f = f; e->r = Rect(0, 0, w, h); e->flags = CFenabled; e->linewrap = linewrap; return (Control*)e; } Control* newbutton(Frame* f, Image* pic, Image* picmask, Rune* lab, Iimage* it, int candisable, int dorelief) { Image* dpic; Image* dpicmono; Image* dpicmask; int w; int h; Rectangle r; Cbutton* b; if(pic != nil) { w = Dx(pic->r); h = Dy(pic->r); } else if(it != nil) { w = it->imwidth; h = it->imheight; } else { w = runestringwidth(fonts[CtlFnt].f, lab); h = ctllinespace; } if(dorelief) { // form image buttons are shown without margins in other browsers w += 2*BUTMARGIN; h += 2*BUTMARGIN; } r = Rect(0, 0, w, h); dpic = nil; dpicmask = nil; if(candisable && pic != nil) { // make "greyed out" image: // - convert pic to monochrome (black where pic is non-white) // - draw pic in White, then DarkGrey shifted (-1,-1) and use // union of those two areas as mask dpicmask = allocimage(display, pic->r, GREY1, 0, DOpaque); dpicmono = allocimage(display, pic->r, GREY1, 0, DWhite); draw(dpicmono, dpicmono->r, pic, display->opaque, ZP); // leave DOpaque in dpicmask only where mono pic is black (==transparent) draw(dpicmask, dpicmask->r, display->transparent, dpicmono, ZP); dpic = allocimage(display, pic->r, pic->chan, 0, DWhite); // draw(dpic, dpic->r, colorimage(White), dpicmask, ZP); draw(dpic, rectaddpt(dpic->r, Pt(-1, -1)), colorimage(DarkGrey), dpicmask, ZP); draw(dpicmask, rectaddpt(dpicmask->r, Pt(-1, -1)), display->opaque, dpicmask, ZP); freeimage(dpicmono); } b = (Cbutton*)emallocz(sizeof(Cbutton)); b->tag = Cbuttontag; b->f = f; b->r = r; b->flags = CFenabled; b->pic = pic; b->picmask = picmask; b->dpic = dpic; b->dpicmask = dpicmask; b->label = lab; b->dorelief = dorelief; return (Control*)b; } Control* newcheckbox(Frame* f, int isradio) { Ccheckbox* cb; cb = (Ccheckbox*)emallocz(sizeof(Ccheckbox)); cb->tag = Ccheckboxtag; cb->f = f; cb->r = Rect(0, 0, CBOXWID, CBOXHT); cb->flags = CFenabled; cb->isradio = isradio; return (Control*)cb; } Control* newselect(Frame* f, int nvis, Option* options, int noptions) { Font* fnt; int w; int i; int h; Control* scr; Cselect* ans; Cscrollbar* pscr; fnt = fonts[CtlFnt].f; w = 0; for(i = 0; i < noptions; i++) w = max(w, runestringwidth(fnt, options[i].display)); w += 2*SELMARGIN; h = ctllinespace*nvis + 2*SELMARGIN; scr = nil; if(nvis < noptions) { scr = newscroll(f, 1, h, SCRFBREADTH); scr->r = rectaddpt(scr->r, Pt(w, 0)); w += SCRFBREADTH; } pscr = (Cscrollbar*)scr; ans = (Cselect*)emallocz(sizeof(Cselect)); ans->tag = Cselecttag; ans->f = f; ans->r = Rect(0, 0, w, h); ans->flags = CFenabled; ans->scr = pscr; ans->nvis = nvis; ans->options = options; ans->noptions = noptions; if(scr != nil) { pscr->ctl = (Control*)ans; scrollset(scr, 0, nvis, noptions, 1); } return (Control*)ans; } Control* newanimimage(Frame* f, CImage* cim, Background bg) { Canimimage *a; a = (Canimimage*)emallocz(sizeof(Canimimage)); a->tag = Canimimagetag; a->f = f; a->r = Rect(0, 0, cim->width, cim->height); a->cim = cim; a->bg = bg; return (Control*)a; } Control* newprogbox(Frame* f) { Cprogbox *b; b = (Cprogbox*)emallocz(sizeof(Cprogbox)); b->tag = Cprogboxtag; b->f = f; b->r = Rect(0, 0, PBOXWID, PBOXHT); b->flags = CFenabled; b->state = Punused; b->bsid = -1; return (Control*)b; } Control* newlabel(Frame* f, Rune* s) { int w; int h; Clabel* l; w = runestringwidth(fonts[CtlFnt].f, s); h = ctllinespace + 2*ENTVMARGIN; // give it same height as an entry box l = (Clabel*)emallocz(sizeof(Clabel)); l->tag = Clabeltag; l->f = f; l->r = Rect(0, 0, w, h); l->s = s; return (Control*)l; } void disable(Control* c) { if(c->flags&CFenabled) { c->flags &= ~CFenabled; if(c->f->cim != nil) drawctl(c, 1); } } void enable(Control* c) { if(!(c->flags&CFenabled)) { c->flags |= CFenabled; if(c->f->cim != nil) drawctl(c, 1); } } void losefocus(Control* c) { if(c->flags&CFhasfocus) { c->flags &= ~CFhasfocus; if(c->f->cim != nil) drawctl(c, 1); } } void gainfocus(Control* c) { if(!(c->flags&CFhasfocus)) { c->flags |= CFhasfocus; if(c->f->cim != nil) drawctl(c, 1); } } void scrollset(Control* c, int v1, int v2, int vmax, int mindelta) { int length; int breadth; int l; Cscrollbar* sc; assert(c->tag == Cscrollbartag); sc = (Cscrollbar*)c; sc->mindelta = mindelta; if(vmax <= 0) { sc->top = 0; sc->bot = 0; } else { if(v1 < 0) v1 = 0; if(v2 > vmax) v2 = vmax; if(v1 > v2) v1 = v2; if(sc->flags&CFscrvert) { length = sc->r.max.y - sc->r.min.y; breadth = sc->r.max.x - sc->r.min.x; } else { length = sc->r.max.x - sc->r.min.x; breadth = sc->r.max.y - sc->r.min.y; } l = length - 2*breadth; assert(l >= 0); sc->top = l*v1/vmax; sc->bot = l*(vmax - v2)/vmax; } drawctl(c, 1); } int dokey(Control* ctl, int keychar) { int ans; int k; int newcurs; int slen; Centry* c; if(!(ctl->flags&CFenabled)) return CAnone; ans = CAnone; switch(ctl->tag) { case Centrytag: c = (Centry*)ctl; newcurs = -1; slen = Strlen(c->s); switch(keychar) { case 'a' & CTRLMASK : newcurs = 0; break; case 'e' & CTRLMASK: newcurs = slen; break; case 'f' & CTRLMASK: if(c->curs < slen) newcurs = c->curs + 1; break; case 'b' & CTRLMASK: if(c->curs > 0) newcurs = c->curs - 1; break; case 'u' & CTRLMASK: entrydelrange(c, 0, slen); break; case 'v' & CTRLMASK: entrysetfromsnarf(c); break; case 'h' & CTRLMASK: if(c->curs > 0) entrydelrange(c, c->curs - 1, c->curs); break; case DEL: if(c->curs < slen) entrydelrange(c, c->curs, c->curs + 1); break; case TAB: ans = CAtabkey; break; default: if(keychar == CR) { if(c->linewrap) keychar = '\n'; else ans = CAreturnkey; } if(keychar > CTRLMASK || (keychar == '\n' && c->linewrap)) { c->s = (Rune*)erealloc(c->s, (slen+2)*sizeof(Rune)); c->s[slen+1] = 0; for(k = slen; k > c->curs; k--) c->s[k] = c->s[k - 1]; c->s[c->curs] = keychar; newcurs = c->curs + 1; } break; } if(newcurs >= 0) { c->curs = newcurs; entryscroll(c); } break; } return ans; } int domouse(Control* ctl, Point p, int mtype) { int ans; int changed; int cx; int x; Font* fnt; Rune* s; Rune s1[2]; int i; int iend; Control* d; Formfield* ff; Form* frm; Formfield* lf; int n; int newfirst; int val; int v; int vmin; int vmax; int b; int vsltop; int vslbot; int actflags; int oldactflags; int down; int* linestarts; int topline; int cursline; int nlines; int lineno; Cbutton* cb; Centry* ce; Ccheckbox* ccb; Cselect* cs; Cscrollbar* csb; if(!(ctl->flags&CFenabled)) return CAnone; ans = CAnone; changed = 0; switch(ctl->tag) { case Cbuttontag: cb = (Cbutton*)ctl; if(mtype == Mlbuttondown) { cb->flags |= CFactive; changed = 1; } else if(mtype == Mmove && cb->ff == nil) { ans = CAflyover; } else if(mtype == Mlbuttonup) { cb->flags &= ~CFactive; changed = 1; ans = CAbuttonpush; } break; case Centrytag: ce = (Centry*)ctl; if(mtype == Mlbuttondown) { x = ce->r.min.x + ENTHMARGIN; fnt = fonts[CtlFnt].f; s = ce->s; s1[1] = 0; i = ce->left; iend = Strlen(s); if(ce->linewrap) { nlines = entrywrapcalc(ce, &linestarts, &topline, &cursline); if(nlines > 1) { lineno = topline + (p.y-(ce->r.min.y+ENTVMARGIN))/ctllinespace; if(lineno >= nlines) lineno = nlines-1; i = linestarts[lineno]; iend = linestarts[lineno+1]; } } for(; i < iend; i++) { s1[0] = s[i]; cx = runestringwidth(fnt, s1); if(p.x < x + cx) break; x += cx; } ce->curs = i; changed = 1; if(!(ce->flags&CFhasfocus)) ans = CAkeyfocus; } break; case Ccheckboxtag: ccb = (Ccheckbox*)ctl; if(mtype == Mlbuttonup) { if(ccb->isradio) { if(!(ccb->flags&CFactive)) { ccb->flags |= CFactive; changed = 1; frm = ccb->ff->form; for(lf = frm->fields; lf != nil; lf = lf->next) { ff = lf; if(ff == ccb->ff) continue; if(ff->ftype == Fradio && !Strcmp(ff->name, ccb->ff->name) && ff->ctlid >= 0) { d = ccb->f->controls[ff->ctlid]; if(d->flags&CFactive) { d->flags &= ~CFactive; drawctl(d, 0); break; } } } } } else { ccb->flags ^= CFactive; changed = 1; } } break; case Cselecttag: cs = (Cselect*)ctl; if(cs->scr != nil && p.x >= cs->r.max.x - SCRFBREADTH) { return domouse((Control*)cs->scr, p, mtype); } else if(mtype == Mlbuttonup) { n = (p.y - (cs->r.min.y + SELMARGIN))/ctllinespace + cs->first; if(n >= 0 && n < cs->noptions) { cs->options[n].selected ^= 1; if(cs->ff != nil && !(cs->ff->flags&FFmultiple) && cs->options[n].selected) { for(i = 0; i < cs->noptions; i++) { if(i != n) cs->options[i].selected = 0; } } changed = 1; } } break; case Cscrollbartag: csb = (Cscrollbar*)ctl; val = 0; if(csb->flags&CFscrvert) { v = p.y; vmin = csb->r.min.y; vmax = csb->r.max.y; b = Dx(csb->r); } else { v = p.x; vmin = csb->r.min.x; vmax = csb->r.max.x; b = Dy(csb->r); } vsltop = vmin + b + csb->top; vslbot = vmax - b - csb->bot; actflags = 0; oldactflags = csb->flags&CFscrallact; down = (mtype == Mlbuttondown); if(v >= vsltop && v < vslbot) { if(mtype == Mldrag && (csb->flags&CFactive)) { actflags = CFactive; val = v - csb->dragv; if(abs(val) > csb->mindelta) { ans = CAscrolldelta; csb->dragv = v; } } else if(down || mtype == Mldrag) { actflags = CFactive; csb->dragv = v; } } else if(down || mtype == Mlbuttonup) { if(v < vmin + b) { if(down) actflags = CFscracta1; else { ans = CAscrollline; val = -1; } } else if(v < vsltop) { if(down) actflags = CFscracttr1; else { ans = CAscrollpage; val = -1; } } else if(v >= vmax - b) { if(down) actflags = CFscracta2; else { ans = CAscrollline; val = 1; } } else if(v >= vslbot) { if(down) actflags = CFscracttr2; else { ans = CAscrollpage; val = 1; } } } csb->flags = (csb->flags&~CFscrallact)|actflags; if(ans != CAnone) { if(csb->ctl != nil) { switch(csb->ctl->tag) { case Cselecttag: cs = (Cselect*)csb->ctl; newfirst = cs->first; switch(ans) { case CAscrollpage: newfirst += val*cs->nvis; break; case CAscrollline: newfirst += val; break; case CAscrolldelta: if(val > 0) newfirst++; else newfirst--; break; } newfirst = max(0, min(newfirst, cs->noptions - cs->nvis)); cs->first = newfirst; scrollset((Control*)csb, newfirst, newfirst + cs->nvis, cs->noptions, 1); drawctl((Control*)cs, 1); return ans; } } else { if(csb->flags&CFscrvert) yscroll(csb->f, ans, val); else xscroll(csb->f, ans, val); } changed = 1; } else if(actflags != oldactflags) { changed = 1; } break; case Cprogboxtag: if(mtype == Mlbuttondown) ans = CAbuttonpush; break; } if(changed) drawctl(ctl, 1); return ans; } void resetctl(Control* ctl) { Option* o; int i; Centry* ce; Ccheckbox* ccb; Cselect* cs; switch(ctl->tag) { case Cbuttontag: ctl->flags &= ~CFactive; break; case Centrytag: ce = (Centry*)ctl; ce->s = nil; ce->curs = 0; ce->left = 0; if(ce->ff != nil && ce->ff->value != nil) ce->s = ce->ff->value; break; case Ccheckboxtag: ccb = (Ccheckbox*)ctl; ccb->flags &= ~CFactive; if(ccb->ff != nil && !(ccb->ff->flags&FFchecked)) ccb->flags |= CFactive; break; case Cselecttag: cs = (Cselect*)ctl; if(cs->ff != nil) { o = cs->ff->options; for(i = 0; i < cs->noptions; i++) { cs->options[i].selected = o->selected; o = o->next; } } cs->first = 0; if(cs->scr != nil) { scrollset((Control*)cs->scr, 0, cs->nvis, cs->noptions, 1); } break; case Canimimagetag: ((Canimimage*)ctl)->cur = 0; break; } drawctl(ctl, 0); } void drawctl(Control* ctl, int flush) { Image* win; Iimage* imi; Image* m; Image* pic; Point p; int w; int h; int x; int y; int relief; Rectangle cursr; Rune* s; Font* fnt; int a; int a1; Point cen; Point p1; Point p2; Point p3; Point p4; Rectangle ir; Rectangle r; Image* black; Image* white; Image* navy; int i; int xr; int yt1; int ys; int yb; int ya2; int yt2; int xt1; int xs; int xa2; int xt2; Rectangle ra1; Rectangle rt1; Rectangle rs; Rectangle rt2; Rectangle ra2; int b; int l; int a1kind; int a2kind; int a1relief; int a2relief; int rsrelief; int iprev; MaskedImage* mim; Image* bgi; int bcol; int rcol; int xw; int cursx; int cursy; Rune** lines; int* linestarts; int nlines; int istart; int iend; int n; Point q; Cbutton* cb; Centry* ce; Ccheckbox* ccb; Cselect* cs; Cscrollbar* csb; Canimimage* ca; Cprogbox* cp; Clabel* cl; if(!rectclip(&ctl->r, ctl->f->r)) return; win = ctl->f->cim; pushclipr(ctl->r); switch(ctl->tag) { case Cbuttontag: cb = (Cbutton*)ctl; if(cb->ff != nil && cb->ff->image != nil && cb->pic == nil) { // check to see if image arrived // (dimensions will have been set by checkffsize, if needed; // this code is only for when the HTML specified the dimensions) imi = (Iimage*)cb->ff->image; assert(imi->tag == Iimagetag); if(imi->ci->mims != nil) { cb->pic = imi->ci->mims[0]->im; cb->picmask = imi->ci->mims[0]->mask; } } if(cb->dorelief || cb->pic == nil) draw(win, cb->r, colorimage(Grey), nil, ZP); if(cb->pic != nil) { if(cb->flags&CFenabled) { pic = cb->pic; m = cb->picmask; } else { pic = cb->dpic; m = cb->dpicmask; } w = Dx(pic->r); h = Dy(pic->r); x = cb->r.min.x + (Dx(cb->r) - w)/2; y = cb->r.min.y + (Dy(cb->r) - h)/2; if((cb->flags&CFactive) && cb->dorelief) { x++; y++; } draw(win, Rect(x, y, x + w, y + h), pic, m, ZP); } else if(cb->label != nil) { p = addpt(cb->r.min, Pt(BUTMARGIN, BUTMARGIN)); if(cb->flags&CFactive) p = addpt(p, Pt(1, 1)); runestring(win, p, colorimage(Black), ZP, fonts[CtlFnt].f, cb->label); } if(cb->dorelief) { relief = ReliefRaised; if(cb->flags&CFactive) relief = ReliefSunk; drawrelief(win, insetrect(cb->r, 2), relief); } break; case Centrytag: ce = (Centry*)ctl; draw(win, ce->r, colorimage(White), nil, ZP); drawrelief(win, insetrect(ce->r, 2), ReliefSunk); p = addpt(ce->r.min, Pt(ENTHMARGIN, ENTVMARGIN)); s = ce->s + ce->left; fnt = fonts[CtlFnt].f; cursx = -1; cursy = p.y; if(ce->linewrap) { nlines = wrapstring(fnt, s, Dx(ce->r)-2*ENTHMARGIN, &lines, &linestarts); q = p; if(nlines > 0) cursx = 0; for(n = 0; n < nlines; n++) { s = lines[n]; runestring(win, q, colorimage(Black), ZP, fnt, s); istart = linestarts[n]; iend = linestarts[n+1]; if(ce->curs >= istart && (ce->curscurs==iend && n == nlines-1))) { if(ce->curs > istart) cursx = runestringnwidth(fnt, s, ce->curs-ce->left); else cursx = 0; cursy = q.y; } q.y += ctllinespace; } } else { runestring(win, p, colorimage(Black), ZP, fnt, s); if(ce->curs >= ce->left) cursx = runestringnwidth(fnt, s, ce->curs - ce->left); } if((ce->flags&CFhasfocus) && cursx >= 0) { cursr = Rect(p.x + cursx - 2, cursy - 1, p.x + cursx, cursy + ctllinespace + 1); draw(win, cursr, colorimage(Black), nil, ZP); } break; case Ccheckboxtag: ccb = (Ccheckbox*)ctl; draw(win, ccb->r, colorimage(White), nil, ZP); if(ccb->isradio) { a = CBOXHT/2; a1 = a - 1; cen = Pt(ccb->r.min.x + a, ccb->r.min.y + a); ellipse(win, cen, a1, a1, 1, colorimage(DarkGrey), ZP); arc(win, cen, a, a, 0, colorimage(Black), ZP, 45, 180); arc(win, cen, a, a, 0, colorimage(Grey), ZP, 225, 180); if(ccb->flags&CFactive) fillellipse(win, cen, 2, 2, colorimage(Black), ZP); } else { ir = insetrect(ccb->r, 2); ir.min.x += CBOXWID - CBOXHT; ir.max.x -= CBOXWID - CBOXHT; drawrelief(win, ir, ReliefSunk); if(ccb->flags&CFactive) { p1 = Pt(ir.min.x, ir.min.y); p2 = Pt(ir.max.x, ir.max.y); p3 = Pt(ir.max.x, ir.min.y); p4 = Pt(ir.min.x, ir.max.y); line(win, p1, p2, Endsquare, Endsquare, 0, colorimage(Black), ZP); line(win, p3, p4, Endsquare, Endsquare, 0, colorimage(Black), ZP); } } break; case Cselecttag: cs = (Cselect*)ctl; black = colorimage(Black); white = colorimage(White); navy = colorimage(Navy); draw(win, cs->r, white, nil, ZP); drawrelief(win, insetrect(cs->r, 2), ReliefSunk); ir = insetrect(cs->r, SELMARGIN); p = ir.min; fnt = fonts[CtlFnt].f; for(i = cs->first; i < cs->noptions && i < cs->first + cs->nvis; i++) { if(cs->options[i].selected) { r = Rect(p.x - SELMARGIN, p.y, cs->r.max.x - SCRFBREADTH, p.y + ctllinespace); draw(win, r, navy, nil, ZP); runestring(win, p, white, ZP, fnt, cs->options[i].display); } else { runestring(win, p, black, ZP, fnt, cs->options[i].display); } p.y += ctllinespace; } if(cs->scr != nil) { cs->scr->r = rectsubpt(cs->scr->r, cs->scr->r.min); cs->scr->r = rectaddpt(cs->scr->r, Pt(cs->r.max.x - SCRFBREADTH, cs->r.min.y)); drawctl((Control*)cs->scr, 0); } break; case Cscrollbartag: csb = (Cscrollbar*)ctl; x = csb->r.min.x; y = csb->r.min.y; if(csb->flags&CFscrvert) { l = csb->r.max.y - csb->r.min.y; b = csb->r.max.x - csb->r.min.x; xr = x + b; yt1 = y + b; ys = yt1 + csb->top; yb = y + l; ya2 = yb - b; yt2 = ya2 - csb->bot; ra1 = Rect(x, y, xr, yt1); rt1 = Rect(x, yt1, xr, ys); rs = Rect(x, ys, xr, yt2); rt2 = Rect(x, yt2, xr, ya2); ra2 = Rect(x, ya2, xr, yb); a1kind = TRIup; a2kind = TRIdown; } else { l = csb->r.max.x - csb->r.min.x; b = csb->r.max.y - csb->r.min.y; yb = y + b; xt1 = x + b; xs = xt1 + csb->top; xr = x + l; xa2 = xr - b; xt2 = xa2 - csb->bot; ra1 = Rect(x, y, xt1, yb); rt1 = Rect(xt1, y, xs, yb); rs = Rect(xs, y, xt2, yb); rt2 = Rect(xt2, y, xa2, yb); ra2 = Rect(xa2, y, xr, yb); a1kind = TRIleft; a2kind = TRIright; } a1relief = ReliefRaised; if(csb->flags&CFscracta1) a1relief = ReliefSunk; a2relief = ReliefRaised; if(csb->flags&CFscracta2) a2relief = ReliefSunk; drawtriangle(win, ra1, a1kind, a1relief); drawtriangle(win, ra2, a2kind, a2relief); drawfill(win, rt1, Grey); rs = insetrect(rs, 2); drawfill(win, rs, Grey); rsrelief = ReliefRaised; if(csb->flags&CFactive) rsrelief = ReliefSunk; drawrelief(win, rs, rsrelief); drawfill(win, rt2, Grey); break; case Canimimagetag: ca = (Canimimage*)ctl; i = ca->cur; if(ca->redraw) i = 0; else if(i > 0) { iprev = i - 1; if(ca->cim->mims[iprev]->bgcolor != -1) { i = iprev; // get i back to before all "reset to previous" // images (which will be skipped in following // image drawing loop) while(i > 0 && ca->cim->mims[i]->bgcolor == -2) i--; } } bgi = colorimage(ca->bg.color); if(ca->bg.image != nil && ca->bg.image->mimslen > 0) bgi = ca->bg.image->mims[0]->im; for(; i <= ca->cur; i++) { mim = ca->cim->mims[i]; if(i > 0 && i < ca->cur && mim->bgcolor == -2) continue; p = addpt(ca->r.min, mim->origin); r = mim->im->r; r = Rpt(p, addpt(p, Pt(Dx(r), Dy(r)))); // IE takes "clear-to-background" disposal method to mean // clear to background of HTML page, ignoring any background // color specified in the GIF. // IE clears to background before frame 0 if(i == 0) draw(win, ca->r, bgi, nil, ZP); if(i != ca->cur && mim->bgcolor >= 0) draw(win, r, bgi, nil, ZP); else draw(win, r, mim->im, mim->mask, ZP); } break; case Cprogboxtag: cp = (Cprogbox*)ctl; if(cp->state != Punused) { bcol = Black; rcol = Grey; xw = (cp->pcnt*PBOXWID + 50)/100; if(xw > PBOXWID) xw = PBOXWID; switch(cp->state) { case Pstart: drawfill(win, cp->r, Grey); bcol = White; break; case Pconnected: bcol = Black; break; case Phavehdr: bcol = DarkGrey; break; case Phavedata: bcol = rcol = DarkGrey; break; case Pdone: xw = PBOXWID; bcol = rcol = DarkestGrey; break; case Perr: bcol = rcol = Red; xw = PBOXWID; break; case Paborted: bcol = rcol = Navy; xw = PBOXWID; break; } drawborder(win, insetrect(cp->r, 3), 3, bcol); if(rcol != Grey) { r = cp->r; r.max.x -= (PBOXWID - xw); drawfill(win, r, rcol); } } break; case Clabeltag: cl = (Clabel*)ctl; p = addpt(cl->r.min, Pt(0, ENTVMARGIN)); runestring(win, p, colorimage(Black), ZP, fonts[CtlFnt].f, cl->s); break; } popclipr(); if(flush) flushimage(display, 1); } // Break s up into substrings that fit in width availw // when printing with font fnt. // The returned linestarts array will contain the indexes into the original // string where the corresponding line starts (which might not be simply // the sum of the preceding lines because of cr/lf's in the original string // which are omitted from the lines array. // Return value is length of plines array. // Empty lines (ending in cr) get put into the array as empty strings. // The start indices array has an entry for the phantom next line, to avoid // the need for special cases in the rest of the code. static int wrapstring(Font* fnt, Rune* s, int availw, Rune*** plines, int** plinestarts) { Strlist* sl; List* sstartl; int sw; int n; int k; int slen; int done; int needbs; int origlen; int kincr; Rune* s1; Rune* s2; int w1; int w2; int s1len; int s2len; Rune** lines; int* linestarts; sl = nil; sstartl = nil; sw = runestringwidth(fnt, s); n = 0; k = 0; slen = Strlen(s); origlen = slen; done = 0; while(!done) { kincr = 0; needbs = 0; if(slen == 0) { s1 = nil; done = 1; } else { // if any newlines in s1, it's a forced break // (and newlines aren't to appear in result) splitl(s, slen, L"\n", &s1, &s1len, &s2, &s2len); if(s2len == 0) { if(sw <= availw) { s1 = s; done = 1; } else needbs = 1; } else { if(runestringnwidth(fnt,s1,s1len) <= availw) { s1 = Strndup(s1, s1len); s = Strndup(s2+1, s2len-1); slen = s2len-1; sw = runestringwidth(fnt, s); kincr = s1len+1; } else needbs = 1; } if(needbs) { if(breakstring(s, sw, fnt, availw, 0, &s1, &w1, &s2, &w2) < 0) { // no break possible s1 = s; done = 1; } else { s = s2; slen = Strlen(s); sw = w2; kincr = Strlen(s1); } } } sl = newstrlist(s1, sl); sstartl = newlist(k, sstartl); k += kincr; n++; } // Use arrays for return values, // reverse back to original order lines = (Rune**)emalloc(n * sizeof(Rune*)); linestarts = (int*)emalloc((n+1) * sizeof(int)); linestarts[n] = origlen; k = n; while(sl != nil) { lines[--k] = sl->val; linestarts[k] = sstartl->val; sl = sl->next; sstartl = sstartl->next; } *plines = lines; *plinestarts = linestarts; return n; } void entryset(Control* c, Rune* s) { Centry* e; assert(c->tag == Centrytag); e = (Centry*)c; e->s = s; e->curs = 0; e->left = 0; drawctl((Control*)e, 1); } // delete given range of characters, and redraw static void entrydelrange(Centry* e, int istart, int iend) { int n; int ns; int ne; Rune* news; Rune* p; n = iend - istart; if(n > 0) { // replace e->s with e->s[0:istart] + e->s[iend:] ns = Strlen(e->s); if(istart == 0 && iend == ns) e->s = nil; else { ne = ns- iend; news = newstr(istart+ne); p = Stradd(news, e->s, istart); p = Stradd(p, e->s+iend, ne); *p = 0; e->s = news; } if(e->curs > istart) { if(e->curs < iend) e->curs = istart; else e->curs = istart + e->curs - iend; } if(e->left > istart) e->left = max(istart - 1, 0); entryscroll(e); } } // Set entry from snarf buffer. static void entrysetfromsnarf(Centry* e) { int f; int n; Rune* s; uchar buf[BIGBUFSIZE]; f = open("/dev/snarf", OREAD); if(f >= 0) { n = read(f, buf, BIGBUFSIZE); if(n > 0) { // trim a trailing newline, as a service... if(buf[n-1] == '\n') n--; s = toStr(buf, n, UTF_8); entryset((Control*)e, s); } close(f); } } // Given e, a Centry with line wrapping, // return number of wrapped lines, and fill in // *pstartlines=line start indices, // *ptopline=line# of top displayed line, // *pcursline# containing cursor static int entrywrapcalc(Centry* e, int** plinestarts, int* ptopline, int* pcursline) { int i, nlines, topline, cursline, i1, i2; Rune** lines; int* linestarts; nlines = wrapstring(fonts[CtlFnt].f, e->s, Dx(e->r)-2*ENTHMARGIN, &lines, &linestarts); topline = 0; cursline = 0; for(i = 0; i < nlines; i++) { i1 = linestarts[i]; i2 = linestarts[i+1]; if(e->left >= i1 && e->left < i2) topline = i; if(e->curs >= i1 && e->curs < i2) cursline = i; } if(e->curs == linestarts[nlines]) cursline = nlines - 1; *plinestarts = linestarts; *ptopline = topline; *pcursline = cursline; return nlines; } // make sure can see cursor and following char or two, and redraw static void entryscroll(Centry* e) { Rune* s; int w; Font* fnt; int wantw; int* linestarts; int cursline, topline, vislines; s = e->s; if(e->linewrap) { // For multiple line entries, c->left is the char // at the beginning of the topmost visible line, // and we just want to scroll to make sure that // the line with the cursor is visible entrywrapcalc(e, &linestarts, &topline, &cursline); if(cursline < topline) topline = cursline; else { vislines = (Dy(e->r)-2*ENTVMARGIN)/ctllinespace; if(cursline >= topline+vislines) topline = cursline-vislines+1; } e->left = linestarts[topline]; } else { if(e->curs < e->left) e->left = e->curs; else if(e->curs > e->left) { fnt = fonts[CtlFnt].f; wantw = Dx(e->r) - 2*ENTHMARGIN - 2*ctlspspace; while(e->left < e->curs - 1) { w = runestringnwidth(fnt, s+e->left, e->curs - e->left); if(w < wantw) break; e->left++; } } } drawctl((Control*)e, 1); } Lay* newlay(int targwidth, int just, int margin, Background bg) { Lay* ans; Item* it; ans = (Lay*)emallocz(sizeof(Lay)); ans->start = newline(); ans->end = newline(); ans->start->pos = Pt(margin, margin); ans->start->next = ans->end; ans->end->prev = ans->start; ans->targetwidth = targwidth - 2*margin; if(ans->targetwidth < 0) ans->targetwidth = 0; ans->margin = margin; ans->background = bg; ans->just = just; it = newispacer(ISPnull); it->state = IFbrk|IFcleft|IFcright; ans->end->items = it; return ans; } Line* newline(void) { return (Line*)emallocz(sizeof(Line)); } Loc* newloc(void) { Loc* ans; ans = (Loc*)emallocz(sizeof(Loc)); ans->lelen = 10; ans->le = (Locelem*)emallocz(ans->lelen * sizeof(Locelem)); return ans; } void addloc(Loc* loc, int kind, Point pos) { if(loc->n == loc->lelen) { loc->lelen += 10; loc->le = (Locelem*)erealloc(loc->le, loc->lelen * sizeof(Locelem)); } loc->le[loc->n].kind = kind; loc->le[loc->n].pos = pos; loc->n++; } // return last frame in loc's path Frame* lastframe(Loc* loc) { int i; for(i = loc->n - 1; i >= 0; i--) if(loc->le[i].kind == LEframe) return loc->le[i].frame; return nil; } void printloc(Loc* loc, char* msg) { int i; trace("%s: Loc with %d components, pos=(%d,%d)\n", msg, loc->n, loc->pos.x, loc->pos.y); for(i = 0; i < loc->n; i++) { switch(loc->le[i].kind) { case LEframe: trace("frame %p\n", (void*)loc->le[i].frame); break; case LEline: trace("line %p\n", (void*)loc->le[i].line); break; case LEitem: trace("item: %p\n%I", (void*)loc->le[i].item, loc->le[i].item); break; case LEtablecell: trace("tablecell: %p, cellid=%d\n", (void*)loc->le[i].tcell, loc->le[i].tcell->cellid); break; case LEcontrol: trace("control %p\n", (void*)loc->le[i].control); break; } } } static Sources* newsources(void) { Sources* ans; ans = emallocz(sizeof(Sources)); return ans; } static Source* newshtml(ByteSource* bs, ItemSource* is) { Shtml* s; s = (Shtml*)emallocz(sizeof(Shtml)); s->tag = Shtmltag; s->bs = bs; s->itsrc = is; return (Source*)s; } static Source* newsimage(ByteSource* bs, CImage* ci, ImageSource* is) { Simage* s; s = (Simage*)emallocz(sizeof(Simage)); s->tag = Simagetag; s->bs = bs; s->ci = ci; s->imsrc = is; return (Source*)s; } static void addsource(Sources* srcs, Source* s) { s->next = srcs->srcs; srcs->srcs = s; srcs->tot++; } static Source* findbs(Sources* srcs, ByteSource* bs) { Source* s; for(s = srcs->srcs; s != nil; s = s->next) { if(s->bs == bs) return s; } return nil; } // spawned to animate images in frame f = args[0] static void animproc(void* arg) { USED(arg); /* TODO Frame* f; Itemlist* aits; int del; int d; vlong tot; Loc* loc; Point p; Control* ctl; MaskedImage** ms; Item* i; Item* it; MaskedImage* m; Control* c; int newdel; Itemlist* al; f = (Frame*)args[0]; f->animpid = pctl(0, nil); aits = nil; del = 10000000; for(i = f->doc->images; i != nil; i = i->nextimage) { ms = i->ci->mims; if(i->ci->mimslen > 1) { loc = findloc(f, ZP, it); if(loc == nil) { if(dbglay) trace("couldn't find item for animated image\n"); continue; } p = loc->le[loc->n - 1].pos; p.x += i->hspace + i->border; ctl = newanimimage(f, i->ci, f->layout->background, 0); ctl->r = rectaddpt(ctl->r, p); i->ctlid = addcontrol(f, ctl); d = ms[0]->delay; if(dbglay) trace("added anim ctl %d for image %S, initial delay %d\n", i->ctlid, i->ci->src->url->url, d); aits = newItempl(it, aits); if(d < del) del = d; } break; } } if(aits == nil) return ; tot = (vlong)0; while(1) { sleep(del); tot = tot + (vlong)del; newdel = 10000000; for(al = aits; al != nil; al = al->tl) { it = al->hd; // TODO: declare Item* tmp_32 tmp_32 = al->hd; switch(tmp_32->tag) { case Iimagetag: i = (Iimage*)tmp_32; ms = i->ci->mims; // TODO: declare Control* tmp_33 tmp_33 = f->controls[i->ctlid]; switch(tmp_33->tag) { case Canimimagetag: c = (Canimimage*)tmp_33; m = ms[c->cur]; d = m->delay; if(d > 0) { d = (int)(tot%((vlong)d)); if(d > 0) d = m->delay - d; } if(d == 0) { c->cur++; if(c->cur == arraylen(ms)) c->cur = 0; d = ms[c->cur]->delay; draw(c, 1); } if(d < newdel) newdel = d; break; } break; } } del = newdel; } */ }