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 */
63 /* Some layout parameters */
65 FRKIDMARGIN = 6, /* default margin around kid frames */
66 IMGHSPACE = 0, /* default hspace for images (0 matches IE, Netscape) */
67 IMGVSPACE = 0, /* default vspace for images */
68 FLTIMGHSPACE = 2, /* default hspace for float images */
69 TABSP = 5, /* default cellspacing for tables */
70 TABPAD = 1, /* default cell padding for tables */
71 LISTTAB = 1, /* number of tabs to indent lists */
72 BQTAB = 1, /* number of tabs to indent blockquotes */
73 HRSZ = 2, /* thickness of horizontal rules */
74 SUBOFF = 4, /* vertical offset for subscripts */
75 SUPOFF = 6, /* vertical offset for superscripts */
76 NBSP = 160 /* non-breaking space character */
79 /* These tables must be sorted */
80 static StringInt *align_tab;
81 static AsciiInt _align_tab[] = {
82 {"baseline", ALbaseline},
86 {"justify", ALjustify},
92 #define NALIGNTAB (sizeof(_align_tab)/sizeof(StringInt))
94 static StringInt *input_tab;
95 static AsciiInt _input_tab[] = {
97 {"checkbox", Fcheckbox},
101 {"password", Fpassword},
107 #define NINPUTTAB (sizeof(_input_tab)/sizeof(StringInt))
109 static StringInt *clear_tab;
110 static AsciiInt _clear_tab[] = {
111 {"all", IFcleft|IFcright},
115 #define NCLEARTAB (sizeof(_clear_tab)/sizeof(StringInt))
117 static StringInt *fscroll_tab;
118 static AsciiInt _fscroll_tab[] = {
119 {"auto", FRhscrollauto|FRvscrollauto},
121 {"yes", FRhscroll|FRvscroll},
123 #define NFSCROLLTAB (sizeof(_fscroll_tab)/sizeof(StringInt))
125 static StringInt *shape_tab;
126 static AsciiInt _shape_tab[] = {
128 {"circle", SHcircle},
132 {"rectangle", SHrect}
134 #define NSHAPETAB (sizeof(_shape_tab)/sizeof(StringInt))
136 static StringInt *method_tab;
137 static AsciiInt _method_tab[] = {
141 #define NMETHODTAB (sizeof(_method_tab)/sizeof(StringInt))
144 static char* _roman[15]= {
145 "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X",
146 "XI", "XII", "XIII", "XIV", "XV"
150 /* List number types */
152 LTdisc, LTsquare, LTcircle, LT1, LTa, LTA, LTi, LTI
159 BLBA = (BL|SPBefore|SPAfter)
162 /* blockbrk[tag] is break info for a block level element, or one */
163 /* of a few others that get the same treatment re ending open paragraphs */
164 /* and requiring a line break / vertical space before them. */
165 /* If we want a line of space before the given element, SPBefore is OR'd in. */
166 /* If we want a line of space after the given element, SPAfter is OR'd in. */
168 static uchar blockbrk[Numtags]= {
183 /*Tblockquote*/ BLBA,
271 /* attrinfo is information about attributes. */
272 /* The AGEN value means that the attribute is generic (applies to almost all elements) */
273 static uchar attrinfo[Numattrs]= {
275 /*Aaccept_charset*/ 0,
343 /*Aondblclick*/ AGEN,
345 /*Aonkeypress*/ AGEN,
348 /*Aonmousedown*/ AGEN,
349 /*Aonmousemove*/ AGEN,
350 /*Aonmouseout*/ AGEN,
351 /*Aonmouseover*/ AGEN,
392 static uchar scriptev[Numattrs]= {
394 /*Aaccept_charset*/ 0,
459 /*Aonblur*/ SEonblur,
460 /*Aonchange*/ SEonchange,
461 /*Aonclick*/ SEonclick,
462 /*Aondblclick*/ SEondblclick,
463 /*Aonfocus*/ SEonfocus,
464 /*Aonkeypress*/ SEonkeypress,
465 /*Aonkeyup*/ SEonkeyup,
466 /*Aonload*/ SEonload,
467 /*Aonmousedown*/ SEonmousedown,
468 /*Aonmousemove*/ SEonmousemove,
469 /*Aonmouseout*/ SEonmouseout,
470 /*Aonmouseover*/ SEonmouseover,
471 /*Aonmouseup*/ SEonmouseup,
472 /*Aonreset*/ SEonreset,
473 /*Aonselect*/ SEonselect,
474 /*Aonsubmit*/ SEonsubmit,
475 /*Aonunload*/ SEonunload,
511 /* Color lookup table */
512 static StringInt *color_tab;
513 static AsciiInt _color_tab[] = {
517 {"fuchsia", 0xFF00FF},
521 {"maroon", 0x800000},
524 {"purple", 0x800080},
526 {"silver", 0xC0C0C0},
531 #define NCOLORS (sizeof(_color_tab)/sizeof(StringInt))
533 static StringInt *targetmap;
534 static int targetmapsize;
537 static int buildinited = 0;
539 #define SMALLBUFSIZE 240
540 #define BIGBUFSIZE 2000
545 static Align aalign(Token* tok);
546 static int acolorval(Token* tok, int attid, int dflt);
547 static void addbrk(Pstate* ps, int sp, int clr);
548 static void additem(Pstate* ps, Item* it, Token* tok);
549 static void addlinebrk(Pstate* ps, int clr);
550 static void addnbsp(Pstate* ps);
551 static void addtext(Pstate* ps, Rune* s);
552 static Dimen adimen(Token* tok, int attid);
553 static int aflagval(Token* tok, int attid);
554 static int aintval(Token* tok, int attid, int dflt);
555 static Rune* astrval(Token* tok, int attid, Rune* dflt);
556 static int atabval(Token* tok, int attid, StringInt* tab, int ntab, int dflt);
557 static int atargval(Token* tok, int dflt);
558 static int auintval(Token* tok, int attid, int dflt);
559 static Rune* aurlval(Token* tok, int attid, Rune* dflt, Rune* base);
560 static Rune* aval(Token* tok, int attid);
561 static void buildinit(void);
562 static Pstate* cell_pstate(Pstate* oldps, int ishead);
563 static void changehang(Pstate* ps, int delta);
564 static void changeindent(Pstate* ps, int delta);
565 static int color(Rune* s, int dflt);
566 static void copystack(Stack* tostk, Stack* fromstk);
567 static int dimprint(char* buf, int nbuf, Dimen d);
568 static Pstate* finishcell(Table* curtab, Pstate* psstk);
569 static void finish_table(Table* t);
570 static void freeanchor(Anchor* a);
571 static void freedestanchor(DestAnchor* da);
572 static void freeform(Form* f);
573 static void freeformfield(Formfield* ff);
574 static void freeitem(Item* it);
575 static void freepstate(Pstate* p);
576 static void freepstatestack(Pstate* pshead);
577 static void freescriptevents(SEvent* ehead);
578 static void freetable(Table* t);
579 static Map* getmap(Docinfo* di, Rune* name);
580 static Rune* getpcdata(Token* toks, int tokslen, int* ptoki);
581 static Pstate* lastps(Pstate* psl);
582 static Rune* listmark(uchar ty, int n);
583 static int listtyval(Token* tok, int dflt);
584 static Align makealign(int halign, int valign);
585 static Background makebackground(Rune* imgurl, int color);
586 static Dimen makedimen(int kind, int spec);
587 static Anchor* newanchor(int index, Rune* name, Rune* href, int target, Anchor* link);
588 static Area* newarea(int shape, Rune* href, int target, Area* link);
589 static DestAnchor* newdestanchor(int index, Rune* name, Item* item, DestAnchor* link);
590 static Docinfo* newdocinfo(void);
591 static Genattr* newgenattr(Rune* id, Rune* class, Rune* style, Rune* title, SEvent* events);
592 static Form* newform(int formid, Rune* name, Rune* action,
593 int target, int method, Form* link);
594 static Formfield* newformfield(int ftype, int fieldid, Form* form, Rune* name,
595 Rune* value, int size, int maxlength, Formfield* link);
596 static Item* newifloat(Item* it, int side);
597 static Item* newiformfield(Formfield* ff);
598 static Item* newiimage(Rune* src, Rune* altrep, int align, int width, int height,
599 int hspace, int vspace, int border, int ismap, Map* map);
600 static Item* newirule(int align, int size, int noshade, Dimen wspec);
601 static Item* newispacer(int spkind);
602 static Item* newitable(Table* t);
603 static ItemSource* newitemsource(Docinfo* di);
604 static Item* newitext(Rune* s, int fnt, int fg, int voff, int ul);
605 static Kidinfo* newkidinfo(int isframeset, Kidinfo* link);
606 static Option* newoption(int selected, Rune* value, Rune* display, Option* link);
607 static Pstate* newpstate(Pstate* link);
608 static SEvent* newscriptevent(int type, Rune* script, SEvent* link);
609 static Table* newtable(int tableid, Align align, Dimen width, int border,
610 int cellspacing, int cellpadding, Background bg, Token* tok, Table* link);
611 static Tablecell* newtablecell(int cellid, int rowspan, int colspan, Align align, Dimen wspec,
612 int hspec, Background bg, int flags, Tablecell* link);
613 static Tablerow* newtablerow(Align align, Background bg, int flags, Tablerow* link);
614 static Dimen parsedim(Rune* s, int ns);
615 static void pop(Stack* stk);
616 static void popfontsize(Pstate* ps);
617 static void popfontstyle(Pstate* ps);
618 static void popjust(Pstate* ps);
619 static int popretnewtop(Stack* stk, int dflt);
620 static int push(Stack* stk, int val);
621 static void pushfontsize(Pstate* ps, int sz);
622 static void pushfontstyle(Pstate* ps, int sty);
623 static void pushjust(Pstate* ps, int j);
624 static Item* textit(Pstate* ps, Rune* s);
625 static Rune* removeallwhite(Rune* s);
626 static void resetdocinfo(Docinfo* d);
627 static void setcurfont(Pstate* ps);
628 static void setcurjust(Pstate* ps);
629 static void setdimarray(Token* tok, int attid, Dimen** pans, int* panslen);
630 static Rune* stringalign(int a);
631 static void targetmapinit(void);
632 static int toint(Rune* s);
633 static int top(Stack* stk, int dflt);
634 static void trim_cell(Tablecell* c);
635 static int validalign(Align a);
636 static int validdimen(Dimen d);
637 static int validformfield(Formfield* f);
638 static int validhalign(int a);
639 static int validptr(void* p);
640 static int validStr(Rune* s);
641 static int validtable(Table* t);
642 static int validtablerow(Tablerow* r);
643 static int validtablecol(Tablecol* c);
644 static int validtablecell(Tablecell* c);
645 static int validvalign(int a);
646 static int Iconv(Fmt *f);
652 roman = _cvtstringtab(_roman, nelem(_roman));
653 color_tab = _cvtstringinttab(_color_tab, nelem(_color_tab));
654 method_tab = _cvtstringinttab(_method_tab, nelem(_method_tab));
655 shape_tab = _cvtstringinttab(_shape_tab, nelem(_shape_tab));
656 fscroll_tab = _cvtstringinttab(_fscroll_tab, nelem(_fscroll_tab));
657 clear_tab = _cvtstringinttab(_clear_tab, nelem(_clear_tab));
658 input_tab = _cvtstringinttab(_input_tab, nelem(_input_tab));
659 align_tab = _cvtstringinttab(_align_tab, nelem(_align_tab));
661 fmtinstall('I', Iconv);
667 newitemsource(Docinfo* di)
673 if(di->mediatype != TextHtml) {
674 ps->curstate &= ~IFwrap;
676 pushfontstyle(ps, FntT);
678 is = (ItemSource*)emalloc(sizeof(ItemSource));
692 static Item *getitems(ItemSource* is, uchar* data, int datalen);
694 /* Parse an html document and create a list of layout items. */
695 /* Allocate and return document info in *pdi. */
696 /* When caller is done with the items, it should call */
697 /* freeitems on the returned result, and then */
698 /* freedocinfo(*pdi). */
700 parsehtml(uchar* data, int datalen, Rune* pagesrc, int mtype, int chset, Docinfo** pdi)
707 di->src = _Strdup(pagesrc);
708 di->base = _Strdup(pagesrc);
709 di->mediatype = mtype;
712 is = newitemsource(di);
713 it = getitems(is, data, datalen);
714 freepstatestack(is->psstk);
719 /* Get a group of tokens for lexer, parse them, and create */
720 /* a list of layout items. */
721 /* When caller is done with the items, it should call */
722 /* freeitems on the returned result. */
724 getitems(ItemSource* is, uchar* data, int datalen)
799 doscripts = 0; /* for now */
803 toks = _gettoks(data, datalen, di->chset, di->mediatype, &tokslen);
805 for(; toki < tokslen; toki++) {
808 fprint(2, "build: curstate %ux, token %T\n", ps->curstate, tok);
817 else if(tag < Numtags + RBRA) {
818 brk = blockbrk[tag - RBRA];
823 addbrk(ps, brksp, 0);
829 /* check common case first (Data), then switch statement on tag */
831 /* Lexing didn't pay attention to SGML record boundary rules: */
832 /* \n after start tag or before end tag to be discarded. */
833 /* (Lex has already discarded all \r's). */
834 /* Some pages assume this doesn't happen in <PRE> text, */
835 /* so we won't do it if literal is true. */
836 /* BUG: won't discard \n before a start tag that begins */
837 /* the next bufferful of tokens. */
844 pt = toks[toki - 1].tag;
845 /* IE and Netscape both ignore this rule (contrary to spec) */
846 /* if previous tag was img */
847 if(pt < Numtags && pt != Timg && j > 0 && s[0] == '\n')
850 if(toki < tokslen - 1) {
851 nt = toks[toki + 1].tag;
852 if(nt >= RBRA && nt < Numtags + RBRA && j > i && s[j - 1] == '\n')
857 s = _Strsubstr(s, i, j);
863 _trimwhite(s, n, &t, &nt);
876 tok->text = nil; /* token doesn't own string anymore */
884 /* Some abbrevs used in following DTD comments */
885 /* %text = #PCDATA */
886 /* | TT | I | B | U | STRIKE | BIG | SMALL | SUB | SUP */
887 /* | EM | STRONG | DFN | CODE | SAMP | KBD | VAR | CITE */
888 /* | A | IMG | APPLET | FONT | BASEFONT | BR | SCRIPT | MAP */
889 /* | INPUT | SELECT | TEXTAREA */
890 /* %block = P | UL | OL | DIR | MENU | DL | PRE | DL | DIV | CENTER */
891 /* | BLOCKQUOTE | FORM | ISINDEX | HR | TABLE */
892 /* %flow = (%text | %block)* */
893 /* %body.content = (%heading | %text | %block | ADDRESS)* */
895 /* <!ELEMENT A - - (%text) -(A)> */
896 /* Anchors are not supposed to be nested, but you sometimes see */
897 /* href anchors inside destination anchors. */
899 if(ps->curanchor != 0) {
901 fprint(2, "warning: nested <A> or missing </A>\n");
904 name = aval(tok, Aname);
905 href = aurlval(tok, Ahref, nil, di->base);
906 /* ignore rel, rev, and title attrs */
908 target = atargval(tok, di->target);
909 di->anchors = newanchor(++is->nanchors, name, href, target, di->anchors);
911 name = _Strdup(name); /* for DestAnchor construction, below */
912 ps->curanchor = is->nanchors;
913 ps->curfg = push(&ps->fgstk, di->link);
914 ps->curul = push(&ps->ulstk, ULunder);
917 /* add a null item to be destination */
918 additem(ps, newispacer(ISPnull), tok);
919 di->dests = newdestanchor(++is->nanchors, name, ps->lastit, di->dests);
924 if(ps->curanchor != 0) {
925 ps->curfg = popretnewtop(&ps->fgstk, di->text);
926 ps->curul = popretnewtop(&ps->ulstk, ULnone);
931 /* <!ELEMENT APPLET - - (PARAM | %text)* > */
932 /* We can't do applets, so ignore PARAMS, and let */
933 /* the %text contents appear for the alternative rep */
936 if(warn && tag == Tapplet)
937 fprint(2, "warning: <APPLET> ignored\n");
940 /* <!ELEMENT AREA - O EMPTY> */
945 fprint(2, "warning: <AREA> not inside <MAP>\n");
948 map->areas = newarea(atabval(tok, Ashape, shape_tab, NSHAPETAB, SHrect),
949 aurlval(tok, Ahref, nil, di->base),
950 atargval(tok, di->target),
952 setdimarray(tok, Acoords, &map->areas->coords, &map->areas->ncoords);
955 /* <!ELEMENT (B|STRONG) - - (%text)*> */
958 pushfontstyle(ps, FntB);
976 /* <!ELEMENT BASE - O EMPTY> */
979 di->base = aurlval(tok, Ahref, di->base, di->base);
982 di->target = atargval(tok, di->target);
985 /* <!ELEMENT BASEFONT - O EMPTY> */
987 ps->adjsize = aintval(tok, Asize, 3) - 3;
990 /* <!ELEMENT (BIG|SMALL) - - (%text)*> */
998 pushfontsize(ps, sz);
1006 /* <!ELEMENT BLOCKQUOTE - - %body.content> */
1008 changeindent(ps, BQTAB);
1011 case Tblockquote+RBRA:
1012 changeindent(ps, -BQTAB);
1015 /* <!ELEMENT BODY O O %body.content> */
1018 bg = makebackground(nil, acolorval(tok, Abgcolor, di->background.color));
1019 bgurl = aurlval(tok, Abackground, nil, di->base);
1021 if(di->backgrounditem != nil)
1022 freeitem((Item*)di->backgrounditem);
1023 /* really should remove old item from di->images list, */
1024 /* but there should only be one BODY element ... */
1025 di->backgrounditem = (Iimage*)newiimage(bgurl, nil, ALnone, 0, 0, 0, 0, 0, 0, nil);
1026 di->backgrounditem->nextimage = di->images;
1027 di->images = di->backgrounditem;
1030 di->background = bg;
1031 di->text = acolorval(tok, Atext, di->text);
1032 di->link = acolorval(tok, Alink, di->link);
1033 di->vlink = acolorval(tok, Avlink, di->vlink);
1034 di->alink = acolorval(tok, Aalink, di->alink);
1035 if(di->text != ps->curfg) {
1036 ps->curfg = di->text;
1042 /* HTML spec says ignore things after </body>, */
1043 /* but IE and Netscape don't */
1044 /* ps.skipping = 1; */
1047 /* <!ELEMENT BR - O EMPTY> */
1049 addlinebrk(ps, atabval(tok, Aclear, clear_tab, NCLEARTAB, 0));
1052 /* <!ELEMENT CAPTION - - (%text;)*> */
1056 fprint(2, "warning: <CAPTION> outside <TABLE>\n");
1059 if(curtab->caption != nil) {
1061 fprint(2, "warning: more than one <CAPTION> in <TABLE>\n");
1065 curtab->caption_place = atabval(tok, Aalign, align_tab, NALIGNTAB, ALtop);
1070 if(curtab == nil || nextps == nil) {
1072 fprint(2, "warning: unexpected </CAPTION>\n");
1075 curtab->caption = ps->items->next;
1085 al = atabval(tok, Aalign, align_tab, NALIGNTAB, ps->curjust);
1094 /* <!ELEMENT DD - O %flow > */
1096 if(ps->hangstk.n == 0) {
1098 fprint(2, "warning: <DD> not inside <DL\n");
1101 h = top(&ps->hangstk, 0);
1103 changehang(ps, -10*LISTTAB);
1106 push(&ps->hangstk, 0);
1109 /*<!ELEMENT (DIR|MENU) - - (LI)+ -(%block) > */
1110 /*<!ELEMENT (OL|UL) - - (LI)+> */
1115 changeindent(ps, LISTTAB);
1116 push(&ps->listtypestk, listtyval(tok, (tag==Tol)? LT1 : LTdisc));
1117 push(&ps->listcntstk, aintval(tok, Astart, 1));
1124 if(ps->listtypestk.n == 0) {
1126 fprint(2, "warning: %T ended no list\n", tok);
1130 pop(&ps->listtypestk);
1131 pop(&ps->listcntstk);
1132 changeindent(ps, -LISTTAB);
1135 /* <!ELEMENT DL - - (DT|DD)+ > */
1137 changeindent(ps, LISTTAB);
1138 push(&ps->hangstk, 0);
1142 if(ps->hangstk.n == 0) {
1144 fprint(2, "warning: unexpected </DL>\n");
1147 changeindent(ps, -LISTTAB);
1148 if(top(&ps->hangstk, 0) != 0)
1149 changehang(ps, -10*LISTTAB);
1153 /* <!ELEMENT DT - O (%text)* > */
1155 if(ps->hangstk.n == 0) {
1157 fprint(2, "warning: <DT> not inside <DL>\n");
1160 h = top(&ps->hangstk, 0);
1163 changehang(ps, -10*LISTTAB);
1164 changehang(ps, 10*LISTTAB);
1165 push(&ps->hangstk, 1);
1168 /* <!ELEMENT FONT - - (%text)*> */
1170 sz = top(&ps->fntsizestk, Normal);
1171 if(_tokaval(tok, Asize, &nsz, 0)) {
1172 if(_prefix(L(Lplus), nsz))
1173 sz = Normal + _Strtol(nsz+1, nil, 10) + ps->adjsize;
1174 else if(_prefix(L(Lminus), nsz))
1175 sz = Normal - _Strtol(nsz+1, nil, 10) + ps->adjsize;
1177 sz = Normal + (_Strtol(nsz, nil, 10) - 3);
1179 ps->curfg = push(&ps->fgstk, acolorval(tok, Acolor, ps->curfg));
1180 pushfontsize(ps, sz);
1184 if(ps->fgstk.n == 0) {
1186 fprint(2, "warning: unexpected </FONT>\n");
1189 ps->curfg = popretnewtop(&ps->fgstk, di->text);
1193 /* <!ELEMENT FORM - - %body.content -(FORM) > */
1195 if(is->curform != nil) {
1197 fprint(2, "warning: <FORM> nested inside another\n");
1200 action = aurlval(tok, Aaction, di->base, di->base);
1202 name = astrval(tok, Aname, s);
1205 target = atargval(tok, di->target);
1206 method = atabval(tok, Amethod, method_tab, NMETHODTAB, HGet);
1207 if(warn && _tokaval(tok, Aenctype, &enctype, 0) &&
1208 _Strcmp(enctype, L(Lappl_form)))
1209 fprint(2, "form enctype %S not handled\n", enctype);
1210 frm = newform(++is->nforms, name, action, target, method, di->forms);
1216 if(is->curform == nil) {
1218 fprint(2, "warning: unexpected </FORM>\n");
1221 /* put fields back in input order */
1222 is->curform->fields = (Formfield*)_revlist((List*)is->curform->fields);
1226 /* <!ELEMENT FRAME - O EMPTY> */
1231 fprint(2, "warning: <FRAME> not in <FRAMESET>\n");
1234 ks->kidinfos = kd = newkidinfo(0, ks->kidinfos);
1235 kd->src = aurlval(tok, Asrc, nil, di->base);
1236 kd->name = aval(tok, Aname);
1237 if(kd->name == nil) {
1238 s = _ltoStr(++is->nframes);
1239 kd->name = _Strdup2(L(Lfr), s);
1242 kd->marginw = auintval(tok, Amarginwidth, 0);
1243 kd->marginh = auintval(tok, Amarginheight, 0);
1244 kd->framebd = auintval(tok, Aframeborder, 1);
1245 kd->flags = atabval(tok, Ascrolling, fscroll_tab, NFSCROLLTAB, kd->flags);
1246 norsz = aflagval(tok, Anoresize);
1248 kd->flags |= FRnoresize;
1251 /* <!ELEMENT FRAMESET - - (FRAME|FRAMESET)+> */
1253 ks = newkidinfo(1, nil);
1258 ks->next = pks->kidinfos;
1261 ks->nextframeset = pks;
1263 setdimarray(tok, Arows, &ks->rows, &ks->nrows);
1264 if(ks->nrows == 0) {
1265 ks->rows = (Dimen*)emalloc(sizeof(Dimen));
1267 ks->rows[0] = makedimen(Dpercent, 100);
1269 setdimarray(tok, Acols, &ks->cols, &ks->ncols);
1270 if(ks->ncols == 0) {
1271 ks->cols = (Dimen*)emalloc(sizeof(Dimen));
1273 ks->cols[0] = makedimen(Dpercent, 100);
1277 case Tframeset+RBRA:
1278 if(is->kidstk == nil) {
1280 fprint(2, "warning: unexpected </FRAMESET>\n");
1284 /* put kids back in original order */
1285 /* and add blank frames to fill out cells */
1286 n = ks->nrows*ks->ncols;
1287 nblank = n - _listlen((List*)ks->kidinfos);
1289 ks->kidinfos = newkidinfo(0, ks->kidinfos);
1290 ks->kidinfos = (Kidinfo*)_revlist((List*)ks->kidinfos);
1291 is->kidstk = is->kidstk->nextframeset;
1292 if(is->kidstk == nil) {
1299 /* <!ELEMENT H1 - - (%text;)*>, etc. */
1307 if(ps->items == ps->lastit)
1309 addbrk(ps, bramt, IFcleft|IFcright);
1310 sz = Verylarge - (tag - Th1);
1313 pushfontsize(ps, sz);
1314 sty = top(&ps->fntstylestk, FntR);
1317 pushfontstyle(ps, sty);
1318 pushjust(ps, atabval(tok, Aalign, align_tab, NALIGNTAB, ps->curjust));
1328 addbrk(ps, 1, IFcleft|IFcright);
1335 /* HTML spec says ignore regular markup in head, */
1336 /* but Netscape and IE don't */
1337 /* ps.skipping = 1; */
1344 /* <!ELEMENT HR - O EMPTY> */
1346 al = atabval(tok, Aalign, align_tab, NALIGNTAB, ALcenter);
1347 sz = auintval(tok, Asize, HRSZ);
1348 wd = adimen(tok, Awidth);
1349 if(dimenkind(wd) == Dnone)
1350 wd = makedimen(Dpercent, 100);
1351 nosh = aflagval(tok, Anoshade);
1352 additem(ps, newirule(al, sz, nosh, wd), tok);
1362 pushfontstyle(ps, FntI);
1365 /* <!ELEMENT IMG - O EMPTY> */
1368 oldcuranchor = ps->curanchor;
1369 if(_tokaval(tok, Ausemap, &usemap, 0)) {
1370 if(!_prefix(L(Lhash), usemap)) {
1372 fprint(2, "warning: can't handle non-local map %S\n", usemap);
1375 map = getmap(di, usemap+1);
1376 if(ps->curanchor == 0) {
1377 di->anchors = newanchor(++is->nanchors, nil, nil, di->target, di->anchors);
1378 ps->curanchor = is->nanchors;
1382 align = atabval(tok, Aalign, align_tab, NALIGNTAB, ALbottom);
1384 if(ps->curanchor != 0)
1386 src = aurlval(tok, Asrc, nil, di->base);
1389 fprint(2, "warning: <img> has no src attribute\n");
1390 ps->curanchor = oldcuranchor;
1393 img = newiimage(src,
1396 auintval(tok, Awidth, 0),
1397 auintval(tok, Aheight, 0),
1398 auintval(tok, Ahspace, IMGHSPACE),
1399 auintval(tok, Avspace, IMGVSPACE),
1400 auintval(tok, Aborder, dfltbd),
1401 aflagval(tok, Aismap),
1403 if(align == ALleft || align == ALright) {
1404 additem(ps, newifloat(img, align), tok);
1405 /* if no hspace specified, use FLTIMGHSPACE */
1406 if(!_tokaval(tok, Ahspace, &val, 0))
1407 ((Iimage*)img)->hspace = FLTIMGHSPACE;
1411 additem(ps, img, tok);
1414 ((Iimage*)img)->nextimage = di->images;
1415 di->images = (Iimage*)img;
1417 ps->curanchor = oldcuranchor;
1420 /* <!ELEMENT INPUT - O EMPTY> */
1423 if(is->curform == nil) {
1425 fprint(2, "<INPUT> not inside <FORM>\n");
1428 is->curform->fields = field = newformfield(
1429 atabval(tok, Atype, input_tab, NINPUTTAB, Ftext),
1430 ++is->curform->nfields,
1434 auintval(tok, Asize, 0),
1435 auintval(tok, Amaxlength, 1000),
1436 is->curform->fields);
1437 if(aflagval(tok, Achecked))
1438 field->flags = FFchecked;
1440 switch(field->ftype) {
1444 if(field->size == 0)
1449 if(field->name == nil) {
1451 fprint(2, "warning: checkbox form field missing name\n");
1454 if(field->value == nil)
1455 field->value = _Strdup(L(Lone));
1459 if(field->name == nil || field->value == nil) {
1461 fprint(2, "warning: radio form field missing name or value\n");
1467 if(field->value == nil)
1468 field->value = _Strdup(L(Lsubmit));
1469 if(field->name == nil)
1470 field->name = _Strdup(L(Lnoname));
1474 src = aurlval(tok, Asrc, nil, di->base);
1477 fprint(2, "warning: image form field missing src\n");
1480 /* width and height attrs aren't specified in HTML 3.2, */
1481 /* but some people provide them and they help avoid */
1483 field->image = newiimage(src,
1484 astrval(tok, Aalt, L(Lsubmit)),
1485 atabval(tok, Aalign, align_tab, NALIGNTAB, ALbottom),
1486 auintval(tok, Awidth, 0), auintval(tok, Aheight, 0),
1488 ii = (Iimage*)field->image;
1489 ii->nextimage = di->images;
1494 if(field->value == nil)
1495 field->value = _Strdup(L(Lreset));
1499 if(field->value == nil)
1500 field->value = _Strdup(L(Lspace));
1503 ffit = newiformfield(field);
1504 additem(ps, ffit, tok);
1505 if(ffit->genattr != nil)
1506 field->events = ffit->genattr->events;
1509 /* <!ENTITY ISINDEX - O EMPTY> */
1512 prompt = astrval(tok, Aprompt, L(Lindex));
1513 target = atargval(tok, di->target);
1514 additem(ps, textit(ps, prompt), tok);
1515 frm = newform(++is->nforms,
1522 ff = newformfield(Ftext,
1525 _Strdup(L(Lisindex)),
1532 additem(ps, newiformfield(ff), tok);
1536 /* <!ELEMENT LI - O %flow> */
1538 if(ps->listtypestk.n == 0) {
1540 fprint(2, "<LI> not in list\n");
1543 ty = top(&ps->listtypestk, 0);
1544 ty2 = listtyval(tok, ty);
1547 push(&ps->listtypestk, ty2);
1549 v = aintval(tok, Avalue, top(&ps->listcntstk, 1));
1550 if(ty == LTdisc || ty == LTsquare || ty == LTcircle)
1551 hang = 10*LISTTAB - 3;
1553 hang = 10*LISTTAB - 1;
1554 changehang(ps, hang);
1555 addtext(ps, listmark(ty, v));
1556 push(&ps->listcntstk, v + 1);
1557 changehang(ps, -hang);
1561 /* <!ELEMENT MAP - - (AREA)+> */
1563 if(_tokaval(tok, Aname, &name, 0))
1564 is->curmap = getmap(di, name);
1571 fprint(2, "warning: unexpected </MAP>\n");
1574 map->areas = (Area*)_revlist((List*)map->areas);
1580 if(_tokaval(tok, Ahttp_equiv, &equiv, 0)) {
1581 val = aval(tok, Acontent);
1583 if(!_Strncmpci(equiv, n, L(Lrefresh)))
1585 else if(!_Strncmpci(equiv, n, L(Lcontent))) {
1587 if(!_Strncmpci(val, n, L(Ljavascript))
1588 || !_Strncmpci(val, n, L(Ljscript1))
1589 || !_Strncmpci(val, n, L(Ljscript)))
1590 di->scripttype = TextJavascript;
1593 fprint(2, "unimplemented script type %S\n", val);
1594 di->scripttype = UnknownType;
1600 /* Nobr is NOT in HMTL 4.0, but it is ubiquitous on the web */
1603 ps->curstate &= ~IFwrap;
1607 ps->curstate |= IFwrap;
1610 /* We do frames, so skip stuff in noframes */
1615 case Tnoframes+RBRA:
1619 /* We do scripts (if enabled), so skip stuff in noscripts */
1625 case Tnoscript+RBRA:
1630 /* <!ELEMENT OPTION - O ( //PCDATA)> */
1632 if(is->curform == nil || is->curform->fields == nil) {
1634 fprint(2, "warning: <OPTION> not in <SELECT>\n");
1637 field = is->curform->fields;
1638 if(field->ftype != Fselect) {
1640 fprint(2, "warning: <OPTION> not in <SELECT>\n");
1643 val = aval(tok, Avalue);
1644 option = newoption(aflagval(tok, Aselected), val, nil, field->options);
1645 field->options = option;
1646 option->display = getpcdata(toks, tokslen, &toki);
1648 option->value = _Strdup(option->display);
1651 /* <!ELEMENT P - O (%text)* > */
1653 pushjust(ps, atabval(tok, Aalign, align_tab, NALIGNTAB, ps->curjust));
1661 /* <!ELEMENT PARAM - O EMPTY> */
1662 /* Do something when we do applets... */
1666 /* <!ELEMENT PRE - - (%text)* -(IMG|BIG|SMALL|SUB|SUP|FONT) > */
1668 ps->curstate &= ~IFwrap;
1671 pushfontstyle(ps, FntT);
1675 ps->curstate |= IFwrap;
1682 /* <!ELEMENT SCRIPT - - CDATA> */
1685 if(!di->hasscripts) {
1686 if(di->scripttype == TextJavascript) {
1687 /* TODO: initialize script if nec. */
1688 /* initjscript(di); */
1693 if(!di->hasscripts) {
1695 fprint(2, "warning: <SCRIPT> ignored\n");
1699 scriptsrc = aurlval(tok, Asrc, nil, di->base);
1701 if(scriptsrc != nil) {
1703 fprint(2, "warning: non-local <SCRIPT> ignored\n");
1707 script = getpcdata(toks, tokslen, &toki);
1711 fprint(2, "script ignored\n");
1721 /* <!ELEMENT SELECT - - (OPTION+)> */
1723 if(is->curform == nil) {
1725 fprint(2, "<SELECT> not inside <FORM>\n");
1728 field = newformfield(Fselect,
1729 ++is->curform->nfields,
1733 auintval(tok, Asize, 0),
1735 is->curform->fields);
1736 is->curform->fields = field;
1737 if(aflagval(tok, Amultiple))
1738 field->flags = FFmultiple;
1739 ffit = newiformfield(field);
1740 additem(ps, ffit, tok);
1741 if(ffit->genattr != nil)
1742 field->events = ffit->genattr->events;
1743 /* throw away stuff until next tag (should be <OPTION>) */
1744 s = getpcdata(toks, tokslen, &toki);
1750 if(is->curform == nil || is->curform->fields == nil) {
1752 fprint(2, "warning: unexpected </SELECT>\n");
1755 field = is->curform->fields;
1756 if(field->ftype != Fselect)
1758 /* put options back in input order */
1759 field->options = (Option*)_revlist((List*)field->options);
1762 /* <!ELEMENT (STRIKE|U) - - (%text)*> */
1765 ps->curul = push(&ps->ulstk, (tag==Tstrike)? ULmid : ULunder);
1770 if(ps->ulstk.n == 0) {
1772 fprint(2, "warning: unexpected %T\n", tok);
1775 ps->curul = popretnewtop(&ps->ulstk, ULnone);
1778 /* <!ELEMENT STYLE - - CDATA> */
1781 fprint(2, "warning: unimplemented <STYLE>\n");
1789 /* <!ELEMENT (SUB|SUP) - - (%text)*> */
1793 ps->curvoff += SUBOFF;
1795 ps->curvoff -= SUPOFF;
1796 push(&ps->voffstk, ps->curvoff);
1797 sz = top(&ps->fntsizestk, Normal);
1798 pushfontsize(ps, sz - 1);
1803 if(ps->voffstk.n == 0) {
1805 fprint(2, "warning: unexpected %T\n", tok);
1808 ps->curvoff = popretnewtop(&ps->voffstk, 0);
1812 /* <!ELEMENT TABLE - - (CAPTION?, TR+)> */
1815 tab = newtable(++is->ntables,
1817 adimen(tok, Awidth),
1818 aflagval(tok, Aborder),
1819 auintval(tok, Acellspacing, TABSP),
1820 auintval(tok, Acellpadding, TABPAD),
1821 makebackground(nil, acolorval(tok, Abgcolor, ps->curbg.color)),
1831 fprint(2, "warning: unexpected </TABLE>\n");
1834 isempty = (curtab->cells == nil);
1837 fprint(2, "warning: <TABLE> has no cells\n");
1840 ps = finishcell(curtab, ps);
1841 if(curtab->rows != nil)
1842 curtab->rows->flags = 0;
1843 finish_table(curtab);
1847 tabitem = newitable(curtab);
1848 al = curtab->align.halign;
1852 additem(ps, newifloat(tabitem, al), tok);
1856 pushjust(ps, ALcenter);
1862 additem(ps, tabitem, curtab->tabletok);
1868 if(is->tabstk == nil) {
1870 fprint(2, "warning: table stack is wrong\n");
1873 is->tabstk = is->tabstk->next;
1874 curtab->next = di->tables;
1875 di->tables = curtab;
1876 curtab = is->tabstk;
1881 /* <!ELEMENT (TH|TD) - O %body.content> */
1882 /* Cells for a row are accumulated in reverse order. */
1883 /* We push ps on a stack, and use a new one to accumulate */
1884 /* the contents of the cell. */
1889 fprint(2, "%T outside <TABLE>\n", tok);
1896 ps = finishcell(curtab, ps);
1898 if(curtab->rows != nil)
1900 if(tr == nil || !tr->flags) {
1902 fprint(2, "%T outside row\n", tok);
1903 tr = newtablerow(makealign(ALnone, ALnone),
1904 makebackground(nil, curtab->background.color),
1909 ps = cell_pstate(ps, tag == Tth);
1911 if(aflagval(tok, Anowrap)) {
1913 ps->curstate &= ~IFwrap;
1917 c = newtablecell(curtab->cells==nil? 1 : curtab->cells->cellid+1,
1918 auintval(tok, Arowspan, 1),
1919 auintval(tok, Acolspan, 1),
1921 adimen(tok, Awidth),
1922 auintval(tok, Aheight, 0),
1923 makebackground(nil, acolorval(tok, Abgcolor, tr->background.color)),
1927 ps->curbg = c->background;
1928 if(c->align.halign == ALnone) {
1929 if(tr->align.halign != ALnone)
1930 c->align.halign = tr->align.halign;
1932 c->align.halign = ALcenter;
1934 c->align.halign = ALleft;
1936 if(c->align.valign == ALnone) {
1937 if(tr->align.valign != ALnone)
1938 c->align.valign = tr->align.valign;
1940 c->align.valign = ALmiddle;
1942 c->nextinrow = tr->cells;
1948 if(curtab == nil || curtab->cells == nil) {
1950 fprint(2, "unexpected %T\n", tok);
1953 ps = finishcell(curtab, ps);
1956 /* <!ELEMENT TEXTAREA - - ( //PCDATA)> */
1958 if(is->curform == nil) {
1960 fprint(2, "<TEXTAREA> not inside <FORM>\n");
1963 field = newformfield(Ftextarea,
1964 ++is->curform->nfields,
1970 is->curform->fields);
1971 is->curform->fields = field;
1972 field->rows = auintval(tok, Arows, 3);
1973 field->cols = auintval(tok, Acols, 50);
1974 field->value = getpcdata(toks, tokslen, &toki);
1975 if(warn && toki < tokslen - 1 && toks[toki + 1].tag != Ttextarea + RBRA)
1976 fprint(2, "warning: <TEXTAREA> data ended by %T\n", &toks[toki + 1]);
1977 ffit = newiformfield(field);
1978 additem(ps, ffit, tok);
1979 if(ffit->genattr != nil)
1980 field->events = ffit->genattr->events;
1983 /* <!ELEMENT TITLE - - ( //PCDATA)* -(%head.misc)> */
1985 di->doctitle = getpcdata(toks, tokslen, &toki);
1986 if(warn && toki < tokslen - 1 && toks[toki + 1].tag != Ttitle + RBRA)
1987 fprint(2, "warning: <TITLE> data ended by %T\n", &toks[toki + 1]);
1990 /* <!ELEMENT TR - O (TH|TD)+> */
1991 /* rows are accumulated in reverse order in curtab->rows */
1995 fprint(2, "warning: <TR> outside <TABLE>\n");
2002 ps = finishcell(curtab, ps);
2003 if(curtab->rows != nil)
2004 curtab->rows->flags = 0;
2005 curtab->rows = newtablerow(aalign(tok),
2006 makebackground(nil, acolorval(tok, Abgcolor, curtab->background.color)),
2012 if(curtab == nil || curtab->rows == nil) {
2014 fprint(2, "warning: unexpected </TR>\n");
2017 ps = finishcell(curtab, ps);
2019 if(tr->cells == nil) {
2021 fprint(2, "warning: empty row\n");
2022 curtab->rows = tr->next;
2029 /* <!ELEMENT (TT|CODE|KBD|SAMP) - - (%text)*> */
2034 pushfontstyle(ps, FntT);
2037 /* Tags that have empty action */
2044 case Tbasefont+RBRA:
2061 case Ttextarea+RBRA:
2066 /* Tags not implemented */
2074 case Tfieldset+RBRA:
2086 case Toptgroup+RBRA:
2092 fprint(2, "warning: unimplemented HTML tag: %S\n", tagnames[tag]);
2098 fprint(2, "warning: unknown HTML tag: %S\n", tok->text);
2102 /* some pages omit trailing </table> */
2103 while(curtab != nil) {
2105 fprint(2, "warning: <TABLE> not closed\n");
2106 if(curtab->cells != nil) {
2107 ps = finishcell(curtab, ps);
2108 if(curtab->cells == nil) {
2110 fprint(2, "warning: empty table\n");
2113 if(curtab->rows != nil)
2114 curtab->rows->flags = 0;
2115 finish_table(curtab);
2117 additem(ps, newitable(curtab), curtab->tabletok);
2121 if(is->tabstk != nil)
2122 is->tabstk = is->tabstk->next;
2123 curtab->next = di->tables;
2124 di->tables = curtab;
2125 curtab = is->tabstk;
2127 outerps = lastps(ps);
2128 ans = outerps->items->next;
2129 /* note: ans may be nil and di->kids not nil, if there's a frameset! */
2130 outerps->items = newispacer(ISPnull);
2131 outerps->lastit = outerps->items;
2133 if(ans != nil && di->hasscripts) {
2134 /* TODO evalscript(nil); */
2140 assert(validitems(ans));
2142 fprint(2, "getitems returning nil\n");
2144 printitems(ans, "getitems returning:");
2149 /* Concatenate together maximal set of Data tokens, starting at toks[toki+1]. */
2150 /* Lexer has ensured that there will either be a following non-data token or */
2151 /* we will be at eof. */
2152 /* Return emallocd trimmed concatenation, and update *ptoki to last used toki */
2154 getpcdata(Token* toks, int tokslen, int* ptoki)
2166 /* first find length of answer */
2167 toki = (*ptoki) + 1;
2168 while(toki < tokslen) {
2170 if(tok->tag == Data) {
2172 anslen += _Strlen(tok->text);
2177 /* now make up the initial answer */
2179 ans = _newstr(anslen);
2181 toki = (*ptoki) + 1;
2182 while(toki < tokslen) {
2184 if(tok->tag == Data) {
2186 p = _Stradd(p, tok->text, _Strlen(tok->text));
2192 _trimwhite(ans, anslen, &trimans, &trimanslen);
2193 if(trimanslen != anslen) {
2195 ans = _Strndup(trimans, trimanslen);
2203 /* If still parsing head of curtab->cells list, finish it off */
2204 /* by transferring the items on the head of psstk to the cell. */
2205 /* Then pop the psstk and return the new psstk. */
2207 finishcell(Table* curtab, Pstate* psstk)
2214 if((c->flags&TFparsing)) {
2215 psstknext = psstk->next;
2216 if(psstknext == nil) {
2218 fprint(2, "warning: parse state stack is wrong\n");
2221 c->content = psstk->items->next;
2222 c->flags &= ~TFparsing;
2231 /* Make a new Pstate for a cell, based on the old pstate, oldps. */
2232 /* Also, put the new ps on the head of the oldps stack. */
2234 cell_pstate(Pstate* oldps, int ishead)
2239 ps = newpstate(oldps);
2241 ps->curanchor = oldps->curanchor;
2242 copystack(&ps->fntstylestk, &oldps->fntstylestk);
2243 copystack(&ps->fntsizestk, &oldps->fntsizestk);
2244 ps->curfont = oldps->curfont;
2245 ps->curfg = oldps->curfg;
2246 ps->curbg = oldps->curbg;
2247 copystack(&ps->fgstk, &oldps->fgstk);
2248 ps->adjsize = oldps->adjsize;
2250 sty = ps->curfont%NumSize;
2251 ps->curfont = FntB*NumSize + sty;
2256 /* Return a new Pstate with default starting state. */
2257 /* Use link to add it to head of a list, if any. */
2259 newpstate(Pstate* link)
2263 ps = (Pstate*)emalloc(sizeof(Pstate));
2264 ps->curfont = DefFnt;
2266 ps->curbg.image = nil;
2267 ps->curbg.color = White;
2269 ps->curjust = ALleft;
2270 ps->curstate = IFwrap;
2271 ps->items = newispacer(ISPnull);
2272 ps->lastit = ps->items;
2273 ps->prelastit = nil;
2278 /* Return last Pstate on psl list */
2283 while(psl->next != nil)
2288 /* Add it to end of ps item chain, adding in current state from ps. */
2289 /* Also, if tok is not nil, scan it for generic attributes and assign */
2290 /* the genattr field of the item accordingly. */
2292 additem(Pstate* ps, Item* it, Token* tok)
2305 fprint(2, "warning: skipping item: %I\n", it);
2308 it->anchorid = ps->curanchor;
2309 it->state |= ps->curstate;
2317 for(a = tok->attr; a != nil; a = a->next) {
2339 assert(aid >= Aonblur && aid <= Aonunload);
2340 e = newscriptevent(scriptev[a->attid], a->value, e);
2347 it->genattr = newgenattr(i, c, s, t, e);
2349 ps->curstate &= ~(IFbrk|IFbrksp|IFnobrk|IFcleft|IFcright);
2350 ps->prelastit = ps->lastit;
2351 ps->lastit->next = it;
2355 /* Make a text item out of s, */
2356 /* using current font, foreground, vertical offset and underline state. */
2358 textit(Pstate* ps, Rune* s)
2361 return newitext(s, ps->curfont, ps->curfg, ps->curvoff + Voffbias, ps->curul);
2364 /* Add text item or items for s, paying attention to */
2365 /* current font, foreground, baseline offset, underline state, */
2366 /* and literal mode. Unless we're in literal mode, compress */
2367 /* whitespace to single blank, and, if curstate has a break, */
2368 /* trim any leading whitespace. Whether in literal mode or not, */
2369 /* turn nonbreaking spaces into spacer items with IFnobrk set. */
2371 /* In literal mode, break up s at newlines and add breaks instead. */
2372 /* Also replace tabs appropriate number of spaces. */
2373 /* In nonliteral mode, break up the items every 100 or so characters */
2374 /* just to make the layout algorithm not go quadratic. */
2376 /* addtext assumes ownership of s. */
2378 addtext(Pstate* ps, Rune* s)
2390 Rune buf[SMALLBUFSIZE];
2401 /* trim trailing blanks from line */
2402 for(k = i; k > j; k--)
2406 additem(ps, textit(ps, _Strndup(s+j, k-j)), nil);
2416 /* make ss = s[j:i] + nsp spaces */
2417 ss = _newstr(i-j+nsp);
2418 p = _Stradd(ss, s+j, i-j);
2419 p = _Stradd(p, L(Ltab2space), nsp);
2421 additem(ps, textit(ps, ss), nil);
2425 else if(s[i] == NBSP) {
2427 additem(ps, textit(ps, _Strndup(s+j, i-j)), nil);
2436 if(j == 0 && i == n) {
2437 /* just transfer s over */
2438 additem(ps, textit(ps, s), nil);
2441 additem(ps, textit(ps, _Strndup(s+j, i-j)), nil);
2446 else { /* not literal mode */
2447 if((ps->curstate&IFbrk) || ps->lastit == ps->items)
2450 if(c >= 256 || !isspace(c))
2455 for(j = i; i < n; i++) {
2456 assert(p+i-j < buf+SMALLBUFSIZE-1);
2460 p = _Stradd(p, s+j, i-j);
2462 additem(ps, textit(ps, _Strndup(buf, p-buf)), nil);
2468 if(c < 256 && isspace(c)) {
2470 p = _Stradd(p, s+j, i-j);
2474 if(c >= 256 || !isspace(c))
2481 p = _Stradd(p, s+j, i+1-j);
2485 additem(ps, textit(ps, _Strndup(buf, p-buf)), nil);
2489 if(i > j && j < n) {
2490 assert(p+i-j < buf+SMALLBUFSIZE-1);
2491 p = _Stradd(p, s+j, i-j);
2493 /* don't add a space if previous item ended in a space */
2494 if(p-buf == 1 && buf[0] == ' ' && ps->lastit != nil) {
2496 if(it->tag == Itexttag) {
2497 ss = ((Itext*)it)->s;
2499 if(k > 0 && ss[k] == ' ')
2504 additem(ps, textit(ps, _Strndup(buf, p-buf)), nil);
2509 /* Add a break to ps->curstate, with extra space if sp is true. */
2510 /* If there was a previous break, combine this one's parameters */
2511 /* with that to make the amt be the max of the two and the clr */
2512 /* be the most general. (amt will be 0 or 1) */
2513 /* Also, if the immediately preceding item was a text item, */
2514 /* trim any whitespace from the end of it, if not in literal mode. */
2515 /* Finally, if this is at the very beginning of the item list */
2516 /* (the only thing there is a null spacer), then don't add the space. */
2518 addbrk(Pstate* ps, int sp, int clr)
2528 state = ps->curstate;
2529 clr = clr|(state&(IFcleft|IFcright));
2530 if(sp && !(ps->lastit == ps->items))
2534 ps->curstate = IFbrk|sp|(state&~(IFcleft|IFcright))|clr;
2535 if(ps->lastit != ps->items) {
2536 if(!ps->literal && ps->lastit->tag == Itexttag) {
2537 t = (Itext*)ps->lastit;
2538 _splitr(t->s, _Strlen(t->s), notwhitespace, &l, &nl, &r, &nr);
2539 /* try to avoid making empty items */
2540 /* but not crucial f the occasional one gets through */
2541 if(nl == 0 && ps->prelastit != nil) {
2542 ps->lastit = ps->prelastit;
2543 ps->lastit->next = nil;
2544 ps->prelastit = nil;
2549 /* need a non-nil pointer to empty string */
2550 /* (_Strdup(L(Lempty)) returns nil) */
2551 t->s = emalloc(sizeof(Rune));
2555 t->s = _Strndup(l, nl);
2563 /* Add break due to a <br> or a newline within a preformatted section. */
2564 /* We add a null item first, with current font's height and ascent, to make */
2565 /* sure that the current line takes up at least that amount of vertical space. */
2566 /* This ensures that <br>s on empty lines cause blank lines, and that */
2567 /* multiple <br>s in a row give multiple blank lines. */
2568 /* However don't add the spacer if the previous item was something that */
2569 /* takes up space itself. */
2571 addlinebrk(Pstate* ps, int clr)
2577 /* don't want break before our null item unless the previous item */
2578 /* was also a null item for the purposes of line breaking */
2579 obrkstate = ps->curstate&(IFbrk|IFbrksp);
2582 if(ps->lastit != nil) {
2583 if(ps->lastit->tag == Ispacertag) {
2584 if(((Ispacer*)ps->lastit)->spkind == ISPvline)
2588 else if(ps->lastit->tag == Ifloattag)
2592 ps->curstate = (ps->curstate&~(IFbrk|IFbrksp))|b;
2593 additem(ps, newispacer(ISPvline), nil);
2594 ps->curstate = (ps->curstate&~(IFbrk|IFbrksp))|obrkstate;
2599 /* Add a nonbreakable space */
2603 /* if nbsp comes right where a break was specified, */
2604 /* do the break anyway (nbsp is being used to generate undiscardable */
2605 /* space rather than to prevent a break) */
2606 if((ps->curstate&IFbrk) == 0)
2607 ps->curstate |= IFnobrk;
2608 additem(ps, newispacer(ISPhspace), nil);
2609 /* but definitely no break on next item */
2610 ps->curstate |= IFnobrk;
2613 /* Change hang in ps.curstate by delta. */
2614 /* The amount is in 1/10ths of tabs, and is the amount that */
2615 /* the current contiguous set of items with a hang value set */
2616 /* is to be shifted left from its normal (indented) place. */
2618 changehang(Pstate* ps, int delta)
2622 amt = (ps->curstate&IFhangmask) + delta;
2625 fprint(2, "warning: hang went negative\n");
2628 ps->curstate = (ps->curstate&~IFhangmask)|amt;
2631 /* Change indent in ps.curstate by delta. */
2633 changeindent(Pstate* ps, int delta)
2637 amt = ((ps->curstate&IFindentmask) >> IFindentshift) + delta;
2640 fprint(2, "warning: indent went negative\n");
2643 ps->curstate = (ps->curstate&~IFindentmask)|(amt << IFindentshift);
2646 /* Push val on top of stack, and also return value pushed */
2648 push(Stack* stk, int val)
2650 if(stk->n == Nestmax) {
2652 fprint(2, "warning: build stack overflow\n");
2655 stk->slots[stk->n++] = val;
2659 /* Pop top of stack */
2667 /*Return top of stack, using dflt if stack is empty */
2669 top(Stack* stk, int dflt)
2673 return stk->slots[stk->n-1];
2676 /* pop, then return new top, with dflt if empty */
2678 popretnewtop(Stack* stk, int dflt)
2685 return stk->slots[stk->n-1];
2688 /* Copy fromstk entries into tostk */
2690 copystack(Stack* tostk, Stack* fromstk)
2696 memmove(tostk->slots, fromstk->slots, n*sizeof(int));
2700 popfontstyle(Pstate* ps)
2702 pop(&ps->fntstylestk);
2707 pushfontstyle(Pstate* ps, int sty)
2709 push(&ps->fntstylestk, sty);
2714 popfontsize(Pstate* ps)
2716 pop(&ps->fntsizestk);
2721 pushfontsize(Pstate* ps, int sz)
2723 push(&ps->fntsizestk, sz);
2728 setcurfont(Pstate* ps)
2733 sty = top(&ps->fntstylestk, FntR);
2734 sz = top(&ps->fntsizestk, Normal);
2739 ps->curfont = sty*NumSize + sz;
2750 pushjust(Pstate* ps, int j)
2752 push(&ps->juststk, j);
2757 setcurjust(Pstate* ps)
2762 j = top(&ps->juststk, ALleft);
2763 if(j != ps->curjust) {
2765 state = ps->curstate;
2766 state &= ~(IFrjust|IFcjust);
2769 else if(j == ALright)
2771 ps->curstate = state;
2775 /* Do final rearrangement after table parsing is finished */
2776 /* and assign cells to grid points */
2778 finish_table(Table* t)
2786 Tablecell** rowspancell;
2802 t->nrow = nrow = _listlen((List*)rl);
2803 t->rows = (Tablerow*)emalloc(nrow * sizeof(Tablerow));
2806 for(row = rl; row != nil; row = rownext) {
2807 /* copy the data from the allocated Tablerow into the array slot */
2809 rownext = row->next;
2815 /* If rowspan is > 1 but this is the last row, */
2816 /* reset the rowspan */
2817 if(c != nil && c->rowspan > 1 && r == nrow-2)
2820 /* reverse row->cells list (along nextinrow pointers) */
2823 cnext = c->nextinrow;
2824 c->nextinrow = row->cells;
2826 rcols += c->colspan;
2833 t->cols = (Tablecol*)emalloc(ncol * sizeof(Tablecol));
2835 /* Reverse cells just so they are drawn in source order. */
2836 /* Also, trim their contents so they don't end in whitespace. */
2837 t->cells = (Tablecell*)_revlist((List*)t->cells);
2838 for(c = t->cells; c != nil; c= c->next)
2840 t->grid = (Tablecell***)emalloc(nrow * sizeof(Tablecell**));
2841 for(i = 0; i < nrow; i++)
2842 t->grid[i] = (Tablecell**)emalloc(ncol * sizeof(Tablecell*));
2844 /* The following arrays keep track of cells that are spanning */
2845 /* multiple rows; rowspancnt[i] is the number of rows left */
2846 /* to be spanned in column i. */
2847 /* When done, cell's (row,col) is upper left grid point. */
2848 rowspancnt = (int*)emalloc(ncol * sizeof(int));
2849 rowspancell = (Tablecell**)emalloc(ncol * sizeof(Tablecell*));
2850 for(ri = 0; ri < nrow; ri++) {
2854 while(ci < ncol || cl != nil) {
2855 if(ci < ncol && rowspancnt[ci] > 0) {
2856 t->grid[ri][ci] = rowspancell[ci];
2869 if(ci + cspan > ncol) {
2870 /* because of row spanning, we calculated */
2871 /* ncol incorrectly; adjust it */
2872 newncol = ci + cspan;
2873 t->cols = (Tablecol*)erealloc(t->cols, newncol * sizeof(Tablecol));
2874 rowspancnt = (int*)erealloc(rowspancnt, newncol * sizeof(int));
2875 rowspancell = (Tablecell**)erealloc(rowspancell, newncol * sizeof(Tablecell*));
2877 memset(t->cols+ncol, 0, k*sizeof(Tablecol));
2878 memset(rowspancnt+ncol, 0, k*sizeof(int));
2879 memset(rowspancell+ncol, 0, k*sizeof(Tablecell*));
2880 for(j = 0; j < nrow; j++) {
2881 t->grid[j] = (Tablecell**)erealloc(t->grid[j], newncol * sizeof(Tablecell*));
2882 memset(t->grid[j], 0, k*sizeof(Tablecell*));
2884 t->ncol = ncol = newncol;
2888 for(i = 0; i < cspan; i++) {
2889 t->grid[ri][ci] = c;
2891 rowspancnt[ci] = rspan - 1;
2892 rowspancell[ci] = c;
2901 /* Remove tail of cell content until it isn't whitespace. */
2903 trim_cell(Tablecell* c)
2916 while(c->content != nil && dropping) {
2919 while(p->next != nil) {
2924 if(!(p->state&IFnobrk)) {
2925 if(p->tag == Itexttag) {
2928 _splitr(s, _Strlen(s), notwhitespace, &x, &nx, &y, &ny);
2929 if(nx != 0 && ny != 0) {
2930 q->s = _Strndup(x, nx);
2946 /* Caller must free answer (eventually). */
2948 listmark(uchar ty, int n)
2961 s[0] = (ty == LTdisc)? 0x2022 /* bullet */
2962 : ((ty == LTsquare)? 0x220e /* filled square */
2963 : 0x2218); /* degree */
2971 t = _Stradd(s, t, n2);
2982 s = _newstr((n <= 25)? 2 : 3);
2988 s[i++] = n2 + (ty == LTa)? 'a' : 'A';
2990 s[i++] = n + (ty == LTa)? 'a' : 'A';
2999 fprint(2, "warning: unimplemented roman number > %d\n", NROMAN);
3005 for(i = 0; i < n2; i++)
3006 s[i] = (ty == LTi)? tolower(t[i]) : t[i];
3014 /* Find map with given name in di.maps. */
3015 /* If not there, add one, copying name. */
3016 /* Ownership of map remains with di->maps list. */
3018 getmap(Docinfo* di, Rune* name)
3022 for(m = di->maps; m != nil; m = m->next) {
3023 if(!_Strcmp(name, m->name))
3026 m = (Map*)emalloc(sizeof(Map));
3027 m->name = _Strdup(name);
3034 /* Transfers ownership of href to Area */
3036 newarea(int shape, Rune* href, int target, Area* link)
3040 a = (Area*)emalloc(sizeof(Area));
3048 /* Return string value associated with attid in tok, nil if none. */
3049 /* Caller must free the result (eventually). */
3051 aval(Token* tok, int attid)
3055 _tokaval(tok, attid, &ans, 1); /* transfers string ownership from token to ans */
3059 /* Like aval, but use dflt if there was no such attribute in tok. */
3060 /* Caller must free the result (eventually). */
3062 astrval(Token* tok, int attid, Rune* dflt)
3066 if(_tokaval(tok, attid, &ans, 1))
3067 return ans; /* transfers string ownership from token to ans */
3069 return _Strdup(dflt);
3072 /* Here we're supposed to convert to an int, */
3073 /* and have a default when not found */
3075 aintval(Token* tok, int attid, int dflt)
3079 if(!_tokaval(tok, attid, &ans, 0) || ans == nil)
3085 /* Like aintval, but result should be >= 0 */
3087 auintval(Token* tok, int attid, int dflt)
3092 if(!_tokaval(tok, attid, &ans, 0) || ans == nil)
3096 return v >= 0? v : 0;
3100 /* int conversion, but with possible error check (if warning) */
3107 ans = _Strtol(s, &eptr, 10);
3110 eptr = _Strclass(eptr, notwhitespace);
3112 fprint(2, "warning: expected integer, got %S\n", s);
3118 /* Attribute value when need a table to convert strings to ints */
3120 atabval(Token* tok, int attid, StringInt* tab, int ntab, int dflt)
3126 if(_tokaval(tok, attid, &aval, 0)) {
3127 if(!_lookup(tab, ntab, aval, _Strlen(aval), &ans)) {
3130 fprint(2, "warning: name not found in table lookup: %S\n", aval);
3136 /* Attribute value when supposed to be a color */
3138 acolorval(Token* tok, int attid, int dflt)
3144 if(_tokaval(tok, attid, &aval, 0))
3145 ans = color(aval, dflt);
3149 /* Attribute value when supposed to be a target frame name */
3151 atargval(Token* tok, int dflt)
3157 if(_tokaval(tok, Atarget, &aval, 0)){
3158 ans = targetid(aval);
3163 /* special for list types, where "i" and "I" are different, */
3164 /* but "square" and "SQUARE" are the same */
3166 listtyval(Token* tok, int dflt)
3173 if(_tokaval(tok, Atype, &aval, 0)) {
3193 fprint(2, "warning: unknown list element type %c\n", aval[0]);
3197 if(!_Strncmpci(aval, n, L(Lcircle)))
3199 else if(!_Strncmpci(aval, n, L(Ldisc)))
3201 else if(!_Strncmpci(aval, n, L(Lsquare)))
3205 fprint(2, "warning: unknown list element type %S\n", aval);
3212 /* Attribute value when value is a URL, possibly relative to base. */
3213 /* FOR NOW: leave the url relative. */
3214 /* Caller must free the result (eventually). */
3216 aurlval(Token* tok, int attid, Rune* dflt, Rune* base)
3223 if(_tokaval(tok, attid, &url, 0) && url != nil)
3224 ans = removeallwhite(url);
3226 ans = _Strdup(dflt);
3230 /* Return copy of s but with all whitespace (even internal) removed. */
3231 /* This fixes some buggy URL specification strings. */
3233 removeallwhite(Rune* s)
3243 for(i = 0; i < n; i++) {
3245 if(c >= 256 || !isspace(c))
3251 for(i = 0; i < n; i++) {
3253 if(c >= 256 || !isspace(c))
3263 /* Attribute value when mere presence of attr implies value of 1, */
3264 /* but if there is an integer there, return it as the value. */
3266 aflagval(Token* tok, int attid)
3272 if(_tokaval(tok, attid, &sval, 0)) {
3281 makealign(int halign, int valign)
3290 /* Make an Align (two alignments, horizontal and vertical) */
3295 atabval(tok, Aalign, align_tab, NALIGNTAB, ALnone),
3296 atabval(tok, Avalign, align_tab, NALIGNTAB, ALnone));
3299 /* Make a Dimen, based on value of attid attr */
3301 adimen(Token* tok, int attid)
3305 if(_tokaval(tok, attid, &wd, 0))
3306 return parsedim(wd, _Strlen(wd));
3308 return makedimen(Dnone, 0);
3311 /* Parse s[0:n] as num[.[num]][unit][%|*] */
3313 parsedim(Rune* s, int ns)
3330 _splitl(s, ns, L(Lnot0to9), &l, &nl, &r, &nr);
3332 spec = 1000*_Strtol(l, nil, 10);
3333 if(nr > 0 && r[0] == '.') {
3334 _splitl(r+1, nr-1, L(Lnot0to9), &f, &nf, &r, &nr);
3337 for(i = 0; i < nf; i++) {
3338 spec = spec + mul*(f[i]-'0');
3350 if(!_Strncmpci(units, 2, L(Lpt)))
3351 spec = (spec*Tkdpi)/72;
3352 else if(!_Strncmpci(units, 2, L(Lpi)))
3353 spec = (spec*12*Tkdpi)/72;
3354 else if(!_Strncmpci(units, 2, L(Lin)))
3356 else if(!_Strncmpci(units, 2, L(Lcm)))
3357 spec = (spec*100*Tkdpi)/254;
3358 else if(!_Strncmpci(units, 2, L(Lmm)))
3359 spec = (spec*10*Tkdpi)/254;
3360 else if(!_Strncmpci(units, 2, L(Lem)))
3364 fprint(2, "warning: unknown units %C%Cs\n", units[0], units[1]);
3370 else if(r[0] == '*')
3376 else if(nr == 1 && r[0] == '*') {
3380 return makedimen(kind, spec);
3384 setdimarray(Token* tok, int attid, Dimen** pans, int* panslen)
3390 Rune* a[SMALLBUFSIZE];
3391 int an[SMALLBUFSIZE];
3393 if(_tokaval(tok, attid, &s, 0)) {
3394 nc = _splitall(s, _Strlen(s), L(Lcommaspace), a, an, SMALLBUFSIZE);
3396 d = (Dimen*)emalloc(nc * sizeof(Dimen));
3397 for(k = 0; k < nc; k++) {
3398 d[k] = parsedim(a[k], an[k]);
3410 makebackground(Rune* imageurl, int color)
3414 bg.image = imageurl;
3420 newitext(Rune* s, int fnt, int fg, int voff, int ul)
3425 t = (Itext*)emalloc(sizeof(Itext));
3426 t->item.tag = Itexttag;
3436 newirule(int align, int size, int noshade, Dimen wspec)
3440 r = (Irule*)emalloc(sizeof(Irule));
3441 r->item.tag = Iruletag;
3444 r->noshade = noshade;
3449 /* Map is owned elsewhere. */
3451 newiimage(Rune* src, Rune* altrep, int align, int width, int height,
3452 int hspace, int vspace, int border, int ismap, Map* map)
3460 i = (Iimage*)emalloc(sizeof(Iimage));
3461 i->item.tag = Iimagetag;
3462 i->item.state = state;
3467 i->imheight = height;
3477 newiformfield(Formfield* ff)
3481 f = (Iformfield*)emalloc(sizeof(Iformfield));
3482 f->item.tag = Iformfieldtag;
3488 newitable(Table* tab)
3492 t = (Itable*)emalloc(sizeof(Itable));
3493 t->item.tag = Itabletag;
3499 newifloat(Item* it, int side)
3503 f = (Ifloat*)emalloc(sizeof(Ifloat));
3504 f->_item.tag = Ifloattag;
3505 f->_item.state = IFwrap;
3512 newispacer(int spkind)
3516 s = (Ispacer*)emalloc(sizeof(Ispacer));
3517 s->item.tag = Ispacertag;
3522 /* Free one item (caller must deal with next pointer) */
3534 free(((Itext*)it)->s);
3542 freeformfield(((Iformfield*)it)->formfield);
3545 freetable(((Itable*)it)->table);
3548 freeitem(((Ifloat*)it)->item);
3557 freescriptevents(ga->events);
3562 /* Free list of items chained through next pointer */
3564 freeitems(Item* ithead)
3578 freeformfield(Formfield* ff)
3588 for(o = ff->options; o != nil; o = onext) {
3606 /* We'll find all the unique cells via t->cells and next pointers. */
3607 /* (Other pointers to cells in the table are duplicates of these) */
3608 for(c = t->cells; c != nil; c = cnext) {
3610 freeitems(c->content);
3612 if(t->grid != nil) {
3613 for(i = 0; i < t->nrow; i++)
3619 freeitems(t->caption);
3631 /* Form doesn't own its fields (Iformfield items do) */
3636 freeforms(Form* fhead)
3641 for(f = fhead; f != nil; f = fnext) {
3648 freeanchor(Anchor* a)
3659 freeanchors(Anchor* ahead)
3664 for(a = ahead; a != nil; a = anext) {
3671 freedestanchor(DestAnchor* da)
3681 freedestanchors(DestAnchor* dahead)
3686 for(da = dahead; da != nil; da = danext) {
3701 static void freekidinfos(Kidinfo* khead);
3704 freekidinfo(Kidinfo* k)
3709 freekidinfos(k->kidinfos);
3719 freekidinfos(Kidinfo* khead)
3724 for(k = khead; k != nil; k = knext) {
3740 for(a = m->areas; a != nil; a = anext) {
3748 freemaps(Map* mhead)
3753 for(m = mhead; m != nil; m = mnext) {
3760 freedocinfo(Docinfo* d)
3766 freeitem((Item*)d->backgrounditem);
3768 freekidinfos(d->kidinfo);
3769 freeanchors(d->anchors);
3770 freedestanchors(d->dests);
3771 freeforms(d->forms);
3773 /* tables, images, and formfields are freed when */
3774 /* the items pointing at them are freed */
3778 /* Currently, someone else owns all the memory */
3779 /* pointed to by things in a Pstate. */
3781 freepstate(Pstate* p)
3787 freepstatestack(Pstate* pshead)
3792 for(p = pshead; p != nil; p = pnext) {
3817 char buf[BIGBUFSIZE];
3819 it = va_arg(f->args, Item*);
3825 cl = state&(IFcleft|IFcright);
3828 if(cl == (IFcleft|IFcright))
3830 else if(cl == IFcleft)
3835 bi = snprint(buf, nbuf, "brk(%d%s)", (state&IFbrksp)? 1 : 0, p);
3838 bi += snprint(buf+bi, nbuf-bi, " nobrk");
3840 bi += snprint(buf+bi, nbuf-bi, " nowrap");
3842 bi += snprint(buf+bi, nbuf-bi, " rjust");
3844 bi += snprint(buf+bi, nbuf-bi, " cjust");
3846 bi += snprint(buf+bi, nbuf-bi, " smap");
3847 indent = (state&IFindentmask) >> IFindentshift;
3849 bi += snprint(buf+bi, nbuf-bi, " indent=%d", indent);
3850 hang = state&IFhangmask;
3852 bi += snprint(buf+bi, nbuf-bi, " hang=%d", hang);
3857 bi += snprint(buf+bi, nbuf-bi, " Text '%S', fnt=%d, fg=%x", t->s, t->fnt, t->fg);
3862 bi += snprint(buf+bi, nbuf-bi, "Rule size=%d, al=%S, wspec=", r->size, stringalign(r->align));
3863 bi += dimprint(buf+bi, nbuf-bi, r->wspec);
3868 bi += snprint(buf+bi, nbuf-bi,
3869 "Image src=%S, alt=%S, al=%S, w=%d, h=%d hsp=%d, vsp=%d, bd=%d, map=%S",
3870 i->imsrc, i->altrep? i->altrep : L(Lempty), stringalign(i->align), i->imwidth, i->imheight,
3871 i->hspace, i->vspace, i->border, i->map?i->map->name : L(Lempty));
3875 ff = ((Iformfield*)it)->formfield;
3876 if(ff->ftype == Ftextarea)
3878 else if(ff->ftype == Fselect)
3881 ty = _revlookup(input_tab, NINPUTTAB, ff->ftype);
3885 bi += snprint(buf+bi, nbuf-bi, "Formfield %S, fieldid=%d, formid=%d, name=%S, value=%S",
3886 ty, ff->fieldid, ff->form->formid, ff->name? ff->name : L(Lempty),
3887 ff->value? ff->value : L(Lempty));
3891 tab = ((Itable*)it)->table;
3892 bi += snprint(buf+bi, nbuf-bi, "Table tableid=%d, width=", tab->tableid);
3893 bi += dimprint(buf+bi, nbuf-bi, tab->width);
3894 bi += snprint(buf+bi, nbuf-bi, ", nrow=%d, ncol=%d, ncell=%d, totw=%d, toth=%d\n",
3895 tab->nrow, tab->ncol, tab->ncell, tab->totw, tab->toth);
3896 for(c = tab->cells; c != nil; c = c->next)
3897 bi += snprint(buf+bi, nbuf-bi, "Cell %d.%d, at (%d,%d) ",
3898 tab->tableid, c->cellid, c->row, c->col);
3899 bi += snprint(buf+bi, nbuf-bi, "End of Table %d", tab->tableid);
3904 bi += snprint(buf+bi, nbuf-bi, "Float, x=%d y=%d, side=%S, it=%I",
3905 fl->x, fl->y, stringalign(fl->side), fl->item);
3906 bi += snprint(buf+bi, nbuf-bi, "\n\t");
3911 switch(((Ispacer*)it)->spkind) {
3922 bi += snprint(buf+bi, nbuf-bi, "Spacer %s ", p);
3925 bi += snprint(buf+bi, nbuf-bi, " w=%d, h=%d, a=%d, anchor=%d\n",
3926 it->width, it->height, it->ascent, it->anchorid);
3928 return fmtstrcpy(f, buf);
3931 /* String version of alignment 'a' */
3937 s = _revlookup(align_tab, NALIGNTAB, a);
3943 /* Put at most nbuf chars of representation of d into buf, */
3944 /* and return number of characters put */
3946 dimprint(char* buf, int nbuf, Dimen d)
3952 n += snprint(buf, nbuf, "%d", dimenspec(d));
3962 printitems(Item* items, char* msg)
3966 fprint(2, "%s\n", msg);
3969 fprint(2, "%I", il);
3975 newgenattr(Rune* id, Rune* class, Rune* style, Rune* title, SEvent* events)
3979 g = (Genattr*)emalloc(sizeof(Genattr));
3989 newformfield(int ftype, int fieldid, Form* form, Rune* name,
3990 Rune* value, int size, int maxlength, Formfield* link)
3994 ff = (Formfield*)emalloc(sizeof(Formfield));
3996 ff->fieldid = fieldid;
4001 ff->maxlength = maxlength;
4007 /* Transfers ownership of value and display to Option. */
4009 newoption(int selected, Rune* value, Rune* display, Option* link)
4013 o = (Option*)emalloc(sizeof(Option));
4014 o->selected = selected;
4016 o->display = display;
4022 newform(int formid, Rune* name, Rune* action, int target, int method, Form* link)
4026 f = (Form*)emalloc(sizeof(Form));
4039 newtable(int tableid, Align align, Dimen width, int border,
4040 int cellspacing, int cellpadding, Background bg, Token* tok, Table* link)
4044 t = (Table*)emalloc(sizeof(Table));
4045 t->tableid = tableid;
4049 t->cellspacing = cellspacing;
4050 t->cellpadding = cellpadding;
4052 t->caption_place = ALbottom;
4053 t->caption_lay = nil;
4061 newtablerow(Align align, Background bg, int flags, Tablerow* link)
4065 tr = (Tablerow*)emalloc(sizeof(Tablerow));
4067 tr->background = bg;
4074 newtablecell(int cellid, int rowspan, int colspan, Align align, Dimen wspec, int hspec,
4075 Background bg, int flags, Tablecell* link)
4079 c = (Tablecell*)emalloc(sizeof(Tablecell));
4082 c->rowspan = rowspan;
4083 c->colspan = colspan;
4094 newanchor(int index, Rune* name, Rune* href, int target, Anchor* link)
4098 a = (Anchor*)emalloc(sizeof(Anchor));
4108 newdestanchor(int index, Rune* name, Item* item, DestAnchor* link)
4112 d = (DestAnchor*)emalloc(sizeof(DestAnchor));
4121 newscriptevent(int type, Rune* script, SEvent* link)
4125 ans = (SEvent*)emalloc(sizeof(SEvent));
4127 ans->script = script;
4133 freescriptevents(SEvent* ehead)
4148 makedimen(int kind, int spec)
4152 if(spec&Dkindmask) {
4154 fprint(2, "warning: dimension spec too big: %d\n", spec);
4157 d.kindspec = kind|spec;
4164 return (d.kindspec&Dkindmask);
4170 return (d.kindspec&Dspecmask);
4174 newkidinfo(int isframeset, Kidinfo* link)
4178 ki = (Kidinfo*)emalloc(sizeof(Kidinfo));
4179 ki->isframeset = isframeset;
4181 ki->flags = FRhscrollauto|FRvscrollauto;
4182 ki->marginw = FRKIDMARGIN;
4183 ki->marginh = FRKIDMARGIN;
4195 d = (Docinfo*)emalloc(sizeof(Docinfo));
4201 resetdocinfo(Docinfo* d)
4203 memset(d, 0, sizeof(Docinfo));
4204 d->background = makebackground(nil, White);
4210 d->chset = ISO_8859_1;
4211 d->scripttype = TextJavascript;
4215 /* Use targetmap array to keep track of name <-> targetid mapping. */
4216 /* Use real malloc(), and never free */
4221 targetmap = (StringInt*)emalloc(targetmapsize*sizeof(StringInt));
4222 memset(targetmap, 0, targetmapsize*sizeof(StringInt));
4223 targetmap[0].key = _Strdup(L(L_top));
4224 targetmap[0].val = FTtop;
4225 targetmap[1].key = _Strdup(L(L_self));
4226 targetmap[1].val = FTself;
4227 targetmap[2].key = _Strdup(L(L_parent));
4228 targetmap[2].val = FTparent;
4229 targetmap[3].key = _Strdup(L(L_blank));
4230 targetmap[3].val = FTblank;
4243 for(i = 0; i < ntargets; i++)
4244 if(_Strcmp(s, targetmap[i].key) == 0)
4245 return targetmap[i].val;
4246 if(i >= targetmapsize) {
4247 targetmapsize += 10;
4248 targetmap = (StringInt*)erealloc(targetmap, targetmapsize*sizeof(StringInt));
4250 targetmap[i].key = (Rune*)emalloc((n+1)*sizeof(Rune));
4251 memmove(targetmap[i].key, s, (n+1)*sizeof(Rune));
4252 targetmap[i].val = i;
4258 targetname(int targid)
4262 for(i = 0; i < ntargets; i++)
4263 if(targetmap[i].val == targid)
4264 return targetmap[i].key;
4265 return L(Lquestion);
4268 /* Convert HTML color spec to RGB value, returning dflt if can't. */
4269 /* Argument is supposed to be a valid HTML color, or "". */
4270 /* Return the RGB value of the color, using dflt if s */
4271 /* is nil or an invalid color. */
4273 color(Rune* s, int dflt)
4280 if(_lookup(color_tab, NCOLORS, s, _Strlen(s), &v))
4284 v = _Strtol(s, &rest, 16);
4292 #define HUGEPIX 10000
4294 /* A "shallow" validitem, that doesn't follow next links */
4295 /* or descend into tables. */
4306 ok = (i->tag >= Itexttag && i->tag <= Ispacertag) &&
4307 (i->next == nil || validptr(i->next)) &&
4308 (i->width >= 0 && i->width < HUGEPIX) &&
4309 (i->height >= 0 && i->height < HUGEPIX) &&
4310 (i->ascent > -HUGEPIX && i->ascent < HUGEPIX) &&
4311 (i->anchorid >= 0) &&
4312 (i->genattr == nil || validptr(i->genattr));
4313 /* also, could check state for ridiculous combinations */
4314 /* also, could check anchorid for within-doc-range */
4319 ok = validStr(ti->s) &&
4320 (ti->fnt >= 0 && ti->fnt < NumStyle*NumSize) &&
4321 (ti->ul == ULnone || ti->ul == ULunder || ti->ul == ULmid);
4325 ok = (validvalign(ri->align) || validhalign(ri->align)) &&
4326 (ri->size >=0 && ri->size < HUGEPIX);
4330 ok = (ii->imsrc == nil || validptr(ii->imsrc)) &&
4331 (ii->item.width >= 0 && ii->item.width < HUGEPIX) &&
4332 (ii->item.height >= 0 && ii->item.height < HUGEPIX) &&
4333 (ii->imwidth >= 0 && ii->imwidth < HUGEPIX) &&
4334 (ii->imheight >= 0 && ii->imheight < HUGEPIX) &&
4335 (ii->altrep == nil || validStr(ii->altrep)) &&
4336 (ii->map == nil || validptr(ii->map)) &&
4337 (validvalign(ii->align) || validhalign(ii->align)) &&
4338 (ii->nextimage == nil || validptr(ii->nextimage));
4341 ok = validformfield(((Iformfield*)i)->formfield);
4344 ok = validptr((Itable*)i);
4348 ok = (fi->side == ALleft || fi->side == ALright) &&
4349 validitem(fi->item) &&
4350 (fi->item->tag == Iimagetag || fi->item->tag == Itabletag);
4353 a = ((Ispacer*)i)->spkind;
4354 ok = a==ISPnull || a==ISPvline || a==ISPhspace || a==ISPgeneral;
4362 /* "deep" validation, that checks whole list of items, */
4363 /* and descends into tables and floated tables. */
4364 /* nil is ok for argument. */
4372 while(i != nil && ok) {
4375 if(i->tag == Itabletag) {
4376 ok = validtable(((Itable*)i)->table);
4378 else if(i->tag == Ifloattag) {
4379 ii = ((Ifloat*)i)->item;
4380 if(ii->tag == Itabletag)
4381 ok = validtable(((Itable*)ii)->table);
4385 fprint(2, "invalid item: %I\n", i);
4393 validformfield(Formfield* f)
4397 ok = (f->next == nil || validptr(f->next)) &&
4398 (f->ftype >= 0 && f->ftype <= Ftextarea) &&
4400 (f->form == nil || validptr(f->form)) &&
4401 (f->name == nil || validStr(f->name)) &&
4402 (f->value == nil || validStr(f->value)) &&
4403 (f->options == nil || validptr(f->options)) &&
4404 (f->image == nil || validitem(f->image)) &&
4405 (f->events == nil || validptr(f->events));
4406 /* when all built, should have f->fieldid < f->form->nfields, */
4407 /* but this may be called during build... */
4411 /* "deep" validation -- checks cell contents too */
4413 validtable(Table* t)
4419 ok = (t->next == nil || validptr(t->next)) &&
4423 validalign(t->align) &&
4424 validdimen(t->width) &&
4425 (t->border >= 0 && t->border < HUGEPIX) &&
4426 (t->cellspacing >= 0 && t->cellspacing < HUGEPIX) &&
4427 (t->cellpadding >= 0 && t->cellpadding < HUGEPIX) &&
4428 validitems(t->caption) &&
4429 (t->caption_place == ALtop || t->caption_place == ALbottom) &&
4430 (t->totw >= 0 && t->totw < HUGEPIX) &&
4431 (t->toth >= 0 && t->toth < HUGEPIX) &&
4432 (t->tabletok == nil || validptr(t->tabletok));
4433 /* during parsing, t->rows has list; */
4434 /* only when parsing is done is t->nrow set > 0 */
4435 if(ok && t->nrow > 0 && t->ncol > 0) {
4436 /* table is "finished" */
4437 for(i = 0; i < t->nrow && ok; i++)
4438 ok = validtablerow(t->rows+i);
4439 for(j = 0; j < t->ncol && ok; j++)
4440 ok = validtablecol(t->cols+j);
4441 for(c = t->cells; c != nil && ok; c = c->next)
4442 ok = validtablecell(c);
4443 for(i = 0; i < t->nrow && ok; i++)
4444 for(j = 0; j < t->ncol && ok; j++)
4445 ok = validptr(t->grid[i][j]);
4453 return a == ALnone || a == ALmiddle || a == ALbottom || a == ALtop || a == ALbaseline;
4459 return a == ALnone || a == ALleft || a == ALcenter || a == ALright ||
4460 a == ALjustify || a == ALchar;
4466 return validhalign(a.halign) && validvalign(a.valign);
4476 s = d.kindspec&Dspecmask;
4477 switch(d.kindspec&Dkindmask) {
4493 validtablerow(Tablerow* r)
4495 return (r->cells == nil || validptr(r->cells)) &&
4496 (r->height >= 0 && r->height < HUGEPIX) &&
4497 (r->ascent > -HUGEPIX && r->ascent < HUGEPIX) &&
4498 validalign(r->align);
4502 validtablecol(Tablecol* c)
4504 return c->width >= 0 && c->width < HUGEPIX
4505 && validalign(c->align);
4509 validtablecell(Tablecell* c)
4513 ok = (c->next == nil || validptr(c->next)) &&
4514 (c->nextinrow == nil || validptr(c->nextinrow)) &&
4515 (c->content == nil || validptr(c->content)) &&
4516 (c->lay == nil || validptr(c->lay)) &&
4519 validalign(c->align) &&
4520 validdimen(c->wspec) &&
4524 if(c->content != nil)
4525 ok = validitems(c->content);
4533 /* TODO: a better job of this. */
4534 /* For now, just dereference, which cause a bomb */
4545 return s != nil && validptr(s);