tbuild.c - plan9port - [fork] Plan 9 from user space
 (HTM) git clone git://src.adamsgaard.dk/plan9port
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       tbuild.c (100723B)
       ---
            1 #include <u.h>
            2 #include <libc.h>
            3 #include <draw.h>
            4 #include <ctype.h>
            5 #include <html.h>
            6 #include "impl.h"
            7 
            8 /* A stack for holding integer values */
            9 enum {
           10         Nestmax = 40        /* max nesting level of lists, font styles, etc. */
           11 };
           12 
           13 struct Stack {
           14         int                n;                                /* next available slot (top of stack is stack[n-1]) */
           15         int                slots[Nestmax];        /* stack entries */
           16 };
           17 
           18 /* Parsing state */
           19 struct Pstate
           20 {
           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 */
           47 };
           48 
           49 struct ItemSource
           50 {
           51         Docinfo*                doc;
           52         Pstate*                psstk;
           53         int                        nforms;
           54         int                        ntables;
           55         int                        nanchors;
           56         int                        nframes;
           57         Form*                curform;
           58         Map*                curmap;
           59         Table*                tabstk;
           60         Kidinfo*                kidstk;
           61 };
           62 
           63 /* Some layout parameters */
           64 enum {
           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 */
           77 };
           78 
           79 /* These tables must be sorted */
           80 static StringInt *align_tab;
           81 static AsciiInt _align_tab[] = {
           82         {"baseline",        ALbaseline},
           83         {"bottom",        ALbottom},
           84         {"center",        ALcenter},
           85         {"char",                ALchar},
           86         {"justify",        ALjustify},
           87         {"left",                ALleft},
           88         {"middle",        ALmiddle},
           89         {"right",                ALright},
           90         {"top",                ALtop}
           91 };
           92 #define NALIGNTAB (sizeof(_align_tab)/sizeof(StringInt))
           93 
           94 static StringInt *input_tab;
           95 static AsciiInt _input_tab[] = {
           96         {"button",        Fbutton},
           97         {"checkbox",        Fcheckbox},
           98         {"file",                Ffile},
           99         {"hidden",        Fhidden},
          100         {"image",        Fimage},
          101         {"password",        Fpassword},
          102         {"radio",                Fradio},
          103         {"reset",                Freset},
          104         {"submit",        Fsubmit},
          105         {"text",                Ftext}
          106 };
          107 #define NINPUTTAB (sizeof(_input_tab)/sizeof(StringInt))
          108 
          109 static StringInt *clear_tab;
          110 static AsciiInt _clear_tab[] = {
          111         {"all",        IFcleft|IFcright},
          112         {"left",        IFcleft},
          113         {"right",        IFcright}
          114 };
          115 #define NCLEARTAB (sizeof(_clear_tab)/sizeof(StringInt))
          116 
          117 static StringInt *fscroll_tab;
          118 static AsciiInt _fscroll_tab[] = {
          119         {"auto",        FRhscrollauto|FRvscrollauto},
          120         {"no",        FRnoscroll},
          121         {"yes",        FRhscroll|FRvscroll},
          122 };
          123 #define NFSCROLLTAB (sizeof(_fscroll_tab)/sizeof(StringInt))
          124 
          125 static StringInt *shape_tab;
          126 static AsciiInt _shape_tab[] = {
          127         {"circ",                SHcircle},
          128         {"circle",                SHcircle},
          129         {"poly",                SHpoly},
          130         {"polygon",        SHpoly},
          131         {"rect",                SHrect},
          132         {"rectangle",        SHrect}
          133 };
          134 #define NSHAPETAB (sizeof(_shape_tab)/sizeof(StringInt))
          135 
          136 static StringInt *method_tab;
          137 static AsciiInt _method_tab[] = {
          138         {"get",                HGet},
          139         {"post",                HPost}
          140 };
          141 #define NMETHODTAB (sizeof(_method_tab)/sizeof(StringInt))
          142 
          143 static Rune** roman;
          144 static char* _roman[15]= {
          145         "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X",
          146         "XI", "XII", "XIII", "XIV", "XV"
          147 };
          148 #define NROMAN 15
          149 
          150 /* List number types */
          151 enum {
          152         LTdisc, LTsquare, LTcircle, LT1, LTa, LTA, LTi, LTI
          153 };
          154 
          155 enum {
          156         SPBefore = 2,
          157         SPAfter = 4,
          158         BL = 1,
          159         BLBA = (BL|SPBefore|SPAfter)
          160 };
          161 
          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. */
          167 
          168 static uchar blockbrk[Numtags]= {
          169 /*Notfound*/ 0,
          170 /*Comment*/ 0,
          171 /*Ta*/ 0,
          172 /*Tabbr*/ 0,
          173 /*Tacronym*/ 0,
          174 /*Taddress*/ BLBA,
          175 /*Tapplet*/ 0,
          176 /*Tarea*/ 0,
          177 /*Tb*/ 0,
          178 /*Tbase*/ 0,
          179 /*Tbasefont*/ 0,
          180 /*Tbdo*/ 0,
          181 /*Tbig*/ 0,
          182 /*Tblink*/ 0,
          183 /*Tblockquote*/ BLBA,
          184 /*Tbody*/ 0,
          185 /*Tbq*/ 0,
          186 /*Tbr*/ 0,
          187 /*Tbutton*/ 0,
          188 /*Tcaption*/ 0,
          189 /*Tcenter*/ BL,
          190 /*Tcite*/ 0,
          191 /*Tcode*/ 0,
          192 /*Tcol*/ 0,
          193 /*Tcolgroup*/ 0,
          194 /*Tdd*/ BL,
          195 /*Tdel*/ 0,
          196 /*Tdfn*/ 0,
          197 /*Tdir*/ BLBA,
          198 /*Tdiv*/ BL,
          199 /*Tdl*/ BLBA,
          200 /*Tdt*/ BL,
          201 /*Tem*/ 0,
          202 /*Tfieldset*/ 0,
          203 /*Tfont*/ 0,
          204 /*Tform*/ BLBA,
          205 /*Tframe*/ 0,
          206 /*Tframeset*/ 0,
          207 /*Th1*/ BL,
          208 /*Th2*/ BL,
          209 /*Th3*/ BL,
          210 /*Th4*/ BL,
          211 /*Th5*/ BL,
          212 /*Th6*/ BL,
          213 /*Thead*/ 0,
          214 /*Thr*/ BL,
          215 /*Thtml*/ 0,
          216 /*Ti*/ 0,
          217 /*Tiframe*/ 0,
          218 /*Timg*/ 0,
          219 /*Tinput*/ 0,
          220 /*Tins*/ 0,
          221 /*Tisindex*/ BLBA,
          222 /*Tkbd*/ 0,
          223 /*Tlabel*/ 0,
          224 /*Tlegend*/ 0,
          225 /*Tli*/ BL,
          226 /*Tlink*/ 0,
          227 /*Tmap*/ 0,
          228 /*Tmenu*/ BLBA,
          229 /*Tmeta*/ 0,
          230 /*Tnobr*/ 0,
          231 /*Tnoframes*/ 0,
          232 /*Tnoscript*/ 0,
          233 /*Tobject*/ 0,
          234 /*Tol*/ BLBA,
          235 /*Toptgroup*/ 0,
          236 /*Toption*/ 0,
          237 /*Tp*/ BLBA,
          238 /*Tparam*/ 0,
          239 /*Tpre*/ BLBA,
          240 /*Tq*/ 0,
          241 /*Ts*/ 0,
          242 /*Tsamp*/ 0,
          243 /*Tscript*/ 0,
          244 /*Tselect*/ 0,
          245 /*Tsmall*/ 0,
          246 /*Tspan*/ 0,
          247 /*Tstrike*/ 0,
          248 /*Tstrong*/ 0,
          249 /*Tstyle*/ 0,
          250 /*Tsub*/ 0,
          251 /*Tsup*/ 0,
          252 /*Ttable*/ 0,
          253 /*Ttbody*/ 0,
          254 /*Ttd*/ 0,
          255 /*Ttextarea*/ 0,
          256 /*Ttfoot*/ 0,
          257 /*Tth*/ 0,
          258 /*Tthead*/ 0,
          259 /*Ttitle*/ 0,
          260 /*Ttr*/ 0,
          261 /*Ttt*/ 0,
          262 /*Tu*/ 0,
          263 /*Tul*/ BLBA,
          264 /*Tvar*/ 0,
          265 };
          266 
          267 enum {
          268         AGEN = 1
          269 };
          270 
          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]= {
          274 /*Aabbr*/ 0,
          275 /*Aaccept_charset*/ 0,
          276 /*Aaccess_key*/ 0,
          277 /*Aaction*/ 0,
          278 /*Aalign*/ 0,
          279 /*Aalink*/ 0,
          280 /*Aalt*/ 0,
          281 /*Aarchive*/ 0,
          282 /*Aaxis*/ 0,
          283 /*Abackground*/ 0,
          284 /*Abgcolor*/ 0,
          285 /*Aborder*/ 0,
          286 /*Acellpadding*/ 0,
          287 /*Acellspacing*/ 0,
          288 /*Achar*/ 0,
          289 /*Acharoff*/ 0,
          290 /*Acharset*/ 0,
          291 /*Achecked*/ 0,
          292 /*Acite*/ 0,
          293 /*Aclass*/ AGEN,
          294 /*Aclassid*/ 0,
          295 /*Aclear*/ 0,
          296 /*Acode*/ 0,
          297 /*Acodebase*/ 0,
          298 /*Acodetype*/ 0,
          299 /*Acolor*/ 0,
          300 /*Acols*/ 0,
          301 /*Acolspan*/ 0,
          302 /*Acompact*/ 0,
          303 /*Acontent*/ 0,
          304 /*Acoords*/ 0,
          305 /*Adata*/ 0,
          306 /*Adatetime*/ 0,
          307 /*Adeclare*/ 0,
          308 /*Adefer*/ 0,
          309 /*Adir*/ 0,
          310 /*Adisabled*/ 0,
          311 /*Aenctype*/ 0,
          312 /*Aface*/ 0,
          313 /*Afor*/ 0,
          314 /*Aframe*/ 0,
          315 /*Aframeborder*/ 0,
          316 /*Aheaders*/ 0,
          317 /*Aheight*/ 0,
          318 /*Ahref*/ 0,
          319 /*Ahreflang*/ 0,
          320 /*Ahspace*/ 0,
          321 /*Ahttp_equiv*/ 0,
          322 /*Aid*/ AGEN,
          323 /*Aismap*/ 0,
          324 /*Alabel*/ 0,
          325 /*Alang*/ 0,
          326 /*Alink*/ 0,
          327 /*Alongdesc*/ 0,
          328 /*Amarginheight*/ 0,
          329 /*Amarginwidth*/ 0,
          330 /*Amaxlength*/ 0,
          331 /*Amedia*/ 0,
          332 /*Amethod*/ 0,
          333 /*Amultiple*/ 0,
          334 /*Aname*/ 0,
          335 /*Anohref*/ 0,
          336 /*Anoresize*/ 0,
          337 /*Anoshade*/ 0,
          338 /*Anowrap*/ 0,
          339 /*Aobject*/ 0,
          340 /*Aonblur*/ AGEN,
          341 /*Aonchange*/ AGEN,
          342 /*Aonclick*/ AGEN,
          343 /*Aondblclick*/ AGEN,
          344 /*Aonfocus*/ AGEN,
          345 /*Aonkeypress*/ AGEN,
          346 /*Aonkeyup*/ AGEN,
          347 /*Aonload*/ AGEN,
          348 /*Aonmousedown*/ AGEN,
          349 /*Aonmousemove*/ AGEN,
          350 /*Aonmouseout*/ AGEN,
          351 /*Aonmouseover*/ AGEN,
          352 /*Aonmouseup*/ AGEN,
          353 /*Aonreset*/ AGEN,
          354 /*Aonselect*/ AGEN,
          355 /*Aonsubmit*/ AGEN,
          356 /*Aonunload*/ AGEN,
          357 /*Aprofile*/ 0,
          358 /*Aprompt*/ 0,
          359 /*Areadonly*/ 0,
          360 /*Arel*/ 0,
          361 /*Arev*/ 0,
          362 /*Arows*/ 0,
          363 /*Arowspan*/ 0,
          364 /*Arules*/ 0,
          365 /*Ascheme*/ 0,
          366 /*Ascope*/ 0,
          367 /*Ascrolling*/ 0,
          368 /*Aselected*/ 0,
          369 /*Ashape*/ 0,
          370 /*Asize*/ 0,
          371 /*Aspan*/ 0,
          372 /*Asrc*/ 0,
          373 /*Astandby*/ 0,
          374 /*Astart*/ 0,
          375 /*Astyle*/ AGEN,
          376 /*Asummary*/ 0,
          377 /*Atabindex*/ 0,
          378 /*Atarget*/ 0,
          379 /*Atext*/ 0,
          380 /*Atitle*/ AGEN,
          381 /*Atype*/ 0,
          382 /*Ausemap*/ 0,
          383 /*Avalign*/ 0,
          384 /*Avalue*/ 0,
          385 /*Avaluetype*/ 0,
          386 /*Aversion*/ 0,
          387 /*Avlink*/ 0,
          388 /*Avspace*/ 0,
          389 /*Awidth*/ 0,
          390 };
          391 
          392 static uchar scriptev[Numattrs]= {
          393 /*Aabbr*/ 0,
          394 /*Aaccept_charset*/ 0,
          395 /*Aaccess_key*/ 0,
          396 /*Aaction*/ 0,
          397 /*Aalign*/ 0,
          398 /*Aalink*/ 0,
          399 /*Aalt*/ 0,
          400 /*Aarchive*/ 0,
          401 /*Aaxis*/ 0,
          402 /*Abackground*/ 0,
          403 /*Abgcolor*/ 0,
          404 /*Aborder*/ 0,
          405 /*Acellpadding*/ 0,
          406 /*Acellspacing*/ 0,
          407 /*Achar*/ 0,
          408 /*Acharoff*/ 0,
          409 /*Acharset*/ 0,
          410 /*Achecked*/ 0,
          411 /*Acite*/ 0,
          412 /*Aclass*/ 0,
          413 /*Aclassid*/ 0,
          414 /*Aclear*/ 0,
          415 /*Acode*/ 0,
          416 /*Acodebase*/ 0,
          417 /*Acodetype*/ 0,
          418 /*Acolor*/ 0,
          419 /*Acols*/ 0,
          420 /*Acolspan*/ 0,
          421 /*Acompact*/ 0,
          422 /*Acontent*/ 0,
          423 /*Acoords*/ 0,
          424 /*Adata*/ 0,
          425 /*Adatetime*/ 0,
          426 /*Adeclare*/ 0,
          427 /*Adefer*/ 0,
          428 /*Adir*/ 0,
          429 /*Adisabled*/ 0,
          430 /*Aenctype*/ 0,
          431 /*Aface*/ 0,
          432 /*Afor*/ 0,
          433 /*Aframe*/ 0,
          434 /*Aframeborder*/ 0,
          435 /*Aheaders*/ 0,
          436 /*Aheight*/ 0,
          437 /*Ahref*/ 0,
          438 /*Ahreflang*/ 0,
          439 /*Ahspace*/ 0,
          440 /*Ahttp_equiv*/ 0,
          441 /*Aid*/ 0,
          442 /*Aismap*/ 0,
          443 /*Alabel*/ 0,
          444 /*Alang*/ 0,
          445 /*Alink*/ 0,
          446 /*Alongdesc*/ 0,
          447 /*Amarginheight*/ 0,
          448 /*Amarginwidth*/ 0,
          449 /*Amaxlength*/ 0,
          450 /*Amedia*/ 0,
          451 /*Amethod*/ 0,
          452 /*Amultiple*/ 0,
          453 /*Aname*/ 0,
          454 /*Anohref*/ 0,
          455 /*Anoresize*/ 0,
          456 /*Anoshade*/ 0,
          457 /*Anowrap*/ 0,
          458 /*Aobject*/ 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,
          476 /*Aprofile*/ 0,
          477 /*Aprompt*/ 0,
          478 /*Areadonly*/ 0,
          479 /*Arel*/ 0,
          480 /*Arev*/ 0,
          481 /*Arows*/ 0,
          482 /*Arowspan*/ 0,
          483 /*Arules*/ 0,
          484 /*Ascheme*/ 0,
          485 /*Ascope*/ 0,
          486 /*Ascrolling*/ 0,
          487 /*Aselected*/ 0,
          488 /*Ashape*/ 0,
          489 /*Asize*/ 0,
          490 /*Aspan*/ 0,
          491 /*Asrc*/ 0,
          492 /*Astandby*/ 0,
          493 /*Astart*/ 0,
          494 /*Astyle*/ 0,
          495 /*Asummary*/ 0,
          496 /*Atabindex*/ 0,
          497 /*Atarget*/ 0,
          498 /*Atext*/ 0,
          499 /*Atitle*/ 0,
          500 /*Atype*/ 0,
          501 /*Ausemap*/ 0,
          502 /*Avalign*/ 0,
          503 /*Avalue*/ 0,
          504 /*Avaluetype*/ 0,
          505 /*Aversion*/ 0,
          506 /*Avlink*/ 0,
          507 /*Avspace*/ 0,
          508 /*Awidth*/ 0,
          509 };
          510 
          511 /* Color lookup table */
          512 static StringInt *color_tab;
          513 static AsciiInt _color_tab[] = {
          514         {"aqua", 0x00FFFF},
          515         {"black",  0x000000},
          516         {"blue", 0x0000CC},
          517         {"fuchsia", 0xFF00FF},
          518         {"gray", 0x808080},
          519         {"green", 0x008000},
          520         {"lime", 0x00FF00},
          521         {"maroon", 0x800000},
          522         {"navy", 0x000080,},
          523         {"olive", 0x808000},
          524         {"purple", 0x800080},
          525         {"red", 0xFF0000},
          526         {"silver", 0xC0C0C0},
          527         {"teal", 0x008080},
          528         {"white", 0xFFFFFF},
          529         {"yellow", 0xFFFF00}
          530 };
          531 #define NCOLORS (sizeof(_color_tab)/sizeof(StringInt))
          532 
          533 static StringInt                 *targetmap;
          534 static int                        targetmapsize;
          535 static int                        ntargets;
          536 
          537 static int buildinited = 0;
          538 
          539 #define SMALLBUFSIZE 240
          540 #define BIGBUFSIZE 2000
          541 
          542 int        dbgbuild = 0;
          543 int        warn = 0;
          544 
          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);
          647 
          648 static void
          649 buildinit(void)
          650 {
          651         _runetabinit();
          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));
          660 
          661         fmtinstall('I', Iconv);
          662         targetmapinit();
          663         buildinited = 1;
          664 }
          665 
          666 static ItemSource*
          667 newitemsource(Docinfo* di)
          668 {
          669         ItemSource*        is;
          670         Pstate*        ps;
          671 
          672         ps = newpstate(nil);
          673         if(di->mediatype != TextHtml) {
          674                 ps->curstate &= ~IFwrap;
          675                 ps->literal = 1;
          676                 pushfontstyle(ps, FntT);
          677         }
          678         is = (ItemSource*)emalloc(sizeof(ItemSource));
          679         is->doc = di;
          680         is->psstk = ps;
          681         is->nforms = 0;
          682         is->ntables = 0;
          683         is->nanchors = 0;
          684         is->nframes = 0;
          685         is->curform = nil;
          686         is->curmap = nil;
          687         is->tabstk = nil;
          688         is->kidstk = nil;
          689         return is;
          690 }
          691 
          692 static Item *getitems(ItemSource* is, uchar* data, int datalen);
          693 
          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). */
          699 Item*
          700 parsehtml(uchar* data, int datalen, Rune* pagesrc, int mtype, int chset, Docinfo** pdi)
          701 {
          702         Item *it;
          703         Docinfo*        di;
          704         ItemSource*        is;
          705 
          706         di = newdocinfo();
          707         di->src = _Strdup(pagesrc);
          708         di->base = _Strdup(pagesrc);
          709         di->mediatype = mtype;
          710         di->chset = chset;
          711         *pdi = di;
          712         is = newitemsource(di);
          713         it = getitems(is, data, datalen);
          714         freepstatestack(is->psstk);
          715         free(is);
          716         return it;
          717 }
          718 
          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. */
          723 static Item*
          724 getitems(ItemSource* is, uchar* data, int datalen)
          725 {
          726         int        i;
          727         int        j;
          728         int        nt;
          729         int        pt;
          730         int        doscripts;
          731         int        tokslen;
          732         int        toki;
          733         int        h;
          734         int        sz;
          735         int        method;
          736         int        n;
          737         int        nblank;
          738         int        norsz;
          739         int        bramt;
          740         int        sty;
          741         int        nosh;
          742         int        oldcuranchor;
          743         int        dfltbd;
          744         int        v;
          745         int        hang;
          746         int        isempty;
          747         int        tag;
          748         int        brksp;
          749         int        target;
          750         uchar        brk;
          751         uchar        flags;
          752         uchar        align;
          753         uchar        al;
          754         uchar        ty;
          755         uchar        ty2;
          756         Pstate*        ps;
          757         Pstate*        nextps;
          758         Pstate*        outerps;
          759         Table*        curtab;
          760         Token*        tok;
          761         Token*        toks;
          762         Docinfo*        di;
          763         Item*        ans;
          764         Item*        img;
          765         Item*        ffit;
          766         Item*        tabitem;
          767         Rune*        s;
          768         Rune*        t;
          769         Rune*        name;
          770         Rune*        enctype;
          771         Rune*        usemap;
          772         Rune*        prompt;
          773         Rune*        equiv;
          774         Rune*        val;
          775         Rune*        nsz;
          776         Rune*        script;
          777         Map*        map;
          778         Form*        frm;
          779         Iimage*        ii;
          780         Kidinfo*        kd;
          781         Kidinfo*        ks;
          782         Kidinfo*        pks;
          783         Dimen        wd;
          784         Option*        option;
          785         Table*        tab;
          786         Tablecell*        c;
          787         Tablerow*        tr;
          788         Formfield*        field;
          789         Formfield*        ff;
          790         Rune*        href;
          791         Rune*        src;
          792         Rune*        scriptsrc;
          793         Rune*        bgurl;
          794         Rune*        action;
          795         Background        bg;
          796 
          797         if(!buildinited)
          798                 buildinit();
          799         doscripts = 0;        /* for now */
          800         ps = is->psstk;
          801         curtab = is->tabstk;
          802         di = is->doc;
          803         toks = _gettoks(data, datalen, di->chset, di->mediatype, &tokslen);
          804         toki = 0;
          805         for(; toki < tokslen; toki++) {
          806                 tok = &toks[toki];
          807                 if(dbgbuild > 1)
          808                         fprint(2, "build: curstate %ux, token %T\n", ps->curstate, tok);
          809                 tag = tok->tag;
          810                 brk = 0;
          811                 brksp = 0;
          812                 if(tag < Numtags) {
          813                         brk = blockbrk[tag];
          814                         if(brk&SPBefore)
          815                                 brksp = 1;
          816                 }
          817                 else if(tag < Numtags + RBRA) {
          818                         brk = blockbrk[tag - RBRA];
          819                         if(brk&SPAfter)
          820                                 brksp = 1;
          821                 }
          822                 if(brk) {
          823                         addbrk(ps, brksp, 0);
          824                         if(ps->inpar) {
          825                                 popjust(ps);
          826                                 ps->inpar = 0;
          827                         }
          828                 }
          829                 /* check common case first (Data), then switch statement on tag */
          830                 if(tag == Data) {
          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. */
          838                         s = tok->text;
          839                         n = _Strlen(s);
          840                         if(!ps->literal) {
          841                                 i = 0;
          842                                 j = n;
          843                                 if(toki > 0) {
          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')
          848                                                 i++;
          849                                 }
          850                                 if(toki < tokslen - 1) {
          851                                         nt = toks[toki + 1].tag;
          852                                         if(nt >= RBRA && nt < Numtags + RBRA && j > i && s[j - 1] == '\n')
          853                                                 j--;
          854                                 }
          855                                 if(i > 0 || j < n) {
          856                                         t = s;
          857                                         s = _Strsubstr(s, i, j);
          858                                         free(t);
          859                                         n = j-i;
          860                                 }
          861                         }
          862                         if(ps->skipwhite) {
          863                                 _trimwhite(s, n, &t, &nt);
          864                                 if(t == nil) {
          865                                         free(s);
          866                                         s = nil;
          867                                 }
          868                                 else if(t != s) {
          869                                         t = _Strndup(t, nt);
          870                                         free(s);
          871                                         s = t;
          872                                 }
          873                                 if(s != nil)
          874                                         ps->skipwhite = 0;
          875                         }
          876                         tok->text = nil;                /* token doesn't own string anymore */
          877                         if(s != nil){
          878                                 addtext(ps, s);
          879                                 s = nil;
          880                         }
          881                 }
          882                 else
          883                         switch(tag) {
          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)* */
          894 
          895                         /* <!ELEMENT A - - (%text) -(A)> */
          896                         /* Anchors are not supposed to be nested, but you sometimes see */
          897                         /* href anchors inside destination anchors. */
          898                         case Ta:
          899                                 if(ps->curanchor != 0) {
          900                                         if(warn)
          901                                                 fprint(2, "warning: nested <A> or missing </A>\n");
          902                                         ps->curanchor = 0;
          903                                 }
          904                                 name = aval(tok, Aname);
          905                                 href = aurlval(tok, Ahref, nil, di->base);
          906                                 /* ignore rel, rev, and title attrs */
          907                                 if(href != nil) {
          908                                         target = atargval(tok, di->target);
          909                                         di->anchors = newanchor(++is->nanchors, name, href, target, di->anchors);
          910                                         if(name != nil)
          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);
          915                                 }
          916                                 if(name != nil) {
          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);
          920                                 }
          921                                 break;
          922 
          923                         case Ta+RBRA :
          924                                 if(ps->curanchor != 0) {
          925                                         ps->curfg = popretnewtop(&ps->fgstk, di->text);
          926                                         ps->curul = popretnewtop(&ps->ulstk, ULnone);
          927                                         ps->curanchor = 0;
          928                                 }
          929                                 break;
          930 
          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 */
          934                         case Tapplet:
          935                         case Tapplet+RBRA:
          936                                 if(warn && tag == Tapplet)
          937                                         fprint(2, "warning: <APPLET> ignored\n");
          938                                 break;
          939 
          940                         /* <!ELEMENT AREA - O EMPTY> */
          941                         case Tarea:
          942                                 map = di->maps;
          943                                 if(map == nil) {
          944                                         if(warn)
          945                                                 fprint(2, "warning: <AREA> not inside <MAP>\n");
          946                                         continue;
          947                                 }
          948                                 map->areas = newarea(atabval(tok, Ashape, shape_tab, NSHAPETAB, SHrect),
          949                                         aurlval(tok, Ahref, nil, di->base),
          950                                         atargval(tok, di->target),
          951                                         map->areas);
          952                                 setdimarray(tok, Acoords, &map->areas->coords, &map->areas->ncoords);
          953                                 break;
          954 
          955                         /* <!ELEMENT (B|STRONG) - - (%text)*> */
          956                         case Tb:
          957                         case Tstrong:
          958                                 pushfontstyle(ps, FntB);
          959                                 break;
          960 
          961                         case Tb+RBRA:
          962                         case Tcite+RBRA:
          963                         case Tcode+RBRA:
          964                         case Tdfn+RBRA:
          965                         case Tem+RBRA:
          966                         case Tkbd+RBRA:
          967                         case Ti+RBRA:
          968                         case Tsamp+RBRA:
          969                         case Tstrong+RBRA:
          970                         case Ttt+RBRA:
          971                         case Tvar+RBRA :
          972                         case Taddress+RBRA:
          973                                 popfontstyle(ps);
          974                                 break;
          975 
          976                         /* <!ELEMENT BASE - O EMPTY> */
          977                         case Tbase:
          978                                 t = di->base;
          979                                 di->base = aurlval(tok, Ahref, di->base, di->base);
          980                                 if(t != nil)
          981                                         free(t);
          982                                 di->target = atargval(tok, di->target);
          983                                 break;
          984 
          985                         /* <!ELEMENT BASEFONT - O EMPTY> */
          986                         case Tbasefont:
          987                                 ps->adjsize = aintval(tok, Asize, 3) - 3;
          988                                 break;
          989 
          990                         /* <!ELEMENT (BIG|SMALL) - - (%text)*> */
          991                         case Tbig:
          992                         case Tsmall:
          993                                 sz = ps->adjsize;
          994                                 if(tag == Tbig)
          995                                         sz += Large;
          996                                 else
          997                                         sz += Small;
          998                                 pushfontsize(ps, sz);
          999                                 break;
         1000 
         1001                         case Tbig+RBRA:
         1002                         case Tsmall+RBRA:
         1003                                 popfontsize(ps);
         1004                                 break;
         1005 
         1006                         /* <!ELEMENT BLOCKQUOTE - - %body.content> */
         1007                         case Tblockquote:
         1008                                 changeindent(ps, BQTAB);
         1009                                 break;
         1010 
         1011                         case Tblockquote+RBRA:
         1012                                 changeindent(ps, -BQTAB);
         1013                                 break;
         1014 
         1015                         /* <!ELEMENT BODY O O %body.content> */
         1016                         case Tbody:
         1017                                 ps->skipping = 0;
         1018                                 bg = makebackground(nil, acolorval(tok, Abgcolor, di->background.color));
         1019                                 bgurl = aurlval(tok, Abackground, nil, di->base);
         1020                                 if(bgurl != nil) {
         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;
         1028                                 }
         1029                                 ps->curbg = bg;
         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;
         1037                                         ps->fgstk.n = 0;
         1038                                 }
         1039                                 break;
         1040 
         1041                         case Tbody+RBRA:
         1042                                 /* HTML spec says ignore things after </body>, */
         1043                                 /* but IE and Netscape don't */
         1044                                 /* ps.skipping = 1; */
         1045                                 break;
         1046 
         1047                         /* <!ELEMENT BR - O EMPTY> */
         1048                         case Tbr:
         1049                                 addlinebrk(ps, atabval(tok, Aclear, clear_tab, NCLEARTAB, 0));
         1050                                 break;
         1051 
         1052                         /* <!ELEMENT CAPTION - - (%text;)*> */
         1053                         case Tcaption:
         1054                                 if(curtab == nil) {
         1055                                         if(warn)
         1056                                                 fprint(2, "warning: <CAPTION> outside <TABLE>\n");
         1057                                         continue;
         1058                                 }
         1059                                 if(curtab->caption != nil) {
         1060                                         if(warn)
         1061                                                 fprint(2, "warning: more than one <CAPTION> in <TABLE>\n");
         1062                                         continue;
         1063                                 }
         1064                                 ps = newpstate(ps);
         1065                                 curtab->caption_place = atabval(tok, Aalign, align_tab, NALIGNTAB, ALtop);
         1066                                 break;
         1067 
         1068                         case Tcaption+RBRA:
         1069                                 nextps = ps->next;
         1070                                 if(curtab == nil || nextps == nil) {
         1071                                         if(warn)
         1072                                                 fprint(2, "warning: unexpected </CAPTION>\n");
         1073                                         continue;
         1074                                 }
         1075                                 curtab->caption = ps->items->next;
         1076                                 free(ps);
         1077                                 ps = nextps;
         1078                                 break;
         1079 
         1080                         case Tcenter:
         1081                         case Tdiv:
         1082                                 if(tag == Tcenter)
         1083                                         al = ALcenter;
         1084                                 else
         1085                                         al = atabval(tok, Aalign, align_tab, NALIGNTAB, ps->curjust);
         1086                                 pushjust(ps, al);
         1087                                 break;
         1088 
         1089                         case Tcenter+RBRA:
         1090                         case Tdiv+RBRA:
         1091                                 popjust(ps);
         1092                                 break;
         1093 
         1094                         /* <!ELEMENT DD - O  %flow > */
         1095                         case Tdd:
         1096                                 if(ps->hangstk.n == 0) {
         1097                                         if(warn)
         1098                                                 fprint(2, "warning: <DD> not inside <DL\n");
         1099                                         continue;
         1100                                 }
         1101                                 h = top(&ps->hangstk, 0);
         1102                                 if(h != 0)
         1103                                         changehang(ps, -10*LISTTAB);
         1104                                 else
         1105                                         addbrk(ps, 0, 0);
         1106                                 push(&ps->hangstk, 0);
         1107                                 break;
         1108 
         1109                         /*<!ELEMENT (DIR|MENU) - - (LI)+ -(%block) > */
         1110                         /*<!ELEMENT (OL|UL) - - (LI)+> */
         1111                         case Tdir:
         1112                         case Tmenu:
         1113                         case Tol:
         1114                         case Tul:
         1115                                 changeindent(ps, LISTTAB);
         1116                                 push(&ps->listtypestk, listtyval(tok, (tag==Tol)? LT1 : LTdisc));
         1117                                 push(&ps->listcntstk, aintval(tok, Astart, 1));
         1118                                 break;
         1119 
         1120                         case Tdir+RBRA:
         1121                         case Tmenu+RBRA:
         1122                         case Tol+RBRA:
         1123                         case Tul+RBRA:
         1124                                 if(ps->listtypestk.n == 0) {
         1125                                         if(warn)
         1126                                                 fprint(2, "warning: %T ended no list\n", tok);
         1127                                         continue;
         1128                                 }
         1129                                 addbrk(ps, 0, 0);
         1130                                 pop(&ps->listtypestk);
         1131                                 pop(&ps->listcntstk);
         1132                                 changeindent(ps, -LISTTAB);
         1133                                 break;
         1134 
         1135                         /* <!ELEMENT DL - - (DT|DD)+ > */
         1136                         case Tdl:
         1137                                 changeindent(ps, LISTTAB);
         1138                                 push(&ps->hangstk, 0);
         1139                                 break;
         1140 
         1141                         case Tdl+RBRA:
         1142                                 if(ps->hangstk.n == 0) {
         1143                                         if(warn)
         1144                                                 fprint(2, "warning: unexpected </DL>\n");
         1145                                         continue;
         1146                                 }
         1147                                 changeindent(ps, -LISTTAB);
         1148                                 if(top(&ps->hangstk, 0) != 0)
         1149                                         changehang(ps, -10*LISTTAB);
         1150                                 pop(&ps->hangstk);
         1151                                 break;
         1152 
         1153                         /* <!ELEMENT DT - O (%text)* > */
         1154                         case Tdt:
         1155                                 if(ps->hangstk.n == 0) {
         1156                                         if(warn)
         1157                                                 fprint(2, "warning: <DT> not inside <DL>\n");
         1158                                         continue;
         1159                                 }
         1160                                 h = top(&ps->hangstk, 0);
         1161                                 pop(&ps->hangstk);
         1162                                 if(h != 0)
         1163                                         changehang(ps, -10*LISTTAB);
         1164                                 changehang(ps, 10*LISTTAB);
         1165                                 push(&ps->hangstk, 1);
         1166                                 break;
         1167 
         1168                         /* <!ELEMENT FONT - - (%text)*> */
         1169                         case Tfont:
         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;
         1176                                         else if(nsz != nil)
         1177                                                 sz = Normal + (_Strtol(nsz, nil, 10) - 3);
         1178                                 }
         1179                                 ps->curfg = push(&ps->fgstk, acolorval(tok, Acolor, ps->curfg));
         1180                                 pushfontsize(ps, sz);
         1181                                 break;
         1182 
         1183                         case Tfont+RBRA:
         1184                                 if(ps->fgstk.n == 0) {
         1185                                         if(warn)
         1186                                                 fprint(2, "warning: unexpected </FONT>\n");
         1187                                         continue;
         1188                                 }
         1189                                 ps->curfg = popretnewtop(&ps->fgstk, di->text);
         1190                                 popfontsize(ps);
         1191                                 break;
         1192 
         1193                         /* <!ELEMENT FORM - - %body.content -(FORM) > */
         1194                         case Tform:
         1195                                 if(is->curform != nil) {
         1196                                         if(warn)
         1197                                                 fprint(2, "warning: <FORM> nested inside another\n");
         1198                                         continue;
         1199                                 }
         1200                                 action = aurlval(tok, Aaction, di->base, di->base);
         1201                                 s = aval(tok, Aid);
         1202                                 name = astrval(tok, Aname, s);
         1203                                 if(s)
         1204                                         free(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);
         1211                                 di->forms = frm;
         1212                                 is->curform = frm;
         1213                                 break;
         1214 
         1215                         case Tform+RBRA:
         1216                                 if(is->curform == nil) {
         1217                                         if(warn)
         1218                                                 fprint(2, "warning: unexpected </FORM>\n");
         1219                                         continue;
         1220                                 }
         1221                                 /* put fields back in input order */
         1222                                 is->curform->fields = (Formfield*)_revlist((List*)is->curform->fields);
         1223                                 is->curform = nil;
         1224                                 break;
         1225 
         1226                         /* <!ELEMENT FRAME - O EMPTY> */
         1227                         case Tframe:
         1228                                 ks = is->kidstk;
         1229                                 if(ks == nil) {
         1230                                         if(warn)
         1231                                                 fprint(2, "warning: <FRAME> not in <FRAMESET>\n");
         1232                                         continue;
         1233                                 }
         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);
         1240                                         free(s);
         1241                                 }
         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);
         1247                                 if(norsz)
         1248                                         kd->flags |= FRnoresize;
         1249                                 break;
         1250 
         1251                         /* <!ELEMENT FRAMESET - - (FRAME|FRAMESET)+> */
         1252                         case Tframeset:
         1253                                 ks = newkidinfo(1, nil);
         1254                                 pks = is->kidstk;
         1255                                 if(pks == nil)
         1256                                         di->kidinfo = ks;
         1257                                 else  {
         1258                                         ks->next = pks->kidinfos;
         1259                                         pks->kidinfos = ks;
         1260                                 }
         1261                                 ks->nextframeset = pks;
         1262                                 is->kidstk = ks;
         1263                                 setdimarray(tok, Arows, &ks->rows, &ks->nrows);
         1264                                 if(ks->nrows == 0) {
         1265                                         ks->rows = (Dimen*)emalloc(sizeof(Dimen));
         1266                                         ks->nrows = 1;
         1267                                         ks->rows[0] = makedimen(Dpercent, 100);
         1268                                 }
         1269                                 setdimarray(tok, Acols, &ks->cols, &ks->ncols);
         1270                                 if(ks->ncols == 0) {
         1271                                         ks->cols = (Dimen*)emalloc(sizeof(Dimen));
         1272                                         ks->ncols = 1;
         1273                                         ks->cols[0] = makedimen(Dpercent, 100);
         1274                                 }
         1275                                 break;
         1276 
         1277                         case Tframeset+RBRA:
         1278                                 if(is->kidstk == nil) {
         1279                                         if(warn)
         1280                                                 fprint(2, "warning: unexpected </FRAMESET>\n");
         1281                                         continue;
         1282                                 }
         1283                                 ks = is->kidstk;
         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);
         1288                                 while(nblank-- > 0)
         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) {
         1293                                         /* end input */
         1294                                         ans = nil;
         1295                                         goto return_ans;
         1296                                 }
         1297                                 break;
         1298 
         1299                         /* <!ELEMENT H1 - - (%text;)*>, etc. */
         1300                         case Th1:
         1301                         case Th2:
         1302                         case Th3:
         1303                         case Th4:
         1304                         case Th5:
         1305                         case Th6:
         1306                                 bramt = 1;
         1307                                 if(ps->items == ps->lastit)
         1308                                         bramt = 0;
         1309                                 addbrk(ps, bramt, IFcleft|IFcright);
         1310                                 sz = Verylarge - (tag - Th1);
         1311                                 if(sz < Tiny)
         1312                                         sz = Tiny;
         1313                                 pushfontsize(ps, sz);
         1314                                 sty = top(&ps->fntstylestk, FntR);
         1315                                 if(tag == Th1)
         1316                                         sty = FntB;
         1317                                 pushfontstyle(ps, sty);
         1318                                 pushjust(ps, atabval(tok, Aalign, align_tab, NALIGNTAB, ps->curjust));
         1319                                 ps->skipwhite = 1;
         1320                                 break;
         1321 
         1322                         case Th1+RBRA:
         1323                         case Th2+RBRA:
         1324                         case Th3+RBRA:
         1325                         case Th4+RBRA:
         1326                         case Th5+RBRA:
         1327                         case Th6+RBRA:
         1328                                 addbrk(ps, 1, IFcleft|IFcright);
         1329                                 popfontsize(ps);
         1330                                 popfontstyle(ps);
         1331                                 popjust(ps);
         1332                                 break;
         1333 
         1334                         case Thead:
         1335                                 /* HTML spec says ignore regular markup in head, */
         1336                                 /* but Netscape and IE don't */
         1337                                 /* ps.skipping = 1; */
         1338                                 break;
         1339 
         1340                         case Thead+RBRA:
         1341                                 ps->skipping = 0;
         1342                                 break;
         1343 
         1344                         /* <!ELEMENT HR - O EMPTY> */
         1345                         case Thr:
         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);
         1353                                 addbrk(ps, 0, 0);
         1354                                 break;
         1355 
         1356                         case Ti:
         1357                         case Tcite:
         1358                         case Tdfn:
         1359                         case Tem:
         1360                         case Tvar:
         1361                         case Taddress:
         1362                                 pushfontstyle(ps, FntI);
         1363                                 break;
         1364 
         1365                         /* <!ELEMENT IMG - O EMPTY> */
         1366                         case Timg:
         1367                                 map = nil;
         1368                                 oldcuranchor = ps->curanchor;
         1369                                 if(_tokaval(tok, Ausemap, &usemap, 0)) {
         1370                                         if(!_prefix(L(Lhash), usemap)) {
         1371                                                 if(warn)
         1372                                                         fprint(2, "warning: can't handle non-local map %S\n", usemap);
         1373                                         }
         1374                                         else {
         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;
         1379                                                 }
         1380                                         }
         1381                                 }
         1382                                 align = atabval(tok, Aalign, align_tab, NALIGNTAB, ALbottom);
         1383                                 dfltbd = 0;
         1384                                 if(ps->curanchor != 0)
         1385                                         dfltbd = 2;
         1386                                 src = aurlval(tok, Asrc, nil, di->base);
         1387                                 if(src == nil) {
         1388                                         if(warn)
         1389                                                 fprint(2, "warning: <img> has no src attribute\n");
         1390                                         ps->curanchor = oldcuranchor;
         1391                                         continue;
         1392                                 }
         1393                                 img = newiimage(src,
         1394                                                 aval(tok, Aalt),
         1395                                                 align,
         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),
         1402                                                 map);
         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;
         1408                                 }
         1409                                 else {
         1410                                         ps->skipwhite = 0;
         1411                                         additem(ps, img, tok);
         1412                                 }
         1413                                 if(!ps->skipping) {
         1414                                         ((Iimage*)img)->nextimage = di->images;
         1415                                         di->images = (Iimage*)img;
         1416                                 }
         1417                                 ps->curanchor = oldcuranchor;
         1418                                 break;
         1419 
         1420                         /* <!ELEMENT INPUT - O EMPTY> */
         1421                         case Tinput:
         1422                                 ps->skipwhite = 0;
         1423                                 if(is->curform == nil) {
         1424                                         if(warn)
         1425                                                 fprint(2, "<INPUT> not inside <FORM>\n");
         1426                                         continue;
         1427                                 }
         1428                                 is->curform->fields = field = newformfield(
         1429                                                 atabval(tok, Atype, input_tab, NINPUTTAB, Ftext),
         1430                                                 ++is->curform->nfields,
         1431                                                 is->curform,
         1432                                                 aval(tok, Aname),
         1433                                                 aval(tok, Avalue),
         1434                                                 auintval(tok, Asize, 0),
         1435                                                 auintval(tok, Amaxlength, 1000),
         1436                                                 is->curform->fields);
         1437                                 if(aflagval(tok, Achecked))
         1438                                         field->flags = FFchecked;
         1439 
         1440                                 switch(field->ftype) {
         1441                                 case Ftext:
         1442                                 case Fpassword:
         1443                                 case Ffile:
         1444                                         if(field->size == 0)
         1445                                                 field->size = 20;
         1446                                         break;
         1447 
         1448                                 case Fcheckbox:
         1449                                         if(field->name == nil) {
         1450                                                 if(warn)
         1451                                                         fprint(2, "warning: checkbox form field missing name\n");
         1452                                                 continue;
         1453                                         }
         1454                                         if(field->value == nil)
         1455                                                 field->value = _Strdup(L(Lone));
         1456                                         break;
         1457 
         1458                                 case Fradio:
         1459                                         if(field->name == nil || field->value == nil) {
         1460                                                 if(warn)
         1461                                                         fprint(2, "warning: radio form field missing name or value\n");
         1462                                                 continue;
         1463                                         }
         1464                                         break;
         1465 
         1466                                 case Fsubmit:
         1467                                         if(field->value == nil)
         1468                                                 field->value = _Strdup(L(Lsubmit));
         1469                                         if(field->name == nil)
         1470                                                 field->name = _Strdup(L(Lnoname));
         1471                                         break;
         1472 
         1473                                 case Fimage:
         1474                                         src = aurlval(tok, Asrc, nil, di->base);
         1475                                         if(src == nil) {
         1476                                                 if(warn)
         1477                                                         fprint(2, "warning: image form field missing src\n");
         1478                                                 continue;
         1479                                         }
         1480                                         /* width and height attrs aren't specified in HTML 3.2, */
         1481                                         /* but some people provide them and they help avoid */
         1482                                         /* a relayout */
         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),
         1487                                                 0, 0, 0, 0, nil);
         1488                                         ii = (Iimage*)field->image;
         1489                                         ii->nextimage = di->images;
         1490                                         di->images = ii;
         1491                                         break;
         1492 
         1493                                 case Freset:
         1494                                         if(field->value == nil)
         1495                                                 field->value = _Strdup(L(Lreset));
         1496                                         break;
         1497 
         1498                                 case Fbutton:
         1499                                         if(field->value == nil)
         1500                                                 field->value = _Strdup(L(Lspace));
         1501                                         break;
         1502                                 }
         1503                                 ffit = newiformfield(field);
         1504                                 additem(ps, ffit, tok);
         1505                                 if(ffit->genattr != nil)
         1506                                         field->events = ffit->genattr->events;
         1507                                 break;
         1508 
         1509                         /* <!ENTITY ISINDEX - O EMPTY> */
         1510                         case Tisindex:
         1511                                 ps->skipwhite = 0;
         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,
         1516                                                 nil,
         1517                                                 di->base,
         1518                                                 target,
         1519                                                 HGet,
         1520                                                 di->forms);
         1521                                 di->forms = frm;
         1522                                 ff = newformfield(Ftext,
         1523                                                 1,
         1524                                                 frm,
         1525                                                 _Strdup(L(Lisindex)),
         1526                                                 nil,
         1527                                                 50,
         1528                                                 1000,
         1529                                                 nil);
         1530                                 frm->fields = ff;
         1531                                 frm->nfields = 1;
         1532                                 additem(ps, newiformfield(ff), tok);
         1533                                 addbrk(ps, 1, 0);
         1534                                 break;
         1535 
         1536                         /* <!ELEMENT LI - O %flow> */
         1537                         case Tli:
         1538                                 if(ps->listtypestk.n == 0) {
         1539                                         if(warn)
         1540                                                 fprint(2, "<LI> not in list\n");
         1541                                         continue;
         1542                                 }
         1543                                 ty = top(&ps->listtypestk, 0);
         1544                                 ty2 = listtyval(tok, ty);
         1545                                 if(ty != ty2) {
         1546                                         ty = ty2;
         1547                                         push(&ps->listtypestk, ty2);
         1548                                 }
         1549                                 v = aintval(tok, Avalue, top(&ps->listcntstk, 1));
         1550                                 if(ty == LTdisc || ty == LTsquare || ty == LTcircle)
         1551                                         hang = 10*LISTTAB - 3;
         1552                                 else
         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);
         1558                                 ps->skipwhite = 1;
         1559                                 break;
         1560 
         1561                         /* <!ELEMENT MAP - - (AREA)+> */
         1562                         case Tmap:
         1563                                 if(_tokaval(tok, Aname, &name, 0))
         1564                                         is->curmap = getmap(di, name);
         1565                                 break;
         1566 
         1567                         case Tmap+RBRA:
         1568                                 map = is->curmap;
         1569                                 if(map == nil) {
         1570                                         if(warn)
         1571                                                 fprint(2, "warning: unexpected </MAP>\n");
         1572                                         continue;
         1573                                 }
         1574                                 map->areas = (Area*)_revlist((List*)map->areas);
         1575                                 break;
         1576 
         1577                         case Tmeta:
         1578                                 if(ps->skipping)
         1579                                         continue;
         1580                                 if(_tokaval(tok, Ahttp_equiv, &equiv, 0)) {
         1581                                         val = aval(tok, Acontent);
         1582                                         n = _Strlen(equiv);
         1583                                         if(!_Strncmpci(equiv, n, L(Lrefresh)))
         1584                                                 di->refresh = val;
         1585                                         else if(!_Strncmpci(equiv, n, L(Lcontent))) {
         1586                                                 n = _Strlen(val);
         1587                                                 if(!_Strncmpci(val, n, L(Ljavascript))
         1588                                                    || !_Strncmpci(val, n, L(Ljscript1))
         1589                                                    || !_Strncmpci(val, n, L(Ljscript)))
         1590                                                         di->scripttype = TextJavascript;
         1591                                                 else {
         1592                                                         if(warn)
         1593                                                                 fprint(2, "unimplemented script type %S\n", val);
         1594                                                         di->scripttype = UnknownType;
         1595                                                 }
         1596                                         }
         1597                                 }
         1598                                 break;
         1599 
         1600                         /* Nobr is NOT in HMTL 4.0, but it is ubiquitous on the web */
         1601                         case Tnobr:
         1602                                 ps->skipwhite = 0;
         1603                                 ps->curstate &= ~IFwrap;
         1604                                 break;
         1605 
         1606                         case Tnobr+RBRA:
         1607                                 ps->curstate |= IFwrap;
         1608                                 break;
         1609 
         1610                         /* We do frames, so skip stuff in noframes */
         1611                         case Tnoframes:
         1612                                 ps->skipping = 1;
         1613                                 break;
         1614 
         1615                         case Tnoframes+RBRA:
         1616                                 ps->skipping = 0;
         1617                                 break;
         1618 
         1619                         /* We do scripts (if enabled), so skip stuff in noscripts */
         1620                         case Tnoscript:
         1621                                 if(doscripts)
         1622                                         ps->skipping = 1;
         1623                                 break;
         1624 
         1625                         case Tnoscript+RBRA:
         1626                                 if(doscripts)
         1627                                         ps->skipping = 0;
         1628                                 break;
         1629 
         1630                         /* <!ELEMENT OPTION - O (        //PCDATA)> */
         1631                         case Toption:
         1632                                 if(is->curform == nil || is->curform->fields == nil) {
         1633                                         if(warn)
         1634                                                 fprint(2, "warning: <OPTION> not in <SELECT>\n");
         1635                                         continue;
         1636                                 }
         1637                                 field = is->curform->fields;
         1638                                 if(field->ftype != Fselect) {
         1639                                         if(warn)
         1640                                                 fprint(2, "warning: <OPTION> not in <SELECT>\n");
         1641                                         continue;
         1642                                 }
         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);
         1647                                 if(val == nil)
         1648                                         option->value = _Strdup(option->display);
         1649                                 break;
         1650 
         1651                         /* <!ELEMENT P - O (%text)* > */
         1652                         case Tp:
         1653                                 pushjust(ps, atabval(tok, Aalign, align_tab, NALIGNTAB, ps->curjust));
         1654                                 ps->inpar = 1;
         1655                                 ps->skipwhite = 1;
         1656                                 break;
         1657 
         1658                         case Tp+RBRA:
         1659                                 break;
         1660 
         1661                         /* <!ELEMENT PARAM - O EMPTY> */
         1662                         /* Do something when we do applets... */
         1663                         case Tparam:
         1664                                 break;
         1665 
         1666                         /* <!ELEMENT PRE - - (%text)* -(IMG|BIG|SMALL|SUB|SUP|FONT) > */
         1667                         case Tpre:
         1668                                 ps->curstate &= ~IFwrap;
         1669                                 ps->literal = 1;
         1670                                 ps->skipwhite = 0;
         1671                                 pushfontstyle(ps, FntT);
         1672                                 break;
         1673 
         1674                         case Tpre+RBRA:
         1675                                 ps->curstate |= IFwrap;
         1676                                 if(ps->literal) {
         1677                                         popfontstyle(ps);
         1678                                         ps->literal = 0;
         1679                                 }
         1680                                 break;
         1681 
         1682                         /* <!ELEMENT SCRIPT - - CDATA> */
         1683                         case Tscript:
         1684                                 if(doscripts) {
         1685                                         if(!di->hasscripts) {
         1686                                                 if(di->scripttype == TextJavascript) {
         1687                                                         /* TODO: initialize script if nec. */
         1688                                                         /* initjscript(di); */
         1689                                                         di->hasscripts = 1;
         1690                                                 }
         1691                                         }
         1692                                 }
         1693                                 if(!di->hasscripts) {
         1694                                         if(warn)
         1695                                                 fprint(2, "warning: <SCRIPT> ignored\n");
         1696                                         ps->skipping = 1;
         1697                                 }
         1698                                 else {
         1699                                         scriptsrc = aurlval(tok, Asrc, nil, di->base);
         1700                                         script = nil;
         1701                                         if(scriptsrc != nil) {
         1702                                                 if(warn)
         1703                                                         fprint(2, "warning: non-local <SCRIPT> ignored\n");
         1704                                                 free(scriptsrc);
         1705                                         }
         1706                                         else {
         1707                                                 script = getpcdata(toks, tokslen, &toki);
         1708                                         }
         1709                                         if(script != nil) {
         1710                                                 if(warn)
         1711                                                         fprint(2, "script ignored\n");
         1712                                                 free(script);
         1713                                         }
         1714                                 }
         1715                                 break;
         1716 
         1717                         case Tscript+RBRA:
         1718                                 ps->skipping = 0;
         1719                                 break;
         1720 
         1721                         /* <!ELEMENT SELECT - - (OPTION+)> */
         1722                         case Tselect:
         1723                                 if(is->curform == nil) {
         1724                                         if(warn)
         1725                                                 fprint(2, "<SELECT> not inside <FORM>\n");
         1726                                         continue;
         1727                                 }
         1728                                 field = newformfield(Fselect,
         1729                                         ++is->curform->nfields,
         1730                                         is->curform,
         1731                                         aval(tok, Aname),
         1732                                         nil,
         1733                                         auintval(tok, Asize, 0),
         1734                                         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);
         1745                                 if(s != nil)
         1746                                         free(s);
         1747                                 break;
         1748 
         1749                         case Tselect+RBRA:
         1750                                 if(is->curform == nil || is->curform->fields == nil) {
         1751                                         if(warn)
         1752                                                 fprint(2, "warning: unexpected </SELECT>\n");
         1753                                         continue;
         1754                                 }
         1755                                 field = is->curform->fields;
         1756                                 if(field->ftype != Fselect)
         1757                                         continue;
         1758                                 /* put options back in input order */
         1759                                 field->options = (Option*)_revlist((List*)field->options);
         1760                                 break;
         1761 
         1762                         /* <!ELEMENT (STRIKE|U) - - (%text)*> */
         1763                         case Tstrike:
         1764                         case Tu:
         1765                                 ps->curul = push(&ps->ulstk, (tag==Tstrike)? ULmid : ULunder);
         1766                                 break;
         1767 
         1768                         case Tstrike+RBRA:
         1769                         case Tu+RBRA:
         1770                                 if(ps->ulstk.n == 0) {
         1771                                         if(warn)
         1772                                                 fprint(2, "warning: unexpected %T\n", tok);
         1773                                         continue;
         1774                                 }
         1775                                 ps->curul = popretnewtop(&ps->ulstk, ULnone);
         1776                                 break;
         1777 
         1778                         /* <!ELEMENT STYLE - - CDATA> */
         1779                         case Tstyle:
         1780                                 if(warn)
         1781                                         fprint(2, "warning: unimplemented <STYLE>\n");
         1782                                 ps->skipping = 1;
         1783                                 break;
         1784 
         1785                         case Tstyle+RBRA:
         1786                                 ps->skipping = 0;
         1787                                 break;
         1788 
         1789                         /* <!ELEMENT (SUB|SUP) - - (%text)*> */
         1790                         case Tsub:
         1791                         case Tsup:
         1792                                 if(tag == Tsub)
         1793                                         ps->curvoff += SUBOFF;
         1794                                 else
         1795                                         ps->curvoff -= SUPOFF;
         1796                                 push(&ps->voffstk, ps->curvoff);
         1797                                 sz = top(&ps->fntsizestk, Normal);
         1798                                 pushfontsize(ps, sz - 1);
         1799                                 break;
         1800 
         1801                         case Tsub+RBRA:
         1802                         case Tsup+RBRA:
         1803                                 if(ps->voffstk.n == 0) {
         1804                                         if(warn)
         1805                                                 fprint(2, "warning: unexpected %T\n", tok);
         1806                                         continue;
         1807                                 }
         1808                                 ps->curvoff = popretnewtop(&ps->voffstk, 0);
         1809                                 popfontsize(ps);
         1810                                 break;
         1811 
         1812                         /* <!ELEMENT TABLE - - (CAPTION?, TR+)> */
         1813                         case Ttable:
         1814                                 ps->skipwhite = 0;
         1815                                 tab = newtable(++is->ntables,
         1816                                                 aalign(tok),
         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)),
         1822                                                 tok,
         1823                                                 is->tabstk);
         1824                                 is->tabstk = tab;
         1825                                 curtab = tab;
         1826                                 break;
         1827 
         1828                         case Ttable+RBRA:
         1829                                 if(curtab == nil) {
         1830                                         if(warn)
         1831                                                 fprint(2, "warning: unexpected </TABLE>\n");
         1832                                         continue;
         1833                                 }
         1834                                 isempty = (curtab->cells == nil);
         1835                                 if(isempty) {
         1836                                         if(warn)
         1837                                                 fprint(2, "warning: <TABLE> has no cells\n");
         1838                                 }
         1839                                 else {
         1840                                         ps = finishcell(curtab, ps);
         1841                                         if(curtab->rows != nil)
         1842                                                 curtab->rows->flags = 0;
         1843                                         finish_table(curtab);
         1844                                 }
         1845                                 ps->skipping = 0;
         1846                                 if(!isempty) {
         1847                                         tabitem = newitable(curtab);
         1848                                         al = curtab->align.halign;
         1849                                         switch(al) {
         1850                                         case ALleft:
         1851                                         case ALright:
         1852                                                 additem(ps, newifloat(tabitem, al), tok);
         1853                                                 break;
         1854                                         default:
         1855                                                 if(al == ALcenter)
         1856                                                         pushjust(ps, ALcenter);
         1857                                                 addbrk(ps, 0, 0);
         1858                                                 if(ps->inpar) {
         1859                                                         popjust(ps);
         1860                                                         ps->inpar = 0;
         1861                                                 }
         1862                                                 additem(ps, tabitem, curtab->tabletok);
         1863                                                 if(al == ALcenter)
         1864                                                         popjust(ps);
         1865                                                 break;
         1866                                         }
         1867                                 }
         1868                                 if(is->tabstk == nil) {
         1869                                         if(warn)
         1870                                                 fprint(2, "warning: table stack is wrong\n");
         1871                                 }
         1872                                 else
         1873                                         is->tabstk = is->tabstk->next;
         1874                                 curtab->next = di->tables;
         1875                                 di->tables = curtab;
         1876                                 curtab = is->tabstk;
         1877                                 if(!isempty)
         1878                                         addbrk(ps, 0, 0);
         1879                                 break;
         1880 
         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. */
         1885                         case Ttd:
         1886                         case Tth:
         1887                                 if(curtab == nil) {
         1888                                         if(warn)
         1889                                                 fprint(2, "%T outside <TABLE>\n", tok);
         1890                                         continue;
         1891                                 }
         1892                                 if(ps->inpar) {
         1893                                         popjust(ps);
         1894                                         ps->inpar = 0;
         1895                                 }
         1896                                 ps = finishcell(curtab, ps);
         1897                                 tr = nil;
         1898                                 if(curtab->rows != nil)
         1899                                         tr = curtab->rows;
         1900                                 if(tr == nil || !tr->flags) {
         1901                                         if(warn)
         1902                                                 fprint(2, "%T outside row\n", tok);
         1903                                         tr = newtablerow(makealign(ALnone, ALnone),
         1904                                                         makebackground(nil, curtab->background.color),
         1905                                                         TFparsing,
         1906                                                         curtab->rows);
         1907                                         curtab->rows = tr;
         1908                                 }
         1909                                 ps = cell_pstate(ps, tag == Tth);
         1910                                 flags = TFparsing;
         1911                                 if(aflagval(tok, Anowrap)) {
         1912                                         flags |= TFnowrap;
         1913                                         ps->curstate &= ~IFwrap;
         1914                                 }
         1915                                 if(tag == Tth)
         1916                                         flags |= TFisth;
         1917                                 c = newtablecell(curtab->cells==nil? 1 : curtab->cells->cellid+1,
         1918                                                 auintval(tok, Arowspan, 1),
         1919                                                 auintval(tok, Acolspan, 1),
         1920                                                 aalign(tok),
         1921                                                 adimen(tok, Awidth),
         1922                                                 auintval(tok, Aheight, 0),
         1923                                                 makebackground(nil, acolorval(tok, Abgcolor, tr->background.color)),
         1924                                                 flags,
         1925                                                 curtab->cells);
         1926                                 curtab->cells = c;
         1927                                 ps->curbg = c->background;
         1928                                 if(c->align.halign == ALnone) {
         1929                                         if(tr->align.halign != ALnone)
         1930                                                 c->align.halign = tr->align.halign;
         1931                                         else if(tag == Tth)
         1932                                                 c->align.halign = ALcenter;
         1933                                         else
         1934                                                 c->align.halign = ALleft;
         1935                                 }
         1936                                 if(c->align.valign == ALnone) {
         1937                                         if(tr->align.valign != ALnone)
         1938                                                 c->align.valign = tr->align.valign;
         1939                                         else
         1940                                                 c->align.valign = ALmiddle;
         1941                                 }
         1942                                 c->nextinrow = tr->cells;
         1943                                 tr->cells = c;
         1944                                 break;
         1945 
         1946                         case Ttd+RBRA:
         1947                         case Tth+RBRA:
         1948                                 if(curtab == nil || curtab->cells == nil) {
         1949                                         if(warn)
         1950                                                 fprint(2, "unexpected %T\n", tok);
         1951                                         continue;
         1952                                 }
         1953                                 ps = finishcell(curtab, ps);
         1954                                 break;
         1955 
         1956                         /* <!ELEMENT TEXTAREA - - (        //PCDATA)> */
         1957                         case Ttextarea:
         1958                                 if(is->curform == nil) {
         1959                                         if(warn)
         1960                                                 fprint(2, "<TEXTAREA> not inside <FORM>\n");
         1961                                         continue;
         1962                                 }
         1963                                 field = newformfield(Ftextarea,
         1964                                         ++is->curform->nfields,
         1965                                         is->curform,
         1966                                         aval(tok, Aname),
         1967                                         nil,
         1968                                         0,
         1969                                         0,
         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;
         1981                                 break;
         1982 
         1983                         /* <!ELEMENT TITLE - - (        //PCDATA)* -(%head.misc)> */
         1984                         case Ttitle:
         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]);
         1988                                 break;
         1989 
         1990                         /* <!ELEMENT TR - O (TH|TD)+> */
         1991                         /* rows are accumulated in reverse order in curtab->rows */
         1992                         case Ttr:
         1993                                 if(curtab == nil) {
         1994                                         if(warn)
         1995                                                 fprint(2, "warning: <TR> outside <TABLE>\n");
         1996                                         continue;
         1997                                 }
         1998                                 if(ps->inpar) {
         1999                                         popjust(ps);
         2000                                         ps->inpar = 0;
         2001                                 }
         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)),
         2007                                         TFparsing,
         2008                                         curtab->rows);
         2009                                 break;
         2010 
         2011                         case Ttr+RBRA:
         2012                                 if(curtab == nil || curtab->rows == nil) {
         2013                                         if(warn)
         2014                                                 fprint(2, "warning: unexpected </TR>\n");
         2015                                         continue;
         2016                                 }
         2017                                 ps = finishcell(curtab, ps);
         2018                                 tr = curtab->rows;
         2019                                 if(tr->cells == nil) {
         2020                                         if(warn)
         2021                                                 fprint(2, "warning: empty row\n");
         2022                                         curtab->rows = tr->next;
         2023                                         tr->next = nil;
         2024                                 }
         2025                                 else
         2026                                         tr->flags = 0;
         2027                                 break;
         2028 
         2029                         /* <!ELEMENT (TT|CODE|KBD|SAMP) - - (%text)*> */
         2030                         case Ttt:
         2031                         case Tcode:
         2032                         case Tkbd:
         2033                         case Tsamp:
         2034                                 pushfontstyle(ps, FntT);
         2035                                 break;
         2036 
         2037                         /* Tags that have empty action */
         2038                         case Tabbr:
         2039                         case Tabbr+RBRA:
         2040                         case Tacronym:
         2041                         case Tacronym+RBRA:
         2042                         case Tarea+RBRA:
         2043                         case Tbase+RBRA:
         2044                         case Tbasefont+RBRA:
         2045                         case Tbr+RBRA:
         2046                         case Tdd+RBRA:
         2047                         case Tdt+RBRA:
         2048                         case Tframe+RBRA:
         2049                         case Thr+RBRA:
         2050                         case Thtml:
         2051                         case Thtml+RBRA:
         2052                         case Timg+RBRA:
         2053                         case Tinput+RBRA:
         2054                         case Tisindex+RBRA:
         2055                         case Tli+RBRA:
         2056                         case Tlink:
         2057                         case Tlink+RBRA:
         2058                         case Tmeta+RBRA:
         2059                         case Toption+RBRA:
         2060                         case Tparam+RBRA:
         2061                         case Ttextarea+RBRA:
         2062                         case Ttitle+RBRA:
         2063                                 break;
         2064 
         2065 
         2066                         /* Tags not implemented */
         2067                         case Tbdo:
         2068                         case Tbdo+RBRA:
         2069                         case Tbutton:
         2070                         case Tbutton+RBRA:
         2071                         case Tdel:
         2072                         case Tdel+RBRA:
         2073                         case Tfieldset:
         2074                         case Tfieldset+RBRA:
         2075                         case Tiframe:
         2076                         case Tiframe+RBRA:
         2077                         case Tins:
         2078                         case Tins+RBRA:
         2079                         case Tlabel:
         2080                         case Tlabel+RBRA:
         2081                         case Tlegend:
         2082                         case Tlegend+RBRA:
         2083                         case Tobject:
         2084                         case Tobject+RBRA:
         2085                         case Toptgroup:
         2086                         case Toptgroup+RBRA:
         2087                         case Tspan:
         2088                         case Tspan+RBRA:
         2089                                 if(warn) {
         2090                                         if(tag > RBRA)
         2091                                                 tag -= RBRA;
         2092                                         fprint(2, "warning: unimplemented HTML tag: %S\n", tagnames[tag]);
         2093                                 }
         2094                                 break;
         2095 
         2096                         default:
         2097                                 if(warn)
         2098                                         fprint(2, "warning: unknown HTML tag: %S\n", tok->text);
         2099                                 break;
         2100                         }
         2101         }
         2102         /* some pages omit trailing </table> */
         2103         while(curtab != nil) {
         2104                 if(warn)
         2105                         fprint(2, "warning: <TABLE> not closed\n");
         2106                 if(curtab->cells != nil) {
         2107                         ps = finishcell(curtab, ps);
         2108                         if(curtab->cells == nil) {
         2109                                 if(warn)
         2110                                         fprint(2, "warning: empty table\n");
         2111                         }
         2112                         else {
         2113                                 if(curtab->rows != nil)
         2114                                         curtab->rows->flags = 0;
         2115                                 finish_table(curtab);
         2116                                 ps->skipping = 0;
         2117                                 additem(ps, newitable(curtab), curtab->tabletok);
         2118                                 addbrk(ps, 0, 0);
         2119                         }
         2120                 }
         2121                 if(is->tabstk != nil)
         2122                         is->tabstk = is->tabstk->next;
         2123                 curtab->next = di->tables;
         2124                 di->tables = curtab;
         2125                 curtab = is->tabstk;
         2126         }
         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         freeitem(outerps->items);
         2131         outerps->items = newispacer(ISPnull);
         2132         outerps->lastit = outerps->items;
         2133         is->psstk = ps;
         2134         if(ans != nil && di->hasscripts) {
         2135                 /* TODO evalscript(nil); */
         2136                 ;
         2137         }
         2138         freeitems(outerps->items);
         2139 
         2140 return_ans:
         2141         if(dbgbuild) {
         2142                 assert(validitems(ans));
         2143                 if(ans == nil)
         2144                         fprint(2, "getitems returning nil\n");
         2145                 else
         2146                         printitems(ans, "getitems returning:");
         2147         }
         2148         _freetokens(toks, tokslen);
         2149         return ans;
         2150 }
         2151 
         2152 /* Concatenate together maximal set of Data tokens, starting at toks[toki+1]. */
         2153 /* Lexer has ensured that there will either be a following non-data token or */
         2154 /* we will be at eof. */
         2155 /* Return emallocd trimmed concatenation, and update *ptoki to last used toki */
         2156 static Rune*
         2157 getpcdata(Token* toks, int tokslen, int* ptoki)
         2158 {
         2159         Rune*        ans;
         2160         Rune*        p;
         2161         Rune*        trimans;
         2162         int        anslen;
         2163         int        trimanslen;
         2164         int        toki;
         2165         Token*        tok;
         2166 
         2167         ans = nil;
         2168         anslen = 0;
         2169         /* first find length of answer */
         2170         toki = (*ptoki) + 1;
         2171         while(toki < tokslen) {
         2172                 tok = &toks[toki];
         2173                 if(tok->tag == Data) {
         2174                         toki++;
         2175                         anslen += _Strlen(tok->text);
         2176                 }
         2177                 else
         2178                         break;
         2179         }
         2180         /* now make up the initial answer */
         2181         if(anslen > 0) {
         2182                 ans = _newstr(anslen);
         2183                 p = ans;
         2184                 toki = (*ptoki) + 1;
         2185                 while(toki < tokslen) {
         2186                         tok = &toks[toki];
         2187                         if(tok->tag == Data) {
         2188                                 toki++;
         2189                                 p = _Stradd(p, tok->text, _Strlen(tok->text));
         2190                         }
         2191                         else
         2192                                 break;
         2193                 }
         2194                 *p = 0;
         2195                 _trimwhite(ans, anslen, &trimans, &trimanslen);
         2196                 if(trimanslen != anslen) {
         2197                         p = ans;
         2198                         ans = _Strndup(trimans, trimanslen);
         2199                         free(p);
         2200                 }
         2201         }
         2202         *ptoki = toki-1;
         2203         return ans;
         2204 }
         2205 
         2206 /* If still parsing head of curtab->cells list, finish it off */
         2207 /* by transferring the items on the head of psstk to the cell. */
         2208 /* Then pop the psstk and return the new psstk. */
         2209 static Pstate*
         2210 finishcell(Table* curtab, Pstate* psstk)
         2211 {
         2212         Tablecell*        c;
         2213         Pstate* psstknext;
         2214 
         2215         c = curtab->cells;
         2216         if(c != nil) {
         2217                 if((c->flags&TFparsing)) {
         2218                         psstknext = psstk->next;
         2219                         if(psstknext == nil) {
         2220                                 if(warn)
         2221                                         fprint(2, "warning: parse state stack is wrong\n");
         2222                         }
         2223                         else {
         2224                                 c->content = psstk->items->next;
         2225                                 c->flags &= ~TFparsing;
         2226                                 freepstate(psstk);
         2227                                 psstk = psstknext;
         2228                         }
         2229                 }
         2230         }
         2231         return psstk;
         2232 }
         2233 
         2234 /* Make a new Pstate for a cell, based on the old pstate, oldps. */
         2235 /* Also, put the new ps on the head of the oldps stack. */
         2236 static Pstate*
         2237 cell_pstate(Pstate* oldps, int ishead)
         2238 {
         2239         Pstate*        ps;
         2240         int        sty;
         2241 
         2242         ps = newpstate(oldps);
         2243         ps->skipwhite = 1;
         2244         ps->curanchor = oldps->curanchor;
         2245         copystack(&ps->fntstylestk, &oldps->fntstylestk);
         2246         copystack(&ps->fntsizestk, &oldps->fntsizestk);
         2247         ps->curfont = oldps->curfont;
         2248         ps->curfg = oldps->curfg;
         2249         ps->curbg = oldps->curbg;
         2250         copystack(&ps->fgstk, &oldps->fgstk);
         2251         ps->adjsize = oldps->adjsize;
         2252         if(ishead) {
         2253                 sty = ps->curfont%NumSize;
         2254                 ps->curfont = FntB*NumSize + sty;
         2255         }
         2256         return ps;
         2257 }
         2258 
         2259 /* Return a new Pstate with default starting state. */
         2260 /* Use link to add it to head of a list, if any. */
         2261 static Pstate*
         2262 newpstate(Pstate* link)
         2263 {
         2264         Pstate*        ps;
         2265 
         2266         ps = (Pstate*)emalloc(sizeof(Pstate));
         2267         ps->curfont = DefFnt;
         2268         ps->curfg = Black;
         2269         ps->curbg.image = nil;
         2270         ps->curbg.color = White;
         2271         ps->curul = ULnone;
         2272         ps->curjust = ALleft;
         2273         ps->curstate = IFwrap;
         2274         ps->items = newispacer(ISPnull);
         2275         ps->lastit = ps->items;
         2276         ps->prelastit = nil;
         2277         ps->next = link;
         2278         return ps;
         2279 }
         2280 
         2281 /* Return last Pstate on psl list */
         2282 static Pstate*
         2283 lastps(Pstate* psl)
         2284 {
         2285         assert(psl != nil);
         2286         while(psl->next != nil)
         2287                 psl = psl->next;
         2288         return psl;
         2289 }
         2290 
         2291 /* Add it to end of ps item chain, adding in current state from ps. */
         2292 /* Also, if tok is not nil, scan it for generic attributes and assign */
         2293 /* the genattr field of the item accordingly. */
         2294 static void
         2295 additem(Pstate* ps, Item* it, Token* tok)
         2296 {
         2297         int        aid;
         2298         int        any;
         2299         Rune*        i;
         2300         Rune*        c;
         2301         Rune*        s;
         2302         Rune*        t;
         2303         Attr*        a;
         2304         SEvent*        e;
         2305 
         2306         if(ps->skipping) {
         2307                 if(warn)
         2308                         fprint(2, "warning: skipping item: %I\n", it);
         2309                 return;
         2310         }
         2311         it->anchorid = ps->curanchor;
         2312         it->state |= ps->curstate;
         2313         if(tok != nil) {
         2314                 any = 0;
         2315                 i = nil;
         2316                 c = nil;
         2317                 s = nil;
         2318                 t = nil;
         2319                 e = nil;
         2320                 for(a = tok->attr; a != nil; a = a->next) {
         2321                         aid = a->attid;
         2322                         if(!attrinfo[aid])
         2323                                 continue;
         2324                         switch(aid) {
         2325                         case Aid:
         2326                                 i = a->value;
         2327                                 break;
         2328 
         2329                         case Aclass:
         2330                                 c = a->value;
         2331                                 break;
         2332 
         2333                         case Astyle:
         2334                                 s = a->value;
         2335                                 break;
         2336 
         2337                         case Atitle:
         2338                                 t = a->value;
         2339                                 break;
         2340 
         2341                         default:
         2342                                 assert(aid >= Aonblur && aid <= Aonunload);
         2343                                 e = newscriptevent(scriptev[a->attid], a->value, e);
         2344                                 break;
         2345                         }
         2346                         a->value = nil;
         2347                         any = 1;
         2348                 }
         2349                 if(any)
         2350                         it->genattr = newgenattr(i, c, s, t, e);
         2351         }
         2352         ps->curstate &= ~(IFbrk|IFbrksp|IFnobrk|IFcleft|IFcright);
         2353         ps->prelastit = ps->lastit;
         2354         ps->lastit->next = it;
         2355         ps->lastit = it;
         2356 }
         2357 
         2358 /* Make a text item out of s, */
         2359 /* using current font, foreground, vertical offset and underline state. */
         2360 static Item*
         2361 textit(Pstate* ps, Rune* s)
         2362 {
         2363         assert(s != nil);
         2364         return newitext(s, ps->curfont, ps->curfg, ps->curvoff + Voffbias, ps->curul);
         2365 }
         2366 
         2367 /* Add text item or items for s, paying attention to */
         2368 /* current font, foreground, baseline offset, underline state, */
         2369 /* and literal mode.  Unless we're in literal mode, compress */
         2370 /* whitespace to single blank, and, if curstate has a break, */
         2371 /* trim any leading whitespace.  Whether in literal mode or not, */
         2372 /* turn nonbreaking spaces into spacer items with IFnobrk set. */
         2373 /* */
         2374 /* In literal mode, break up s at newlines and add breaks instead. */
         2375 /* Also replace tabs appropriate number of spaces. */
         2376 /* In nonliteral mode, break up the items every 100 or so characters */
         2377 /* just to make the layout algorithm not go quadratic. */
         2378 /* */
         2379 /* addtext assumes ownership of s. */
         2380 static void
         2381 addtext(Pstate* ps, Rune* s)
         2382 {
         2383         int        n;
         2384         int        i;
         2385         int        j;
         2386         int        k;
         2387         int        col;
         2388         int        c;
         2389         int        nsp;
         2390         Item*        it;
         2391         Rune*        ss;
         2392         Rune*        p;
         2393         Rune        buf[SMALLBUFSIZE];
         2394 
         2395         assert(s != nil);
         2396         n = runestrlen(s);
         2397         i = 0;
         2398         j = 0;
         2399         if(ps->literal) {
         2400                 col = 0;
         2401                 while(i < n) {
         2402                         if(s[i] == '\n') {
         2403                                 if(i > j) {
         2404                                         /* trim trailing blanks from line */
         2405                                         for(k = i; k > j; k--)
         2406                                                 if(s[k - 1] != ' ')
         2407                                                         break;
         2408                                         if(k > j)
         2409                                                 additem(ps, textit(ps, _Strndup(s+j, k-j)), nil);
         2410                                 }
         2411                                 addlinebrk(ps, 0);
         2412                                 j = i + 1;
         2413                                 col = 0;
         2414                         }
         2415                         else {
         2416                                 if(s[i] == '\t') {
         2417                                         col += i - j;
         2418                                         nsp = 8 - (col%8);
         2419                                         /* make ss = s[j:i] + nsp spaces */
         2420                                         ss = _newstr(i-j+nsp);
         2421                                         p = _Stradd(ss, s+j, i-j);
         2422                                         p = _Stradd(p, L(Ltab2space), nsp);
         2423                                         *p = 0;
         2424                                         additem(ps, textit(ps, ss), nil);
         2425                                         col += nsp;
         2426                                         j = i + 1;
         2427                                 }
         2428                                 else if(s[i] == NBSP) {
         2429                                         if(i > j)
         2430                                                 additem(ps, textit(ps, _Strndup(s+j, i-j)), nil);
         2431                                         addnbsp(ps);
         2432                                         col += (i - j) + 1;
         2433                                         j = i + 1;
         2434                                 }
         2435                         }
         2436                         i++;
         2437                 }
         2438                 if(i > j) {
         2439                         if(j == 0 && i == n) {
         2440                                 /* just transfer s over */
         2441                                 additem(ps, textit(ps, s), nil);
         2442                         }
         2443                         else {
         2444                                 additem(ps, textit(ps, _Strndup(s+j, i-j)), nil);
         2445                                 free(s);
         2446                         }
         2447                 }
         2448                 else {
         2449                         free(s);
         2450                 }
         2451         }
         2452         else {        /* not literal mode */
         2453                 if((ps->curstate&IFbrk) || ps->lastit == ps->items)
         2454                         while(i < n) {
         2455                                 c = s[i];
         2456                                 if(c >= 256 || !isspace(c))
         2457                                         break;
         2458                                 i++;
         2459                         }
         2460                 p = buf;
         2461                 for(j = i; i < n; i++) {
         2462                         assert(p+i-j < buf+SMALLBUFSIZE-1);
         2463                         c = s[i];
         2464                         if(c == NBSP) {
         2465                                 if(i > j)
         2466                                         p = _Stradd(p, s+j, i-j);
         2467                                 if(p > buf)
         2468                                         additem(ps, textit(ps, _Strndup(buf, p-buf)), nil);
         2469                                 p = buf;
         2470                                 addnbsp(ps);
         2471                                 j = i + 1;
         2472                                 continue;
         2473                         }
         2474                         if(c < 256 && isspace(c)) {
         2475                                 if(i > j)
         2476                                         p = _Stradd(p, s+j, i-j);
         2477                                 *p++ = ' ';
         2478                                 while(i < n - 1) {
         2479                                         c = s[i + 1];
         2480                                         if(c >= 256 || !isspace(c))
         2481                                                 break;
         2482                                         i++;
         2483                                 }
         2484                                 j = i + 1;
         2485                         }
         2486                         if(i - j >= 100) {
         2487                                 p = _Stradd(p, s+j, i+1-j);
         2488                                 j = i + 1;
         2489                         }
         2490                         if(p-buf >= 100) {
         2491                                 additem(ps, textit(ps, _Strndup(buf, p-buf)), nil);
         2492                                 p = buf;
         2493                         }
         2494                 }
         2495                 if(i > j && j < n) {
         2496                         assert(p+i-j < buf+SMALLBUFSIZE-1);
         2497                         p = _Stradd(p, s+j, i-j);
         2498                 }
         2499                 /* don't add a space if previous item ended in a space */
         2500                 if(p-buf == 1 && buf[0] == ' ' && ps->lastit != nil) {
         2501                         it = ps->lastit;
         2502                         if(it->tag == Itexttag) {
         2503                                 ss = ((Itext*)it)->s;
         2504                                 k = _Strlen(ss);
         2505                                 if(k > 0 && ss[k] == ' ')
         2506                                         p = buf;
         2507                         }
         2508                 }
         2509                 if(p > buf)
         2510                         additem(ps, textit(ps, _Strndup(buf, p-buf)), nil);
         2511                 free(s);
         2512         }
         2513 }
         2514 
         2515 /* Add a break to ps->curstate, with extra space if sp is true. */
         2516 /* If there was a previous break, combine this one's parameters */
         2517 /* with that to make the amt be the max of the two and the clr */
         2518 /* be the most general. (amt will be 0 or 1) */
         2519 /* Also, if the immediately preceding item was a text item, */
         2520 /* trim any whitespace from the end of it, if not in literal mode. */
         2521 /* Finally, if this is at the very beginning of the item list */
         2522 /* (the only thing there is a null spacer), then don't add the space. */
         2523 static void
         2524 addbrk(Pstate* ps, int sp, int clr)
         2525 {
         2526         int        state;
         2527         Rune*        l;
         2528         int                nl;
         2529         Rune*        r;
         2530         int                nr;
         2531         Itext*        t;
         2532         Rune*        s;
         2533 
         2534         state = ps->curstate;
         2535         clr = clr|(state&(IFcleft|IFcright));
         2536         if(sp && !(ps->lastit == ps->items))
         2537                 sp = IFbrksp;
         2538         else
         2539                 sp = 0;
         2540         ps->curstate = IFbrk|sp|(state&~(IFcleft|IFcright))|clr;
         2541         if(ps->lastit != ps->items) {
         2542                 if(!ps->literal && ps->lastit->tag == Itexttag) {
         2543                         t = (Itext*)ps->lastit;
         2544                         _splitr(t->s, _Strlen(t->s), notwhitespace, &l, &nl, &r, &nr);
         2545                         /* try to avoid making empty items */
         2546                         /* but not crucial f the occasional one gets through */
         2547                         if(nl == 0 && ps->prelastit != nil) {
         2548                                 ps->lastit = ps->prelastit;
         2549                                 ps->lastit->next = nil;
         2550                                 ps->prelastit = nil;
         2551                         }
         2552                         else {
         2553                                 s = t->s;
         2554                                 if(nl == 0) {
         2555                                         /* need a non-nil pointer to empty string */
         2556                                         /* (_Strdup(L(Lempty)) returns nil) */
         2557                                         t->s = emalloc(sizeof(Rune));
         2558                                         t->s[0] = 0;
         2559                                 }
         2560                                 else
         2561                                         t->s = _Strndup(l, nl);
         2562                                 if(s)
         2563                                         free(s);
         2564                         }
         2565                 }
         2566         }
         2567 }
         2568 
         2569 /* Add break due to a <br> or a newline within a preformatted section. */
         2570 /* We add a null item first, with current font's height and ascent, to make */
         2571 /* sure that the current line takes up at least that amount of vertical space. */
         2572 /* This ensures that <br>s on empty lines cause blank lines, and that */
         2573 /* multiple <br>s in a row give multiple blank lines. */
         2574 /* However don't add the spacer if the previous item was something that */
         2575 /* takes up space itself. */
         2576 static void
         2577 addlinebrk(Pstate* ps, int clr)
         2578 {
         2579         int        obrkstate;
         2580         int        b;
         2581         int        addit;
         2582 
         2583         /* don't want break before our null item unless the previous item */
         2584         /* was also a null item for the purposes of line breaking */
         2585         obrkstate = ps->curstate&(IFbrk|IFbrksp);
         2586         b = IFnobrk;
         2587         addit = 0;
         2588         if(ps->lastit != nil) {
         2589                 if(ps->lastit->tag == Ispacertag) {
         2590                         if(((Ispacer*)ps->lastit)->spkind == ISPvline)
         2591                                 b = IFbrk;
         2592                         addit = 1;
         2593                 }
         2594                 else if(ps->lastit->tag == Ifloattag)
         2595                         addit = 1;
         2596         }
         2597         if(addit) {
         2598                 ps->curstate = (ps->curstate&~(IFbrk|IFbrksp))|b;
         2599                 additem(ps, newispacer(ISPvline), nil);
         2600                 ps->curstate = (ps->curstate&~(IFbrk|IFbrksp))|obrkstate;
         2601         }
         2602         addbrk(ps, 0, clr);
         2603 }
         2604 
         2605 /* Add a nonbreakable space */
         2606 static void
         2607 addnbsp(Pstate* ps)
         2608 {
         2609         /* if nbsp comes right where a break was specified, */
         2610         /* do the break anyway (nbsp is being used to generate undiscardable */
         2611         /* space rather than to prevent a break) */
         2612         if((ps->curstate&IFbrk) == 0)
         2613                 ps->curstate |= IFnobrk;
         2614         additem(ps, newispacer(ISPhspace), nil);
         2615         /* but definitely no break on next item */
         2616         ps->curstate |= IFnobrk;
         2617 }
         2618 
         2619 /* Change hang in ps.curstate by delta. */
         2620 /* The amount is in 1/10ths of tabs, and is the amount that */
         2621 /* the current contiguous set of items with a hang value set */
         2622 /* is to be shifted left from its normal (indented) place. */
         2623 static void
         2624 changehang(Pstate* ps, int delta)
         2625 {
         2626         int        amt;
         2627 
         2628         amt = (ps->curstate&IFhangmask) + delta;
         2629         if(amt < 0) {
         2630                 if(warn)
         2631                         fprint(2, "warning: hang went negative\n");
         2632                 amt = 0;
         2633         }
         2634         ps->curstate = (ps->curstate&~IFhangmask)|amt;
         2635 }
         2636 
         2637 /* Change indent in ps.curstate by delta. */
         2638 static void
         2639 changeindent(Pstate* ps, int delta)
         2640 {
         2641         int        amt;
         2642 
         2643         amt = ((ps->curstate&IFindentmask) >> IFindentshift) + delta;
         2644         if(amt < 0) {
         2645                 if(warn)
         2646                         fprint(2, "warning: indent went negative\n");
         2647                 amt = 0;
         2648         }
         2649         ps->curstate = (ps->curstate&~IFindentmask)|(amt << IFindentshift);
         2650 }
         2651 
         2652 /* Push val on top of stack, and also return value pushed */
         2653 static int
         2654 push(Stack* stk, int val)
         2655 {
         2656         if(stk->n == Nestmax) {
         2657                 if(warn)
         2658                         fprint(2, "warning: build stack overflow\n");
         2659         }
         2660         else
         2661                 stk->slots[stk->n++] = val;
         2662         return val;
         2663 }
         2664 
         2665 /* Pop top of stack */
         2666 static void
         2667 pop(Stack* stk)
         2668 {
         2669         if(stk->n > 0)
         2670                 --stk->n;
         2671 }
         2672 
         2673 /*Return top of stack, using dflt if stack is empty */
         2674 static int
         2675 top(Stack* stk, int dflt)
         2676 {
         2677         if(stk->n == 0)
         2678                 return dflt;
         2679         return stk->slots[stk->n-1];
         2680 }
         2681 
         2682 /* pop, then return new top, with dflt if empty */
         2683 static int
         2684 popretnewtop(Stack* stk, int dflt)
         2685 {
         2686         if(stk->n == 0)
         2687                 return dflt;
         2688         stk->n--;
         2689         if(stk->n == 0)
         2690                 return dflt;
         2691         return stk->slots[stk->n-1];
         2692 }
         2693 
         2694 /* Copy fromstk entries into tostk */
         2695 static void
         2696 copystack(Stack* tostk, Stack* fromstk)
         2697 {
         2698         int n;
         2699 
         2700         n = fromstk->n;
         2701         tostk->n = n;
         2702         memmove(tostk->slots, fromstk->slots, n*sizeof(int));
         2703 }
         2704 
         2705 static void
         2706 popfontstyle(Pstate* ps)
         2707 {
         2708         pop(&ps->fntstylestk);
         2709         setcurfont(ps);
         2710 }
         2711 
         2712 static void
         2713 pushfontstyle(Pstate* ps, int sty)
         2714 {
         2715         push(&ps->fntstylestk, sty);
         2716         setcurfont(ps);
         2717 }
         2718 
         2719 static void
         2720 popfontsize(Pstate* ps)
         2721 {
         2722         pop(&ps->fntsizestk);
         2723         setcurfont(ps);
         2724 }
         2725 
         2726 static void
         2727 pushfontsize(Pstate* ps, int sz)
         2728 {
         2729         push(&ps->fntsizestk, sz);
         2730         setcurfont(ps);
         2731 }
         2732 
         2733 static void
         2734 setcurfont(Pstate* ps)
         2735 {
         2736         int        sty;
         2737         int        sz;
         2738 
         2739         sty = top(&ps->fntstylestk, FntR);
         2740         sz = top(&ps->fntsizestk, Normal);
         2741         if(sz < Tiny)
         2742                 sz = Tiny;
         2743         if(sz > Verylarge)
         2744                 sz = Verylarge;
         2745         ps->curfont = sty*NumSize + sz;
         2746 }
         2747 
         2748 static void
         2749 popjust(Pstate* ps)
         2750 {
         2751         pop(&ps->juststk);
         2752         setcurjust(ps);
         2753 }
         2754 
         2755 static void
         2756 pushjust(Pstate* ps, int j)
         2757 {
         2758         push(&ps->juststk, j);
         2759         setcurjust(ps);
         2760 }
         2761 
         2762 static void
         2763 setcurjust(Pstate* ps)
         2764 {
         2765         int        j;
         2766         int        state;
         2767 
         2768         j = top(&ps->juststk, ALleft);
         2769         if(j != ps->curjust) {
         2770                 ps->curjust = j;
         2771                 state = ps->curstate;
         2772                 state &= ~(IFrjust|IFcjust);
         2773                 if(j == ALcenter)
         2774                         state |= IFcjust;
         2775                 else if(j == ALright)
         2776                         state |= IFrjust;
         2777                 ps->curstate = state;
         2778         }
         2779 }
         2780 
         2781 /* Do final rearrangement after table parsing is finished */
         2782 /* and assign cells to grid points */
         2783 static void
         2784 finish_table(Table* t)
         2785 {
         2786         int        ncol;
         2787         int        nrow;
         2788         int        r;
         2789         Tablerow*        rl;
         2790         Tablecell*        cl;
         2791         int*        rowspancnt;
         2792         Tablecell**        rowspancell;
         2793         int        ri;
         2794         int        ci;
         2795         Tablecell*        c;
         2796         Tablecell*        cnext;
         2797         Tablerow*        row;
         2798         Tablerow*        rownext;
         2799         int        rcols;
         2800         int        newncol;
         2801         int        k;
         2802         int        j;
         2803         int        cspan;
         2804         int        rspan;
         2805         int        i;
         2806 
         2807         rl = t->rows;
         2808         t->nrow = nrow = _listlen((List*)rl);
         2809         t->rows = (Tablerow*)emalloc(nrow * sizeof(Tablerow));
         2810         ncol = 0;
         2811         r = nrow - 1;
         2812         for(row = rl; row != nil; row = rownext) {
         2813                 /* copy the data from the allocated Tablerow into the array slot */
         2814                 t->rows[r] = *row;
         2815                 rownext = row->next;
         2816                 row = &t->rows[r];
         2817                 r--;
         2818                 rcols = 0;
         2819                 c = row->cells;
         2820 
         2821                 /* If rowspan is > 1 but this is the last row, */
         2822                 /* reset the rowspan */
         2823                 if(c != nil && c->rowspan > 1 && r == nrow-2)
         2824                                 c->rowspan = 1;
         2825 
         2826                 /* reverse row->cells list (along nextinrow pointers) */
         2827                 row->cells = nil;
         2828                 while(c != nil) {
         2829                         cnext = c->nextinrow;
         2830                         c->nextinrow = row->cells;
         2831                         row->cells = c;
         2832                         rcols += c->colspan;
         2833                         c = cnext;
         2834                 }
         2835                 if(rcols > ncol)
         2836                         ncol = rcols;
         2837         }
         2838         t->ncol = ncol;
         2839         t->cols = (Tablecol*)emalloc(ncol * sizeof(Tablecol));
         2840 
         2841         /* Reverse cells just so they are drawn in source order. */
         2842         /* Also, trim their contents so they don't end in whitespace. */
         2843         t->cells = (Tablecell*)_revlist((List*)t->cells);
         2844         for(c = t->cells; c != nil; c= c->next)
         2845                 trim_cell(c);
         2846         t->grid = (Tablecell***)emalloc(nrow * sizeof(Tablecell**));
         2847         for(i = 0; i < nrow; i++)
         2848                 t->grid[i] = (Tablecell**)emalloc(ncol * sizeof(Tablecell*));
         2849 
         2850         /* The following arrays keep track of cells that are spanning */
         2851         /* multiple rows;  rowspancnt[i] is the number of rows left */
         2852         /* to be spanned in column i. */
         2853         /* When done, cell's (row,col) is upper left grid point. */
         2854         rowspancnt = (int*)emalloc(ncol * sizeof(int));
         2855         rowspancell = (Tablecell**)emalloc(ncol * sizeof(Tablecell*));
         2856         for(ri = 0; ri < nrow; ri++) {
         2857                 row = &t->rows[ri];
         2858                 cl = row->cells;
         2859                 ci = 0;
         2860                 while(ci < ncol || cl != nil) {
         2861                         if(ci < ncol && rowspancnt[ci] > 0) {
         2862                                 t->grid[ri][ci] = rowspancell[ci];
         2863                                 rowspancnt[ci]--;
         2864                                 ci++;
         2865                         }
         2866                         else {
         2867                                 if(cl == nil) {
         2868                                         ci++;
         2869                                         continue;
         2870                                 }
         2871                                 c = cl;
         2872                                 cl = cl->nextinrow;
         2873                                 cspan = c->colspan;
         2874                                 rspan = c->rowspan;
         2875                                 if(ci + cspan > ncol) {
         2876                                         /* because of row spanning, we calculated */
         2877                                         /* ncol incorrectly; adjust it */
         2878                                         newncol = ci + cspan;
         2879                                         t->cols = (Tablecol*)erealloc(t->cols, newncol * sizeof(Tablecol));
         2880                                         rowspancnt = (int*)erealloc(rowspancnt, newncol * sizeof(int));
         2881                                         rowspancell = (Tablecell**)erealloc(rowspancell, newncol * sizeof(Tablecell*));
         2882                                         k = newncol-ncol;
         2883                                         memset(t->cols+ncol, 0, k*sizeof(Tablecol));
         2884                                         memset(rowspancnt+ncol, 0, k*sizeof(int));
         2885                                         memset(rowspancell+ncol, 0, k*sizeof(Tablecell*));
         2886                                         for(j = 0; j < nrow; j++) {
         2887                                                 t->grid[j] = (Tablecell**)erealloc(t->grid[j], newncol * sizeof(Tablecell*));
         2888                                                 memset(t->grid[j], 0, k*sizeof(Tablecell*));
         2889                                         }
         2890                                         t->ncol = ncol = newncol;
         2891                                 }
         2892                                 c->row = ri;
         2893                                 c->col = ci;
         2894                                 for(i = 0; i < cspan; i++) {
         2895                                         t->grid[ri][ci] = c;
         2896                                         if(rspan > 1) {
         2897                                                 rowspancnt[ci] = rspan - 1;
         2898                                                 rowspancell[ci] = c;
         2899                                         }
         2900                                         ci++;
         2901                                 }
         2902                         }
         2903                 }
         2904         }
         2905 }
         2906 
         2907 /* Remove tail of cell content until it isn't whitespace. */
         2908 static void
         2909 trim_cell(Tablecell* c)
         2910 {
         2911         int        dropping;
         2912         Rune*        s;
         2913         Rune*        x;
         2914         Rune*        y;
         2915         int                nx;
         2916         int                ny;
         2917         Item*        p;
         2918         Itext*        q;
         2919         Item*        pprev;
         2920 
         2921         dropping = 1;
         2922         while(c->content != nil && dropping) {
         2923                 p = c->content;
         2924                 pprev = nil;
         2925                 while(p->next != nil) {
         2926                         pprev = p;
         2927                         p = p->next;
         2928                 }
         2929                 dropping = 0;
         2930                 if(!(p->state&IFnobrk)) {
         2931                         if(p->tag == Itexttag) {
         2932                                 q = (Itext*)p;
         2933                                 s = q->s;
         2934                                 _splitr(s, _Strlen(s), notwhitespace, &x, &nx, &y, &ny);
         2935                                 if(nx != 0 && ny != 0) {
         2936                                         q->s = _Strndup(x, nx);
         2937                                         free(s);
         2938                                 }
         2939                                 break;
         2940                         }
         2941                 }
         2942                 if(dropping) {
         2943                         if(pprev == nil)
         2944                                 c->content = nil;
         2945                         else
         2946                                 pprev->next = nil;
         2947                         freeitem(p);
         2948                 }
         2949         }
         2950 }
         2951 
         2952 /* Caller must free answer (eventually). */
         2953 static Rune*
         2954 listmark(uchar ty, int n)
         2955 {
         2956         Rune*        s;
         2957         Rune*        t;
         2958         int        n2;
         2959         int        i;
         2960 
         2961         s = nil;
         2962         switch(ty) {
         2963         case LTdisc:
         2964         case LTsquare:
         2965         case LTcircle:
         2966                 s = _newstr(1);
         2967                 s[0] = (ty == LTdisc)? 0x2022                /* bullet */
         2968                         : ((ty == LTsquare)? 0x220e        /* filled square */
         2969                             : 0x2218);                                /* degree */
         2970                 s[1] = 0;
         2971                 break;
         2972 
         2973         case LT1:
         2974                 t = _ltoStr(n);
         2975                 n2 = _Strlen(t);
         2976                 s = _newstr(n2+1);
         2977                 t = _Stradd(s, t, n2);
         2978                 *t++ = '.';
         2979                 *t = 0;
         2980                 break;
         2981 
         2982         case LTa:
         2983         case LTA:
         2984                 n--;
         2985                 i = 0;
         2986                 if(n < 0)
         2987                         n = 0;
         2988                 s = _newstr((n <= 25)? 2 : 3);
         2989                 if(n > 25) {
         2990                         n2 = n%26;
         2991                         n /= 26;
         2992                         if(n2 > 25)
         2993                                 n2 = 25;
         2994                         s[i++] = n2 + (ty == LTa)? 'a' : 'A';
         2995                 }
         2996                 s[i++] = n + (ty == LTa)? 'a' : 'A';
         2997                 s[i++] = '.';
         2998                 s[i] = 0;
         2999                 break;
         3000 
         3001         case LTi:
         3002         case LTI:
         3003                 if(n >= NROMAN) {
         3004                         if(warn)
         3005                                 fprint(2, "warning: unimplemented roman number > %d\n", NROMAN);
         3006                         n = NROMAN;
         3007                 }
         3008                 t = roman[n - 1];
         3009                 n2 = _Strlen(t);
         3010                 s = _newstr(n2+1);
         3011                 for(i = 0; i < n2; i++)
         3012                         s[i] = (ty == LTi)? tolower(t[i]) : t[i];
         3013                 s[i++] = '.';
         3014                 s[i] = 0;
         3015                 break;
         3016         }
         3017         return s;
         3018 }
         3019 
         3020 /* Find map with given name in di.maps. */
         3021 /* If not there, add one, copying name. */
         3022 /* Ownership of map remains with di->maps list. */
         3023 static Map*
         3024 getmap(Docinfo* di, Rune* name)
         3025 {
         3026         Map*        m;
         3027 
         3028         for(m = di->maps; m != nil; m = m->next) {
         3029                 if(!_Strcmp(name, m->name))
         3030                         return m;
         3031         }
         3032         m = (Map*)emalloc(sizeof(Map));
         3033         m->name = _Strdup(name);
         3034         m->areas = nil;
         3035         m->next = di->maps;
         3036         di->maps = m;
         3037         return m;
         3038 }
         3039 
         3040 /* Transfers ownership of href to Area */
         3041 static Area*
         3042 newarea(int shape, Rune* href, int target, Area* link)
         3043 {
         3044         Area* a;
         3045 
         3046         a = (Area*)emalloc(sizeof(Area));
         3047         a->shape = shape;
         3048         a->href = href;
         3049         a->target = target;
         3050         a->next = link;
         3051         return a;
         3052 }
         3053 
         3054 /* Return string value associated with attid in tok, nil if none. */
         3055 /* Caller must free the result (eventually). */
         3056 static Rune*
         3057 aval(Token* tok, int attid)
         3058 {
         3059         Rune*        ans;
         3060 
         3061         _tokaval(tok, attid, &ans, 1);        /* transfers string ownership from token to ans */
         3062         return ans;
         3063 }
         3064 
         3065 /* Like aval, but use dflt if there was no such attribute in tok. */
         3066 /* Caller must free the result (eventually). */
         3067 static Rune*
         3068 astrval(Token* tok, int attid, Rune* dflt)
         3069 {
         3070         Rune*        ans;
         3071 
         3072         if(_tokaval(tok, attid, &ans, 1))
         3073                 return ans;        /* transfers string ownership from token to ans */
         3074         else
         3075                 return _Strdup(dflt);
         3076 }
         3077 
         3078 /* Here we're supposed to convert to an int, */
         3079 /* and have a default when not found */
         3080 static int
         3081 aintval(Token* tok, int attid, int dflt)
         3082 {
         3083         Rune*        ans;
         3084 
         3085         if(!_tokaval(tok, attid, &ans, 0) || ans == nil)
         3086                 return dflt;
         3087         else
         3088                 return toint(ans);
         3089 }
         3090 
         3091 /* Like aintval, but result should be >= 0 */
         3092 static int
         3093 auintval(Token* tok, int attid, int dflt)
         3094 {
         3095         Rune* ans;
         3096         int v;
         3097 
         3098         if(!_tokaval(tok, attid, &ans, 0) || ans == nil)
         3099                 return dflt;
         3100         else {
         3101                 v = toint(ans);
         3102                 return v >= 0? v : 0;
         3103         }
         3104 }
         3105 
         3106 /* int conversion, but with possible error check (if warning) */
         3107 static int
         3108 toint(Rune* s)
         3109 {
         3110         int ans;
         3111         Rune* eptr;
         3112 
         3113         ans = _Strtol(s, &eptr, 10);
         3114         if(warn) {
         3115                 if(*eptr != 0) {
         3116                         eptr = _Strclass(eptr, notwhitespace);
         3117                         if(eptr != nil)
         3118                                 fprint(2, "warning: expected integer, got %S\n", s);
         3119                 }
         3120         }
         3121         return ans;
         3122 }
         3123 
         3124 /* Attribute value when need a table to convert strings to ints */
         3125 static int
         3126 atabval(Token* tok, int attid, StringInt* tab, int ntab, int dflt)
         3127 {
         3128         Rune*        aval;
         3129         int        ans;
         3130 
         3131         ans = dflt;
         3132         if(_tokaval(tok, attid, &aval, 0)) {
         3133                 if(!_lookup(tab, ntab, aval, _Strlen(aval), &ans)) {
         3134                         ans = dflt;
         3135                         if(warn)
         3136                                 fprint(2, "warning: name not found in table lookup: %S\n", aval);
         3137                 }
         3138         }
         3139         return ans;
         3140 }
         3141 
         3142 /* Attribute value when supposed to be a color */
         3143 static int
         3144 acolorval(Token* tok, int attid, int dflt)
         3145 {
         3146         Rune*        aval;
         3147         int        ans;
         3148 
         3149         ans = dflt;
         3150         if(_tokaval(tok, attid, &aval, 0))
         3151                 ans = color(aval, dflt);
         3152         return ans;
         3153 }
         3154 
         3155 /* Attribute value when supposed to be a target frame name */
         3156 static int
         3157 atargval(Token* tok, int dflt)
         3158 {
         3159         int        ans;
         3160         Rune*        aval;
         3161 
         3162         ans = dflt;
         3163         if(_tokaval(tok, Atarget, &aval, 0)){
         3164                 ans = targetid(aval);
         3165         }
         3166         return ans;
         3167 }
         3168 
         3169 /* special for list types, where "i" and "I" are different, */
         3170 /* but "square" and "SQUARE" are the same */
         3171 static int
         3172 listtyval(Token* tok, int dflt)
         3173 {
         3174         Rune*        aval;
         3175         int        ans;
         3176         int        n;
         3177 
         3178         ans = dflt;
         3179         if(_tokaval(tok, Atype, &aval, 0)) {
         3180                 n = _Strlen(aval);
         3181                 if(n == 1) {
         3182                         switch(aval[0]) {
         3183                         case '1':
         3184                                 ans = LT1;
         3185                                 break;
         3186                         case 'A':
         3187                                 ans = LTA;
         3188                                 break;
         3189                         case 'I':
         3190                                 ans = LTI;
         3191                                 break;
         3192                         case 'a':
         3193                                 ans = LTa;
         3194                                 break;
         3195                         case 'i':
         3196                                 ans = LTi;
         3197                         default:
         3198                                 if(warn)
         3199                                         fprint(2, "warning: unknown list element type %c\n", aval[0]);
         3200                         }
         3201                 }
         3202                 else {
         3203                         if(!_Strncmpci(aval, n, L(Lcircle)))
         3204                                 ans = LTcircle;
         3205                         else if(!_Strncmpci(aval, n, L(Ldisc)))
         3206                                 ans = LTdisc;
         3207                         else if(!_Strncmpci(aval, n, L(Lsquare)))
         3208                                 ans = LTsquare;
         3209                         else {
         3210                                 if(warn)
         3211                                         fprint(2, "warning: unknown list element type %S\n", aval);
         3212                         }
         3213                 }
         3214         }
         3215         return ans;
         3216 }
         3217 
         3218 /* Attribute value when value is a URL, possibly relative to base. */
         3219 /* FOR NOW: leave the url relative. */
         3220 /* Caller must free the result (eventually). */
         3221 static Rune*
         3222 aurlval(Token* tok, int attid, Rune* dflt, Rune* base)
         3223 {
         3224         Rune*        ans;
         3225         Rune*        url;
         3226 
         3227         USED(base);
         3228         ans = nil;
         3229         if(_tokaval(tok, attid, &url, 0) && url != nil)
         3230                 ans = removeallwhite(url);
         3231         if(ans == nil)
         3232                 ans = _Strdup(dflt);
         3233         return ans;
         3234 }
         3235 
         3236 /* Return copy of s but with all whitespace (even internal) removed. */
         3237 /* This fixes some buggy URL specification strings. */
         3238 static Rune*
         3239 removeallwhite(Rune* s)
         3240 {
         3241         int        j;
         3242         int        n;
         3243         int        i;
         3244         int        c;
         3245         Rune*        ans;
         3246 
         3247         j = 0;
         3248         n = _Strlen(s);
         3249         for(i = 0; i < n; i++) {
         3250                 c = s[i];
         3251                 if(c >= 256 || !isspace(c))
         3252                         j++;
         3253         }
         3254         if(j < n) {
         3255                 ans = _newstr(j);
         3256                 j = 0;
         3257                 for(i = 0; i < n; i++) {
         3258                         c = s[i];
         3259                         if(c >= 256 || !isspace(c))
         3260                                 ans[j++] = c;
         3261                 }
         3262                 ans[j] = 0;
         3263         }
         3264         else
         3265                 ans = _Strdup(s);
         3266         return ans;
         3267 }
         3268 
         3269 /* Attribute value when mere presence of attr implies value of 1, */
         3270 /* but if there is an integer there, return it as the value. */
         3271 static int
         3272 aflagval(Token* tok, int attid)
         3273 {
         3274         int        val;
         3275         Rune*        sval;
         3276 
         3277         val = 0;
         3278         if(_tokaval(tok, attid, &sval, 0)) {
         3279                 val = 1;
         3280                 if(sval != nil)
         3281                         val = toint(sval);
         3282         }
         3283         return val;
         3284 }
         3285 
         3286 static Align
         3287 makealign(int halign, int valign)
         3288 {
         3289         Align        al;
         3290 
         3291         al.halign = halign;
         3292         al.valign = valign;
         3293         return al;
         3294 }
         3295 
         3296 /* Make an Align (two alignments, horizontal and vertical) */
         3297 static Align
         3298 aalign(Token* tok)
         3299 {
         3300         return makealign(
         3301                 atabval(tok, Aalign, align_tab, NALIGNTAB, ALnone),
         3302                 atabval(tok, Avalign, align_tab, NALIGNTAB, ALnone));
         3303 }
         3304 
         3305 /* Make a Dimen, based on value of attid attr */
         3306 static Dimen
         3307 adimen(Token* tok, int attid)
         3308 {
         3309         Rune*        wd;
         3310 
         3311         if(_tokaval(tok, attid, &wd, 0))
         3312                 return parsedim(wd, _Strlen(wd));
         3313         else
         3314                 return makedimen(Dnone, 0);
         3315 }
         3316 
         3317 /* Parse s[0:n] as num[.[num]][unit][%|*] */
         3318 static Dimen
         3319 parsedim(Rune* s, int ns)
         3320 {
         3321         int        kind;
         3322         int        spec;
         3323         Rune*        l;
         3324         int        nl;
         3325         Rune*        r;
         3326         int        nr;
         3327         int        mul;
         3328         int        i;
         3329         Rune*        f;
         3330         int        nf;
         3331         int        Tkdpi;
         3332         Rune*        units;
         3333 
         3334         kind = Dnone;
         3335         spec = 0;
         3336         _splitl(s, ns, L(Lnot0to9), &l, &nl, &r, &nr);
         3337         if(nl != 0) {
         3338                 spec = 1000*_Strtol(l, nil, 10);
         3339                 if(nr > 0 && r[0] == '.') {
         3340                         _splitl(r+1, nr-1, L(Lnot0to9), &f, &nf, &r, &nr);
         3341                         if(nf != 0) {
         3342                                 mul = 100;
         3343                                 for(i = 0; i < nf; i++) {
         3344                                         spec = spec + mul*(f[i]-'0');
         3345                                         mul = mul/10;
         3346                                 }
         3347                         }
         3348                 }
         3349                 kind = Dpixels;
         3350                 if(nr != 0) {
         3351                         if(nr >= 2) {
         3352                                 Tkdpi = 100;
         3353                                 units = r;
         3354                                 r = r+2;
         3355                                 nr -= 2;
         3356                                 if(!_Strncmpci(units, 2, L(Lpt)))
         3357                                         spec = (spec*Tkdpi)/72;
         3358                                 else if(!_Strncmpci(units, 2, L(Lpi)))
         3359                                         spec = (spec*12*Tkdpi)/72;
         3360                                 else if(!_Strncmpci(units, 2, L(Lin)))
         3361                                         spec = spec*Tkdpi;
         3362                                 else if(!_Strncmpci(units, 2, L(Lcm)))
         3363                                         spec = (spec*100*Tkdpi)/254;
         3364                                 else if(!_Strncmpci(units, 2, L(Lmm)))
         3365                                         spec = (spec*10*Tkdpi)/254;
         3366                                 else if(!_Strncmpci(units, 2, L(Lem)))
         3367                                         spec = spec*15;
         3368                                 else {
         3369                                         if(warn)
         3370                                                 fprint(2, "warning: unknown units %C%Cs\n", units[0], units[1]);
         3371                                 }
         3372                         }
         3373                         if(nr >= 1) {
         3374                                 if(r[0] == '%')
         3375                                         kind = Dpercent;
         3376                                 else if(r[0] == '*')
         3377                                         kind = Drelative;
         3378                         }
         3379                 }
         3380                 spec = spec/1000;
         3381         }
         3382         else if(nr == 1 && r[0] == '*') {
         3383                 spec = 1;
         3384                 kind = Drelative;
         3385         }
         3386         return makedimen(kind, spec);
         3387 }
         3388 
         3389 static void
         3390 setdimarray(Token* tok, int attid, Dimen** pans, int* panslen)
         3391 {
         3392         Rune*        s;
         3393         Dimen*        d;
         3394         int        k;
         3395         int        nc;
         3396         Rune* a[SMALLBUFSIZE];
         3397         int        an[SMALLBUFSIZE];
         3398 
         3399         if(_tokaval(tok, attid, &s, 0)) {
         3400                 nc = _splitall(s, _Strlen(s), L(Lcommaspace), a, an, SMALLBUFSIZE);
         3401                 if(nc > 0) {
         3402                         d = (Dimen*)emalloc(nc * sizeof(Dimen));
         3403                         for(k = 0; k < nc; k++) {
         3404                                 d[k] = parsedim(a[k], an[k]);
         3405                         }
         3406                         *pans = d;
         3407                         *panslen = nc;
         3408                         return;
         3409                 }
         3410         }
         3411         *pans = nil;
         3412         *panslen = 0;
         3413 }
         3414 
         3415 static Background
         3416 makebackground(Rune* imageurl, int color)
         3417 {
         3418         Background bg;
         3419 
         3420         bg.image = imageurl;
         3421         bg.color = color;
         3422         return bg;
         3423 }
         3424 
         3425 static Item*
         3426 newitext(Rune* s, int fnt, int fg, int voff, int ul)
         3427 {
         3428         Itext* t;
         3429 
         3430         assert(s != nil);
         3431         t = (Itext*)emalloc(sizeof(Itext));
         3432         t->item.tag = Itexttag;
         3433         t->s = s;
         3434         t->fnt = fnt;
         3435         t->fg = fg;
         3436         t->voff = voff;
         3437         t->ul = ul;
         3438         return (Item*)t;
         3439 }
         3440 
         3441 static Item*
         3442 newirule(int align, int size, int noshade, Dimen wspec)
         3443 {
         3444         Irule* r;
         3445 
         3446         r = (Irule*)emalloc(sizeof(Irule));
         3447         r->item.tag = Iruletag;
         3448         r->align = align;
         3449         r->size = size;
         3450         r->noshade = noshade;
         3451         r->wspec = wspec;
         3452         return (Item*)r;
         3453 }
         3454 
         3455 /* Map is owned elsewhere. */
         3456 static Item*
         3457 newiimage(Rune* src, Rune* altrep, int align, int width, int height,
         3458                 int hspace, int vspace, int border, int ismap, Map* map)
         3459 {
         3460         Iimage* i;
         3461         int        state;
         3462 
         3463         state = 0;
         3464         if(ismap)
         3465                 state = IFsmap;
         3466         i = (Iimage*)emalloc(sizeof(Iimage));
         3467         i->item.tag = Iimagetag;
         3468         i->item.state = state;
         3469         i->imsrc = src;
         3470         i->altrep = altrep;
         3471         i->align = align;
         3472         i->imwidth = width;
         3473         i->imheight = height;
         3474         i->hspace = hspace;
         3475         i->vspace = vspace;
         3476         i->border = border;
         3477         i->map = map;
         3478         i->ctlid = -1;
         3479         return (Item*)i;
         3480 }
         3481 
         3482 static Item*
         3483 newiformfield(Formfield* ff)
         3484 {
         3485         Iformfield* f;
         3486 
         3487         f = (Iformfield*)emalloc(sizeof(Iformfield));
         3488         f->item.tag = Iformfieldtag;
         3489         f->formfield = ff;
         3490         return (Item*)f;
         3491 }
         3492 
         3493 static Item*
         3494 newitable(Table* tab)
         3495 {
         3496         Itable* t;
         3497 
         3498         t = (Itable*)emalloc(sizeof(Itable));
         3499         t->item.tag = Itabletag;
         3500         t->table = tab;
         3501         return (Item*)t;
         3502 }
         3503 
         3504 static Item*
         3505 newifloat(Item* it, int side)
         3506 {
         3507         Ifloat* f;
         3508 
         3509         f = (Ifloat*)emalloc(sizeof(Ifloat));
         3510         f->_item.tag = Ifloattag;
         3511         f->_item.state = IFwrap;
         3512         f->item = it;
         3513         f->side = side;
         3514         return (Item*)f;
         3515 }
         3516 
         3517 static Item*
         3518 newispacer(int spkind)
         3519 {
         3520         Ispacer* s;
         3521 
         3522         s = (Ispacer*)emalloc(sizeof(Ispacer));
         3523         s->item.tag = Ispacertag;
         3524         s->spkind = spkind;
         3525         return (Item*)s;
         3526 }
         3527 
         3528 /* Free one item (caller must deal with next pointer) */
         3529 static void
         3530 freeitem(Item* it)
         3531 {
         3532         Iimage* ii;
         3533         Genattr* ga;
         3534 
         3535         if(it == nil)
         3536                 return;
         3537 
         3538         switch(it->tag) {
         3539         case Itexttag:
         3540                 free(((Itext*)it)->s);
         3541                 break;
         3542         case Iimagetag:
         3543                 ii = (Iimage*)it;
         3544                 free(ii->imsrc);
         3545                 free(ii->altrep);
         3546                 break;
         3547         case Iformfieldtag:
         3548                 freeformfield(((Iformfield*)it)->formfield);
         3549                 break;
         3550         case Itabletag:
         3551                 freetable(((Itable*)it)->table);
         3552                 break;
         3553         case Ifloattag:
         3554                 freeitem(((Ifloat*)it)->item);
         3555                 break;
         3556         }
         3557         ga = it->genattr;
         3558         if(ga != nil) {
         3559                 free(ga->id);
         3560                 free(ga->class);
         3561                 free(ga->style);
         3562                 free(ga->title);
         3563                 freescriptevents(ga->events);
         3564         }
         3565         free(it);
         3566 }
         3567 
         3568 /* Free list of items chained through next pointer */
         3569 void
         3570 freeitems(Item* ithead)
         3571 {
         3572         Item* it;
         3573         Item* itnext;
         3574 
         3575         it = ithead;
         3576         while(it != nil) {
         3577                 itnext = it->next;
         3578                 freeitem(it);
         3579                 it = itnext;
         3580         }
         3581 }
         3582 
         3583 static void
         3584 freeformfield(Formfield* ff)
         3585 {
         3586         Option* o;
         3587         Option* onext;
         3588 
         3589         if(ff == nil)
         3590                 return;
         3591 
         3592         free(ff->name);
         3593         free(ff->value);
         3594         for(o = ff->options; o != nil; o = onext) {
         3595                 onext = o->next;
         3596                 free(o->value);
         3597                 free(o->display);
         3598         }
         3599         free(ff);
         3600 }
         3601 
         3602 static void
         3603 freetable(Table* t)
         3604 {
         3605         int i;
         3606         Tablecell* c;
         3607         Tablecell* cnext;
         3608 
         3609         if(t == nil)
         3610                 return;
         3611 
         3612         /* We'll find all the unique cells via t->cells and next pointers. */
         3613         /* (Other pointers to cells in the table are duplicates of these) */
         3614         for(c = t->cells; c != nil; c = cnext) {
         3615                 cnext = c->next;
         3616                 freeitems(c->content);
         3617         }
         3618         if(t->grid != nil) {
         3619                 for(i = 0; i < t->nrow; i++)
         3620                         free(t->grid[i]);
         3621                 free(t->grid);
         3622         }
         3623         free(t->rows);
         3624         free(t->cols);
         3625         freeitems(t->caption);
         3626         free(t);
         3627 }
         3628 
         3629 static void
         3630 freeform(Form* f)
         3631 {
         3632         if(f == nil)
         3633                 return;
         3634 
         3635         free(f->name);
         3636         free(f->action);
         3637         /* Form doesn't own its fields (Iformfield items do) */
         3638         free(f);
         3639 }
         3640 
         3641 static void
         3642 freeforms(Form* fhead)
         3643 {
         3644         Form* f;
         3645         Form* fnext;
         3646 
         3647         for(f = fhead; f != nil; f = fnext) {
         3648                 fnext = f->next;
         3649                 freeform(f);
         3650         }
         3651 }
         3652 
         3653 static void
         3654 freeanchor(Anchor* a)
         3655 {
         3656         if(a == nil)
         3657                 return;
         3658 
         3659         free(a->name);
         3660         free(a->href);
         3661         free(a);
         3662 }
         3663 
         3664 static void
         3665 freeanchors(Anchor* ahead)
         3666 {
         3667         Anchor* a;
         3668         Anchor* anext;
         3669 
         3670         for(a = ahead; a != nil; a = anext) {
         3671                 anext = a->next;
         3672                 freeanchor(a);
         3673         }
         3674 }
         3675 
         3676 static void
         3677 freedestanchor(DestAnchor* da)
         3678 {
         3679         if(da == nil)
         3680                 return;
         3681 
         3682         free(da->name);
         3683         free(da);
         3684 }
         3685 
         3686 static void
         3687 freedestanchors(DestAnchor* dahead)
         3688 {
         3689         DestAnchor* da;
         3690         DestAnchor* danext;
         3691 
         3692         for(da = dahead; da != nil; da = danext) {
         3693                 danext = da->next;
         3694                 freedestanchor(da);
         3695         }
         3696 }
         3697 
         3698 static void
         3699 freearea(Area* a)
         3700 {
         3701         if(a == nil)
         3702                 return;
         3703         free(a->href);
         3704         free(a->coords);
         3705 }
         3706 
         3707 static void freekidinfos(Kidinfo* khead);
         3708 
         3709 static void
         3710 freekidinfo(Kidinfo* k)
         3711 {
         3712         if(k->isframeset) {
         3713                 free(k->rows);
         3714                 free(k->cols);
         3715                 freekidinfos(k->kidinfos);
         3716         }
         3717         else {
         3718                 free(k->src);
         3719                 free(k->name);
         3720         }
         3721         free(k);
         3722 }
         3723 
         3724 static void
         3725 freekidinfos(Kidinfo* khead)
         3726 {
         3727         Kidinfo* k;
         3728         Kidinfo* knext;
         3729 
         3730         for(k = khead; k != nil; k = knext) {
         3731                 knext = k->next;
         3732                 freekidinfo(k);
         3733         }
         3734 }
         3735 
         3736 static void
         3737 freemap(Map* m)
         3738 {
         3739         Area* a;
         3740         Area* anext;
         3741 
         3742         if(m == nil)
         3743                 return;
         3744 
         3745         free(m->name);
         3746         for(a = m->areas; a != nil; a = anext) {
         3747                 anext = a->next;
         3748                 freearea(a);
         3749         }
         3750         free(m);
         3751 }
         3752 
         3753 static void
         3754 freemaps(Map* mhead)
         3755 {
         3756         Map* m;
         3757         Map* mnext;
         3758 
         3759         for(m = mhead; m != nil; m = mnext) {
         3760                 mnext = m->next;
         3761                 freemap(m);
         3762         }
         3763 }
         3764 
         3765 void
         3766 freedocinfo(Docinfo* d)
         3767 {
         3768         if(d == nil)
         3769                 return;
         3770         free(d->src);
         3771         free(d->base);
         3772         freeitem((Item*)d->backgrounditem);
         3773         free(d->refresh);
         3774         freekidinfos(d->kidinfo);
         3775         freeanchors(d->anchors);
         3776         freedestanchors(d->dests);
         3777         freeforms(d->forms);
         3778         freemaps(d->maps);
         3779         /* tables, images, and formfields are freed when */
         3780         /* the items pointing at them are freed */
         3781         free(d);
         3782 }
         3783 
         3784 /* Currently, someone else owns all the memory */
         3785 /* pointed to by things in a Pstate. */
         3786 static void
         3787 freepstate(Pstate* p)
         3788 {
         3789         free(p);
         3790 }
         3791 
         3792 static void
         3793 freepstatestack(Pstate* pshead)
         3794 {
         3795         Pstate* p;
         3796         Pstate* pnext;
         3797 
         3798         for(p = pshead; p != nil; p = pnext) {
         3799                 pnext = p->next;
         3800                 free(p);
         3801         }
         3802 }
         3803 
         3804 static int
         3805 Iconv(Fmt *f)
         3806 {
         3807         Item*        it;
         3808         Itext*        t;
         3809         Irule*        r;
         3810         Iimage*        i;
         3811         Ifloat*        fl;
         3812         int        state;
         3813         Formfield*        ff;
         3814         Rune*        ty;
         3815         Tablecell*        c;
         3816         Table*        tab;
         3817         char*        p;
         3818         int        cl;
         3819         int        hang;
         3820         int        indent;
         3821         int        bi;
         3822         int        nbuf;
         3823         char        buf[BIGBUFSIZE];
         3824 
         3825         it = va_arg(f->args, Item*);
         3826         bi = 0;
         3827         nbuf = sizeof(buf);
         3828         state = it->state;
         3829         nbuf = nbuf-1;
         3830         if(state&IFbrk) {
         3831                 cl = state&(IFcleft|IFcright);
         3832                 p = "";
         3833                 if(cl) {
         3834                         if(cl == (IFcleft|IFcright))
         3835                                 p = " both";
         3836                         else if(cl == IFcleft)
         3837                                 p = " left";
         3838                         else
         3839                                 p = " right";
         3840                 }
         3841                 bi = snprint(buf, nbuf, "brk(%d%s)", (state&IFbrksp)? 1 : 0, p);
         3842         }
         3843         if(state&IFnobrk)
         3844                 bi += snprint(buf+bi, nbuf-bi, " nobrk");
         3845         if(!(state&IFwrap))
         3846                 bi += snprint(buf+bi, nbuf-bi, " nowrap");
         3847         if(state&IFrjust)
         3848                 bi += snprint(buf+bi, nbuf-bi, " rjust");
         3849         if(state&IFcjust)
         3850                 bi += snprint(buf+bi, nbuf-bi, " cjust");
         3851         if(state&IFsmap)
         3852                 bi += snprint(buf+bi, nbuf-bi, " smap");
         3853         indent = (state&IFindentmask) >> IFindentshift;
         3854         if(indent > 0)
         3855                 bi += snprint(buf+bi, nbuf-bi, " indent=%d", indent);
         3856         hang = state&IFhangmask;
         3857         if(hang > 0)
         3858                 bi += snprint(buf+bi, nbuf-bi, " hang=%d", hang);
         3859 
         3860         switch(it->tag) {
         3861         case Itexttag:
         3862                 t = (Itext*)it;
         3863                 bi += snprint(buf+bi, nbuf-bi, " Text '%S', fnt=%d, fg=%x", t->s, t->fnt, t->fg);
         3864                 break;
         3865 
         3866         case Iruletag:
         3867                 r = (Irule*)it;
         3868                 bi += snprint(buf+bi, nbuf-bi, "Rule size=%d, al=%S, wspec=", r->size, stringalign(r->align));
         3869                 bi += dimprint(buf+bi, nbuf-bi, r->wspec);
         3870                 break;
         3871 
         3872         case Iimagetag:
         3873                 i = (Iimage*)it;
         3874                 bi += snprint(buf+bi, nbuf-bi,
         3875                         "Image src=%S, alt=%S, al=%S, w=%d, h=%d hsp=%d, vsp=%d, bd=%d, map=%S",
         3876                         i->imsrc, i->altrep? i->altrep : L(Lempty), stringalign(i->align), i->imwidth, i->imheight,
         3877                         i->hspace, i->vspace, i->border, i->map?i->map->name : L(Lempty));
         3878                 break;
         3879 
         3880         case Iformfieldtag:
         3881                 ff = ((Iformfield*)it)->formfield;
         3882                 if(ff->ftype == Ftextarea)
         3883                         ty = L(Ltextarea);
         3884                 else if(ff->ftype == Fselect)
         3885                         ty = L(Lselect);
         3886                 else {
         3887                         ty = _revlookup(input_tab, NINPUTTAB, ff->ftype);
         3888                         if(ty == nil)
         3889                                 ty = L(Lnone);
         3890                 }
         3891                 bi += snprint(buf+bi, nbuf-bi, "Formfield %S, fieldid=%d, formid=%d, name=%S, value=%S",
         3892                         ty, ff->fieldid, ff->form->formid, ff->name?  ff->name : L(Lempty),
         3893                         ff->value? ff->value : L(Lempty));
         3894                 break;
         3895 
         3896         case Itabletag:
         3897                 tab = ((Itable*)it)->table;
         3898                 bi += snprint(buf+bi, nbuf-bi, "Table tableid=%d, width=", tab->tableid);
         3899                 bi += dimprint(buf+bi, nbuf-bi, tab->width);
         3900                 bi += snprint(buf+bi, nbuf-bi, ", nrow=%d, ncol=%d, ncell=%d, totw=%d, toth=%d\n",
         3901                         tab->nrow, tab->ncol, tab->ncell, tab->totw, tab->toth);
         3902                 for(c = tab->cells; c != nil; c = c->next)
         3903                         bi += snprint(buf+bi, nbuf-bi, "Cell %d.%d, at (%d,%d) ",
         3904                                         tab->tableid, c->cellid, c->row, c->col);
         3905                 bi += snprint(buf+bi, nbuf-bi, "End of Table %d", tab->tableid);
         3906                 break;
         3907 
         3908         case Ifloattag:
         3909                 fl = (Ifloat*)it;
         3910                 bi += snprint(buf+bi, nbuf-bi, "Float, x=%d y=%d, side=%S, it=%I",
         3911                         fl->x, fl->y, stringalign(fl->side), fl->item);
         3912                 bi += snprint(buf+bi, nbuf-bi, "\n\t");
         3913                 break;
         3914 
         3915         case Ispacertag:
         3916                 p = "";
         3917                 switch(((Ispacer*)it)->spkind) {
         3918                 case ISPnull:
         3919                         p = "null";
         3920                         break;
         3921                 case ISPvline:
         3922                         p = "vline";
         3923                         break;
         3924                 case ISPhspace:
         3925                         p = "hspace";
         3926                         break;
         3927                 }
         3928                 bi += snprint(buf+bi, nbuf-bi, "Spacer %s ", p);
         3929                 break;
         3930         }
         3931         bi += snprint(buf+bi, nbuf-bi, " w=%d, h=%d, a=%d, anchor=%d\n",
         3932                         it->width, it->height, it->ascent, it->anchorid);
         3933         buf[bi] = 0;
         3934         return fmtstrcpy(f, buf);
         3935 }
         3936 
         3937 /* String version of alignment 'a' */
         3938 static Rune*
         3939 stringalign(int a)
         3940 {
         3941         Rune*        s;
         3942 
         3943         s = _revlookup(align_tab, NALIGNTAB, a);
         3944         if(s == nil)
         3945                 s = L(Lnone);
         3946         return s;
         3947 }
         3948 
         3949 /* Put at most nbuf chars of representation of d into buf, */
         3950 /* and return number of characters put */
         3951 static int
         3952 dimprint(char* buf, int nbuf, Dimen d)
         3953 {
         3954         int        n;
         3955         int        k;
         3956 
         3957         n = 0;
         3958         n += snprint(buf, nbuf, "%d", dimenspec(d));
         3959         k = dimenkind(d);
         3960         if(k == Dpercent)
         3961                 buf[n++] = '%';
         3962         if(k == Drelative)
         3963                 buf[n++] = '*';
         3964         return n;
         3965 }
         3966 
         3967 void
         3968 printitems(Item* items, char* msg)
         3969 {
         3970         Item*        il;
         3971 
         3972         fprint(2, "%s\n", msg);
         3973         il = items;
         3974         while(il != nil) {
         3975                 fprint(2, "%I", il);
         3976                 il = il->next;
         3977         }
         3978 }
         3979 
         3980 static Genattr*
         3981 newgenattr(Rune* id, Rune* class, Rune* style, Rune* title, SEvent* events)
         3982 {
         3983         Genattr* g;
         3984 
         3985         g = (Genattr*)emalloc(sizeof(Genattr));
         3986         g->id = id;
         3987         g->class = class;
         3988         g->style = style;
         3989         g->title = title;
         3990         g->events = events;
         3991         return g;
         3992 }
         3993 
         3994 static Formfield*
         3995 newformfield(int ftype, int fieldid, Form* form, Rune* name,
         3996                 Rune* value, int size, int maxlength, Formfield* link)
         3997 {
         3998         Formfield* ff;
         3999 
         4000         ff = (Formfield*)emalloc(sizeof(Formfield));
         4001         ff->ftype = ftype;
         4002         ff->fieldid = fieldid;
         4003         ff->form = form;
         4004         ff->name = name;
         4005         ff->value = value;
         4006         ff->size = size;
         4007         ff->maxlength = maxlength;
         4008         ff->ctlid = -1;
         4009         ff->next = link;
         4010         return ff;
         4011 }
         4012 
         4013 /* Transfers ownership of value and display to Option. */
         4014 static Option*
         4015 newoption(int selected, Rune* value, Rune* display, Option* link)
         4016 {
         4017         Option *o;
         4018 
         4019         o = (Option*)emalloc(sizeof(Option));
         4020         o->selected = selected;
         4021         o->value = value;
         4022         o->display = display;
         4023         o->next = link;
         4024         return o;
         4025 }
         4026 
         4027 static Form*
         4028 newform(int formid, Rune* name, Rune* action, int target, int method, Form* link)
         4029 {
         4030         Form* f;
         4031 
         4032         f = (Form*)emalloc(sizeof(Form));
         4033         f->formid = formid;
         4034         f->name = name;
         4035         f->action = action;
         4036         f->target = target;
         4037         f->method = method;
         4038         f->nfields = 0;
         4039         f->fields = nil;
         4040         f->next = link;
         4041         return f;
         4042 }
         4043 
         4044 static Table*
         4045 newtable(int tableid, Align align, Dimen width, int border,
         4046         int cellspacing, int cellpadding, Background bg, Token* tok, Table* link)
         4047 {
         4048         Table* t;
         4049 
         4050         t = (Table*)emalloc(sizeof(Table));
         4051         t->tableid = tableid;
         4052         t->align = align;
         4053         t->width = width;
         4054         t->border = border;
         4055         t->cellspacing = cellspacing;
         4056         t->cellpadding = cellpadding;
         4057         t->background = bg;
         4058         t->caption_place = ALbottom;
         4059         t->caption_lay = nil;
         4060         t->tabletok = tok;
         4061         t->tabletok = nil;
         4062         t->next = link;
         4063         return t;
         4064 }
         4065 
         4066 static Tablerow*
         4067 newtablerow(Align align, Background bg, int flags, Tablerow* link)
         4068 {
         4069         Tablerow* tr;
         4070 
         4071         tr = (Tablerow*)emalloc(sizeof(Tablerow));
         4072         tr->align = align;
         4073         tr->background = bg;
         4074         tr->flags = flags;
         4075         tr->next = link;
         4076         return tr;
         4077 }
         4078 
         4079 static Tablecell*
         4080 newtablecell(int cellid, int rowspan, int colspan, Align align, Dimen wspec, int hspec,
         4081                 Background bg, int flags, Tablecell* link)
         4082 {
         4083         Tablecell* c;
         4084 
         4085         c = (Tablecell*)emalloc(sizeof(Tablecell));
         4086         c->cellid = cellid;
         4087         c->lay = nil;
         4088         c->rowspan = rowspan;
         4089         c->colspan = colspan;
         4090         c->align = align;
         4091         c->flags = flags;
         4092         c->wspec = wspec;
         4093         c->hspec = hspec;
         4094         c->background = bg;
         4095         c->next = link;
         4096         return c;
         4097 }
         4098 
         4099 static Anchor*
         4100 newanchor(int index, Rune* name, Rune* href, int target, Anchor* link)
         4101 {
         4102         Anchor* a;
         4103 
         4104         a = (Anchor*)emalloc(sizeof(Anchor));
         4105         a->index = index;
         4106         a->name = name;
         4107         a->href = href;
         4108         a->target = target;
         4109         a->next = link;
         4110         return a;
         4111 }
         4112 
         4113 static DestAnchor*
         4114 newdestanchor(int index, Rune* name, Item* item, DestAnchor* link)
         4115 {
         4116         DestAnchor* d;
         4117 
         4118         d = (DestAnchor*)emalloc(sizeof(DestAnchor));
         4119         d->index = index;
         4120         d->name = name;
         4121         d->item = item;
         4122         d->next = link;
         4123         return d;
         4124 }
         4125 
         4126 static SEvent*
         4127 newscriptevent(int type, Rune* script, SEvent* link)
         4128 {
         4129         SEvent* ans;
         4130 
         4131         ans = (SEvent*)emalloc(sizeof(SEvent));
         4132         ans->type = type;
         4133         ans->script = script;
         4134         ans->next = link;
         4135         return ans;
         4136 }
         4137 
         4138 static void
         4139 freescriptevents(SEvent* ehead)
         4140 {
         4141         SEvent* e;
         4142         SEvent* nexte;
         4143 
         4144         e = ehead;
         4145         while(e != nil) {
         4146                 nexte = e->next;
         4147                 free(e->script);
         4148                 free(e);
         4149                 e = nexte;
         4150         }
         4151 }
         4152 
         4153 static Dimen
         4154 makedimen(int kind, int spec)
         4155 {
         4156         Dimen d;
         4157 
         4158         if(spec&Dkindmask) {
         4159                 if(warn)
         4160                         fprint(2, "warning: dimension spec too big: %d\n", spec);
         4161                 spec = 0;
         4162         }
         4163         d.kindspec = kind|spec;
         4164         return d;
         4165 }
         4166 
         4167 int
         4168 dimenkind(Dimen d)
         4169 {
         4170         return (d.kindspec&Dkindmask);
         4171 }
         4172 
         4173 int
         4174 dimenspec(Dimen d)
         4175 {
         4176         return (d.kindspec&Dspecmask);
         4177 }
         4178 
         4179 static Kidinfo*
         4180 newkidinfo(int isframeset, Kidinfo* link)
         4181 {
         4182         Kidinfo*        ki;
         4183 
         4184         ki = (Kidinfo*)emalloc(sizeof(Kidinfo));
         4185         ki->isframeset = isframeset;
         4186         if(!isframeset) {
         4187                 ki->flags = FRhscrollauto|FRvscrollauto;
         4188                 ki->marginw = FRKIDMARGIN;
         4189                 ki->marginh = FRKIDMARGIN;
         4190                 ki->framebd = 1;
         4191         }
         4192         ki->next = link;
         4193         return ki;
         4194 }
         4195 
         4196 static Docinfo*
         4197 newdocinfo(void)
         4198 {
         4199         Docinfo*        d;
         4200 
         4201         d = (Docinfo*)emalloc(sizeof(Docinfo));
         4202         resetdocinfo(d);
         4203         return d;
         4204 }
         4205 
         4206 static void
         4207 resetdocinfo(Docinfo* d)
         4208 {
         4209         memset(d, 0, sizeof(Docinfo));
         4210         d->background = makebackground(nil, White);
         4211         d->text = Black;
         4212         d->link = Blue;
         4213         d->vlink = Blue;
         4214         d->alink = Blue;
         4215         d->target = FTself;
         4216         d->chset = ISO_8859_1;
         4217         d->scripttype = TextJavascript;
         4218         d->frameid = -1;
         4219 }
         4220 
         4221 /* Use targetmap array to keep track of name <-> targetid mapping. */
         4222 /* Use real malloc(), and never free */
         4223 static void
         4224 targetmapinit(void)
         4225 {
         4226         targetmapsize = 10;
         4227         targetmap = (StringInt*)emalloc(targetmapsize*sizeof(StringInt));
         4228         memset(targetmap, 0, targetmapsize*sizeof(StringInt));
         4229         targetmap[0].key = _Strdup(L(L_top));
         4230         targetmap[0].val = FTtop;
         4231         targetmap[1].key = _Strdup(L(L_self));
         4232         targetmap[1].val = FTself;
         4233         targetmap[2].key = _Strdup(L(L_parent));
         4234         targetmap[2].val = FTparent;
         4235         targetmap[3].key = _Strdup(L(L_blank));
         4236         targetmap[3].val = FTblank;
         4237         ntargets = 4;
         4238 }
         4239 
         4240 int
         4241 targetid(Rune* s)
         4242 {
         4243         int i;
         4244         int n;
         4245 
         4246         n = _Strlen(s);
         4247         if(n == 0)
         4248                 return FTself;
         4249         for(i = 0; i < ntargets; i++)
         4250                 if(_Strcmp(s, targetmap[i].key) == 0)
         4251                         return targetmap[i].val;
         4252         if(i >= targetmapsize) {
         4253                 targetmapsize += 10;
         4254                 targetmap = (StringInt*)erealloc(targetmap, targetmapsize*sizeof(StringInt));
         4255         }
         4256         targetmap[i].key = (Rune*)emalloc((n+1)*sizeof(Rune));
         4257         memmove(targetmap[i].key, s, (n+1)*sizeof(Rune));
         4258         targetmap[i].val = i;
         4259         ntargets++;
         4260         return i;
         4261 }
         4262 
         4263 Rune*
         4264 targetname(int targid)
         4265 {
         4266         int i;
         4267 
         4268         for(i = 0; i < ntargets; i++)
         4269                 if(targetmap[i].val == targid)
         4270                         return targetmap[i].key;
         4271         return L(Lquestion);
         4272 }
         4273 
         4274 /* Convert HTML color spec to RGB value, returning dflt if can't. */
         4275 /* Argument is supposed to be a valid HTML color, or "". */
         4276 /* Return the RGB value of the color, using dflt if s */
         4277 /* is nil or an invalid color. */
         4278 static int
         4279 color(Rune* s, int dflt)
         4280 {
         4281         int v;
         4282         Rune* rest;
         4283 
         4284         if(s == nil)
         4285                 return dflt;
         4286         if(_lookup(color_tab, NCOLORS, s, _Strlen(s), &v))
         4287                 return v;
         4288         if(s[0] == '#')
         4289                 s++;
         4290         v = _Strtol(s, &rest, 16);
         4291         if(*rest == 0)
         4292                 return v;
         4293         return dflt;
         4294 }
         4295 
         4296 /* Debugging */
         4297 
         4298 #define HUGEPIX 10000
         4299 
         4300 /* A "shallow" validitem, that doesn't follow next links */
         4301 /* or descend into tables. */
         4302 static int
         4303 validitem(Item* i)
         4304 {
         4305         int ok;
         4306         Itext* ti;
         4307         Irule* ri;
         4308         Iimage* ii;
         4309         Ifloat* fi;
         4310         int a;
         4311 
         4312         ok = (i->tag >= Itexttag && i->tag <= Ispacertag) &&
         4313                 (i->next == nil || validptr(i->next)) &&
         4314                 (i->width >= 0 && i->width < HUGEPIX) &&
         4315                 (i->height >= 0 && i->height < HUGEPIX) &&
         4316                 (i->ascent > -HUGEPIX && i->ascent < HUGEPIX) &&
         4317                 (i->anchorid >= 0) &&
         4318                 (i->genattr == nil || validptr(i->genattr));
         4319         /* also, could check state for ridiculous combinations */
         4320         /* also, could check anchorid for within-doc-range */
         4321         if(ok)
         4322                 switch(i->tag) {
         4323                 case Itexttag:
         4324                         ti = (Itext*)i;
         4325                         ok = validStr(ti->s) &&
         4326                                 (ti->fnt >= 0 && ti->fnt < NumStyle*NumSize) &&
         4327                                 (ti->ul == ULnone || ti->ul == ULunder || ti->ul == ULmid);
         4328                         break;
         4329                 case Iruletag:
         4330                         ri = (Irule*)i;
         4331                         ok = (validvalign(ri->align) || validhalign(ri->align)) &&
         4332                                 (ri->size >=0 && ri->size < HUGEPIX);
         4333                         break;
         4334                 case Iimagetag:
         4335                         ii = (Iimage*)i;
         4336                         ok = (ii->imsrc == nil || validptr(ii->imsrc)) &&
         4337                                 (ii->item.width >= 0 && ii->item.width < HUGEPIX) &&
         4338                                 (ii->item.height >= 0 && ii->item.height < HUGEPIX) &&
         4339                                 (ii->imwidth >= 0 && ii->imwidth < HUGEPIX) &&
         4340                                 (ii->imheight >= 0 && ii->imheight < HUGEPIX) &&
         4341                                 (ii->altrep == nil || validStr(ii->altrep)) &&
         4342                                 (ii->map == nil || validptr(ii->map)) &&
         4343                                 (validvalign(ii->align) || validhalign(ii->align)) &&
         4344                                 (ii->nextimage == nil || validptr(ii->nextimage));
         4345                         break;
         4346                 case Iformfieldtag:
         4347                         ok = validformfield(((Iformfield*)i)->formfield);
         4348                         break;
         4349                 case Itabletag:
         4350                         ok = validptr((Itable*)i);
         4351                         break;
         4352                 case Ifloattag:
         4353                         fi = (Ifloat*)i;
         4354                         ok = (fi->side == ALleft || fi->side == ALright) &&
         4355                                 validitem(fi->item) &&
         4356                                 (fi->item->tag == Iimagetag || fi->item->tag == Itabletag);
         4357                         break;
         4358                 case Ispacertag:
         4359                         a = ((Ispacer*)i)->spkind;
         4360                         ok = a==ISPnull || a==ISPvline || a==ISPhspace || a==ISPgeneral;
         4361                         break;
         4362                 default:
         4363                         ok = 0;
         4364                 }
         4365         return ok;
         4366 }
         4367 
         4368 /* "deep" validation, that checks whole list of items, */
         4369 /* and descends into tables and floated tables. */
         4370 /* nil is ok for argument. */
         4371 int
         4372 validitems(Item* i)
         4373 {
         4374         int ok;
         4375         Item* ii;
         4376 
         4377         ok = 1;
         4378         while(i != nil && ok) {
         4379                 ok = validitem(i);
         4380                 if(ok) {
         4381                         if(i->tag == Itabletag) {
         4382                                 ok = validtable(((Itable*)i)->table);
         4383                         }
         4384                         else if(i->tag == Ifloattag) {
         4385                                 ii = ((Ifloat*)i)->item;
         4386                                 if(ii->tag == Itabletag)
         4387                                         ok = validtable(((Itable*)ii)->table);
         4388                         }
         4389                 }
         4390                 if(!ok) {
         4391                         fprint(2, "invalid item: %I\n", i);
         4392                 }
         4393                 i = i->next;
         4394         }
         4395         return ok;
         4396 }
         4397 
         4398 static int
         4399 validformfield(Formfield* f)
         4400 {
         4401         int ok;
         4402 
         4403         ok = (f->next == nil || validptr(f->next)) &&
         4404                 (f->ftype >= 0 && f->ftype <= Ftextarea) &&
         4405                 f->fieldid >= 0 &&
         4406                 (f->form == nil || validptr(f->form)) &&
         4407                 (f->name == nil || validStr(f->name)) &&
         4408                 (f->value == nil || validStr(f->value)) &&
         4409                 (f->options == nil || validptr(f->options)) &&
         4410                 (f->image == nil || validitem(f->image)) &&
         4411                 (f->events == nil || validptr(f->events));
         4412         /* when all built, should have f->fieldid < f->form->nfields, */
         4413         /* but this may be called during build... */
         4414         return ok;
         4415 }
         4416 
         4417 /* "deep" validation -- checks cell contents too */
         4418 static int
         4419 validtable(Table* t)
         4420 {
         4421         int ok;
         4422         int i, j;
         4423         Tablecell* c;
         4424 
         4425         ok = (t->next == nil || validptr(t->next)) &&
         4426                 t->nrow >= 0 &&
         4427                 t->ncol >= 0 &&
         4428                 t->ncell >= 0 &&
         4429                 validalign(t->align) &&
         4430                 validdimen(t->width) &&
         4431                 (t->border >= 0 && t->border < HUGEPIX) &&
         4432                 (t->cellspacing >= 0 && t->cellspacing < HUGEPIX) &&
         4433                 (t->cellpadding >= 0 && t->cellpadding < HUGEPIX) &&
         4434                 validitems(t->caption) &&
         4435                 (t->caption_place == ALtop || t->caption_place == ALbottom) &&
         4436                 (t->totw >= 0 && t->totw < HUGEPIX) &&
         4437                 (t->toth >= 0 && t->toth < HUGEPIX) &&
         4438                 (t->tabletok == nil || validptr(t->tabletok));
         4439         /* during parsing, t->rows has list; */
         4440         /* only when parsing is done is t->nrow set > 0 */
         4441         if(ok && t->nrow > 0 && t->ncol > 0) {
         4442                 /* table is "finished" */
         4443                 for(i = 0; i < t->nrow && ok; i++)
         4444                         ok = validtablerow(t->rows+i);
         4445                 for(j = 0; j < t->ncol && ok; j++)
         4446                         ok = validtablecol(t->cols+j);
         4447                 for(c = t->cells; c != nil && ok; c = c->next)
         4448                         ok = validtablecell(c);
         4449                 for(i = 0; i < t->nrow && ok; i++)
         4450                         for(j = 0; j < t->ncol && ok; j++)
         4451                                 ok = validptr(t->grid[i][j]);
         4452         }
         4453         return ok;
         4454 }
         4455 
         4456 static int
         4457 validvalign(int a)
         4458 {
         4459         return a == ALnone || a == ALmiddle || a == ALbottom || a == ALtop || a == ALbaseline;
         4460 }
         4461 
         4462 static int
         4463 validhalign(int a)
         4464 {
         4465         return a == ALnone || a == ALleft || a == ALcenter || a == ALright ||
         4466                         a == ALjustify || a == ALchar;
         4467 }
         4468 
         4469 static int
         4470 validalign(Align a)
         4471 {
         4472         return validhalign(a.halign) && validvalign(a.valign);
         4473 }
         4474 
         4475 static int
         4476 validdimen(Dimen d)
         4477 {
         4478         int ok;
         4479         int s;
         4480 
         4481         ok = 0;
         4482         s = d.kindspec&Dspecmask;
         4483         switch(d.kindspec&Dkindmask) {
         4484         case Dnone:
         4485                 ok = s==0;
         4486                 break;
         4487         case Dpixels:
         4488                 ok = s < HUGEPIX;
         4489                 break;
         4490         case Dpercent:
         4491         case Drelative:
         4492                 ok = 1;
         4493                 break;
         4494         }
         4495         return ok;
         4496 }
         4497 
         4498 static int
         4499 validtablerow(Tablerow* r)
         4500 {
         4501         return (r->cells == nil || validptr(r->cells)) &&
         4502                 (r->height >= 0 && r->height < HUGEPIX) &&
         4503                 (r->ascent > -HUGEPIX && r->ascent < HUGEPIX) &&
         4504                 validalign(r->align);
         4505 }
         4506 
         4507 static int
         4508 validtablecol(Tablecol* c)
         4509 {
         4510         return c->width >= 0 && c->width < HUGEPIX
         4511                 && validalign(c->align);
         4512 }
         4513 
         4514 static int
         4515 validtablecell(Tablecell* c)
         4516 {
         4517         int ok;
         4518 
         4519         ok = (c->next == nil || validptr(c->next)) &&
         4520                 (c->nextinrow == nil || validptr(c->nextinrow)) &&
         4521                 (c->content == nil || validptr(c->content)) &&
         4522                 (c->lay == nil || validptr(c->lay)) &&
         4523                 c->rowspan >= 0 &&
         4524                 c->colspan >= 0 &&
         4525                 validalign(c->align) &&
         4526                 validdimen(c->wspec) &&
         4527                 c->row >= 0 &&
         4528                 c->col >= 0;
         4529         if(ok) {
         4530                 if(c->content != nil)
         4531                         ok = validitems(c->content);
         4532         }
         4533         return ok;
         4534 }
         4535 
         4536 static int
         4537 validptr(void* p)
         4538 {
         4539         /* TODO: a better job of this. */
         4540         /* For now, just dereference, which cause a bomb */
         4541         /* if not valid */
         4542         static char c;
         4543 
         4544         c = *((char*)p);
         4545         USED(c);
         4546         return 1;
         4547 }
         4548 
         4549 static int
         4550 validStr(Rune* s)
         4551 {
         4552         return s != nil && validptr(s);
         4553 }