8 // A stack for holding integer values
10 Nestmax = 40 // max nesting level of lists, font styles, etc.
14 int n; // next available slot (top of stack is stack[n-1])
15 int slots[Nestmax]; // stack entries
21 Pstate* next; // in stack of Pstates
22 int skipping; // true when we shouldn't add items
23 int skipwhite; // true when we should strip leading space
24 int curfont; // font index for current font
25 int curfg; // current foreground color
26 Background curbg; // current background
27 int curvoff; // current baseline offset
28 uchar curul; // current underline/strike state
29 uchar curjust; // current justify state
30 int curanchor; // current (href) anchor id (if in one), or 0
31 int curstate; // current value of item state
32 int literal; // current literal state
33 int inpar; // true when in a paragraph-like construct
34 int adjsize; // current font size adjustment
35 Item* items; // dummy head of item list we're building
36 Item* lastit; // tail of item list we're building
37 Item* prelastit; // item before lastit
38 Stack fntstylestk; // style stack
39 Stack fntsizestk; // size stack
40 Stack fgstk; // text color stack
41 Stack ulstk; // underline stack
42 Stack voffstk; // vertical offset stack
43 Stack listtypestk; // list type stack
44 Stack listcntstk; // list counter stack
45 Stack juststk; // justification stack
46 Stack hangstk; // hanging stack
64 // Some layout parameters
66 FRKIDMARGIN = 6, // default margin around kid frames
67 IMGHSPACE = 0, // default hspace for images (0 matches IE, Netscape)
68 IMGVSPACE = 0, // default vspace for images
69 FLTIMGHSPACE = 2, // default hspace for float images
70 TABSP = 5, // default cellspacing for tables
71 TABPAD = 1, // default cell padding for tables
72 LISTTAB = 1, // number of tabs to indent lists
73 BQTAB = 1, // number of tabs to indent blockquotes
74 HRSZ = 2, // thickness of horizontal rules
75 SUBOFF = 4, // vertical offset for subscripts
76 SUPOFF = 6, // vertical offset for superscripts
77 NBSP = 160 // non-breaking space character
80 // These tables must be sorted
81 static StringInt align_tab[] = {
82 {L"baseline", ALbaseline},
83 {L"bottom", ALbottom},
84 {L"center", ALcenter},
86 {L"justify", ALjustify},
88 {L"middle", ALmiddle},
92 #define NALIGNTAB (sizeof(align_tab)/sizeof(StringInt))
94 static StringInt input_tab[] = {
96 {L"checkbox", Fcheckbox},
100 {L"password", Fpassword},
103 {L"submit", Fsubmit},
106 #define NINPUTTAB (sizeof(input_tab)/sizeof(StringInt))
108 static StringInt clear_tab[] = {
109 {L"all", IFcleft|IFcright},
113 #define NCLEARTAB (sizeof(clear_tab)/sizeof(StringInt))
115 static StringInt fscroll_tab[] = {
116 {L"auto", FRhscrollauto|FRvscrollauto},
118 {L"yes", FRhscroll|FRvscroll},
120 #define NFSCROLLTAB (sizeof(fscroll_tab)/sizeof(StringInt))
122 static StringInt shape_tab[] = {
124 {L"circle", SHcircle},
126 {L"polygon", SHpoly},
128 {L"rectangle", SHrect}
130 #define NSHAPETAB (sizeof(shape_tab)/sizeof(StringInt))
132 static StringInt method_tab[] = {
136 #define NMETHODTAB (sizeof(method_tab)/sizeof(StringInt))
138 static Rune* roman[15]= {
139 L"I", L"II", L"III", L"IV", L"V", L"VI", L"VII", L"VIII", L"IX", L"X",
140 L"XI", L"XII", L"XIII", L"XIV", L"XV"
146 LTdisc, LTsquare, LTcircle, LT1, LTa, LTA, LTi, LTI
153 BLBA = (BL|SPBefore|SPAfter)
156 // blockbrk[tag] is break info for a block level element, or one
157 // of a few others that get the same treatment re ending open paragraphs
158 // and requiring a line break / vertical space before them.
159 // If we want a line of space before the given element, SPBefore is OR'd in.
160 // If we want a line of space after the given element, SPAfter is OR'd in.
162 static uchar blockbrk[Numtags]= {
163 [Taddress] BLBA, [Tblockquote] BLBA, [Tcenter] BL,
164 [Tdir] BLBA, [Tdiv] BL, [Tdd] BL, [Tdl] BLBA,
165 [Tdt] BL, [Tform] BLBA,
166 // headings and tables get breaks added manually
167 [Th1] BL, [Th2] BL, [Th3] BL,
168 [Th4] BL, [Th5] BL, [Th6] BL,
169 [Thr] BL, [Tisindex] BLBA, [Tli] BL, [Tmenu] BLBA,
170 [Tol] BLBA, [Tp] BLBA, [Tpre] BLBA,
178 // attrinfo is information about attributes.
179 // The AGEN value means that the attribute is generic (applies to almost all elements)
180 static uchar attrinfo[Numattrs]= {
181 [Aid] AGEN, [Aclass] AGEN, [Astyle] AGEN, [Atitle] AGEN,
182 [Aonblur] AGEN, [Aonchange] AGEN, [Aonclick] AGEN,
183 [Aondblclick] AGEN, [Aonfocus] AGEN, [Aonkeypress] AGEN,
184 [Aonkeyup] AGEN, [Aonload] AGEN, [Aonmousedown] AGEN,
185 [Aonmousemove] AGEN, [Aonmouseout] AGEN, [Aonmouseover] AGEN,
186 [Aonmouseup] AGEN, [Aonreset] AGEN, [Aonselect] AGEN,
187 [Aonsubmit] AGEN, [Aonunload] AGEN
190 static uchar scriptev[Numattrs]= {
191 [Aonblur] SEonblur, [Aonchange] SEonchange, [Aonclick] SEonclick,
192 [Aondblclick] SEondblclick, [Aonfocus] SEonfocus, [Aonkeypress] SEonkeypress,
193 [Aonkeyup] SEonkeyup, [Aonload] SEonload, [Aonmousedown] SEonmousedown,
194 [Aonmousemove] SEonmousemove, [Aonmouseout] SEonmouseout, [Aonmouseover] SEonmouseover,
195 [Aonmouseup] SEonmouseup, [Aonreset] SEonreset, [Aonselect] SEonselect,
196 [Aonsubmit] SEonsubmit, [Aonunload] SEonunload
199 // Color lookup table
200 static StringInt color_tab[] = {
202 {L"black", 0x000000},
204 {L"fuchsia", 0xFF00FF},
206 {L"green", 0x008000},
208 {L"maroon", 0x800000},
209 {L"navy", 0x000080,},
210 {L"olive", 0x808000},
211 {L"purple", 0x800080},
213 {L"silver", 0xC0C0C0},
215 {L"white", 0xFFFFFF},
216 {L"yellow", 0xFFFF00}
218 #define NCOLORS (sizeof(color_tab)/sizeof(StringInt))
220 static StringInt *targetmap;
221 static int targetmapsize;
224 static int buildinited = 0;
226 #define SMALLBUFSIZE 240
227 #define BIGBUFSIZE 2000
232 static Align aalign(Token* tok);
233 static int acolorval(Token* tok, int attid, int dflt);
234 static void addbrk(Pstate* ps, int sp, int clr);
235 static void additem(Pstate* ps, Item* it, Token* tok);
236 static void addlinebrk(Pstate* ps, int clr);
237 static void addnbsp(Pstate* ps);
238 static void addtext(Pstate* ps, Rune* s);
239 static Dimen adimen(Token* tok, int attid);
240 static int aflagval(Token* tok, int attid);
241 static int aintval(Token* tok, int attid, int dflt);
242 static Rune* astrval(Token* tok, int attid, Rune* dflt);
243 static int atabval(Token* tok, int attid, StringInt* tab, int ntab, int dflt);
244 static int atargval(Token* tok, int dflt);
245 static int auintval(Token* tok, int attid, int dflt);
246 static Rune* aurlval(Token* tok, int attid, Rune* dflt, Rune* base);
247 static Rune* aval(Token* tok, int attid);
248 static void buildinit(void);
249 static Pstate* cell_pstate(Pstate* oldps, int ishead);
250 static void changehang(Pstate* ps, int delta);
251 static void changeindent(Pstate* ps, int delta);
252 static int color(Rune* s, int dflt);
253 static void copystack(Stack* tostk, Stack* fromstk);
254 static int dimprint(char* buf, int nbuf, Dimen d);
255 static Pstate* finishcell(Table* curtab, Pstate* psstk);
256 static void finish_table(Table* t);
257 static void freeanchor(Anchor* a);
258 static void freedestanchor(DestAnchor* da);
259 static void freeform(Form* f);
260 static void freeformfield(Formfield* ff);
261 static void freeitem(Item* it);
262 static void freepstate(Pstate* p);
263 static void freepstatestack(Pstate* pshead);
264 static void freescriptevents(SEvent* ehead);
265 static void freetable(Table* t);
266 static Map* getmap(Docinfo* di, Rune* name);
267 static Rune* getpcdata(Token* toks, int tokslen, int* ptoki);
268 static Pstate* lastps(Pstate* psl);
269 static Rune* listmark(uchar ty, int n);
270 static int listtyval(Token* tok, int dflt);
271 static Align makealign(int halign, int valign);
272 static Background makebackground(Rune* imgurl, int color);
273 static Dimen makedimen(int kind, int spec);
274 static Anchor* newanchor(int index, Rune* name, Rune* href, int target, Anchor* link);
275 static Area* newarea(int shape, Rune* href, int target, Area* link);
276 static DestAnchor* newdestanchor(int index, Rune* name, Item* item, DestAnchor* link);
277 static Docinfo* newdocinfo(void);
278 static Genattr* newgenattr(Rune* id, Rune* class, Rune* style, Rune* title, SEvent* events);
279 static Form* newform(int formid, Rune* name, Rune* action,
280 int target, int method, Form* link);
281 static Formfield* newformfield(int ftype, int fieldid, Form* form, Rune* name,
282 Rune* value, int size, int maxlength, Formfield* link);
283 static Item* newifloat(Item* it, int side);
284 static Item* newiformfield(Formfield* ff);
285 static Item* newiimage(Rune* src, Rune* altrep, int align, int width, int height,
286 int hspace, int vspace, int border, int ismap, Map* map);
287 static Item* newirule(int align, int size, int noshade, int color, Dimen wspec);
288 static Item* newispacer(int spkind);
289 static Item* newitable(Table* t);
290 static ItemSource* newitemsource(Docinfo* di);
291 static Item* newitext(Rune* s, int fnt, int fg, int voff, int ul);
292 static Kidinfo* newkidinfo(int isframeset, Kidinfo* link);
293 static Option* newoption(int selected, Rune* value, Rune* display, Option* link);
294 static Pstate* newpstate(Pstate* link);
295 static SEvent* newscriptevent(int type, Rune* script, SEvent* link);
296 static Table* newtable(int tableid, Align align, Dimen width, int border,
297 int cellspacing, int cellpadding, Background bg, Token* tok, Table* link);
298 static Tablecell* newtablecell(int cellid, int rowspan, int colspan, Align align, Dimen wspec,
299 int hspec, Background bg, int flags, Tablecell* link);
300 static Tablerow* newtablerow(Align align, Background bg, int flags, Tablerow* link);
301 static Dimen parsedim(Rune* s, int ns);
302 static void pop(Stack* stk);
303 static void popfontsize(Pstate* ps);
304 static void popfontstyle(Pstate* ps);
305 static void popjust(Pstate* ps);
306 static int popretnewtop(Stack* stk, int dflt);
307 static int push(Stack* stk, int val);
308 static void pushfontsize(Pstate* ps, int sz);
309 static void pushfontstyle(Pstate* ps, int sty);
310 static void pushjust(Pstate* ps, int j);
311 static Item* textit(Pstate* ps, Rune* s);
312 static Rune* removeallwhite(Rune* s);
313 static void resetdocinfo(Docinfo* d);
314 static void setcurfont(Pstate* ps);
315 static void setcurjust(Pstate* ps);
316 static void setdimarray(Token* tok, int attid, Dimen** pans, int* panslen);
317 static Rune* stringalign(int a);
318 static void targetmapinit(void);
319 static int toint(Rune* s);
320 static int top(Stack* stk, int dflt);
321 static void trim_cell(Tablecell* c);
322 static int validalign(Align a);
323 static int validdimen(Dimen d);
324 static int validformfield(Formfield* f);
325 static int validhalign(int a);
326 static int validptr(void* p);
327 static int validStr(Rune* s);
328 static int validtable(Table* t);
329 static int validtablerow(Tablerow* r);
330 static int validtablecol(Tablecol* c);
331 static int validtablecell(Tablecell* c);
332 static int validvalign(int a);
333 static int Iconv(Fmt *f);
338 fmtinstall('I', Iconv);
344 newitemsource(Docinfo* di)
350 if(di->mediatype != TextHtml) {
351 ps->curstate &= ~IFwrap;
353 pushfontstyle(ps, FntT);
355 is = (ItemSource*)emalloc(sizeof(ItemSource));
371 linkitems(Docinfo *di, Item *it)
380 /* link image to docinfo */
381 ((Iimage*)it)->nextimage = di->images;
382 di->images = (Iimage*)it;
385 /* link formfield to form */
386 ff = ((Iformfield*)it)->formfield;
387 if(ff != nil && ff->form != nil){
388 for(ff = ff->form->fields; ff != nil; ff = ff->next){
389 if(ff == ((Iformfield*)it)->formfield)
394 ((Iformfield*)it)->formfield->next = nil;
396 ff->next = ((Iformfield*)it)->formfield;
399 ff = ((Iformfield*)it)->formfield;
400 ff->form->fields = ff;
402 linkitems(di, ff->image);
406 /* link table to docinfo */
407 tt = ((Itable*)it)->table;
411 tt->next = di->tables;
413 linkitems(di, tt->caption);
414 for(c = tt->cells; c != nil; c = c->next)
415 linkitems(di, c->content);
418 linkitems(di, ((Ifloat*)it)->item);
426 static Item *getitems(ItemSource* is, uchar* data, int datalen);
428 // Parse an html document and create a list of layout items.
429 // Allocate and return document info in *pdi.
430 // When caller is done with the items, it should call
431 // freeitems on the returned result, and then
432 // freedocinfo(*pdi).
434 parsehtml(uchar* data, int datalen, Rune* pagesrc, int mtype, int chset, Docinfo** pdi)
441 di->src = _Strdup(pagesrc);
442 di->base = _Strdup(pagesrc);
443 di->mediatype = mtype;
446 is = newitemsource(di);
447 it = getitems(is, data, datalen);
448 freepstatestack(is->psstk);
453 // Get a group of tokens for lexer, parse them, and create
454 // a list of layout items.
455 // When caller is done with the items, it should call
456 // freeitems on the returned result.
458 getitems(ItemSource* is, uchar* data, int datalen)
533 doscripts = 0; // for now
537 toks = _gettoks(data, datalen, di->chset, di->mediatype, &tokslen);
539 for(; toki < tokslen; toki++) {
542 fprint(2, "build: curstate %ux, token %T\n", ps->curstate, tok);
551 else if(tag < Numtags + RBRA) {
552 brk = blockbrk[tag - RBRA];
557 addbrk(ps, brksp, 0);
563 // check common case first (Data), then switch statement on tag
565 // Lexing didn't pay attention to SGML record boundary rules:
566 // \n after start tag or before end tag to be discarded.
567 // (Lex has already discarded all \r's).
568 // Some pages assume this doesn't happen in <PRE> text,
569 // so we won't do it if literal is true.
570 // BUG: won't discard \n before a start tag that begins
571 // the next bufferful of tokens.
578 pt = toks[toki - 1].tag;
579 // IE and Netscape both ignore this rule (contrary to spec)
580 // if previous tag was img
581 if(pt < Numtags && pt != Timg && j > 0 && s[0] == '\n')
584 if(toki < tokslen - 1) {
585 nt = toks[toki + 1].tag;
586 if(nt >= RBRA && nt < Numtags + RBRA && j > i && s[j - 1] == '\n')
591 s = _Strsubstr(s, i, j);
597 _trimwhite(s, n, &t, &nt);
610 tok->text = nil; // token doesn't own string anymore
616 // Some abbrevs used in following DTD comments
618 // | TT | I | B | U | STRIKE | BIG | SMALL | SUB | SUP
619 // | EM | STRONG | DFN | CODE | SAMP | KBD | VAR | CITE
620 // | A | IMG | APPLET | FONT | BASEFONT | BR | SCRIPT | MAP
621 // | INPUT | SELECT | TEXTAREA
622 // %block = P | UL | OL | DIR | MENU | DL | PRE | DL | DIV | CENTER
623 // | BLOCKQUOTE | FORM | ISINDEX | HR | TABLE
624 // %flow = (%text | %block)*
625 // %body.content = (%heading | %text | %block | ADDRESS)*
627 // <!ELEMENT A - - (%text) -(A)>
628 // Anchors are not supposed to be nested, but you sometimes see
629 // href anchors inside destination anchors.
631 if(ps->curanchor != 0) {
633 fprint(2, "warning: nested <A> or missing </A>\n");
636 name = aval(tok, Aname);
637 href = aurlval(tok, Ahref, nil, di->base);
638 // ignore rel, rev, and title attrs
640 target = atargval(tok, di->target);
641 di->anchors = newanchor(++is->nanchors, name, href, target, di->anchors);
643 name = _Strdup(name); // for DestAnchor construction, below
644 ps->curanchor = is->nanchors;
645 ps->curfg = push(&ps->fgstk, di->link);
646 ps->curul = push(&ps->ulstk, ULunder);
649 // add a null item to be destination
650 additem(ps, newispacer(ISPnull), tok);
651 di->dests = newdestanchor(++is->nanchors, name, ps->lastit, di->dests);
656 if(ps->curanchor != 0) {
657 ps->curfg = popretnewtop(&ps->fgstk, di->text);
658 ps->curul = popretnewtop(&ps->ulstk, ULnone);
663 // <!ELEMENT APPLET - - (PARAM | %text)* >
664 // We can't do applets, so ignore PARAMS, and let
665 // the %text contents appear for the alternative rep
668 if(warn && tag == Tapplet)
669 fprint(2, "warning: <APPLET> ignored\n");
672 // <!ELEMENT AREA - O EMPTY>
677 fprint(2, "warning: <AREA> not inside <MAP>\n");
680 map->areas = newarea(atabval(tok, Ashape, shape_tab, NSHAPETAB, SHrect),
681 aurlval(tok, Ahref, nil, di->base),
682 atargval(tok, di->target),
684 setdimarray(tok, Acoords, &map->areas->coords, &map->areas->ncoords);
687 // <!ELEMENT (B|STRONG) - - (%text)*>
690 pushfontstyle(ps, FntB);
708 // <!ELEMENT BASE - O EMPTY>
711 di->base = aurlval(tok, Ahref, di->base, di->base);
714 di->target = atargval(tok, di->target);
717 // <!ELEMENT BASEFONT - O EMPTY>
719 ps->adjsize = aintval(tok, Asize, 3) - 3;
722 // <!ELEMENT (BIG|SMALL) - - (%text)*>
730 pushfontsize(ps, sz);
738 // <!ELEMENT BLOCKQUOTE - - %body.content>
740 changeindent(ps, BQTAB);
743 case Tblockquote+RBRA:
744 changeindent(ps, -BQTAB);
747 // <!ELEMENT BODY O O %body.content>
750 bg = makebackground(nil, acolorval(tok, Abgcolor, di->background.color));
751 bgurl = aurlval(tok, Abackground, nil, di->base);
753 if(di->backgrounditem != nil)
754 freeitem(di->backgrounditem);
755 di->backgrounditem = (Iimage*)newiimage(bgurl, nil, ALnone, 0, 0, 0, 0, 0, 0, nil);
759 di->text = acolorval(tok, Atext, di->text);
760 di->link = acolorval(tok, Alink, di->link);
761 di->vlink = acolorval(tok, Avlink, di->vlink);
762 di->alink = acolorval(tok, Aalink, di->alink);
763 if(di->text != ps->curfg) {
764 ps->curfg = di->text;
770 // HTML spec says ignore things after </body>,
771 // but IE and Netscape don't
775 // <!ELEMENT BR - O EMPTY>
777 addlinebrk(ps, atabval(tok, Aclear, clear_tab, NCLEARTAB, 0));
780 // <!ELEMENT CAPTION - - (%text;)*>
784 fprint(2, "warning: <CAPTION> outside <TABLE>\n");
787 if(curtab->caption != nil) {
789 fprint(2, "warning: more than one <CAPTION> in <TABLE>\n");
793 curtab->caption_place = atabval(tok, Aalign, align_tab, NALIGNTAB, ALtop);
798 if(curtab == nil || nextps == nil) {
800 fprint(2, "warning: unexpected </CAPTION>\n");
803 if(curtab->caption != nil)
804 freeitems(curtab->caption);
805 curtab->caption = ps->items->next;
806 ps->items->next = nil;
816 al = atabval(tok, Aalign, align_tab, NALIGNTAB, ps->curjust);
825 // <!ELEMENT DD - O %flow >
827 if(ps->hangstk.n == 0) {
829 fprint(2, "warning: <DD> not inside <DL\n");
832 h = top(&ps->hangstk, 0);
834 changehang(ps, -10*LISTTAB);
837 push(&ps->hangstk, 0);
840 //<!ELEMENT (DIR|MENU) - - (LI)+ -(%block) >
841 //<!ELEMENT (OL|UL) - - (LI)+>
846 changeindent(ps, LISTTAB);
847 push(&ps->listtypestk, listtyval(tok, (tag==Tol)? LT1 : LTdisc));
848 push(&ps->listcntstk, aintval(tok, Astart, 1));
855 if(ps->listtypestk.n == 0) {
857 fprint(2, "warning: %T ended no list\n", tok);
861 pop(&ps->listtypestk);
862 pop(&ps->listcntstk);
863 changeindent(ps, -LISTTAB);
866 // <!ELEMENT DL - - (DT|DD)+ >
868 changeindent(ps, LISTTAB);
869 push(&ps->hangstk, 0);
873 if(ps->hangstk.n == 0) {
875 fprint(2, "warning: unexpected </DL>\n");
878 changeindent(ps, -LISTTAB);
879 if(top(&ps->hangstk, 0) != 0)
880 changehang(ps, -10*LISTTAB);
884 // <!ELEMENT DT - O (%text)* >
886 if(ps->hangstk.n == 0) {
888 fprint(2, "warning: <DT> not inside <DL>\n");
891 h = top(&ps->hangstk, 0);
894 changehang(ps, -10*LISTTAB);
895 changehang(ps, 10*LISTTAB);
896 push(&ps->hangstk, 1);
899 // <!ELEMENT FONT - - (%text)*>
901 sz = top(&ps->fntsizestk, Normal);
902 if(_tokaval(tok, Asize, &nsz, 0)) {
903 if(_prefix(L"+", nsz))
904 sz = Normal + _Strtol(nsz+1, nil, 10) + ps->adjsize;
905 else if(_prefix(L"-", nsz))
906 sz = Normal - _Strtol(nsz+1, nil, 10) + ps->adjsize;
908 sz = Normal + (_Strtol(nsz, nil, 10) - 3);
910 ps->curfg = push(&ps->fgstk, acolorval(tok, Acolor, ps->curfg));
911 pushfontsize(ps, sz);
915 if(ps->fgstk.n == 0) {
917 fprint(2, "warning: unexpected </FONT>\n");
920 ps->curfg = popretnewtop(&ps->fgstk, di->text);
924 // <!ELEMENT FORM - - %body.content -(FORM) >
926 if(is->curform != nil) {
928 fprint(2, "warning: <FORM> nested inside another\n");
931 action = aurlval(tok, Aaction, di->base, di->base);
933 name = astrval(tok, Aname, s);
936 target = atargval(tok, di->target);
937 method = atabval(tok, Amethod, method_tab, NMETHODTAB, HGet);
938 if(warn && _tokaval(tok, Aenctype, &enctype, 0) &&
939 _Strcmp(enctype, L"application/x-www-form-urlencoded"))
940 fprint(2, "form enctype %S not handled\n", enctype);
941 frm = newform(++is->nforms, name, action, target, method, di->forms);
947 if(is->curform == nil) {
949 fprint(2, "warning: unexpected </FORM>\n");
955 // <!ELEMENT FRAME - O EMPTY>
960 fprint(2, "warning: <FRAME> not in <FRAMESET>\n");
963 ks->kidinfos = kd = newkidinfo(0, ks->kidinfos);
964 kd->src = aurlval(tok, Asrc, nil, di->base);
965 kd->name = aval(tok, Aname);
967 kd->name = runesmprint("_fr%d", ++is->nframes);
968 kd->marginw = auintval(tok, Amarginwidth, 0);
969 kd->marginh = auintval(tok, Amarginheight, 0);
970 kd->framebd = auintval(tok, Aframeborder, 1);
971 kd->flags = atabval(tok, Ascrolling, fscroll_tab, NFSCROLLTAB, kd->flags);
972 norsz = aflagval(tok, Anoresize);
974 kd->flags |= FRnoresize;
977 // <!ELEMENT FRAMESET - - (FRAME|FRAMESET)+>
979 ks = newkidinfo(1, nil);
984 ks->next = pks->kidinfos;
987 ks->nextframeset = pks;
989 setdimarray(tok, Arows, &ks->rows, &ks->nrows);
991 ks->rows = (Dimen*)emalloc(sizeof(Dimen));
993 ks->rows[0] = makedimen(Dpercent, 100);
995 setdimarray(tok, Acols, &ks->cols, &ks->ncols);
997 ks->cols = (Dimen*)emalloc(sizeof(Dimen));
999 ks->cols[0] = makedimen(Dpercent, 100);
1003 case Tframeset+RBRA:
1004 if(is->kidstk == nil) {
1006 fprint(2, "warning: unexpected </FRAMESET>\n");
1010 // put kids back in original order
1011 // and add blank frames to fill out cells
1012 n = ks->nrows*ks->ncols;
1013 nblank = n - _listlen((List*)ks->kidinfos);
1015 ks->kidinfos = newkidinfo(0, ks->kidinfos);
1016 ks->kidinfos = (Kidinfo*)_revlist((List*)ks->kidinfos);
1017 is->kidstk = is->kidstk->nextframeset;
1018 if(is->kidstk == nil) {
1025 // <!ELEMENT H1 - - (%text;)*>, etc.
1033 if(ps->items == ps->lastit)
1035 addbrk(ps, bramt, IFcleft|IFcright);
1036 sz = Verylarge - (tag - Th1);
1039 pushfontsize(ps, sz);
1040 sty = top(&ps->fntstylestk, FntR);
1043 pushfontstyle(ps, sty);
1044 pushjust(ps, atabval(tok, Aalign, align_tab, NALIGNTAB, ps->curjust));
1054 addbrk(ps, 1, IFcleft|IFcright);
1061 // HTML spec says ignore regular markup in head,
1062 // but Netscape and IE don't
1070 // <!ELEMENT HR - O EMPTY>
1072 al = atabval(tok, Aalign, align_tab, NALIGNTAB, ALcenter);
1073 sz = auintval(tok, Asize, HRSZ);
1074 wd = adimen(tok, Awidth);
1075 if(dimenkind(wd) == Dnone)
1076 wd = makedimen(Dpercent, 100);
1077 nosh = aflagval(tok, Anoshade);
1078 color = acolorval(tok, Acolor, 0);
1079 additem(ps, newirule(al, sz, nosh, color, wd), tok);
1089 pushfontstyle(ps, FntI);
1092 // <!ELEMENT IMG - O EMPTY>
1095 oldcuranchor = ps->curanchor;
1096 if(_tokaval(tok, Ausemap, &usemap, 0)) {
1097 if(!_prefix(L"#", usemap)) {
1099 fprint(2, "warning: can't handle non-local map %S\n", usemap);
1102 map = getmap(di, usemap+1);
1103 if(ps->curanchor == 0) {
1104 di->anchors = newanchor(++is->nanchors, nil, nil, di->target, di->anchors);
1105 ps->curanchor = is->nanchors;
1109 align = atabval(tok, Aalign, align_tab, NALIGNTAB, ALbottom);
1111 if(ps->curanchor != 0)
1113 src = aurlval(tok, Asrc, nil, di->base);
1116 fprint(2, "warning: <img> has no src attribute\n");
1117 ps->curanchor = oldcuranchor;
1120 img = newiimage(src,
1123 auintval(tok, Awidth, 0),
1124 auintval(tok, Aheight, 0),
1125 auintval(tok, Ahspace, IMGHSPACE),
1126 auintval(tok, Avspace, IMGVSPACE),
1127 auintval(tok, Aborder, dfltbd),
1128 aflagval(tok, Aismap),
1130 if(align == ALleft || align == ALright) {
1131 additem(ps, newifloat(img, align), tok);
1132 // if no hspace specified, use FLTIMGHSPACE
1133 if(!_tokaval(tok, Ahspace, &val, 0))
1134 ((Iimage*)img)->hspace = FLTIMGHSPACE;
1138 additem(ps, img, tok);
1140 ps->curanchor = oldcuranchor;
1143 // <!ELEMENT INPUT - O EMPTY>
1146 if(is->curform == nil) {
1148 fprint(2, "<INPUT> not inside <FORM>\n");
1151 field = newformfield(
1152 atabval(tok, Atype, input_tab, NINPUTTAB, Ftext),
1153 ++is->curform->nfields,
1157 auintval(tok, Asize, 0),
1158 auintval(tok, Amaxlength, 1000),
1160 if(aflagval(tok, Achecked))
1161 field->flags = FFchecked;
1163 switch(field->ftype) {
1167 if(field->size == 0)
1172 if(field->name == nil) {
1174 fprint(2, "warning: checkbox form field missing name\n");
1177 if(field->value == nil)
1178 field->value = _Strdup(L"1");
1182 if(field->name == nil || field->value == nil) {
1184 fprint(2, "warning: radio form field missing name or value\n");
1190 if(field->value == nil)
1191 field->value = _Strdup(L"Submit");
1192 if(field->name == nil)
1193 field->name = _Strdup(L"_no_name_submit_");
1197 src = aurlval(tok, Asrc, nil, di->base);
1200 fprint(2, "warning: image form field missing src\n");
1203 // width and height attrs aren't specified in HTML 3.2,
1204 // but some people provide them and they help avoid
1206 field->image = newiimage(src,
1207 astrval(tok, Aalt, L"Submit"),
1208 atabval(tok, Aalign, align_tab, NALIGNTAB, ALbottom),
1209 auintval(tok, Awidth, 0), auintval(tok, Aheight, 0),
1214 if(field->value == nil)
1215 field->value = _Strdup(L"Reset");
1219 if(field->value == nil)
1220 field->value = _Strdup(L" ");
1223 ffit = newiformfield(field);
1224 additem(ps, ffit, tok);
1225 if(ffit->genattr != nil)
1226 field->events = ffit->genattr->events;
1229 // <!ENTITY ISINDEX - O EMPTY>
1232 prompt = astrval(tok, Aprompt, L"Index search terms:");
1233 target = atargval(tok, di->target);
1234 additem(ps, textit(ps, prompt), tok);
1235 frm = newform(++is->nforms,
1242 ff = newformfield(Ftext,
1245 _Strdup(L"_ISINDEX_"),
1250 additem(ps, newiformfield(ff), tok);
1254 // <!ELEMENT LI - O %flow>
1256 if(ps->listtypestk.n == 0) {
1258 fprint(2, "<LI> not in list\n");
1261 ty = top(&ps->listtypestk, 0);
1262 ty2 = listtyval(tok, ty);
1265 push(&ps->listtypestk, ty2);
1267 v = aintval(tok, Avalue, top(&ps->listcntstk, 1));
1268 if(ty == LTdisc || ty == LTsquare || ty == LTcircle)
1269 hang = 10*LISTTAB - 3;
1271 hang = 10*LISTTAB - 1;
1272 changehang(ps, hang);
1273 addtext(ps, listmark(ty, v));
1274 push(&ps->listcntstk, v + 1);
1275 changehang(ps, -hang);
1279 // <!ELEMENT MAP - - (AREA)+>
1281 if(_tokaval(tok, Aname, &name, 0))
1282 is->curmap = getmap(di, name);
1289 fprint(2, "warning: unexpected </MAP>\n");
1292 map->areas = (Area*)_revlist((List*)map->areas);
1298 if(_tokaval(tok, Ahttp_equiv, &equiv, 0)) {
1299 val = aval(tok, Acontent);
1301 if(!_Strncmpci(equiv, n, L"refresh"))
1303 else if(!_Strncmpci(equiv, n, L"content-script-type")) {
1305 if(!_Strncmpci(val, n, L"javascript")
1306 || !_Strncmpci(val, n, L"jscript1.1")
1307 || !_Strncmpci(val, n, L"jscript"))
1308 di->scripttype = TextJavascript;
1311 fprint(2, "unimplemented script type %S\n", val);
1312 di->scripttype = UnknownType;
1318 // Nobr is NOT in HMTL 4.0, but it is ubiquitous on the web
1321 ps->curstate &= ~IFwrap;
1325 ps->curstate |= IFwrap;
1328 // We do frames, so skip stuff in noframes
1333 case Tnoframes+RBRA:
1337 // We do scripts (if enabled), so skip stuff in noscripts
1343 case Tnoscript+RBRA:
1348 // <!ELEMENT OPTION - O ( //PCDATA)>
1350 if(is->curform == nil || is->curform->fields == nil) {
1352 fprint(2, "warning: <OPTION> not in <SELECT>\n");
1355 field = is->curform->fields;
1356 if(field->ftype != Fselect) {
1358 fprint(2, "warning: <OPTION> not in <SELECT>\n");
1361 val = aval(tok, Avalue);
1362 option = newoption(aflagval(tok, Aselected), val, nil, field->options);
1363 field->options = option;
1364 option->display = getpcdata(toks, tokslen, &toki);
1366 option->value = _Strdup(option->display);
1369 // <!ELEMENT P - O (%text)* >
1371 pushjust(ps, atabval(tok, Aalign, align_tab, NALIGNTAB, ps->curjust));
1379 // <!ELEMENT PARAM - O EMPTY>
1380 // Do something when we do applets...
1384 // <!ELEMENT PRE - - (%text)* -(IMG|BIG|SMALL|SUB|SUP|FONT) >
1386 ps->curstate &= ~IFwrap;
1389 pushfontstyle(ps, FntT);
1393 ps->curstate |= IFwrap;
1400 // <!ELEMENT SCRIPT - - CDATA>
1403 if(!di->hasscripts) {
1404 if(di->scripttype == TextJavascript) {
1405 // TODO: initialize script if nec.
1411 if(!di->hasscripts) {
1413 fprint(2, "warning: <SCRIPT> ignored\n");
1417 scriptsrc = aurlval(tok, Asrc, nil, di->base);
1419 if(scriptsrc != nil) {
1421 fprint(2, "warning: non-local <SCRIPT> ignored\n");
1425 script = getpcdata(toks, tokslen, &toki);
1429 fprint(2, "script ignored\n");
1439 // <!ELEMENT SELECT - - (OPTION+)>
1441 if(is->curform == nil) {
1443 fprint(2, "<SELECT> not inside <FORM>\n");
1446 is->curfield = field = newformfield(Fselect,
1447 ++is->curform->nfields,
1451 auintval(tok, Asize, 0),
1454 if(aflagval(tok, Amultiple))
1455 field->flags = FFmultiple;
1456 ffit = newiformfield(field);
1457 additem(ps, ffit, tok);
1458 if(ffit->genattr != nil)
1459 field->events = ffit->genattr->events;
1460 // throw away stuff until next tag (should be <OPTION>)
1461 s = getpcdata(toks, tokslen, &toki);
1467 if(is->curform == nil || is->curfield == nil) {
1469 fprint(2, "warning: unexpected </SELECT>\n");
1472 field = is->curfield;
1473 if(field->ftype != Fselect)
1475 // put options back in input order
1476 field->options = (Option*)_revlist((List*)field->options);
1480 // <!ELEMENT (STRIKE|U) - - (%text)*>
1483 ps->curul = push(&ps->ulstk, (tag==Tstrike)? ULmid : ULunder);
1488 if(ps->ulstk.n == 0) {
1490 fprint(2, "warning: unexpected %T\n", tok);
1493 ps->curul = popretnewtop(&ps->ulstk, ULnone);
1496 // <!ELEMENT STYLE - - CDATA>
1499 fprint(2, "warning: unimplemented <STYLE>\n");
1507 // <!ELEMENT (SUB|SUP) - - (%text)*>
1511 ps->curvoff += SUBOFF;
1513 ps->curvoff -= SUPOFF;
1514 push(&ps->voffstk, ps->curvoff);
1515 sz = top(&ps->fntsizestk, Normal);
1516 pushfontsize(ps, sz - 1);
1521 if(ps->voffstk.n == 0) {
1523 fprint(2, "warning: unexpected %T\n", tok);
1526 ps->curvoff = popretnewtop(&ps->voffstk, 0);
1530 // <!ELEMENT TABLE - - (CAPTION?, TR+)>
1533 tab = newtable(++is->ntables,
1535 adimen(tok, Awidth),
1536 aflagval(tok, Aborder),
1537 auintval(tok, Acellspacing, TABSP),
1538 auintval(tok, Acellpadding, TABPAD),
1539 makebackground(nil, acolorval(tok, Abgcolor, ps->curbg.color)),
1549 fprint(2, "warning: unexpected </TABLE>\n");
1552 isempty = (curtab->cells == nil);
1555 fprint(2, "warning: <TABLE> has no cells\n");
1558 ps = finishcell(curtab, ps);
1559 if(curtab->rows != nil)
1560 curtab->rows->flags = 0;
1561 finish_table(curtab);
1565 tabitem = newitable(curtab);
1566 al = curtab->align.halign;
1570 additem(ps, newifloat(tabitem, al), tok);
1574 pushjust(ps, ALcenter);
1580 additem(ps, tabitem, curtab->tabletok);
1586 if(is->tabstk == nil) {
1588 fprint(2, "warning: table stack is wrong\n");
1591 is->tabstk = is->tabstk->next;
1592 curtab = is->tabstk;
1597 // <!ELEMENT (TH|TD) - O %body.content>
1598 // Cells for a row are accumulated in reverse order.
1599 // We push ps on a stack, and use a new one to accumulate
1600 // the contents of the cell.
1605 fprint(2, "%T outside <TABLE>\n", tok);
1612 ps = finishcell(curtab, ps);
1614 if(curtab->rows != nil)
1616 if(tr == nil || !tr->flags) {
1618 fprint(2, "%T outside row\n", tok);
1619 tr = newtablerow(makealign(ALnone, ALnone),
1620 makebackground(nil, curtab->background.color),
1625 ps = cell_pstate(ps, tag == Tth);
1627 if(aflagval(tok, Anowrap)) {
1629 ps->curstate &= ~IFwrap;
1633 c = newtablecell(curtab->cells==nil? 1 : curtab->cells->cellid+1,
1634 auintval(tok, Arowspan, 1),
1635 auintval(tok, Acolspan, 1),
1637 adimen(tok, Awidth),
1638 auintval(tok, Aheight, 0),
1639 makebackground(nil, acolorval(tok, Abgcolor, tr->background.color)),
1643 ps->curbg = c->background;
1644 if(c->align.halign == ALnone) {
1645 if(tr->align.halign != ALnone)
1646 c->align.halign = tr->align.halign;
1648 c->align.halign = ALcenter;
1650 c->align.halign = ALleft;
1652 if(c->align.valign == ALnone) {
1653 if(tr->align.valign != ALnone)
1654 c->align.valign = tr->align.valign;
1656 c->align.valign = ALmiddle;
1658 c->nextinrow = tr->cells;
1664 if(curtab == nil || curtab->cells == nil) {
1666 fprint(2, "unexpected %T\n", tok);
1669 ps = finishcell(curtab, ps);
1672 // <!ELEMENT TEXTAREA - - ( //PCDATA)>
1674 if(is->curform == nil) {
1676 fprint(2, "<TEXTAREA> not inside <FORM>\n");
1679 field = newformfield(Ftextarea,
1680 ++is->curform->nfields,
1687 field->rows = auintval(tok, Arows, 3);
1688 field->cols = auintval(tok, Acols, 50);
1689 field->value = getpcdata(toks, tokslen, &toki);
1690 if(warn && toki < tokslen - 1 && toks[toki + 1].tag != Ttextarea + RBRA)
1691 fprint(2, "warning: <TEXTAREA> data ended by %T\n", &toks[toki + 1]);
1692 ffit = newiformfield(field);
1693 additem(ps, ffit, tok);
1694 if(ffit->genattr != nil)
1695 field->events = ffit->genattr->events;
1698 // <!ELEMENT TITLE - - ( //PCDATA)* -(%head.misc)>
1700 di->doctitle = getpcdata(toks, tokslen, &toki);
1701 if(warn && toki < tokslen - 1 && toks[toki + 1].tag != Ttitle + RBRA)
1702 fprint(2, "warning: <TITLE> data ended by %T\n", &toks[toki + 1]);
1705 // <!ELEMENT TR - O (TH|TD)+>
1706 // rows are accumulated in reverse order in curtab->rows
1710 fprint(2, "warning: <TR> outside <TABLE>\n");
1717 ps = finishcell(curtab, ps);
1718 if(curtab->rows != nil)
1719 curtab->rows->flags = 0;
1720 curtab->rows = newtablerow(aalign(tok),
1721 makebackground(nil, acolorval(tok, Abgcolor, curtab->background.color)),
1727 if(curtab == nil || curtab->rows == nil) {
1729 fprint(2, "warning: unexpected </TR>\n");
1732 ps = finishcell(curtab, ps);
1734 if(tr->cells == nil) {
1736 fprint(2, "warning: empty row\n");
1737 curtab->rows = tr->next;
1745 // <!ELEMENT (TT|CODE|KBD|SAMP) - - (%text)*>
1750 pushfontstyle(ps, FntT);
1753 // Tags that have empty action
1760 case Tbasefont+RBRA:
1777 case Ttextarea+RBRA:
1782 // Tags not implemented
1790 case Tfieldset+RBRA:
1802 case Toptgroup+RBRA:
1808 fprint(2, "warning: unimplemented HTML tag: %S\n", tagnames[tag]);
1814 fprint(2, "warning: unknown HTML tag: %S\n", tok->text);
1818 // some pages omit trailing </table>
1819 while(curtab != nil) {
1821 fprint(2, "warning: <TABLE> not closed\n");
1822 if(curtab->cells != nil) {
1823 ps = finishcell(curtab, ps);
1824 if(curtab->cells == nil) {
1826 fprint(2, "warning: empty table\n");
1829 if(curtab->rows != nil)
1830 curtab->rows->flags = 0;
1831 finish_table(curtab);
1833 additem(ps, newitable(curtab), curtab->tabletok);
1837 if(is->tabstk != nil)
1838 is->tabstk = is->tabstk->next;
1839 curtab = is->tabstk;
1841 outerps = lastps(ps);
1842 ans = outerps->items->next;
1843 freeitem(outerps->items);
1844 // note: ans may be nil and di->kids not nil, if there's a frameset!
1845 outerps->items = newispacer(ISPnull);
1846 outerps->lastit = outerps->items;
1847 outerps->prelastit = nil;
1849 if(ans != nil && di->hasscripts) {
1850 // TODO evalscript(nil);
1856 assert(validitems(ans));
1858 fprint(2, "getitems returning nil\n");
1860 printitems(ans, "getitems returning:");
1863 _freetokens(toks, tokslen);
1867 // Concatenate together maximal set of Data tokens, starting at toks[toki+1].
1868 // Lexer has ensured that there will either be a following non-data token or
1869 // we will be at eof.
1870 // Return emallocd trimmed concatenation, and update *ptoki to last used toki
1872 getpcdata(Token* toks, int tokslen, int* ptoki)
1884 // first find length of answer
1885 toki = (*ptoki) + 1;
1886 while(toki < tokslen) {
1888 if(tok->tag == Data) {
1890 anslen += _Strlen(tok->text);
1895 // now make up the initial answer
1897 ans = _newstr(anslen);
1899 toki = (*ptoki) + 1;
1900 while(toki < tokslen) {
1902 if(tok->tag == Data) {
1904 p = _Stradd(p, tok->text, _Strlen(tok->text));
1910 _trimwhite(ans, anslen, &trimans, &trimanslen);
1911 if(trimanslen != anslen) {
1913 ans = _Strndup(trimans, trimanslen);
1921 // If still parsing head of curtab->cells list, finish it off
1922 // by transferring the items on the head of psstk to the cell.
1923 // Then pop the psstk and return the new psstk.
1925 finishcell(Table* curtab, Pstate* psstk)
1932 if((c->flags&TFparsing)) {
1933 psstknext = psstk->next;
1934 if(psstknext == nil) {
1936 fprint(2, "warning: parse state stack is wrong\n");
1939 if(c->content != nil)
1940 freeitems(c->content);
1941 c->content = psstk->items->next;
1942 psstk->items->next = nil;
1943 c->flags &= ~TFparsing;
1952 // Make a new Pstate for a cell, based on the old pstate, oldps.
1953 // Also, put the new ps on the head of the oldps stack.
1955 cell_pstate(Pstate* oldps, int ishead)
1960 ps = newpstate(oldps);
1962 ps->curanchor = oldps->curanchor;
1963 copystack(&ps->fntstylestk, &oldps->fntstylestk);
1964 copystack(&ps->fntsizestk, &oldps->fntsizestk);
1965 ps->curfont = oldps->curfont;
1966 ps->curfg = oldps->curfg;
1967 ps->curbg = oldps->curbg;
1968 copystack(&ps->fgstk, &oldps->fgstk);
1969 ps->adjsize = oldps->adjsize;
1971 sty = ps->curfont%NumSize;
1972 ps->curfont = FntB*NumSize + sty;
1977 // Return a new Pstate with default starting state.
1978 // Use link to add it to head of a list, if any.
1980 newpstate(Pstate* link)
1984 ps = (Pstate*)emalloc(sizeof(Pstate));
1985 ps->curfont = DefFnt;
1987 ps->curbg.image = nil;
1988 ps->curbg.color = White;
1990 ps->curjust = ALleft;
1991 ps->curstate = IFwrap;
1992 ps->items = newispacer(ISPnull);
1993 ps->lastit = ps->items;
1994 ps->prelastit = nil;
1999 // Return last Pstate on psl list
2004 while(psl->next != nil)
2009 // Add it to end of ps item chain, adding in current state from ps.
2010 // Also, if tok is not nil, scan it for generic attributes and assign
2011 // the genattr field of the item accordingly.
2013 additem(Pstate* ps, Item* it, Token* tok)
2026 fprint(2, "warning: skipping item: %I\n", it);
2030 it->anchorid = ps->curanchor;
2031 it->state |= ps->curstate;
2039 for(a = tok->attr; a != nil; a = a->next) {
2061 assert(aid >= Aonblur && aid <= Aonunload);
2062 e = newscriptevent(scriptev[a->attid], a->value, e);
2069 it->genattr = newgenattr(i, c, s, t, e);
2071 ps->curstate &= ~(IFbrk|IFbrksp|IFnobrk|IFcleft|IFcright);
2072 ps->prelastit = ps->lastit;
2073 ps->lastit->next = it;
2077 // Make a text item out of s,
2078 // using current font, foreground, vertical offset and underline state.
2080 textit(Pstate* ps, Rune* s)
2083 return newitext(s, ps->curfont, ps->curfg, ps->curvoff + Voffbias, ps->curul);
2086 // Add text item or items for s, paying attention to
2087 // current font, foreground, baseline offset, underline state,
2088 // and literal mode. Unless we're in literal mode, compress
2089 // whitespace to single blank, and, if curstate has a break,
2090 // trim any leading whitespace. Whether in literal mode or not,
2091 // turn nonbreaking spaces into spacer items with IFnobrk set.
2093 // In literal mode, break up s at newlines and add breaks instead.
2094 // Also replace tabs appropriate number of spaces.
2095 // In nonliteral mode, break up the items every 100 or so characters
2096 // just to make the layout algorithm not go quadratic.
2098 // addtext assumes ownership of s.
2100 addtext(Pstate* ps, Rune* s)
2112 Rune buf[SMALLBUFSIZE];
2123 // trim trailing blanks from line
2124 for(k = i; k > j; k--)
2128 additem(ps, textit(ps, _Strndup(s+j, k-j)), nil);
2138 // make ss = s[j:i] + nsp spaces
2139 ss = _newstr(i-j+nsp);
2140 p = _Stradd(ss, s+j, i-j);
2141 p = _Stradd(p, L" ", nsp);
2143 additem(ps, textit(ps, ss), nil);
2147 else if(s[i] == NBSP) {
2149 additem(ps, textit(ps, _Strndup(s+j, i-j)), nil);
2158 if(j == 0 && i == n) {
2159 // just transfer s over
2160 additem(ps, textit(ps, s), nil);
2163 additem(ps, textit(ps, _Strndup(s+j, i-j)), nil);
2168 else { // not literal mode
2169 if((ps->curstate&IFbrk) || ps->lastit == ps->items)
2172 if(c >= 256 || !isspace(c))
2177 for(j = i; i < n; i++) {
2178 assert(p+i-j < buf+SMALLBUFSIZE-1);
2182 p = _Stradd(p, s+j, i-j);
2184 additem(ps, textit(ps, _Strndup(buf, p-buf)), nil);
2190 if(c < 256 && isspace(c)) {
2192 p = _Stradd(p, s+j, i-j);
2196 if(c >= 256 || !isspace(c))
2203 p = _Stradd(p, s+j, i+1-j);
2207 additem(ps, textit(ps, _Strndup(buf, p-buf)), nil);
2211 if(i > j && j < n) {
2212 assert(p+i-j < buf+SMALLBUFSIZE-1);
2213 p = _Stradd(p, s+j, i-j);
2215 // don't add a space if previous item ended in a space
2216 if(p-buf == 1 && buf[0] == ' ' && ps->lastit != nil) {
2218 if(it->tag == Itexttag) {
2219 ss = ((Itext*)it)->s;
2221 if(k > 0 && ss[k] == ' ')
2226 additem(ps, textit(ps, _Strndup(buf, p-buf)), nil);
2231 // Add a break to ps->curstate, with extra space if sp is true.
2232 // If there was a previous break, combine this one's parameters
2233 // with that to make the amt be the max of the two and the clr
2234 // be the most general. (amt will be 0 or 1)
2235 // Also, if the immediately preceding item was a text item,
2236 // trim any whitespace from the end of it, if not in literal mode.
2237 // Finally, if this is at the very beginning of the item list
2238 // (the only thing there is a null spacer), then don't add the space.
2240 addbrk(Pstate* ps, int sp, int clr)
2250 state = ps->curstate;
2251 clr = clr|(state&(IFcleft|IFcright));
2252 if(sp && !(ps->lastit == ps->items))
2256 ps->curstate = IFbrk|sp|(state&~(IFcleft|IFcright))|clr;
2257 if(ps->lastit != ps->items) {
2258 if(!ps->literal && ps->lastit->tag == Itexttag) {
2259 t = (Itext*)ps->lastit;
2260 _splitr(t->s, _Strlen(t->s), notwhitespace, &l, &nl, &r, &nr);
2261 // try to avoid making empty items
2262 // but not crucial f the occasional one gets through
2263 if(nl == 0 && ps->prelastit != nil) {
2264 freeitems(ps->lastit);
2265 ps->lastit = ps->prelastit;
2266 ps->lastit->next = nil;
2267 ps->prelastit = nil;
2272 // need a non-nil pointer to empty string
2273 // (_Strdup(L"") returns nil)
2274 t->s = emalloc(sizeof(Rune));
2278 t->s = _Strndup(l, nl);
2286 // Add break due to a <br> or a newline within a preformatted section.
2287 // We add a null item first, with current font's height and ascent, to make
2288 // sure that the current line takes up at least that amount of vertical space.
2289 // This ensures that <br>s on empty lines cause blank lines, and that
2290 // multiple <br>s in a row give multiple blank lines.
2291 // However don't add the spacer if the previous item was something that
2292 // takes up space itself.
2294 addlinebrk(Pstate* ps, int clr)
2299 // don't want break before our null item unless the previous item
2300 // was also a null item for the purposes of line breaking
2301 obrkstate = ps->curstate&(IFbrk|IFbrksp);
2303 if(ps->lastit != nil) {
2304 if(ps->lastit->tag == Ispacertag) {
2305 if(((Ispacer*)ps->lastit)->spkind == ISPvline)
2309 ps->curstate = (ps->curstate&~(IFbrk|IFbrksp))|b;
2310 additem(ps, newispacer(ISPvline), nil);
2311 ps->curstate = (ps->curstate&~(IFbrk|IFbrksp))|obrkstate;
2315 // Add a nonbreakable space
2319 // if nbsp comes right where a break was specified,
2320 // do the break anyway (nbsp is being used to generate undiscardable
2321 // space rather than to prevent a break)
2322 if((ps->curstate&IFbrk) == 0)
2323 ps->curstate |= IFnobrk;
2324 additem(ps, newispacer(ISPhspace), nil);
2325 // but definitely no break on next item
2326 ps->curstate |= IFnobrk;
2329 // Change hang in ps.curstate by delta.
2330 // The amount is in 1/10ths of tabs, and is the amount that
2331 // the current contiguous set of items with a hang value set
2332 // is to be shifted left from its normal (indented) place.
2334 changehang(Pstate* ps, int delta)
2338 amt = (ps->curstate&IFhangmask) + delta;
2341 fprint(2, "warning: hang went negative\n");
2344 ps->curstate = (ps->curstate&~IFhangmask)|amt;
2347 // Change indent in ps.curstate by delta.
2349 changeindent(Pstate* ps, int delta)
2353 amt = ((ps->curstate&IFindentmask) >> IFindentshift) + delta;
2356 fprint(2, "warning: indent went negative\n");
2359 ps->curstate = (ps->curstate&~IFindentmask)|(amt << IFindentshift);
2362 // Push val on top of stack, and also return value pushed
2364 push(Stack* stk, int val)
2366 if(stk->n == Nestmax) {
2368 fprint(2, "warning: build stack overflow\n");
2371 stk->slots[stk->n++] = val;
2383 //Return top of stack, using dflt if stack is empty
2385 top(Stack* stk, int dflt)
2389 return stk->slots[stk->n-1];
2392 // pop, then return new top, with dflt if empty
2394 popretnewtop(Stack* stk, int dflt)
2401 return stk->slots[stk->n-1];
2404 // Copy fromstk entries into tostk
2406 copystack(Stack* tostk, Stack* fromstk)
2412 memmove(tostk->slots, fromstk->slots, n*sizeof(int));
2416 popfontstyle(Pstate* ps)
2418 pop(&ps->fntstylestk);
2423 pushfontstyle(Pstate* ps, int sty)
2425 push(&ps->fntstylestk, sty);
2430 popfontsize(Pstate* ps)
2432 pop(&ps->fntsizestk);
2437 pushfontsize(Pstate* ps, int sz)
2439 push(&ps->fntsizestk, sz);
2444 setcurfont(Pstate* ps)
2449 sty = top(&ps->fntstylestk, FntR);
2450 sz = top(&ps->fntsizestk, Normal);
2455 ps->curfont = sty*NumSize + sz;
2466 pushjust(Pstate* ps, int j)
2468 push(&ps->juststk, j);
2473 setcurjust(Pstate* ps)
2478 j = top(&ps->juststk, ALleft);
2479 if(j != ps->curjust) {
2481 state = ps->curstate;
2482 state &= ~(IFrjust|IFcjust);
2485 else if(j == ALright)
2487 ps->curstate = state;
2491 // Do final rearrangement after table parsing is finished
2492 // and assign cells to grid points
2494 finish_table(Table* t)
2502 Tablecell** rowspancell;
2518 t->nrow = nrow = _listlen((List*)rl);
2519 t->rows = (Tablerow*)emalloc(nrow * sizeof(Tablerow));
2522 for(row = rl; row != nil; row = rownext) {
2523 // copy the data from the allocated Tablerow into the array slot
2525 rownext = row->next;
2532 // If rowspan is > 1 but this is the last row,
2533 // reset the rowspan
2534 if(c != nil && c->rowspan > 1 && r == nrow-2)
2537 // reverse row->cells list (along nextinrow pointers)
2540 cnext = c->nextinrow;
2541 c->nextinrow = row->cells;
2543 rcols += c->colspan;
2550 t->cols = (Tablecol*)emalloc(ncol * sizeof(Tablecol));
2552 // Reverse cells just so they are drawn in source order.
2553 // Also, trim their contents so they don't end in whitespace.
2554 t->cells = (Tablecell*)_revlist((List*)t->cells);
2555 for(c = t->cells; c != nil; c= c->next)
2557 t->grid = (Tablecell***)emalloc(nrow * sizeof(Tablecell**));
2558 for(i = 0; i < nrow; i++)
2559 t->grid[i] = (Tablecell**)emalloc(ncol * sizeof(Tablecell*));
2561 // The following arrays keep track of cells that are spanning
2562 // multiple rows; rowspancnt[i] is the number of rows left
2563 // to be spanned in column i.
2564 // When done, cell's (row,col) is upper left grid point.
2565 rowspancnt = (int*)emalloc(ncol * sizeof(int));
2566 rowspancell = (Tablecell**)emalloc(ncol * sizeof(Tablecell*));
2567 for(ri = 0; ri < nrow; ri++) {
2571 while(ci < ncol || cl != nil) {
2572 if(ci < ncol && rowspancnt[ci] > 0) {
2573 t->grid[ri][ci] = rowspancell[ci];
2586 if(ci + cspan > ncol) {
2587 // because of row spanning, we calculated
2588 // ncol incorrectly; adjust it
2589 newncol = ci + cspan;
2590 t->cols = (Tablecol*)erealloc(t->cols, newncol * sizeof(Tablecol));
2591 rowspancnt = (int*)erealloc(rowspancnt, newncol * sizeof(int));
2592 rowspancell = (Tablecell**)erealloc(rowspancell, newncol * sizeof(Tablecell*));
2594 memset(t->cols+ncol, 0, k*sizeof(Tablecol));
2595 memset(rowspancnt+ncol, 0, k*sizeof(int));
2596 memset(rowspancell+ncol, 0, k*sizeof(Tablecell*));
2597 for(j = 0; j < nrow; j++) {
2598 t->grid[j] = (Tablecell**)erealloc(t->grid[j], newncol * sizeof(Tablecell*));
2599 memset(t->grid[j], 0, k*sizeof(Tablecell*));
2601 t->ncol = ncol = newncol;
2605 for(i = 0; i < cspan; i++) {
2606 t->grid[ri][ci] = c;
2608 rowspancnt[ci] = rspan - 1;
2609 rowspancell[ci] = c;
2620 // Remove tail of cell content until it isn't whitespace.
2622 trim_cell(Tablecell* c)
2635 while(c->content != nil && dropping) {
2638 while(p->next != nil) {
2643 if(!(p->state&IFnobrk)) {
2644 if(p->tag == Itexttag) {
2647 _splitr(s, _Strlen(s), notwhitespace, &x, &nx, &y, &ny);
2648 if(nx != 0 && ny != 0) {
2649 q->s = _Strndup(x, nx);
2665 // Caller must free answer (eventually).
2667 listmark(uchar ty, int n)
2680 s[0] = (ty == LTdisc)? 0x2022 // bullet
2681 : ((ty == LTsquare)? 0x220e // filled square
2682 : 0x2218); // degree
2687 s = runesmprint("%d.", n);
2696 s = _newstr((n <= 25)? 2 : 3);
2702 s[i++] = n2 + (ty == LTa)? 'a' : 'A';
2704 s[i++] = n + (ty == LTa)? 'a' : 'A';
2713 fprint(2, "warning: unimplemented roman number > %d\n", NROMAN);
2719 for(i = 0; i < n2; i++)
2720 s[i] = (ty == LTi)? tolower(t[i]) : t[i];
2728 // Find map with given name in di.maps.
2729 // If not there, add one, copying name.
2730 // Ownership of map remains with di->maps list.
2732 getmap(Docinfo* di, Rune* name)
2736 for(m = di->maps; m != nil; m = m->next) {
2737 if(!_Strcmp(name, m->name))
2740 m = (Map*)emalloc(sizeof(Map));
2741 m->name = _Strdup(name);
2748 // Transfers ownership of href to Area
2750 newarea(int shape, Rune* href, int target, Area* link)
2754 a = (Area*)emalloc(sizeof(Area));
2762 // Return string value associated with attid in tok, nil if none.
2763 // Caller must free the result (eventually).
2765 aval(Token* tok, int attid)
2769 _tokaval(tok, attid, &ans, 1); // transfers string ownership from token to ans
2773 // Like aval, but use dflt if there was no such attribute in tok.
2774 // Caller must free the result (eventually).
2776 astrval(Token* tok, int attid, Rune* dflt)
2780 if(_tokaval(tok, attid, &ans, 1))
2781 return ans; // transfers string ownership from token to ans
2783 return _Strdup(dflt);
2786 // Here we're supposed to convert to an int,
2787 // and have a default when not found
2789 aintval(Token* tok, int attid, int dflt)
2793 if(!_tokaval(tok, attid, &ans, 0) || ans == nil)
2799 // Like aintval, but result should be >= 0
2801 auintval(Token* tok, int attid, int dflt)
2806 if(!_tokaval(tok, attid, &ans, 0) || ans == nil)
2810 return v >= 0? v : 0;
2814 // int conversion, but with possible error check (if warning)
2821 ans = _Strtol(s, &eptr, 10);
2824 eptr = _Strclass(eptr, notwhitespace);
2826 fprint(2, "warning: expected integer, got %S\n", s);
2832 // Attribute value when need a table to convert strings to ints
2834 atabval(Token* tok, int attid, StringInt* tab, int ntab, int dflt)
2840 if(_tokaval(tok, attid, &aval, 0)) {
2841 if(!_lookup(tab, ntab, aval, _Strlen(aval), &ans)) {
2844 fprint(2, "warning: name not found in table lookup: %S\n", aval);
2850 // Attribute value when supposed to be a color
2852 acolorval(Token* tok, int attid, int dflt)
2858 if(_tokaval(tok, attid, &aval, 0))
2859 ans = color(aval, dflt);
2863 // Attribute value when supposed to be a target frame name
2865 atargval(Token* tok, int dflt)
2871 if(_tokaval(tok, Atarget, &aval, 0)){
2872 ans = targetid(aval);
2877 // special for list types, where "i" and "I" are different,
2878 // but "square" and "SQUARE" are the same
2880 listtyval(Token* tok, int dflt)
2887 if(_tokaval(tok, Atype, &aval, 0)) {
2907 fprint(2, "warning: unknown list element type %c\n", aval[0]);
2911 if(!_Strncmpci(aval, n, L"circle"))
2913 else if(!_Strncmpci(aval, n, L"disc"))
2915 else if(!_Strncmpci(aval, n, L"square"))
2919 fprint(2, "warning: unknown list element type %S\n", aval);
2926 // Attribute value when value is a URL, possibly relative to base.
2927 // FOR NOW: leave the url relative.
2928 // Caller must free the result (eventually).
2930 aurlval(Token* tok, int attid, Rune* dflt, Rune* base)
2937 if(_tokaval(tok, attid, &url, 0) && url != nil)
2938 ans = removeallwhite(url);
2940 ans = _Strdup(dflt);
2944 // Return copy of s but with all whitespace (even internal) removed.
2945 // This fixes some buggy URL specification strings.
2947 removeallwhite(Rune* s)
2957 for(i = 0; i < n; i++) {
2959 if(c >= 256 || !isspace(c))
2965 for(i = 0; i < n; i++) {
2967 if(c >= 256 || !isspace(c))
2977 // Attribute value when mere presence of attr implies value of 1,
2978 // but if there is an integer there, return it as the value.
2980 aflagval(Token* tok, int attid)
2986 if(_tokaval(tok, attid, &sval, 0)) {
2995 makealign(int halign, int valign)
3004 // Make an Align (two alignments, horizontal and vertical)
3009 atabval(tok, Aalign, align_tab, NALIGNTAB, ALnone),
3010 atabval(tok, Avalign, align_tab, NALIGNTAB, ALnone));
3013 // Make a Dimen, based on value of attid attr
3015 adimen(Token* tok, int attid)
3019 if(_tokaval(tok, attid, &wd, 0))
3020 return parsedim(wd, _Strlen(wd));
3022 return makedimen(Dnone, 0);
3025 // Parse s[0:n] as num[.[num]][unit][%|*]
3027 parsedim(Rune* s, int ns)
3044 _splitl(s, ns, L"^0-9", &l, &nl, &r, &nr);
3046 spec = 1000*_Strtol(l, nil, 10);
3047 if(nr > 0 && r[0] == '.') {
3048 _splitl(r+1, nr-1, L"^0-9", &f, &nf, &r, &nr);
3051 for(i = 0; i < nf; i++) {
3052 spec = spec + mul*(f[i]-'0');
3064 if(!_Strncmpci(units, 2, L"pt"))
3065 spec = (spec*Tkdpi)/72;
3066 else if(!_Strncmpci(units, 2, L"pi"))
3067 spec = (spec*12*Tkdpi)/72;
3068 else if(!_Strncmpci(units, 2, L"in"))
3070 else if(!_Strncmpci(units, 2, L"cm"))
3071 spec = (spec*100*Tkdpi)/254;
3072 else if(!_Strncmpci(units, 2, L"mm"))
3073 spec = (spec*10*Tkdpi)/254;
3074 else if(!_Strncmpci(units, 2, L"em"))
3078 fprint(2, "warning: unknown units %C%Cs\n", units[0], units[1]);
3084 else if(r[0] == '*')
3090 else if(nr == 1 && r[0] == '*') {
3094 return makedimen(kind, spec);
3098 setdimarray(Token* tok, int attid, Dimen** pans, int* panslen)
3104 Rune* a[SMALLBUFSIZE];
3105 int an[SMALLBUFSIZE];
3107 if(_tokaval(tok, attid, &s, 0)) {
3108 nc = _splitall(s, _Strlen(s), L", ", a, an, SMALLBUFSIZE);
3110 d = (Dimen*)emalloc(nc * sizeof(Dimen));
3111 for(k = 0; k < nc; k++) {
3112 d[k] = parsedim(a[k], an[k]);
3124 makebackground(Rune* imageurl, int color)
3128 bg.image = imageurl;
3134 newitext(Rune* s, int fnt, int fg, int voff, int ul)
3139 t = (Itext*)emalloc(sizeof(Itext));
3150 newirule(int align, int size, int noshade, int color, Dimen wspec)
3154 r = (Irule*)emalloc(sizeof(Irule));
3158 r->noshade = noshade;
3164 // Map is owned elsewhere.
3166 newiimage(Rune* src, Rune* altrep, int align, int width, int height,
3167 int hspace, int vspace, int border, int ismap, Map* map)
3175 i = (Iimage*)emalloc(sizeof(Iimage));
3182 i->imheight = height;
3192 newiformfield(Formfield* ff)
3196 f = (Iformfield*)emalloc(sizeof(Iformfield));
3197 f->tag = Iformfieldtag;
3203 newitable(Table* tab)
3207 t = (Itable*)emalloc(sizeof(Itable));
3214 newifloat(Item* it, int side)
3218 f = (Ifloat*)emalloc(sizeof(Ifloat));
3227 newispacer(int spkind)
3231 s = (Ispacer*)emalloc(sizeof(Ispacer));
3232 s->tag = Ispacertag;
3237 // Free one item (caller must deal with next pointer)
3249 free(((Itext*)it)->s);
3257 freeformfield(((Iformfield*)it)->formfield);
3260 freetable(((Itable*)it)->table);
3263 freeitem(((Ifloat*)it)->item);
3272 freescriptevents(ga->events);
3278 // Free list of items chained through next pointer
3280 freeitems(Item* ithead)
3294 freeformfield(Formfield* ff)
3304 freeitem(ff->image);
3305 for(o = ff->options; o != nil; o = onext) {
3323 // We'll find all the unique cells via t->cells and next pointers.
3324 // (Other pointers to cells in the table are duplicates of these)
3325 for(c = t->cells; c != nil; c = cnext) {
3327 freeitems(c->content);
3330 if(t->grid != nil) {
3331 for(i = 0; i < t->nrow; i++)
3337 freeitems(t->caption);
3349 // Form doesn't own its fields (Iformfield items do)
3354 freeforms(Form* fhead)
3359 for(f = fhead; f != nil; f = fnext) {
3366 freeanchor(Anchor* a)
3377 freeanchors(Anchor* ahead)
3382 for(a = ahead; a != nil; a = anext) {
3389 freedestanchor(DestAnchor* da)
3399 freedestanchors(DestAnchor* dahead)
3404 for(da = dahead; da != nil; da = danext) {
3419 static void freekidinfos(Kidinfo* khead);
3422 freekidinfo(Kidinfo* k)
3427 freekidinfos(k->kidinfos);
3437 freekidinfos(Kidinfo* khead)
3442 for(k = khead; k != nil; k = knext) {
3458 for(a = m->areas; a != nil; a = anext) {
3466 freemaps(Map* mhead)
3471 for(m = mhead; m != nil; m = mnext) {
3478 freedocinfo(Docinfo* d)
3485 freeitem(d->backgrounditem);
3487 freekidinfos(d->kidinfo);
3488 freeanchors(d->anchors);
3489 freedestanchors(d->dests);
3490 freeforms(d->forms);
3492 // tables, images, and formfields are freed when
3493 // the items pointing at them are freed
3498 freepstate(Pstate* p)
3500 freeitems(p->items);
3505 freepstatestack(Pstate* pshead)
3510 for(p = pshead; p != nil; p = pnext) {
3535 char buf[BIGBUFSIZE];
3537 it = va_arg(f->args, Item*);
3543 cl = state&(IFcleft|IFcright);
3546 if(cl == (IFcleft|IFcright))
3548 else if(cl == IFcleft)
3553 bi = snprint(buf, nbuf, "brk(%d%s)", (state&IFbrksp)? 1 : 0, p);
3556 bi += snprint(buf+bi, nbuf-bi, " nobrk");
3558 bi += snprint(buf+bi, nbuf-bi, " nowrap");
3560 bi += snprint(buf+bi, nbuf-bi, " rjust");
3562 bi += snprint(buf+bi, nbuf-bi, " cjust");
3564 bi += snprint(buf+bi, nbuf-bi, " smap");
3565 indent = (state&IFindentmask) >> IFindentshift;
3567 bi += snprint(buf+bi, nbuf-bi, " indent=%d", indent);
3568 hang = state&IFhangmask;
3570 bi += snprint(buf+bi, nbuf-bi, " hang=%d", hang);
3575 bi += snprint(buf+bi, nbuf-bi, " Text '%S', fnt=%d, fg=%x", t->s, t->fnt, t->fg);
3580 bi += snprint(buf+bi, nbuf-bi, "Rule size=%d, al=%S, wspec=", r->size, stringalign(r->align));
3581 bi += dimprint(buf+bi, nbuf-bi, r->wspec);
3586 bi += snprint(buf+bi, nbuf-bi,
3587 "Image src=%S, alt=%S, al=%S, w=%d, h=%d hsp=%d, vsp=%d, bd=%d, map=%S",
3588 i->imsrc, i->altrep? i->altrep : L"", stringalign(i->align), i->imwidth, i->imheight,
3589 i->hspace, i->vspace, i->border, i->map? i->map->name : L"");
3593 ff = ((Iformfield*)it)->formfield;
3594 if(ff->ftype == Ftextarea)
3596 else if(ff->ftype == Fselect)
3599 ty = _revlookup(input_tab, NINPUTTAB, ff->ftype);
3603 bi += snprint(buf+bi, nbuf-bi, "Formfield %S, fieldid=%d, formid=%d, name=%S, value=%S",
3604 ty, ff->fieldid, ff->form->formid, ff->name? ff->name : L"",
3605 ff->value? ff->value : L"");
3609 tab = ((Itable*)it)->table;
3610 bi += snprint(buf+bi, nbuf-bi, "Table tableid=%d, width=", tab->tableid);
3611 bi += dimprint(buf+bi, nbuf-bi, tab->width);
3612 bi += snprint(buf+bi, nbuf-bi, ", nrow=%d, ncol=%d, ncell=%d, totw=%d, toth=%d\n",
3613 tab->nrow, tab->ncol, tab->ncell, tab->totw, tab->toth);
3614 for(c = tab->cells; c != nil; c = c->next)
3615 bi += snprint(buf+bi, nbuf-bi, "Cell %d.%d, at (%d,%d) ",
3616 tab->tableid, c->cellid, c->row, c->col);
3617 bi += snprint(buf+bi, nbuf-bi, "End of Table %d", tab->tableid);
3622 bi += snprint(buf+bi, nbuf-bi, "Float, x=%d y=%d, side=%S, it=%I",
3623 fl->x, fl->y, stringalign(fl->side), fl->item);
3624 bi += snprint(buf+bi, nbuf-bi, "\n\t");
3629 switch(((Ispacer*)it)->spkind) {
3640 bi += snprint(buf+bi, nbuf-bi, "Spacer %s ", p);
3643 bi += snprint(buf+bi, nbuf-bi, " w=%d, h=%d, a=%d, anchor=%d\n",
3644 it->width, it->height, it->ascent, it->anchorid);
3646 return fmtstrcpy(f, buf);
3649 // String version of alignment 'a'
3655 s = _revlookup(align_tab, NALIGNTAB, a);
3661 // Put at most nbuf chars of representation of d into buf,
3662 // and return number of characters put
3664 dimprint(char* buf, int nbuf, Dimen d)
3670 n += snprint(buf, nbuf, "%d", dimenspec(d));
3680 printitems(Item* items, char* msg)
3684 fprint(2, "%s\n", msg);
3687 fprint(2, "%I", il);
3693 newgenattr(Rune* id, Rune* class, Rune* style, Rune* title, SEvent* events)
3697 g = (Genattr*)emalloc(sizeof(Genattr));
3707 newformfield(int ftype, int fieldid, Form* form, Rune* name,
3708 Rune* value, int size, int maxlength, Formfield* link)
3712 ff = (Formfield*)emalloc(sizeof(Formfield));
3714 ff->fieldid = fieldid;
3719 ff->maxlength = maxlength;
3725 // Transfers ownership of value and display to Option.
3727 newoption(int selected, Rune* value, Rune* display, Option* link)
3731 o = (Option*)emalloc(sizeof(Option));
3732 o->selected = selected;
3734 o->display = display;
3740 newform(int formid, Rune* name, Rune* action, int target, int method, Form* link)
3744 f = (Form*)emalloc(sizeof(Form));
3757 newtable(int tableid, Align align, Dimen width, int border,
3758 int cellspacing, int cellpadding, Background bg, Token* tok, Table* link)
3762 t = (Table*)emalloc(sizeof(Table));
3763 t->tableid = tableid;
3767 t->cellspacing = cellspacing;
3768 t->cellpadding = cellpadding;
3770 t->caption_place = ALbottom;
3771 t->caption_lay = nil;
3778 newtablerow(Align align, Background bg, int flags, Tablerow* link)
3782 tr = (Tablerow*)emalloc(sizeof(Tablerow));
3784 tr->background = bg;
3791 newtablecell(int cellid, int rowspan, int colspan, Align align, Dimen wspec, int hspec,
3792 Background bg, int flags, Tablecell* link)
3796 c = (Tablecell*)emalloc(sizeof(Tablecell));
3799 c->rowspan = rowspan;
3800 c->colspan = colspan;
3811 newanchor(int index, Rune* name, Rune* href, int target, Anchor* link)
3815 a = (Anchor*)emalloc(sizeof(Anchor));
3825 newdestanchor(int index, Rune* name, Item* item, DestAnchor* link)
3829 d = (DestAnchor*)emalloc(sizeof(DestAnchor));
3838 newscriptevent(int type, Rune* script, SEvent* link)
3842 ans = (SEvent*)emalloc(sizeof(SEvent));
3844 ans->script = script;
3850 freescriptevents(SEvent* ehead)
3865 makedimen(int kind, int spec)
3869 if(spec&Dkindmask) {
3871 fprint(2, "warning: dimension spec too big: %d\n", spec);
3874 d.kindspec = kind|spec;
3881 return (d.kindspec&Dkindmask);
3887 return (d.kindspec&Dspecmask);
3891 newkidinfo(int isframeset, Kidinfo* link)
3895 ki = (Kidinfo*)emalloc(sizeof(Kidinfo));
3896 ki->isframeset = isframeset;
3898 ki->flags = FRhscrollauto|FRvscrollauto;
3899 ki->marginw = FRKIDMARGIN;
3900 ki->marginh = FRKIDMARGIN;
3912 d = (Docinfo*)emalloc(sizeof(Docinfo));
3918 resetdocinfo(Docinfo* d)
3920 memset(d, 0, sizeof(Docinfo));
3921 d->background = makebackground(nil, White);
3927 d->chset = ISO_8859_1;
3928 d->scripttype = TextJavascript;
3932 // Use targetmap array to keep track of name <-> targetid mapping.
3933 // Use real malloc(), and never free
3940 l = targetmapsize*sizeof *targetmap;
3941 targetmap = emalloc(l);
3942 memset(targetmap, 0, l);
3943 targetmap[0].key = _Strdup(L"_top");
3944 targetmap[0].val = FTtop;
3945 targetmap[1].key = _Strdup(L"_self");
3946 targetmap[1].val = FTself;
3947 targetmap[2].key = _Strdup(L"_parent");
3948 targetmap[2].val = FTparent;
3949 targetmap[3].key = _Strdup(L"_blank");
3950 targetmap[3].val = FTblank;
3963 for(i = 0; i < ntargets; i++)
3964 if(_Strcmp(s, targetmap[i].key) == 0)
3965 return targetmap[i].val;
3966 if(i == targetmapsize) {
3967 targetmapsize += 10;
3968 targetmap = erealloc(targetmap, targetmapsize*sizeof(StringInt));
3970 targetmap[i].key = _Strdup(s);
3971 targetmap[i].val = i;
3977 targetname(int targid)
3981 for(i = 0; i < ntargets; i++)
3982 if(targetmap[i].val == targid)
3983 return targetmap[i].key;
3987 // Convert HTML color spec to RGB value, returning dflt if can't.
3988 // Argument is supposed to be a valid HTML color, or "".
3989 // Return the RGB value of the color, using dflt if s
3990 // is nil or an invalid color.
3992 color(Rune* s, int dflt)
3999 if(_lookup(color_tab, NCOLORS, s, _Strlen(s), &v))
4003 v = _Strtol(s, &rest, 16);
4011 #define HUGEPIX 10000
4013 // A "shallow" validitem, that doesn't follow next links
4014 // or descend into tables.
4025 ok = (i->tag >= Itexttag && i->tag <= Ispacertag) &&
4026 (i->next == nil || validptr(i->next)) &&
4027 (i->width >= 0 && i->width < HUGEPIX) &&
4028 (i->height >= 0 && i->height < HUGEPIX) &&
4029 (i->ascent > -HUGEPIX && i->ascent < HUGEPIX) &&
4030 (i->anchorid >= 0) &&
4031 (i->genattr == nil || validptr(i->genattr));
4032 // also, could check state for ridiculous combinations
4033 // also, could check anchorid for within-doc-range
4038 ok = validStr(ti->s) &&
4039 (ti->fnt >= 0 && ti->fnt < NumStyle*NumSize) &&
4040 (ti->ul == ULnone || ti->ul == ULunder || ti->ul == ULmid);
4044 ok = (validvalign(ri->align) || validhalign(ri->align)) &&
4045 (ri->size >=0 && ri->size < HUGEPIX);
4049 ok = (ii->imsrc == nil || validptr(ii->imsrc)) &&
4050 (ii->width >= 0 && ii->width < HUGEPIX) &&
4051 (ii->height >= 0 && ii->height < HUGEPIX) &&
4052 (ii->imwidth >= 0 && ii->imwidth < HUGEPIX) &&
4053 (ii->imheight >= 0 && ii->imheight < HUGEPIX) &&
4054 (ii->altrep == nil || validStr(ii->altrep)) &&
4055 (ii->map == nil || validptr(ii->map)) &&
4056 (validvalign(ii->align) || validhalign(ii->align)) &&
4057 (ii->nextimage == nil || validptr(ii->nextimage));
4060 ok = validformfield(((Iformfield*)i)->formfield);
4063 ok = validptr((Itable*)i);
4067 ok = (fi->side == ALleft || fi->side == ALright) &&
4068 validitem(fi->item) &&
4069 (fi->item->tag == Iimagetag || fi->item->tag == Itabletag);
4072 a = ((Ispacer*)i)->spkind;
4073 ok = a==ISPnull || a==ISPvline || a==ISPhspace || a==ISPgeneral;
4081 // "deep" validation, that checks whole list of items,
4082 // and descends into tables and floated tables.
4083 // nil is ok for argument.
4091 while(i != nil && ok) {
4094 if(i->tag == Itabletag) {
4095 ok = validtable(((Itable*)i)->table);
4097 else if(i->tag == Ifloattag) {
4098 ii = ((Ifloat*)i)->item;
4099 if(ii->tag == Itabletag)
4100 ok = validtable(((Itable*)ii)->table);
4104 fprint(2, "invalid item: %I\n", i);
4112 validformfield(Formfield* f)
4116 ok = (f->next == nil || validptr(f->next)) &&
4117 (f->ftype >= 0 && f->ftype <= Ftextarea) &&
4119 (f->form == nil || validptr(f->form)) &&
4120 (f->name == nil || validStr(f->name)) &&
4121 (f->value == nil || validStr(f->value)) &&
4122 (f->options == nil || validptr(f->options)) &&
4123 (f->image == nil || validitem(f->image)) &&
4124 (f->events == nil || validptr(f->events));
4125 // when all built, should have f->fieldid < f->form->nfields,
4126 // but this may be called during build...
4130 // "deep" validation -- checks cell contents too
4132 validtable(Table* t)
4138 ok = (t->next == nil || validptr(t->next)) &&
4142 validalign(t->align) &&
4143 validdimen(t->width) &&
4144 (t->border >= 0 && t->border < HUGEPIX) &&
4145 (t->cellspacing >= 0 && t->cellspacing < HUGEPIX) &&
4146 (t->cellpadding >= 0 && t->cellpadding < HUGEPIX) &&
4147 validitems(t->caption) &&
4148 (t->caption_place == ALtop || t->caption_place == ALbottom) &&
4149 (t->totw >= 0 && t->totw < HUGEPIX) &&
4150 (t->toth >= 0 && t->toth < HUGEPIX) &&
4151 (t->tabletok == nil || validptr(t->tabletok));
4152 // during parsing, t->rows has list;
4153 // only when parsing is done is t->nrow set > 0
4154 if(ok && t->nrow > 0 && t->ncol > 0) {
4155 // table is "finished"
4156 for(i = 0; i < t->nrow && ok; i++)
4157 ok = validtablerow(t->rows+i);
4158 for(j = 0; j < t->ncol && ok; j++)
4159 ok = validtablecol(t->cols+j);
4160 for(c = t->cells; c != nil && ok; c = c->next)
4161 ok = validtablecell(c);
4162 for(i = 0; i < t->nrow && ok; i++)
4163 for(j = 0; j < t->ncol && ok; j++)
4164 ok = validptr(t->grid[i][j]);
4172 return a == ALnone || a == ALmiddle || a == ALbottom || a == ALtop || a == ALbaseline;
4178 return a == ALnone || a == ALleft || a == ALcenter || a == ALright ||
4179 a == ALjustify || a == ALchar;
4185 return validhalign(a.halign) && validvalign(a.valign);
4195 s = d.kindspec&Dspecmask;
4196 switch(d.kindspec&Dkindmask) {
4212 validtablerow(Tablerow* r)
4214 return (r->cells == nil || validptr(r->cells)) &&
4215 (r->height >= 0 && r->height < HUGEPIX) &&
4216 (r->ascent > -HUGEPIX && r->ascent < HUGEPIX) &&
4217 validalign(r->align);
4221 validtablecol(Tablecol* c)
4223 return c->width >= 0 && c->width < HUGEPIX
4224 && validalign(c->align);
4228 validtablecell(Tablecell* c)
4232 ok = (c->next == nil || validptr(c->next)) &&
4233 (c->nextinrow == nil || validptr(c->nextinrow)) &&
4234 (c->content == nil || validptr(c->content)) &&
4235 (c->lay == nil || validptr(c->lay)) &&
4238 validalign(c->align) &&
4239 validdimen(c->wspec) &&
4243 if(c->content != nil)
4244 ok = validitems(c->content);
4252 // TODO: a better job of this.
4253 // For now, just dereference, which cause a bomb
4264 return s != nil && validptr(s);