#include "i.h" // Some layout parameters enum { FRKIDMARGIN = 6, // default margin around kid frames IMGHSPACE = 0, // default hspace for images (0 matches IE, Netscape) IMGVSPACE = 0, // default vspace for images FLTIMGHSPACE = 2, // default hspace for float images TABSP = 5, // default cellspacing for tables TABPAD = 1, // default cell padding for tables LISTTAB = 1, // number of tabs to indent lists BQTAB = 1, // number of tabs to indent blockquotes HRSZ = 2, // thickness of horizontal rules SUBOFF = 4, // vertical offset for subscripts SUPOFF = 6, // vertical offset for superscripts NBSP = 160 // non-breaking space character }; // These tables must be sorted static StringInt align_tab[] = { {L"baseline", ALbaseline}, {L"bottom", ALbottom}, {L"center", ALcenter}, {L"char", ALchar}, {L"justify", ALjustify}, {L"left", ALleft}, {L"middle", ALmiddle}, {L"right", ALright}, {L"top", ALtop} }; #define NALIGNTAB (sizeof(align_tab)/sizeof(StringInt)) static StringInt input_tab[] = { {L"button", Fbutton}, {L"checkbox", Fcheckbox}, {L"file", Ffile}, {L"hidden", Fhidden}, {L"image", Fimage}, {L"password", Fpassword}, {L"radio", Fradio}, {L"reset", Freset}, {L"submit", Fsubmit}, {L"text", Ftext} }; #define NINPUTTAB (sizeof(input_tab)/sizeof(StringInt)) static StringInt clear_tab[] = { {L"all", IFcleft|IFcright}, {L"left", IFcleft}, {L"right", IFcright} }; #define NCLEARTAB (sizeof(clear_tab)/sizeof(StringInt)) static StringInt fscroll_tab[] = { {L"auto", FRhscrollauto|FRvscrollauto}, {L"no", FRnoscroll}, {L"yes", FRhscroll|FRvscroll}, }; #define NFSCROLLTAB (sizeof(fscroll_tab)/sizeof(StringInt)) static StringInt shape_tab[] = { {L"circ", SHcircle}, {L"circle", SHcircle}, {L"poly", SHpoly}, {L"polygon", SHpoly}, {L"rect", SHrect}, {L"rectangle", SHrect} }; #define NSHAPETAB (sizeof(shape_tab)/sizeof(StringInt)) static StringInt method_tab[] = { {L"get", HGet}, {L"post", HPost} }; #define NMETHODTAB (sizeof(method_tab)/sizeof(StringInt)) static Rune* roman[15]= { L"I", L"II", L"III", L"IV", L"V", L"VI", L"VII", L"VIII", L"IX", L"X", L"XI", L"XII", L"XIII", L"XIV", L"XV" }; #define NROMAN 15 enum { SPBefore = 2, SPAfter = 4, BL = 1, BLBA = (BL|SPBefore|SPAfter) }; // blockbrk[tag] is break info for a block level element, or one // of a few others that get the same treatment re ending open paragraphs // and requiring a line break / vertical space before them. // If we want a line of space before the given element, SPBefore is OR'd in. // If we want a line of space after the given element, SPAfter is OR'd in. static uchar blockbrk[Numtags]= { [Taddress] BLBA, [Tblockquote] BLBA, [Tcenter] BL, [Tdir] BLBA, [Tdiv] BL, [Tdd] BL, [Tdl] BLBA, [Tdt] BL, [Tform] BLBA, // headings and tables get breaks added manually [Th1] BL, [Th2] BL, [Th3] BL, [Th4] BL, [Th5] BL, [Th6] BL, [Thr] BL, [Tisindex] BLBA, [Tli] BL, [Tmenu] BLBA, [Tol] BLBA, [Tp] BLBA, [Tpre] BLBA, [Tul] BLBA }; enum { AGEN = 1 }; // attrinfo is information about attributes. // The AGEN value means that the attribute is generic (applies to almost all elements) static uchar attrinfo[Numattrs]= { [Aid] AGEN, [Aclass] AGEN, [Astyle] AGEN, [Atitle] AGEN, [Aonblur] AGEN, [Aonchange] AGEN, [Aonclick] AGEN, [Aondblclick] AGEN, [Aonfocus] AGEN, [Aonkeypress] AGEN, [Aonkeyup] AGEN, [Aonload] AGEN, [Aonmousedown] AGEN, [Aonmousemove] AGEN, [Aonmouseout] AGEN, [Aonmouseover] AGEN, [Aonmouseup] AGEN, [Aonreset] AGEN, [Aonselect] AGEN, [Aonsubmit] AGEN, [Aonunload] AGEN }; int dbgbuild = 0; static Token* lexstring(Rune* s, int* panslen); static Rune* getpcdata(Token* toks, int tokslen, int* ptoki); static Pstate* finishcell(Table* curtab, Pstate* psstk); static Pstate* cell_pstate(Pstate* oldps, int ishead); static Pstate* newpstate(Pstate* link); static Pstate* lastps(Pstate* psl); static void additem(Pstate* ps, Item* it, Token* tok); static Item* textit(Pstate* ps, Rune* s); static void addtext(Pstate* ps, Rune* s); static void addbrk(Pstate* ps, int sp, int clr); static void addlinebrk(Pstate* ps, int clr); static void addnbsp(Pstate* ps); static void changehang(Pstate* ps, int delta); static void changeindent(Pstate* ps, int delta); static int push(Stack* stk, int val); static void pop(Stack* stk); static int popretnewtop(Stack* stk, int dflt); static int top(Stack* stk, int dflt); static void copystack(Stack* tostk, Stack* fromstk); static void popfontstyle(Pstate* ps); static void pushfontstyle(Pstate* ps, int sty); static void popfontsize(Pstate* ps); static void pushfontsize(Pstate* ps, int sz); static void setcurfont(Pstate* ps); static void popjust(Pstate* ps); static void pushjust(Pstate* ps, int j); static void setcurjust(Pstate* ps); static void finish_table(Table* t); static void trim_cell(Tablecell* c); static Rune* listmark(uchar ty, int n); static Map* getmap(Docinfo* di, Rune* name); static Rune* aval(Token* tok, int attid); static Rune* astrval(Token* tok, int attid, Rune* dflt); static int aintval(Token* tok, int attid, int dflt); static int auintval(Token* tok, int attid, int dflt); static int toint(Rune* s); static int atabval(Token* tok, int attid, StringInt* tab, int ntab, int dflt); static int acolorval(Token* tok, int attid, int dflt); static int atargval(Token* tok, int dflt); static int listtyval(Token* tok, int dflt); static ParsedUrl* aurlval(Token* tok, int attid, ParsedUrl* dflt, ParsedUrl* base); static Rune* removeallwhite(Rune* s); static int aflagval(Token* tok, int attid); static Align makealign(int halign, int valign); static Align aalign(Token* tok); static Dimen adimen(Token* tok, int attid); static Dimen parsedim(Rune* s, int ns); static void setdimarray(Token* tok, int attid, Dimen** pans, int* panslen); static Rune* stringalign(int a); static int dimprint(char* buf, int nbuf, Dimen d); int Ifmt(Fmt *f); static int validvalign(int a); static int validhalign(int a); static int validalign(Align a); static int validdimen(Dimen d); void buildinit(void) { fmtinstall('I', Ifmt); } // Assume f has been reset, and then had any values from HTTP headers // filled in (e.g., base, chset). ItemSource* newitemsource(ByteSource* bs, Docinfo* di, int mtype) { ItemSource* is; Pstate* ps; ps = newpstate(nil); if(mtype != TextHtml) { ps->curstate &= ~IFwrap; ps->literal = 1; pushfontstyle(ps, FntT); } is = (ItemSource*)emalloc(sizeof(ItemSource)); is->ts = newtokensource(bs, di->chset, mtype); is->mtype = mtype; is->doc = di; is->psstk = ps; is->nforms = 0; is->ntables = 0; is->nanchors = 0; is->nframes = 0; is->curform = nil; is->curmap = nil; is->tabstk = nil; is->kidstk = nil; return is; } // Get a group of tokens for lexer, parse them, and create // a list of layout items. Item* getitems(ItemSource* is) { int i; int j; int nt; int pt; int doscripts; int tokslen; int toki; int h; int sz; int method; int n; int nblank; int norsz; int bramt; int sty; int nosh; int oldcuranchor; int dfltbd; int v; int hang; int newtokslen; int isempty; int ns; int scripttoki; int tag; int brksp; int target; uchar brk; uchar flags; uchar align; uchar al; uchar ty; uchar ty2; Pstate* ps; Pstate* outerps; Table* curtab; Token* tok; Token* toks; Token* newtoks; Token* scripttoks; Docinfo* di; Item* ans; Item* img; Item* ffit; Item* tabitem; Rune* s; Rune* t; Rune* name; Rune* enctype; Rune* usemap; Rune* prompt; Rune* equiv; Rune* val; Rune* nsz; Rune* err; Rune* replace; Rune* script; Map* map; Form* frm; Iimage* ii; Kidinfo* kd; Kidinfo* ks; Kidinfo* pks; Dimen wd; Option* option; Table* tab; Tablecell* c; Tablerow* tr; Formfield* field; Formfield* ff; ParsedUrl* href; ParsedUrl* src; ParsedUrl* scriptsrc; ParsedUrl* bgurl; ParsedUrl* action; Background bg; dbgbuild = config.dbg['h']; doscripts = config.doscripts; ps = is->psstk; curtab = nil; if(is->tabstk != nil) curtab = is->tabstk; toks = nil; tokslen = 0; toki = 0; for(di = is->doc; ; toki++) { if(toki == tokslen) { outerps = lastps(ps); if(outerps->items->next != nil) break; toks = gettoks(is->ts, &tokslen); if(dbgbuild) trace("build: got %d tokens from token source\n", tokslen); if(tokslen == 0) break; toki = 0; } tok = &toks[toki]; if(dbgbuild > 1) trace("build: curstate %ux, token %T\n", ps->curstate, tok); tag = tok->tag; brk = 0; brksp = 0; if(tag < Numtags) { brk = blockbrk[tag]; if(brk&SPBefore) brksp = 1; } else if(tag < Numtags + RBRA) { brk = blockbrk[tag - RBRA]; if(brk&SPAfter) brksp = 1; } if(brk) { addbrk(ps, brksp, 0); if(ps->inpar) { popjust(ps); ps->inpar = 0; } } // check common case first (Data), then case statement on tag if(tag == Data) { // Lexing didn't pay attention to SGML record boundary rules: // \n after start tag or before end tag to be discarded. // (Lex has already discarded all \r's). // Some pages assume this doesn't happen in
text, // so we won't do it if literal is true. // BUG: won't discard \n before a start tag that begins // the next bufferful of tokens. s = tok->text; n = Strlen(s); if(!ps->literal) { i = 0; j = n; if(toki > 0) { pt = toks[toki - 1].tag; // IE and Netscape both ignore this rule (contrary to spec) // if previous tag was img if(pt < Numtags && pt != Timg && j > 0 && s[0] == '\n') i++; } if(toki < tokslen - 1) { nt = toks[toki + 1].tag; if(nt >= RBRA && nt < Numtags + RBRA && j > i && s[j - 1] == '\n') j--; } if(i > 0 || j < n) { s = Strsubstr(s, i, j); n = j-i; } } if(ps->skipwhite) { trimwhite(s, n, &t, &nt); if(t == nil) s = nil; else if(t != s) s = Strndup(t, nt); if(s != nil) ps->skipwhite = 0; } tok->text = nil; // token doesn't own string anymore if(s != nil) addtext(ps, s); } else switch(tag) { // Some abbrevs used in following DTD comments // %text = #PCDATA // | TT | I | B | U | STRIKE | BIG | SMALL | SUB | SUP // | EM | STRONG | DFN | CODE | SAMP | KBD | VAR | CITE // | A | IMG | APPLET | FONT | BASEFONT | BR | SCRIPT | MAP // | INPUT | SELECT | TEXTAREA // %block = P | UL | OL | DIR | MENU | DL | PRE | DL | DIV | CENTER // | BLOCKQUOTE | FORM | ISINDEX | HR | TABLE // %flow = (%text | %block)* // %body.content = (%heading | %text | %block | ADDRESS)* // // Anchors are not supposed to be nested, but you sometimes see // href anchors inside destination anchors. case Ta: if(ps->curanchor != 0) { if(warn) trace("warning: nested or missing \n"); ps->curanchor = 0; } name = aval(tok, Aname); href = aurlval(tok, Ahref, nil, di->base); // ignore rel, rev, and title attrs if(href != nil) { target = atargval(tok, di->target); di->anchors = newanchor(++is->nanchors, name, href, target, di->anchors); ps->curanchor = is->nanchors; ps->curfg = push(&ps->fgstk, di->link); ps->curul = push(&ps->ulstk, ULunder); } if(name != nil) { // add a null item to be destination additem(ps, newispacer(ISPnull), tok); di->dests = newdestanchor(++is->nanchors, name, ps->lastit, di->dests); } break; case Ta+RBRA : if(ps->curanchor != 0) { ps->curfg = popretnewtop(&ps->fgstk, di->text); ps->curul = popretnewtop(&ps->ulstk, ULnone); ps->curanchor = 0; } break; // // We can't do applets, so ignore PARAMS, and let // the %text contents appear for the alternative rep case Tapplet: case Tapplet+RBRA: if(warn && tag == Tapplet) trace("warning: