summaryrefslogtreecommitdiff
path: root/appl/charon/build.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/charon/build.b')
-rw-r--r--appl/charon/build.b2862
1 files changed, 2862 insertions, 0 deletions
diff --git a/appl/charon/build.b b/appl/charon/build.b
new file mode 100644
index 00000000..cf3a0b09
--- /dev/null
+++ b/appl/charon/build.b
@@ -0,0 +1,2862 @@
+implement Build;
+
+include "common.m";
+
+# local copies from CU
+sys: Sys;
+CU: CharonUtils;
+ ByteSource, CImage, ImageCache, color, Nameval: import CU;
+
+D: Draw;
+ Point, Rect, Image: import D;
+S: String;
+T: StringIntTab;
+C: Ctype;
+LX: Lex;
+ RBRA, Token, TokenSource: import LX;
+U: Url;
+ Parsedurl: import U;
+J: Script;
+
+ctype: array of byte;
+
+whitespace : con " \t\n\r";
+notwhitespace : con "^ \t\n\r";
+
+# These tables must be sorted
+align_tab := array[] of { T->StringInt
+ ("baseline", int Abaseline),
+ ("bottom", int Abottom),
+ ("center", int Acenter),
+ ("char", int Achar),
+ ("justify", int Ajustify),
+ ("left", int Aleft),
+ ("middle", int Amiddle),
+ ("right", int Aright),
+ ("top", int Atop),
+};
+
+input_tab := array[] of { T->StringInt
+ ("button", Fbutton),
+ ("checkbox", Fcheckbox),
+ ("file", Ffile),
+ ("hidden", Fhidden),
+ ("image", Fimage),
+ ("password", Fpassword),
+ ("radio", Fradio),
+ ("reset", Freset),
+ ("submit", Fsubmit),
+ ("text", Ftext),
+};
+
+clear_tab := array[] of { T->StringInt
+ ("all", IFcleft|IFcright),
+ ("left", IFcleft),
+ ("right", IFcright),
+};
+
+fscroll_tab := array[] of { T->StringInt
+ ("auto", FRhscrollauto|FRvscrollauto),
+ ("no", FRnoscroll),
+ ("yes", FRhscroll|FRvscroll),
+};
+
+# blockbrk[tag] is break info for a block level element, or one
+# of a few others that get the same treatment re ending open paragraphs
+# and requiring a line break / vertical space before them.
+# If we want a line of space before the given element, SPBefore is OR'd in.
+# If we want a line of space after the given element, SPAfter is OR'd in.
+SPBefore: con byte 2;
+SPAfter: con byte 4;
+BL: con byte 1;
+BLBA: con BL|SPBefore|SPAfter;
+blockbrk := array[LX->Numtags] of {
+ LX->Taddress => BLBA, LX->Tblockquote => BLBA, LX->Tcenter => BL,
+ LX->Tdir => BLBA, LX->Tdiv => BL, LX->Tdd => BL, LX->Tdl => BLBA,
+ LX->Tdt => BL, LX->Tform => BLBA,
+ # headings and tables get breaks added manually
+ LX->Th1 => BL, LX->Th2 => BL, LX->Th3 => BL,
+ LX->Th4 => BL, LX->Th5 => BL, LX->Th6 => BL,
+ LX->Thr => BL, LX->Tisindex => BLBA, LX->Tli => BL, LX->Tmenu => BLBA,
+ LX->Tol => BLBA, LX->Tp => BLBA, LX->Tpre => BLBA,
+ LX->Tul => BLBA, LX->Txmp => BLBA,
+ * => byte 0
+};
+
+# attrinfo is information about attributes.
+# The AGEN value means that the attribute is generic (applies to almost all elements)
+AGEN: con byte 1;
+attrinfo := array[LX->Numattrs] of {
+ LX->Aid => AGEN, LX->Aclass => AGEN, LX->Astyle => AGEN, LX->Atitle => AGEN,
+ LX->Aonabort => AGEN, LX->Aonblur => AGEN, LX->Aonchange => AGEN,
+ LX->Aonclick => AGEN, LX->Aondblclick => AGEN, LX->Aonerror => AGEN,
+ LX->Aonfocus => AGEN, LX->Aonkeydown => AGEN, LX->Aonkeypress => AGEN, LX->Aonkeyup => AGEN,
+ LX->Aonload => AGEN, LX->Aonmousedown => AGEN, LX->Aonmousemove => AGEN,
+ LX->Aonmouseout => AGEN, LX->Aonmouseover => AGEN,
+ LX->Aonmouseup => AGEN, LX->Aonreset => AGEN, LX->Aonresize => AGEN, LX->Aonselect => AGEN,
+ LX->Aonsubmit => AGEN, LX->Aonunload => AGEN,
+ * => byte 0
+};
+
+# Some constants
+FRKIDMARGIN: con 6; # default margin around kid frames
+IMGHSPACE: con 0; # default hspace for images (0 matches IE, Netscape)
+IMGVSPACE: con 0; # default vspace for images
+FLTIMGHSPACE: con 2; # default hspace for float images
+TABSP: con 2; # default cellspacing for tables
+TABPAD: con 2; # default cell padding for tables
+LISTTAB: con 1; # number of tabs to indent lists
+BQTAB: con 1; # number of tabs to indent blockquotes
+HRSZ: con 2; # thickness of horizontal rules
+SUBOFF: con 4; # vertical offset for subscripts
+SUPOFF: con 6; # vertical offset for superscripts
+NBSP: con ' '; # non-breaking space character
+
+dbg := 0;
+warn := 0;
+doscripts := 0;
+
+utf8 : Btos;
+latin1 : Btos;
+
+init(cu: CharonUtils)
+{
+ CU = cu;
+ sys = load Sys Sys->PATH;
+ D = load Draw Draw->PATH;
+ S = load String String->PATH;;
+ T = load StringIntTab StringIntTab->PATH;
+ U = load Url Url->PATH;
+ if (U != nil)
+ U->init();
+ C = cu->C;
+ J = cu->J;
+ LX = cu->LX;
+ ctype = C->ctype;
+ utf8 = CU->getconv("utf8");
+ latin1 = CU->getconv("latin1");
+ if (utf8 == nil || latin1 == nil) {
+ sys->print("cannot load utf8 or latin1 charset converter\n");
+ raise "EXinternal:build init";
+ }
+ dbg = int (CU->config).dbg['h'];
+ warn = (int (CU->config).dbg['w']) || dbg;
+ doscripts = (CU->config).doscripts && J != nil;
+}
+
+# Assume f has been reset, and then had any values from HTTP headers
+# filled in (e.g., base, chset).
+ItemSource.new(bs: ref ByteSource, f: ref Layout->Frame, mtype: int) : ref ItemSource
+{
+ di := f.doc;
+# sys->print("chset = %s\n", di.chset);
+ chset := CU->getconv(di.chset);
+ if (chset == nil)
+ chset = latin1;
+ ts := TokenSource.new(bs, chset, mtype);
+ psstk := list of { Pstate.new() };
+ if(mtype != CU->TextHtml) {
+ ps := hd psstk;
+ ps.curstate &= ~IFwrap;
+ ps.literal = 1;
+ pushfontstyle(ps, FntT);
+ }
+ return ref ItemSource(ts, mtype, di, f, psstk, 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, nil);
+}
+
+ItemSource.getitems(is: self ref ItemSource) : ref Item
+{
+ psstk := is.psstk;
+ ps := hd psstk; # ps is always same as hd psstk
+ curtab: ref Table = nil; # curtab is always same as hd is.tabstk
+ if(is.tabstk != nil)
+ curtab = hd is.tabstk;
+ toks := is.toks;
+ is.toks = nil;
+ tokslen := len toks;
+ toki := 0;
+ di := is.doc;
+TokLoop:
+ for(;; toki++) {
+ if(toki >= tokslen) {
+ outerps := lastps(psstk);
+ if(outerps.items.next != nil)
+ break;
+ toks = is.ts.gettoks();
+ tokslen = len toks;
+ if(dbg)
+ sys->print("build: got %d tokens from token source\n", tokslen);
+ if(tokslen == 0)
+ break;
+ toki = 0;
+ }
+ tok := toks[toki];
+ if(dbg > 1)
+ sys->print("build: curstate %ux, token %s\n", ps.curstate, tok.tostring());
+ tag := tok.tag;
+ brk := byte 0;
+ brksp := 0;
+ if(tag < LX->Numtags) {
+ brk = blockbrk[tag];
+ if((brk&SPBefore) != byte 0)
+ brksp = 1;
+ }
+ else if(tag < LX->Numtags+RBRA) {
+ brk = blockbrk[tag-RBRA];
+ if((brk&SPAfter) != byte 0)
+ brksp = 1;
+ }
+ if(brk != byte 0) {
+ addbrk(ps, brksp, 0);
+ if(ps.inpar) {
+ popjust(ps);
+ ps.inpar = 0;
+ }
+ }
+ # check common case first (Data), then case statement on tag
+ if(tag == LX->Data) {
+ # Lexing didn't pay attention to SGML record boundary rules:
+ # \n after start tag or before end tag to be discarded.
+ # (Lex has already discarded all \r's).
+ # Some pages assume this doesn't happen in <PRE> text,
+ # so we won't do it if literal is true.
+ # BUG: won't discard \n before a start tag that begins
+ # the next bufferful of tokens.
+ s := tok.text;
+ if(!ps.literal) {
+ i := 0;
+ j := len s;
+ if(toki > 0) {
+ pt := toks[toki-1].tag;
+ # IE and Netscape both ignore this rule (contrary to spec)
+ # if previous tag was img
+ if(pt < LX->Numtags && pt != LX->Timg && j>0 && s[0]=='\n')
+ i++;
+ }
+ if(toki < tokslen-1) {
+ nt := toks[toki+1].tag;
+ if(nt >= RBRA && nt < LX->Numtags+RBRA && j>i && s[j-1]=='\n')
+ j--;
+ }
+ if(i>0 || j <len s)
+ s = s[i:j];
+ }
+ if(ps.skipwhite) {
+ s = S->drop(s, whitespace);
+ if(s != "")
+ ps.skipwhite = 0;
+ }
+ if(s != "")
+ addtext(ps, s);
+ }
+ else case tag {
+ # Some abbrevs used in following DTD comments
+ # %text = #PCDATA
+ # | TT | I | B | U | STRIKE | BIG | SMALL | SUB | SUP
+ # | EM | STRONG | DFN | CODE | SAMP | KBD | VAR | CITE
+ # | A | IMG | APPLET | FONT | BASEFONT | BR | SCRIPT | MAP
+ # | INPUT | SELECT | TEXTAREA
+ # %block = P | UL | OL | DIR | MENU | DL | PRE | DL | DIV | CENTER
+ # | BLOCKQUOTE | FORM | ISINDEX | HR | TABLE
+ # %flow = (%text | %block)*
+ # %body.content = (%heading | %text | %block | ADDRESS)*
+
+ # <!ELEMENT A - - (%text) -(A)>
+ # Anchors are not supposed to be nested, but you sometimes see
+ # href anchors inside destination anchors.
+ LX->Ta =>
+ if(ps.curanchor != 0) {
+ if(warn)
+ sys->print("warning: nested <A> or missing </A>\n");
+ endanchor(ps, di.text);
+ }
+ name := aval(tok, LX->Aname);
+ href := aurlval(tok, LX->Ahref, nil, di.base);
+ target := astrval(tok, LX->Atarget, di.target);
+ ga := getgenattr(tok);
+ evl : list of Lex->Attr = nil;
+ if(ga != nil) {
+ evl = ga.events;
+ if(evl != nil && doscripts)
+ di.hasscripts = 1;
+ }
+ # ignore rel, rev, and title attrs
+ if(href != nil) {
+ di.anchors = ref Anchor(++is.nanchors, name, href, target, evl, 0) :: di.anchors;
+ ps.curanchor = is.nanchors;
+ ps.curfg = di.link;
+ ps.fgstk = ps.curfg :: ps.fgstk;
+ # underline, too
+ ps.ulstk = ULunder :: ps.ulstk;
+ ps.curul = ULunder;
+ }
+ if(name != nil) {
+ # add a null item to be destination
+ brkstate := ps.curstate & IFbrk;
+ additem(ps, Item.newspacer(ISPnull, 0), tok);
+ ps.curstate |= brkstate; # not quite right
+ di.dests = ref DestAnchor(++is.nanchors, name, ps.lastit) :: di.dests;
+ }
+
+ LX->Ta+RBRA =>
+ endanchor(ps, di.text);
+
+ # <!ELEMENT APPLET - - (PARAM | %text)* >
+ # We can't do applets, so ignore PARAMS, and let
+ # the %text contents appear for the alternative rep
+ LX->Tapplet or LX->Tapplet+RBRA =>
+ if(warn && tag == LX->Tapplet)
+ sys->print("warning: <APPLET> ignored\n");
+
+ # <!ELEMENT AREA - O EMPTY>
+ LX->Tarea =>
+ map := is.curmap;
+ if(map == nil) {
+ if(warn)
+ sys->print("warning: <AREA> not inside <MAP>\n");
+ continue;
+ }
+ map.areas = Area(S->tolower(astrval(tok, LX->Ashape, "rect")),
+ aurlval(tok, LX->Ahref, nil, di.base),
+ astrval(tok, LX->Atarget, di.target),
+ dimlist(tok, LX->Acoords)) :: map.areas;
+
+ # <!ELEMENT (B|STRONG) - - (%text)*>
+ LX->Tb or LX->Tstrong =>
+ pushfontstyle(ps, FntB);
+
+ LX->Tb+RBRA or LX->Tcite+RBRA
+ or LX->Tcode+RBRA or LX->Tdfn+RBRA
+ or LX->Tem+RBRA or LX->Tkbd+RBRA
+ or LX->Ti+RBRA or LX->Tsamp+RBRA
+ or LX->Tstrong+RBRA or LX->Ttt+RBRA
+ or LX->Tvar+RBRA or LX->Taddress+RBRA =>
+ popfontstyle(ps);
+
+ # <!ELEMENT BASE - O EMPTY>
+ LX->Tbase =>
+ di.base = aurlval(tok, LX->Ahref, di.base, di.base);
+ di.target = astrval(tok, LX->Atarget, di.target);
+
+ # <!ELEMENT BASEFONT - O EMPTY>
+ LX->Tbasefont =>
+ ps.adjsize = aintval(tok, LX->Asize, 3) - 3;
+
+ # <!ELEMENT (BIG|SMALL) - - (%text)*>
+ LX->Tbig or LX->Tsmall =>
+ sz := ps.adjsize;
+ if(tag == LX->Tbig)
+ sz += Large;
+ else
+ sz += Small;
+ pushfontsize(ps, sz);
+
+ LX->Tbig+RBRA or LX->Tsmall+RBRA =>
+ popfontsize(ps);
+
+ # <!ELEMENT BLOCKQUOTE - - %body.content>
+ LX->Tblockquote =>
+ changeindent(ps, BQTAB);
+
+ LX->Tblockquote+RBRA =>
+ changeindent(ps, -BQTAB);
+
+ # <!ELEMENT BODY O O %body.content>
+ LX->Tbody =>
+ ps.skipping = 0;
+ bg := Background(nil, color(aval(tok, LX->Abgcolor), di.background.color));
+ bgurl := aurlval(tok, LX->Abackground, nil, di.base);
+ if(bgurl != nil) {
+ pick ni := Item.newimage(di, bgurl, nil,"", Anone, 0, 0, 0, 0, 0, 0, 1, nil, nil, nil){
+ Iimage =>
+ bg.image = ni;
+ }
+ di.images = bg.image :: di.images;
+ }
+ di.background = ps.curbg = bg;
+ ps.curbg.image = nil;
+ di.text = color(aval(tok, LX->Atext), di.text);
+ di.link = color(aval(tok, LX->Alink), di.link);
+ di.vlink = color(aval(tok, LX->Avlink), di.vlink);
+ di.alink = color(aval(tok, LX->Aalink), di.alink);
+ if(doscripts) {
+ ga := getgenattr(tok);
+ if(ga != nil && ga.events != nil) {
+ di.events = ga.events;
+ di.hasscripts = 1;
+ }
+ }
+ if(di.text != ps.curfg) {
+ ps.curfg = di.text;
+ ps.fgstk = nil;
+ }
+
+ LX->Tbody+RBRA =>
+ # HTML spec says ignore things after </body>,
+ # but IE and Netscape don't
+ # ps.skipping = 1;
+ ;
+
+ # <!ELEMENT BR - O EMPTY>
+ LX->Tbr =>
+ addlinebrk(ps, atabval(tok, LX->Aclear, clear_tab, 0));
+
+ # <!ELEMENT CAPTION - - (%text;)*>
+ LX->Tcaption =>
+ if(curtab == nil) {
+ if(warn)
+ sys->print("warning: <CAPTION> outside <TABLE>\n");
+ continue;
+ }
+ if(curtab.caption != nil) {
+ if(warn)
+ sys->print("warning: more than one <CAPTION> in <TABLE>\n");
+ continue;
+ }
+ ps = Pstate.new();
+ psstk = ps :: psstk;
+ curtab.caption_place =atabbval(tok, LX->Aalign, align_tab, Atop);
+
+ LX->Tcaption+RBRA =>
+ if(curtab == nil || tl psstk == nil) {
+ if(warn)
+ sys->print("warning: unexpected </CAPTION>\n");
+ continue;
+ }
+ curtab.caption = ps.items.next;
+ psstk = tl psstk;
+ ps = hd psstk;
+
+ LX->Tcenter or LX->Tdiv =>
+ if(tag == LX->Tcenter)
+ al := Acenter;
+ else
+ al = atabbval(tok, LX->Aalign, align_tab, ps.curjust);
+ pushjust(ps, al);
+
+ LX->Tcenter+RBRA or LX->Tdiv+RBRA =>
+ popjust(ps);
+
+ # <!ELEMENT DD - O %flow >
+ LX->Tdd =>
+ if(ps.hangstk == nil) {
+ if(warn)
+ sys->print("warning: <DD> not inside <DL\n");
+ continue;
+ }
+ h := hd ps.hangstk;
+ if(h != 0)
+ changehang(ps, -10*LISTTAB);
+ else
+ addbrk(ps, 0, 0);
+ ps.hangstk = 0 :: ps.hangstk;
+
+ #<!ELEMENT (DIR|MENU) - - (LI)+ -(%block) >
+ #<!ELEMENT (OL|UL) - - (LI)+>
+ LX->Tdir or LX->Tmenu or LX->Tol or LX->Tul =>
+ changeindent(ps, LISTTAB);
+ if(tag == LX->Tol)
+ tydef := LT1;
+ else
+ tydef = LTdisc;
+ start := aintval(tok, LX->Astart, 1);
+ ps.listtypestk = listtyval(tok, tydef) :: ps.listtypestk;
+ ps.listcntstk = start :: ps.listcntstk;
+
+ LX->Tdir+RBRA or LX->Tmenu+RBRA
+ or LX->Tol+RBRA or LX->Tul+RBRA =>
+ if(ps.listtypestk == nil) {
+ if(warn)
+ sys->print("warning: %s ended no list\n", tok.tostring());
+ continue;
+ }
+ addbrk(ps, 0, 0);
+ ps.listtypestk = tl ps.listtypestk;
+ ps.listcntstk = tl ps.listcntstk;
+ changeindent(ps, -LISTTAB);
+
+ # <!ELEMENT DL - - (DT|DD)+ >
+ LX->Tdl =>
+ changeindent(ps, LISTTAB);
+ ps.hangstk = 0 :: ps.hangstk;
+
+ LX->Tdl+RBRA =>
+ if(ps.hangstk == nil) {
+ if(warn)
+ sys->print("warning: unexpected </DL>\n");
+ continue;
+ }
+ changeindent(ps, -LISTTAB);
+ if(hd ps.hangstk != 0)
+ changehang(ps, -10*LISTTAB);
+ ps.hangstk = tl ps.hangstk;
+
+ # <!ELEMENT DT - O (%text)* >
+ LX->Tdt =>
+ if(ps.hangstk == nil) {
+ if(warn)
+ sys->print("warning: <DT> not inside <DL>\n");
+ continue;
+ }
+ h := hd ps.hangstk;
+ ps.hangstk = tl ps.hangstk;
+ if(h != 0)
+ changehang(ps, -10*LISTTAB);
+ changehang(ps, 10*LISTTAB);
+ ps.hangstk = 1 :: ps.hangstk;
+
+ # <!ELEMENT FONT - - (%text)*>
+ LX->Tfont =>
+ sz := stackhd(ps.fntsizestk, Normal);
+ (szfnd, nsz) := tok.aval(LX->Asize);
+ if(szfnd) {
+ if(S->prefix("+", nsz))
+ sz = Normal + int (nsz[1:]) + ps.adjsize;
+ else if(S->prefix("-", nsz))
+ sz = Normal - int (nsz[1:]) + ps.adjsize;
+ else if(nsz != "")
+ sz = Normal + ( int nsz - 3);
+ }
+ ps.curfg = color(aval(tok, LX->Acolor), ps.curfg);
+ ps.fgstk = ps.curfg :: ps.fgstk;
+ pushfontsize(ps, sz);
+
+ LX->Tfont+RBRA =>
+ if(ps.fgstk == nil) {
+ if(warn)
+ sys->print("warning: unexpected </FONT>\n");
+ continue;
+ }
+ ps.fgstk = tl ps.fgstk;
+ if(ps.fgstk == nil)
+ ps.curfg = di.text;
+ else
+ ps.curfg = hd ps.fgstk;
+ popfontsize(ps);
+
+ # <!ELEMENT FORM - - %body.content -(FORM) >
+ LX->Tform =>
+ if(is.curform != nil) {
+ if(warn)
+ sys->print("warning: <FORM> nested inside another\n");
+ continue;
+ }
+ action := aurlval(tok, LX->Aaction, di.base, di.base);
+ name := astrval(tok, LX->Aname, aval(tok, LX->Aid));
+ target := astrval(tok, LX->Atarget, di.target);
+ smethod := S->tolower(astrval(tok, LX->Amethod, "get"));
+ method := CU->HGet;
+ if(smethod == "post")
+ method = CU->HPost;
+ else if(smethod != "get") {
+ if(warn)
+ sys->print("warning: unknown form method %s\n", smethod);
+ }
+ (ecfnd, enctype) := tok.aval(LX->Aenctype);
+ if(warn && ecfnd && enctype != "application/x-www-form-urlencoded")
+ sys->print("form enctype %s not handled\n", enctype);
+ ga := getgenattr(tok);
+ evl : list of Lex->Attr = nil;
+ if(ga != nil) {
+ evl = ga.events;
+ if(evl != nil && doscripts)
+ di.hasscripts = 1;
+ }
+ frm := Form.new(++is.nforms, name, action, target, method, evl);
+ di.forms = frm :: di.forms;
+ is.curform = frm;
+
+ LX->Tform+RBRA =>
+ if(is.curform == nil) {
+ if(warn)
+ sys->print("warning: unexpected </FORM>\n");
+ continue;
+ }
+ # put fields back in input order
+ fields : list of ref Formfield = nil;
+ for(fl := is.curform.fields; fl != nil; fl = tl fl)
+ fields = hd fl :: fields;
+ is.curform.fields = fields;
+ is.curform.state = FormDone;
+ is.curform = nil;
+
+ # HTML 4
+ # <!ELEMENT FRAME - O EMPTY>
+ LX->Tframe =>
+ if(is.kidstk == nil) {
+ if(warn)
+ sys->print("warning: <FRAME> not in <FRAMESET>\n");
+ continue;
+ }
+ ks := hd is.kidstk;
+ kd := Kidinfo.new(0);
+ kd.src = aurlval(tok, LX->Asrc, nil, di.base);
+ kd.name = aval(tok, LX->Aname);
+ if(kd.name == "")
+ kd.name = "_fr" + string (++is.nframes);
+ kd.marginw = aintval(tok, LX->Amarginwidth, 0);
+ kd.marginh = aintval(tok, LX->Amarginheight, 0);
+ kd.framebd = aintval(tok, LX->Aframeborder, ks.framebd);
+ kd.flags = atabval(tok, LX->Ascrolling, fscroll_tab, kd.flags);
+ norsz := aboolval(tok, LX->Anoresize);
+ if(norsz)
+ kd.flags |= FRnoresize;
+ ks.kidinfos = kd :: ks.kidinfos;
+
+ # HTML 4
+ # <!ELEMENT FRAMESET - - (FRAME|FRAMESET)+>
+ LX->Tframeset =>
+ ks := Kidinfo.new(1);
+ if(is.kidstk == nil)
+ di.kidinfo = ks;
+ else {
+ pks := hd is.kidstk;
+ pks.kidinfos = ks :: pks.kidinfos;
+ }
+ is.kidstk = ks :: is.kidstk;
+ ks.framebd = aintval(tok, LX->Aborder, 1);
+ ks.rows = dimlist(tok, LX->Arows);
+ if(ks.rows == nil)
+ ks.rows = array[] of {Dimen.make(Dpercent,100)};
+ ks.cols = dimlist(tok, LX->Acols);
+ if(ks.cols == nil)
+ ks.cols = array[] of {Dimen.make(Dpercent,100)};
+ if(doscripts) {
+ ga := getgenattr(tok);
+ if(ga != nil && ga.events != nil) {
+ di.events = ga.events;
+ di.hasscripts = 1;
+ }
+ }
+
+ LX->Tframeset+RBRA =>
+ if(is.kidstk == nil) {
+ if(warn)
+ sys->print("warning: unexpected </FRAMESET>\n");
+ continue;
+ }
+ ks := hd is.kidstk;
+ # put kids back in original order
+ # and add blank frames to fill out cells
+ n := (len ks.rows) * (len ks.cols);
+ nblank := n - len ks.kidinfos;
+ while(nblank-- > 0)
+ ks.kidinfos = Kidinfo.new(0) :: ks.kidinfos;
+ kids : list of ref Kidinfo = nil;
+ for(kl := ks.kidinfos; kl != nil; kl = tl kl)
+ kids = hd kl :: kids;
+ ks.kidinfos= kids;
+ is.kidstk = tl is.kidstk;
+ if(is.kidstk == nil) {
+ for(;;) {
+ toks = is.ts.gettoks();
+ if(len toks == 0)
+ break;
+ }
+ tokslen = 0;
+ }
+
+ # <!ELEMENT H1 - - (%text;)*>, etc.
+ LX->Th1 or LX->Th2 or LX->Th3
+ or LX->Th4 or LX->Th5 or LX->Th6 =>
+ # don't want extra space if this is first addition
+ # to this item list (BUG: problem if first of bufferful)
+ bramt := 1;
+ if(ps.items == ps.lastit)
+ bramt = 0;
+ addbrk(ps, bramt, IFcleft|IFcright);
+ # assume Th2 = Th1+1, etc.
+ sz := Verylarge - (tag - LX->Th1);
+ if(sz < Tiny)
+ sz = Tiny;
+ pushfontsize(ps, sz);
+ sty := stackhd(ps.fntstylestk, FntR);
+ if(tag == LX->Th1)
+ sty = FntB;
+ pushfontstyle(ps, sty);
+ pushjust(ps, atabbval(tok, LX->Aalign, align_tab, ps.curjust));
+ ps.skipwhite = 1;
+
+ LX->Th1+RBRA or LX->Th2+RBRA
+ or LX->Th3+RBRA or LX->Th4+RBRA
+ or LX->Th5+RBRA or LX->Th6+RBRA =>
+ addbrk(ps, 1, IFcleft|IFcright);
+ popfontsize(ps);
+ popfontstyle(ps);
+ popjust(ps);
+
+ LX->Thead =>
+ # HTML spec says ignore regular markup in head,
+ # but Netscape and IE don't
+ # ps.skipping = 1;
+ ;
+
+ LX->Thead+RBRA =>
+ ps.skipping = 0;
+
+ # <!ELEMENT HR - O EMPTY>
+ LX->Thr =>
+ al := atabbval(tok, LX->Aalign, align_tab, Acenter);
+ sz := aintval(tok, LX->Asize, HRSZ);
+ wd := makedimen(tok, LX->Awidth);
+ if(wd.kind() == Dnone)
+ wd = Dimen.make(Dpercent, 100);
+ nosh := aboolval(tok, LX->Anoshade);
+ additem(ps, Item.newrule(al, sz, nosh, wd), tok);
+ addbrk(ps, 0, 0);
+
+ # <!ELEMENT (I|CITE|DFN|EM|VAR) - - (%text)*>
+ LX->Ti or LX->Tcite or LX->Tdfn
+ or LX->Tem or LX->Tvar or LX->Taddress =>
+ pushfontstyle(ps, FntI);
+
+ # <!ELEMENT IMG - O EMPTY>
+ LX->Timage or # common html error supported by other browsers
+ LX->Timg =>
+ tok.tag = LX->Timg;
+ map : ref Map = nil;
+ usemap := aval(tok, LX->Ausemap);
+ oldcuranchor := ps.curanchor;
+ if(usemap != "") {
+ # can't handle non-local maps
+ if(!S->prefix("#", usemap)) {
+ if(warn)
+ sys->print("warning: can't handle non-local map %s\n", usemap);
+ }
+ else {
+ map = getmap(di, usemap[1:]);
+ if(ps.curanchor == 0) {
+ # make an anchor so charon's easy test for whether
+ # there's an action for the item works
+ di.anchors = ref Anchor(++is.nanchors, "", nil, di.target, nil, 0) :: di.anchors;
+ ps.curanchor = is.nanchors;
+ }
+ }
+ }
+ align := atabbval(tok, LX->Aalign, align_tab, Abottom);
+ dfltbd := 0;
+ if(ps.curanchor != 0)
+ dfltbd = 2;
+ src := aurlval(tok, LX->Asrc, nil, di.base);
+ if(src == nil) {
+ if(warn)
+ sys->print("warning: <img> has no src attribute\n");
+ ps.curanchor = oldcuranchor;
+ continue;
+ }
+ img := Item.newimage(di, src,
+ aurlval(tok, LX->Alowsrc, nil, di.base),
+ aval(tok, LX->Aalt),
+ align,
+ aintval(tok, LX->Awidth, 0),
+ aintval(tok, LX->Aheight, 0),
+ aintval(tok, LX->Ahspace, IMGHSPACE),
+ aintval(tok, LX->Avspace, IMGVSPACE),
+ aintval(tok, LX->Aborder, dfltbd),
+ aboolval(tok, LX->Aismap),
+ 0, # not a background image
+ map,
+ aval(tok, LX->Aname),
+ getgenattr(tok));
+ if(align == Aleft || align == Aright) {
+ additem(ps, Item.newfloat(img, align), tok);
+ # if no hspace specified, use FLTIMGHSPACE
+ (fnd,nil) := tok.aval(LX->Ahspace);
+ if(!fnd) {
+ pick ii := img {
+ Iimage =>
+ ii.hspace = byte FLTIMGHSPACE;
+ }
+ }
+ } else {
+ ps.skipwhite = 0;
+ additem(ps, img, tok);
+ }
+ if(!ps.skipping)
+ di.images = img :: di.images;
+ ps.curanchor = oldcuranchor;
+
+ # <!ELEMENT INPUT - O EMPTY>
+ LX->Tinput =>
+ if (ps.skipping)
+ continue;
+ ps.skipwhite = 0;
+ if(is.curform ==nil) {
+ if(warn)
+ sys->print("<INPUT> not inside <FORM>\n");
+ continue;
+ }
+ field := Formfield.new(atabval(tok, LX->Atype, input_tab, Ftext),
+ ++is.curform.nfields, # fieldid
+ is.curform, # form
+ aval(tok, LX->Aname),
+ aval(tok, LX->Avalue),
+ aintval(tok, LX->Asize, 0),
+ aintval(tok, LX->Amaxlength, 1000));
+ if(aboolval(tok, LX->Achecked))
+ field.flags = FFchecked;
+
+ case field.ftype {
+ Ftext or Fpassword or Ffile =>
+ if(field.size == 0)
+ field.size = 20;
+ Fcheckbox =>
+ if(field.name == "") {
+ if(warn)
+ sys->print("warning: checkbox form field missing name\n");
+# continue;
+ }
+ if(field.value == "")
+ field.value = "1";
+ Fradio =>
+ if(field.name == "" || field.value == "") {
+ if(warn)
+ sys->print("warning: radio form field missing name or value\n");
+# continue;
+ }
+ Fsubmit =>
+ if(field.value == "")
+ field.value = "Submit";
+ if(field.name == "")
+ field.name = "_no_name_submit_";
+ Fimage =>
+ src := aurlval(tok, LX->Asrc, nil, di.base);
+ if(src == nil) {
+ if(warn)
+ sys->print("warning: image form field missing src\n");
+# continue;
+ } else {
+ # width and height attrs aren't specified in HTML 3.2,
+ # but some people provide them and they help avoid
+ # a relayout
+ field.image = Item.newimage(di, src,
+ aurlval(tok, LX->Alowsrc, nil, di.base),
+ astrval(tok, LX->Aalt, "Submit"),
+ atabbval(tok, LX->Aalign, align_tab, Abottom),
+ aintval(tok, LX->Awidth, 0),
+ aintval(tok, LX->Aheight, 0),
+ 0, 0, 0, 0, 0, nil, field.name, nil);
+ di.images = field.image :: di.images;
+ }
+ Freset =>
+ if(field.value == "")
+ field.value = "Reset";
+ Fbutton =>
+ if(field.value == "")
+ field.value = " ";
+ }
+ is.curform.fields = field :: is.curform.fields;
+ ffit := Item.newformfield(field);
+ additem(ps, ffit, tok);
+ if(ffit.genattr != nil) {
+ field.events = ffit.genattr.events;
+ if(field.events != nil && doscripts)
+ di.hasscripts = 1;
+ }
+
+ # <!ENTITY ISINDEX - O EMPTY>
+ LX->Tisindex =>
+ ps.skipwhite = 0;
+ prompt := astrval(tok, LX->Aprompt, "Index search terms:");
+ target := astrval(tok, LX->Atarget, di.target);
+ additem(ps, textit(ps, prompt), tok);
+ frm := Form.new(++is.nforms, "", di.base, target, CU->HGet, nil);
+ ff := Formfield.new(Ftext, 1, frm, "_ISINDEX_", "", 50, 1000);
+ frm.fields = ff :: nil;
+ frm.nfields = 1;
+ di.forms = frm :: di.forms;
+ additem(ps, Item.newformfield(ff), tok);
+ addbrk(ps, 1, 0);
+
+ # <!ELEMENT LI - O %flow>
+ LX->Tli =>
+ if(ps.listtypestk == nil) {
+ if(warn)
+ sys->print("<LI> not in list\n");
+ continue;
+ }
+ ty := hd ps.listtypestk;
+ ty2 := listtyval(tok, ty);
+ if(ty != ty2) {
+ ty = ty2;
+ ps.listtypestk = ty2 :: tl ps.listtypestk;
+ }
+ v := aintval(tok, LX->Avalue, hd ps.listcntstk);
+ if(ty == LTdisc || ty == LTsquare || ty == LTcircle)
+ hang := 10*LISTTAB - 3;
+ else
+ hang = 10*LISTTAB - 1;
+ changehang(ps, hang);
+ addtext(ps, listmark(ty, v));
+ ps.listcntstk = (v+1) :: (tl ps.listcntstk);
+ changehang(ps, -hang);
+ ps.skipwhite = 1;
+
+ # <!ELEMENT MAP - - (AREA)+>
+ LX->Tmap =>
+ is.curmap = getmap(di, aval(tok, LX->Aname));
+
+ LX->Tmap+RBRA =>
+ map := is.curmap;
+ if(map == nil) {
+ if(warn)
+ sys->print("warning: unexpected </MAP>\n");
+ continue;
+ }
+ # put areas back in input order
+ areas : list of Area = nil;
+ for(al := map.areas; al != nil; al = tl al)
+ areas = hd al :: areas;
+ map.areas = areas;
+ is.curmap = nil;
+
+ LX->Tmeta =>
+ if(ps.skipping)
+ continue;
+ (fnd, equiv) := tok.aval(LX->Ahttp_equiv);
+ if(fnd) {
+ v := aval(tok, LX->Acontent);
+ case S->tolower(equiv) {
+ "set-cookie" =>
+ if((CU->config).docookies > 0) {
+ url := di.src;
+ CU->setcookie(v, url.host, url.path);
+ }
+ "refresh" =>
+ di.refresh = v;
+ "content-script-type" =>
+ if(v == "javascript" || v == "javascript1.1" || v == "jscript")
+ di.scripttype = CU->TextJavascript;
+ # TODO: other kinds
+ else {
+ if(warn)
+ sys->print("unimplemented script type %s\n", v);
+ di.scripttype = CU->UnknownType;
+ }
+ "content-type" =>
+ (nil, parms) := S->splitl(v, ";");
+ if (parms != nil) {
+ nvs := Nameval.namevals(parms[1:], ';');
+ (got, s) := Nameval.find(nvs, "charset");
+ if (got) {
+# sys->print("HTTP-EQUIV charset: %s\n", s);
+ btos := CU->getconv(s);
+ if (btos != nil)
+ is.ts.setchset(btos);
+ else if (warn)
+ sys->print("cannot set charset %s\n", s);
+ }
+ }
+ }
+ }
+
+ # Nobr is NOT in HMTL 4.0, but it is ubiquitous on the web
+ LX->Tnobr =>
+ ps.skipwhite = 0;
+ ps.curstate &= ~IFwrap;
+
+ LX->Tnobr+RBRA =>
+ ps.curstate |= IFwrap;
+
+ # We do frames, so skip stuff in noframes
+ LX->Tnoframes =>
+ ps.skipping = 1;
+
+ LX->Tnoframes+RBRA =>
+ ps.skipping = 0;
+
+ # We do scripts (if enabled), so skip stuff in noscripts
+ LX->Tnoscript =>
+ if(doscripts)
+ ps.skipping = 1;
+
+ LX->Tnoscript+RBRA =>
+ if(doscripts)
+ ps.skipping = 0;
+
+ # <!ELEMENT OPTION - O (#PCDATA)>
+ LX->Toption =>
+ if(is.curform == nil || is.curform.fields == nil) {
+ if(warn)
+ sys->print("warning: <OPTION> not in <SELECT>\n");
+ continue;
+ }
+ field := hd is.curform.fields;
+ if(field.ftype != Fselect) {
+ if(warn)
+ sys->print("warning: <OPTION> not in <SELECT>\n");
+ continue;
+ }
+ val := aval(tok, LX->Avalue);
+ option := ref Option(aboolval(tok, LX->Aselected),
+ val, "");
+ field.options = option :: field.options;
+ (option.display, toki) = getpcdata(toks, toki);
+ option.display = optiontext(option.display);
+ if(val == "")
+ option.value = option.display;
+
+ # <!ELEMENT P - O (%text)* >
+ LX->Tp =>
+ pushjust(ps, atabbval(tok, LX->Aalign, align_tab, ps.curjust));
+ ps.inpar = 1;
+ ps.skipwhite = 1;
+
+ LX->Tp+RBRA =>
+ ;
+
+ # <!ELEMENT PARAM - O EMPTY>
+ # Do something when we do applets...
+ LX->Tparam =>
+ ;
+
+ # <!ELEMENT PRE - - (%text)* -(IMG|BIG|SMALL|SUB|SUP|FONT) >
+ LX->Tpre =>
+ ps.curstate &= ~IFwrap;
+ ps.literal = 1;
+ ps.skipwhite = 0;
+ pushfontstyle(ps, FntT);
+
+ LX->Tpre+RBRA =>
+ ps.curstate |= IFwrap;
+ if(ps.literal) {
+ popfontstyle(ps);
+ ps.literal = 0;
+ }
+
+ # <!ELEMENT SCRIPT - - CDATA>
+ LX->Tscript =>
+ if(!doscripts) {
+ if(warn)
+ sys->print("warning: <SCRIPT> ignored\n");
+ ps.skipping = 1;
+ break;
+ }
+ script := "";
+ scripttoki := toki;
+ (script, toki) = getpcdata(toks, toki);
+
+ # check language version
+ lang := astrval(tok, LX->Alanguage, "javascript");
+ lang = S->tolower(lang);
+ lang = trim_white(lang);
+
+ # should give preference to type
+ supported := 0;
+ for (v := 0; v < len J->versions; v++)
+ if (J->versions[v] == lang) {
+ supported = 1;
+ break;
+ }
+ if (!supported)
+ break;
+
+ di.hasscripts = 1;
+ scriptsrc := aurlval(tok, LX->Asrc, nil, di.base);
+ if(scriptsrc != nil && is.reqdurl == nil) {
+ is.reqdurl = scriptsrc;
+ toki = scripttoki;
+ # is.reqddata will contain script next time round
+ break TokLoop;
+ }
+ if (is.reqddata != nil) {
+ script = CU->stripscript(string is.reqddata);
+ is.reqddata = nil;
+ is.reqdurl = nil;
+ }
+
+ if(script == "")
+ break;
+#sys->print("SCRIPT (ver %s)\n%s\nENDSCRIPT\n", lang, script);
+ (err, replace, nil) := J->evalscript(is.frame, script);
+ if(err != "") {
+ if(warn)
+ sys->print("Javascript error: %s\n", err);
+ } else {
+ # First, worry about possible transfer back of new values
+ if(di.text != ps.curfg) {
+ # The following isn't nearly good enough
+ # (if the fgstk isn't nil, need to replace bottom of stack;
+ # and need to do similar things for all other pstates).
+ # But Netscape 4.0 doesn't do anything at all if change
+ # foreground in a script!
+ if(ps.fgstk == nil)
+ ps.curfg = di.text;
+ }
+ scripttoks := lexstring(replace);
+ ns := len scripttoks;
+ if(ns > 0) {
+ # splice scripttoks into toks, replacing <SCRIPT>...</SCRIPT>
+ if(toki+1 < tokslen && toks[toki+1].tag == LX->Tscript+RBRA)
+ toki++;
+ newtokslen := tokslen - (toki+1-scripttoki) + ns;
+ newtoks := array[newtokslen] of ref Token;
+ newtoks[0:] = toks[0:scripttoki];
+ newtoks[scripttoki:] = scripttoks;
+ if(toki+1 < tokslen)
+ newtoks[scripttoki+ns:] = toks[toki+1:tokslen];
+ toks = newtoks;
+ tokslen = newtokslen;
+ toki = scripttoki-1;
+ scripttoks = nil;
+ }
+ }
+
+ LX->Tscript+RBRA =>
+ ps.skipping = 0;
+
+ # <!ELEMENT SELECT - - (OPTION+)>
+ LX->Tselect =>
+ if(is.curform ==nil) {
+ if(warn)
+ sys->print("<SELECT> not inside <FORM>\n");
+ continue;
+ }
+ field := Formfield.new(Fselect,
+ ++is.curform.nfields, # fieldid
+ is.curform, # form
+ aval(tok, LX->Aname),
+ "", # value
+ aintval(tok, LX->Asize, 1),
+ 0); # maxlength
+ if(aboolval(tok, LX->Amultiple))
+ field.flags = FFmultiple;
+ is.curform.fields = field :: is.curform.fields;
+ ffit := Item.newformfield(field);
+ additem(ps, ffit, tok);
+ if(ffit.genattr != nil) {
+ field.events = ffit.genattr.events;
+ if(field.events != nil && doscripts)
+ di.hasscripts = 1;
+ }
+ # throw away stuff until next tag (should be <OPTION>)
+ (nil, toki) = getpcdata(toks, toki);
+
+ LX->Tselect+RBRA =>
+ if(is.curform == nil || is.curform.fields == nil) {
+ if(warn)
+ sys->print("warning: unexpected </SELECT>\n");
+ continue;
+ }
+ field := hd is.curform.fields;
+ if(field.ftype != Fselect)
+ continue;
+ # put options back in input order
+ opts : list of ref Option = nil;
+ select := 0;
+ for(ol := field.options; ol != nil; ol = tl ol) {
+ o := hd ol;
+ if (o.selected)
+ select = 1;
+ opts = o :: opts;
+ }
+ # Single-choice select fields preselect the first option if none explicitly selected
+ if (!select && !int(field.flags & FFmultiple) && opts != nil)
+ (hd opts).selected = 1;
+ field.options = opts;
+
+ # <!ELEMENT (STRIKE|U) - - (%text)*>
+ LX->Tstrike or LX->Tu =>
+ if(tag == LX->Tstrike)
+ ulty := ULmid;
+ else
+ ulty = ULunder;
+ ps.ulstk = ulty :: ps.ulstk;
+ ps.curul = ulty;
+
+ LX->Tstrike+RBRA or LX->Tu+RBRA =>
+ if(ps.ulstk == nil) {
+ if(warn)
+ sys->print("warning: unexpected %s\n", tok.tostring());
+ continue;
+ }
+ ps.ulstk = tl ps.ulstk;
+ if(ps.ulstk != nil)
+ ps.curul = hd ps.ulstk;
+ else
+ ps.curul = ULnone;
+
+ # <!ELEMENT STYLE - - CDATA>
+ LX->Tstyle =>
+ if(warn)
+ sys->print("warning: unimplemented <STYLE>\n");
+ ps.skipping = 1;
+
+ LX->Tstyle+RBRA =>
+ ps.skipping = 0;
+
+ # <!ELEMENT (SUB|SUP) - - (%text)*>
+ LX->Tsub or LX->Tsup =>
+ if(tag == LX->Tsub)
+ ps.curvoff += SUBOFF;
+ else
+ ps.curvoff -= SUPOFF;
+ ps.voffstk = ps.curvoff :: ps.voffstk;
+ sz := stackhd(ps.fntsizestk, Normal);
+ pushfontsize(ps, sz-1);
+
+ LX->Tsub+RBRA or LX->Tsup+RBRA =>
+ if(ps.voffstk == nil) {
+ if(warn)
+ sys->print("warning: unexpected %s\n", tok.tostring());
+ continue;
+ }
+ ps.voffstk = tl ps.voffstk;
+ if(ps.voffstk != nil)
+ ps.curvoff = hd ps.voffstk;
+ else
+ ps.curvoff = 0;
+ popfontsize(ps);
+
+ # <!ELEMENT TABLE - - (CAPTION?, TR+)>
+ LX->Ttable =>
+ if (ps.skipping)
+ continue;
+ ps.skipwhite = 0;
+ # Handle an html error (seen on deja.com)
+ # ... sometimes see a nested <table> outside of a cell
+ # imitate observed behaviour of IE/Navigator
+ if (curtab != nil && curtab.cells == nil) {
+ curtab.align = makealign(tok);
+ curtab.width = makedimen(tok, LX->Awidth);
+ curtab.border = aflagval(tok, LX->Aborder);
+ curtab.cellspacing = aintval(tok, LX->Acellspacing, TABSP);
+ curtab.cellpadding = aintval(tok, LX->Acellpadding, TABPAD);
+ curtab.background = Background(nil, color(aval(tok, LX->Abgcolor), -1));
+ curtab.tabletok = tok;
+ continue;
+ }
+ tab := Table.new(++is.ntables, # tableid
+ makealign(tok), # align
+ makedimen(tok, LX->Awidth),
+ aflagval(tok, LX->Aborder),
+ aintval(tok, LX->Acellspacing, TABSP),
+ aintval(tok, LX->Acellpadding, TABPAD),
+# Background(nil, color(aval(tok, LX->Abgcolor), ps.curbg.color)),
+ Background(nil, color(aval(tok, LX->Abgcolor), -1)),
+ tok);
+ is.tabstk = tab :: is.tabstk;
+ di.tables = tab :: di.tables;
+ curtab = tab;
+ # HTML spec says:
+ # don't add items to outer state (until </table>)
+ # but IE and Netscape don't do that
+
+ LX->Ttable+RBRA =>
+ if (ps.skipping)
+ continue;
+ if(curtab == nil) {
+ if(warn)
+ sys->print("warning: unexpected </TABLE>\n");
+ continue;
+ }
+ isempty := (curtab.cells == nil);
+ if(isempty) {
+ if(warn)
+ sys->print("warning: <TABLE> has no cells\n");
+ }
+ else {
+ (ps, psstk) = finishcell(curtab, psstk);
+ if(curtab.currows != nil)
+ (hd curtab.currows).flags = byte 0;
+ finish_table(curtab);
+ }
+ ps.skipping = 0;
+ if(!isempty) {
+ tabitem := Item.newtable(curtab);
+ al := int curtab.align.halign;
+ case al {
+ int Aleft or int Aright =>
+ additem(ps, Item.newfloat(tabitem, byte al), tok);
+ * =>
+ if(al == int Acenter)
+ pushjust(ps, Acenter);
+ addbrk(ps, 0, 0);
+ if(ps.inpar) {
+ popjust(ps);
+ ps.inpar = 0;
+ }
+ additem(ps, tabitem, curtab.tabletok);
+ if(al == int Acenter)
+ popjust(ps);
+ }
+ }
+ if(is.tabstk == nil) {
+ if(warn)
+ sys->print("warning: table stack is wrong\n");
+ }
+ else
+ is.tabstk = tl is.tabstk;
+ if(is.tabstk == nil)
+ curtab = nil;
+ else
+ curtab = hd is.tabstk;
+ if(!isempty) {
+ # the code at the beginning to add a break after table
+ # changed the nested ps, not the current one
+ addbrk(ps, 0, 0);
+ }
+
+ # <!ELEMENT (TH|TD) - O %body.content>
+ # Cells for a row are accumulated in reverse order.
+ # We push ps on a stack, and use a new one to accumulate
+ # the contents of the cell.
+ LX->Ttd or LX->Tth =>
+ if (ps.skipping)
+ continue;
+ if(curtab == nil) {
+ if(warn)
+ sys->print("%s outside <TABLE>\n", tok.tostring());
+ continue;
+ }
+ if(ps.inpar) {
+ popjust(ps);
+ ps.inpar = 0;
+ }
+ (ps, psstk) = finishcell(curtab, psstk);
+ tr : ref Tablerow = nil;
+ if(curtab.currows != nil)
+ tr = hd curtab.currows;
+ if(tr == nil || tr.flags == byte 0) {
+ if(warn)
+ sys->print("%s outside row\n", tok.tostring());
+ tr = Tablerow.new(Align(Anone,Anone), curtab.background, TFparsing);
+ curtab.currows = tr :: curtab.currows;
+ }
+ ps = cell_pstate(ps, tag == LX->Tth);
+ psstk = ps :: psstk;
+ flags := TFparsing;
+ width := makedimen(tok, LX->Awidth);
+
+ # nowrap only applies if no width has been specified
+ if(width.kind() == Dnone && aboolval(tok, LX->Anowrap)) {
+ flags |= TFnowrap;
+ ps.curstate &= ~IFwrap;
+ }
+ if(tag == LX->Tth)
+ flags |= TFisth;
+ bg := Background(nil, color(aval(tok, LX->Abgcolor), tr.background.color));
+ c := Tablecell.new(len curtab.cells + 1, # cell id
+ aintval(tok, LX->Arowspan, 1),
+ aintval(tok, LX->Acolspan, 1),
+ makealign(tok),
+ width,
+ aintval(tok, LX->Aheight, 0),
+ bg,
+ flags);
+
+ bgurl := aurlval(tok, LX->Abackground, nil, di.base);
+ if(bgurl != nil) {
+ pick ni := Item.newimage(di, bgurl, nil,"", Anone, 0, 0, 0, 0, 0, 0, 1, nil, nil, nil){
+ Iimage =>
+ bg.image = ni;
+ }
+ di.images = bg.image :: di.images;
+ }
+ c.background = ps.curbg = bg;
+ ps.curbg.image = nil;
+ if(c.align.halign == Anone) {
+ if(tr.align.halign != Anone)
+ c.align.halign = tr.align.halign;
+ else if(tag == LX->Tth)
+ c.align.halign = Acenter;
+ else
+ c.align.halign = Aleft;
+ }
+ if(c.align.valign == Anone) {
+ if(tr.align.valign != Anone)
+ c.align.valign = tr.align.valign;
+ else
+ c.align.valign = Amiddle;
+ }
+ curtab.cells = c :: curtab.cells;
+ tr.cells = c :: tr.cells;
+
+ LX->Ttd+RBRA or LX->Tth+RBRA =>
+ if (ps.skipping)
+ continue;
+ if(curtab == nil || curtab.cells == nil) {
+ if(warn)
+ sys->print("unexpected %s\n", tok.tostring());
+ continue;
+ }
+ (ps, psstk) = finishcell(curtab, psstk);
+
+ # <!ELEMENT TEXTAREA - - (#PCDATA)>
+ LX->Ttextarea =>
+ if(is.curform ==nil) {
+ if(warn)
+ sys->print("<TEXTAREA> not inside <FORM>\n");
+ continue;
+ }
+ nrows := aintval(tok, LX->Arows, 3);
+ ncols := aintval(tok, LX->Acols, 50);
+ ft := Ftextarea;
+ if (ncols == 0 || nrows == 0)
+ ft = Fhidden;
+ field := Formfield.new(ft,
+ ++is.curform.nfields, # fieldid
+ is.curform, # form
+ aval(tok, LX->Aname),
+ "", # value
+ 0, 0); # size, maxlength
+ field.rows = nrows;
+ field.cols = ncols;
+ is.curform.fields = field :: is.curform.fields;
+ (field.value, toki) = getpcdata(toks, toki);
+ if(warn && toki < tokslen-1 && toks[toki+1].tag != LX->Ttextarea+RBRA)
+ sys->print("warning: <TEXTAREA> data ended by %s\n", toks[toki+1].tostring());
+ ffit := Item.newformfield(field);
+ additem(ps, ffit, tok);
+ if(ffit.genattr != nil) {
+ field.events = ffit.genattr.events;
+ if(field.events != nil && doscripts)
+ di.hasscripts = 1;
+ }
+
+ # <!ELEMENT TITLE - - (#PCDATA)* -(%head.misc)>
+ LX->Ttitle =>
+ (di.doctitle, toki) = getpcdata(toks, toki);
+ if(warn && toki < tokslen-1 && toks[toki+1].tag != LX->Ttitle+RBRA)
+ sys->print("warning: <TITLE> data ended by %s\n", toks[toki+1].tostring());
+
+ # <!ELEMENT TR - O (TH|TD)+>
+ # rows are accumulated in reverse order in curtab.currows
+ LX->Ttr =>
+ if (ps.skipping)
+ continue;
+ if(curtab == nil) {
+ if(warn)
+ sys->print("warning: <TR> outside <TABLE>\n");
+ continue;
+ }
+ if(ps.inpar) {
+ popjust(ps);
+ ps.inpar = 0;
+ }
+ (ps, psstk) = finishcell(curtab, psstk);
+ if(curtab.currows != nil)
+ (hd curtab.currows).flags = byte 0;
+ tr := Tablerow.new(makealign(tok),
+ Background(nil, color(aval(tok, LX->Abgcolor), curtab.background.color)),
+ TFparsing);
+ curtab.currows = tr :: curtab.currows;
+
+ LX->Ttr+RBRA =>
+ if (ps.skipping)
+ continue;
+ if(curtab == nil || curtab.currows == nil) {
+ if(warn)
+ sys->print("warning: unexpected </TR>\n");
+ continue;
+ }
+ (ps, psstk) = finishcell(curtab, psstk);
+ tr := hd curtab.currows;
+ if(tr.cells == nil) {
+ if(warn)
+ sys->print("warning: empty row\n");
+ curtab.currows = tl curtab.currows;
+ }
+ else
+ tr.flags = byte 0; # done parsing
+
+ # <!ELEMENT (TT|CODE|KBD|SAMP) - - (%text)*>
+ LX->Ttt or LX->Tcode or LX->Tkbd or LX->Tsamp =>
+ pushfontstyle(ps, FntT);
+
+ # <!ELEMENT (XMP|LISTING) - - %literal >
+ # additional support exists in LX to ignore character escapes etc.
+ LX->Txmp =>
+ ps.curstate &= ~IFwrap;
+ ps.literal = 1;
+ ps.skipwhite = 0;
+ pushfontstyle(ps, FntT);
+
+ LX->Txmp+RBRA =>
+ ps.curstate |= IFwrap;
+ if(ps.literal) {
+ popfontstyle(ps);
+ ps.literal = 0;
+ }
+
+ # Tags that have empty action
+
+ LX->Tabbr or LX->Tabbr+RBRA
+ or LX->Tacronym or LX->Tacronym+RBRA
+ or LX->Tarea+RBRA
+ or LX->Tbase+RBRA
+ or LX->Tbasefont+RBRA
+ or LX->Tbr+RBRA
+ or LX->Tdd+RBRA
+ or LX->Tdt+RBRA
+ or LX->Tframe+RBRA
+ or LX->Thr+RBRA
+ or LX->Thtml
+ or LX->Thtml+RBRA
+ or LX->Timg+RBRA
+ or LX->Tinput+RBRA
+ or LX->Tisindex+RBRA
+ or LX->Tli+RBRA
+ or LX->Tlink or LX->Tlink+RBRA
+ or LX->Tmeta+RBRA
+ or LX->Toption+RBRA
+ or LX->Tparam+RBRA
+ or LX->Ttextarea+RBRA
+ or LX->Ttitle+RBRA
+ =>
+ ;
+
+ # Tags not implemented
+ LX->Tbdo or LX->Tbdo+RBRA
+ or LX->Tbutton or LX->Tbutton+RBRA
+ or LX->Tdel or LX->Tdel+RBRA
+ or LX->Tfieldset or LX->Tfieldset+RBRA
+ or LX->Tiframe or LX->Tiframe+RBRA
+ or LX->Tins or LX->Tins+RBRA
+ or LX->Tlabel or LX->Tlabel+RBRA
+ or LX->Tlegend or LX->Tlegend+RBRA
+ or LX->Tobject or LX->Tobject+RBRA
+ or LX->Toptgroup or LX->Toptgroup+RBRA
+ or LX->Tspan or LX->Tspan+RBRA
+ =>
+ if(warn) {
+ if(tag > RBRA)
+ tag -= RBRA;
+ sys->print("warning: unimplemented HTML tag: %s\n", LX->tagnames[tag]);
+ }
+
+ * =>
+ if(warn)
+ sys->print("warning: unknown HTML tag: %s\n", tok.text);
+ }
+ }
+ if (toki < tokslen)
+ is.toks = toks[toki:];
+ if(tokslen == 0) {
+ # we might have hit eof from lexer
+ # some pages omit trailing </table>
+ bs := is.ts.b;
+ if(bs.eof && bs.lim == bs.edata) {
+ while(curtab != nil) {
+ if(warn)
+ sys->print("warning: <TABLE> not closed\n");
+ if(curtab.cells != nil) {
+ (ps, psstk) = finishcell(curtab, psstk);
+ if(curtab.currows != nil)
+ (hd curtab.currows).flags = byte 0;
+ finish_table(curtab);
+ ps.skipping = 0;
+ additem(ps, Item.newtable(curtab), curtab.tabletok);
+ addbrk(ps, 0, 0);
+ }
+ if(is.tabstk != nil)
+ is.tabstk = tl is.tabstk;
+ if(is.tabstk == nil)
+ curtab = nil;
+ else
+ curtab = hd is.tabstk;
+ }
+ }
+ }
+ outerps := lastps(psstk);
+ ans := outerps.items.next;
+ # note: ans may be nil and di.kids not nil, if there's a frameset!
+ outerps.items = Item.newspacer(ISPnull, 0);
+ outerps.lastit = outerps.items;
+ is.psstk = psstk;
+
+ if(dbg) {
+ if(ans == nil)
+ sys->print("getitems returning nil\n");
+ else
+ ans.printlist("getitems returning:");
+ }
+ return ans;
+}
+
+endanchor(ps: ref Pstate, docfg: int)
+{
+ if(ps.curanchor != 0) {
+ if(ps.fgstk != nil) {
+ ps.fgstk = tl ps.fgstk;
+ if(ps.fgstk == nil)
+ ps.curfg = docfg;
+ else
+ ps.curfg = hd ps.fgstk;
+ }
+ ps.curanchor = 0;
+ if(ps.ulstk != nil) {
+ ps.ulstk = tl ps.ulstk;
+ if(ps.ulstk == nil)
+ ps.curul = ULnone;
+ else
+ ps.curul = hd ps.ulstk;
+ }
+ }
+}
+
+lexstring(s: string) : array of ref Token
+{
+ bs := ByteSource.stringsource(s);
+ ts := TokenSource.new(bs, utf8, CU->TextHtml);
+ ans : array of ref Token = nil;
+ # gettoks might return answer in several hunks
+ for(;;) {
+ toks := ts.gettoks();
+ if(toks == nil)
+ break;
+ if(ans != nil) {
+ newans := array[len ans + len toks] of ref Token;
+ newans[0:] = ans;
+ newans[len ans:] = toks;
+ ans = newans;
+ }
+ else
+ ans = toks;
+ }
+ return ans;
+}
+
+lastps(psl: list of ref Pstate) : ref Pstate
+{
+ if(psl == nil)
+ CU->raisex("EXInternal: empty pstate stack");
+ while(tl psl != nil)
+ psl = tl psl;
+ return hd psl;
+}
+
+# Concatenate together maximal set of Data tokens, starting at toks[toki+1].
+# Lexer has ensured that there will either be a following non-data token or
+# we will be at eof.
+# Return (trimmed concatenation, last used toki).
+getpcdata(toks: array of ref Token, toki: int) : (string, int)
+{
+ ans := "";
+ tokslen := len toks;
+ toki++;
+ for(;;) {
+ if(toki >= tokslen)
+ break;
+ tok := toks[toki];
+ if(tok.tag == LX->Data) {
+ toki++;
+ ans = ans + tok.text;
+ }
+ else
+ break;
+ }
+ return (trim_white(ans), toki-1);
+}
+
+optiontext(str : string) : string
+{
+ ans := "";
+ lastc := 0;
+ for (i := 0; i < len str; i++) {
+ if (str[i] > 16r20)
+ ans[len ans] = str[i];
+ else if (lastc > 16r20)
+ ans[len ans] = ' ';
+ lastc = str[i];
+ }
+ return ans;
+}
+
+finishcell(curtab: ref Table, psstk: list of ref Pstate) : (ref Pstate, list of ref Pstate)
+{
+ if(curtab.cells != nil) {
+ c := hd curtab.cells;
+ if((c.flags&TFparsing) != byte 0) {
+ if(tl psstk == nil) {
+ if(warn)
+ sys->print("warning: parse state stack is wrong\n");
+ }
+ else {
+ ps := hd psstk;
+ c.content = ps.items.next;
+ c.flags &= ~TFparsing;
+ psstk = tl psstk;
+ }
+ }
+ }
+ return (hd psstk, psstk);
+}
+
+Pstate.new() : ref Pstate
+{
+ ps := ref Pstate (
+ 0, 0, DefFnt, # skipping, skipwhite, curfont
+ CU->Black, # curfg
+ Background(nil, CU->White),
+ 0, # curvoff
+ ULnone, Aleft, # curul, curjust
+ 0, IFwrap, # curanchor, curstate
+ 0, 0, 0, # literal, inpar, adjsize
+ nil, nil, nil, # items, lastit, prelastit
+ nil, nil, nil, nil, # fntstylestk, fntsizestk, fgstk, ulstk
+ nil, nil, nil, nil, # voffstk, listtypestk, listcntstk, juststk
+ nil); # hangstk
+ ps.items = Item.newspacer(ISPnull, 0);
+ ps.lastit = ps.items;
+ ps.prelastit = nil;
+ return ps;
+}
+
+cell_pstate(oldps: ref Pstate, ishead: int) : ref Pstate
+{
+ ps := Pstate.new();
+ ps.skipwhite = 1;
+ ps.curanchor = oldps.curanchor;
+ ps.fntstylestk = oldps.fntstylestk;
+ ps.fntsizestk = oldps.fntsizestk;
+ ps.curfont = oldps.curfont;
+ ps.curfg = oldps.curfg;
+ ps.curbg = oldps.curbg;
+ ps.fgstk = oldps.fgstk;
+ ps.adjsize = oldps.adjsize;
+ if(ishead) {
+ # make bold
+ sty := ps.curfont%NumSize;
+ ps.curfont = FntB*NumSize + sty;
+ }
+ return ps;
+}
+
+trim_white(data: string): string
+{
+ data = S->drop(data, whitespace);
+ (l,r) := S->splitr(data, notwhitespace);
+ return l;
+}
+
+# Add it to end of ps item chain, adding in current state from ps.
+# Also, if tok is not nil, scan it for generic attributes and assign
+# the genattr field of the item accordingly.
+additem(ps: ref Pstate, it: ref Item, tok: ref LX->Token)
+{
+ if(ps.skipping) {
+ if(warn) {
+ sys->print("warning: skipping item:\n");
+ it.print();
+ }
+ return;
+ }
+ it.anchorid = ps.curanchor;
+ it.state |= ps.curstate;
+ if(tok != nil)
+ it.genattr = getgenattr(tok);
+ ps.curstate &= ~(IFbrk|IFbrksp|IFnobrk|IFcleft|IFcright);
+ ps.prelastit = ps.lastit;
+ ps.lastit.next = it;
+ ps.lastit = it;
+}
+
+getgenattr(tok: ref LX->Token) : ref Genattr
+{
+ any := 0;
+ i, c, s, t: string;
+ e: list of LX->Attr = nil;
+ for(al := tok.attr; al != nil; al = tl al) {
+ a := hd al;
+ aid := a.attid;
+ if(attrinfo[aid] == byte 0)
+ continue;
+ case aid {
+ LX->Aid =>
+ i = a.value;
+ any = 1;
+ LX->Aclass =>
+ c = a.value;
+ any = 1;
+ LX->Astyle =>
+ s = a.value;
+ any = 1;
+ LX->Atitle =>
+ t = a.value;
+ any = 1;
+ * =>
+ CU->assert(aid >= LX->Aonabort && aid <= LX->Aonunload);
+ e = a :: e;
+ any = 1;
+ }
+ }
+ if(any)
+ return ref Genattr(i, c, s, t, e, 0);
+ return nil;
+}
+
+textit(ps: ref Pstate, s: string) : ref Item
+{
+ return Item.newtext(s, ps.curfont, ps.curfg, ps.curvoff+Voffbias, ps.curul);
+}
+
+# Add text item or items for s, paying attention to
+# current font, foreground, baseline offset, underline state,
+# and literal mode. Unless we're in literal mode, compress
+# whitespace to single blank, and, if curstate has a break,
+# trim any leading whitespace. Whether in literal mode or not,
+# turn nonbreaking spaces into spacer items with IFnobrk set.
+#
+# In literal mode, break up s at newlines and add breaks instead.
+# Also replace tabs appropriate number of spaces.
+# In nonliteral mode, break up the items every 100 or so characters
+# just to make the layout algorithm not go quadratic.
+#
+# This code could be written much shorter using the String module
+# split and drop functions, but we want this part to go fast.
+addtext(ps: ref Pstate, s: string)
+{
+ n := len s;
+ i := 0;
+ j := 0;
+ if(ps.literal) {
+ col := 0;
+ while(i < n) {
+ if(s[i] == '\n') {
+ if(i > j) {
+ # trim trailing blanks from line
+ for(k := i; k > j; k--)
+ if(s[k-1] != ' ')
+ break;
+ if(k > j)
+ additem(ps, textit(ps, s[j:k]), nil);
+ }
+ addlinebrk(ps, 0);
+ j = i+1;
+ col = 0;
+ }
+ else {
+ if(s[i] == '\t') {
+ col += i-j;
+ nsp := 8 - (col % 8);
+ additem(ps, textit(ps, s[j:i] + " "[0:nsp]), nil);
+ col += nsp;
+ j = i+1;
+ }
+ else if(s[i] == NBSP) {
+ if(i > j)
+ additem(ps, textit(ps, s[j:i]), nil);
+ addnbsp(ps);
+ col += (i-j) + 1;
+ j = i+1;
+ }
+ }
+ i++;
+ }
+ if(i > j)
+ additem(ps, textit(ps, s[j:i]), nil);
+ }
+ else {
+ if((ps.curstate&IFbrk) || ps.lastit == ps.items)
+ while(i < n) {
+ c := s[i];
+ if(c >= C->NCTYPE || ctype[c] != C->W)
+ break;
+ i++;
+ }
+ ss := "";
+ j = i;
+ for( ; i < n; i++) {
+ c := s[i];
+ if(c == NBSP) {
+ if(i > j)
+ ss += s[j:i];
+ if(ss != "")
+ additem(ps, textit(ps, ss), nil);
+ ss = "";
+ addnbsp(ps);
+ j = i + 1;
+ continue;
+ }
+ if(c < C->NCTYPE && ctype[c] == C->W) {
+ ss += s[j:i] + " ";
+ while(i < n-1) {
+ c = s[i+1];
+ if(c >= C->NCTYPE || ctype[c] != C->W)
+ break;
+ i++;
+ }
+ j = i + 1;
+ }
+ if(i - j >= 100) {
+ ss += s[j:i+1];
+ j = i + 1;
+ }
+ if(len ss >= 100) {
+ additem(ps, textit(ps, ss), nil);
+ ss = "";
+ }
+ }
+ if(i > j && j < n)
+ ss += s[j:i];
+ # don't add a space if previous item ended in a space
+ if(ss == " " && ps.lastit != nil) {
+ pick t := ps.lastit {
+ Itext =>
+ sp := t.s;
+ nsp := len sp;
+ if(nsp > 0 && sp[nsp-1] == ' ')
+ ss = "";
+ }
+ }
+ if(ss != "")
+ additem(ps, textit(ps, ss), nil);
+ }
+}
+
+# Add a break to ps.curstate, with extra space if sp is true.
+# If there was a previous break, combine this one's parameters
+# with that to make the amt be the max of the two and the clr
+# be the most general. (amt will be 0 or 1)
+# Also, if the immediately preceding item was a text item,
+# trim any whitespace from the end of it, if not in literal mode.
+# Finally, if this is at the very beginning of the item list
+# (the only thing there is a null spacer), then don't add the space.
+addbrk(ps: ref Pstate, sp: int, clr: int)
+{
+ state := ps.curstate;
+ clr = clr | (state&(IFcleft|IFcright));
+ if(sp && !(ps.lastit == ps.items))
+ sp = IFbrksp;
+ else
+ sp = 0;
+ ps.curstate = IFbrk | sp | (state&~(IFcleft|IFcright)) | clr;
+ if(ps.lastit != ps.items) {
+ if(!ps.literal && tagof ps.lastit == tagof Item.Itext) {
+ pick t := ps.lastit {
+ Itext =>
+ (l,nil) := S->splitr(t.s, notwhitespace);
+ # try to avoid making empty items
+ # (but not crucial if the occasional one gets through)
+ if(l == "" && ps.prelastit != nil) {
+ ps.lastit = ps.prelastit;
+ ps.lastit.next = nil;
+ ps.prelastit = nil;
+ }
+ else
+ t.s = l;
+ }
+ }
+ }
+}
+
+# Add break due to a <br> or a newline within a preformatted section.
+# We add a null item first, with current font's height and ascent, to make
+# sure that the current line takes up at least that amount of vertical space.
+# This ensures that <br>s on empty lines cause blank lines, and that
+# multiple <br>s in a row give multiple blank lines.
+# However don't add the spacer if the previous item was something that
+# takes up space itself. [[ I think this is not what we want; see
+# MR inf983435. --Ravi ]]
+addlinebrk(ps: ref Pstate, clr: int)
+{
+ # don't want break before our null item unless the previous item
+ # was also a null item for the purposes of line breaking
+ obrkstate := ps.curstate & (IFbrk|IFbrksp);
+ b := IFnobrk;
+ if(ps.lastit != nil) {
+ pick pit := ps.lastit {
+ Ispacer =>
+ if(pit.spkind == ISPvline)
+ b = IFbrk;
+ }
+ }
+ ps.curstate = (ps.curstate & ~(IFbrk|IFbrksp)) | b;
+ additem(ps, Item.newspacer(ISPvline, ps.curfont), nil);
+ ps.curstate = (ps.curstate & ~(IFbrk|IFbrksp)) | obrkstate;
+ addbrk(ps, 0, clr);
+}
+
+# Add a nonbreakable space
+addnbsp(ps: ref Pstate)
+{
+ # if nbsp comes right where a break was specified,
+ # do the break anyway (nbsp is being used to generate undiscardable
+ # space rather than to prevent a break)
+ if((ps.curstate&IFbrk) == 0)
+ ps.curstate |= IFnobrk;
+ additem(ps, Item.newspacer(ISPhspace, ps.curfont), nil);
+ # but definitely no break on next item
+ ps.curstate |= IFnobrk;
+}
+
+# Change hang in ps.curstate by delta.
+# The amount is in 1/10ths of tabs, and is the amount that
+# the current contiguous set of items with a hang value set
+# is to be shifted left from its normal (indented) place.
+changehang(ps: ref Pstate, delta: int)
+{
+ amt := (ps.curstate&IFhangmask) + delta;
+ if(amt < 0) {
+ if(warn)
+ sys->print("warning: hang went negative\n");
+ amt = 0;
+ }
+ ps.curstate = (ps.curstate&~IFhangmask) | amt;
+}
+
+# Change indent in ps.curstate by delta.
+changeindent(ps: ref Pstate, delta: int)
+{
+ amt := ((ps.curstate&IFindentmask)>>IFindentshift) + delta;
+ if(amt < 0) {
+ if(warn)
+ sys->print("warning: indent went negative\n");
+ amt = 0;
+ }
+ ps.curstate = (ps.curstate&~IFindentmask) | (amt<<IFindentshift);
+}
+
+stackhd(stk: list of int, dflt: int) : int
+{
+ if(stk == nil)
+ return dflt;
+ return hd stk;
+}
+
+popfontstyle(ps: ref Pstate)
+{
+ if(ps.fntstylestk != nil)
+ ps.fntstylestk = tl ps.fntstylestk;
+ setcurfont(ps);
+}
+
+pushfontstyle(ps: ref Pstate, sty: int)
+{
+ ps.fntstylestk = sty :: ps.fntstylestk;
+ setcurfont(ps);
+}
+
+popfontsize(ps: ref Pstate)
+{
+ if(ps.fntsizestk != nil)
+ ps.fntsizestk = tl ps.fntsizestk;
+ setcurfont(ps);
+}
+
+pushfontsize(ps: ref Pstate, sz: int)
+{
+ ps.fntsizestk = sz :: ps.fntsizestk;
+ setcurfont(ps);
+}
+
+setcurfont(ps: ref Pstate)
+{
+ sty := FntR;
+ sz := Normal;
+ if(ps.fntstylestk != nil)
+ sty = hd ps.fntstylestk;
+ if(ps.fntsizestk != nil)
+ sz = hd ps.fntsizestk;
+ if(sz < Tiny)
+ sz = Tiny;
+ if(sz > Verylarge)
+ sz = Verylarge;
+ ps.curfont = sty*NumSize + sz;
+}
+
+popjust(ps: ref Pstate)
+{
+ if(ps.juststk != nil)
+ ps.juststk = tl ps.juststk;
+ setcurjust(ps);
+}
+
+pushjust(ps: ref Pstate, j: byte)
+{
+ ps.juststk = j :: ps.juststk;
+ setcurjust(ps);
+}
+
+setcurjust(ps: ref Pstate)
+{
+ if(ps.juststk != nil)
+ j := hd ps.juststk;
+ else
+ j = Aleft;
+ if(j != ps.curjust) {
+ ps.curjust = j;
+ state := ps.curstate;
+ state &= ~(IFrjust|IFcjust);
+ if(j == Acenter)
+ state |= IFcjust;
+ else if(j == Aright)
+ state |= IFrjust;
+ ps.curstate = state;
+ }
+}
+
+# Do final rearrangement after table parsing is finished
+# and assign cells to grid points
+finish_table(t: ref Table)
+{
+ t.nrow = len t.currows;
+ t.rows = array[t.nrow] of ref Tablerow;
+ ncol := 0;
+ r := t.nrow-1;
+ for(rl := t.currows; rl != nil; rl = tl rl) {
+ row := hd rl;
+ t.rows[r--] = row;
+ rcols := 0;
+ cl := row.cells;
+ # If rowspan is > 1 but this is the last row,
+ # reset the rowspan
+ if(cl != nil && (hd cl).rowspan > 1 && rl == t.currows)
+ (hd cl).rowspan = 1;
+ row.cells = nil;
+ while(cl != nil) {
+ c := hd cl;
+ row.cells = c :: row.cells;
+ rcols += c.colspan;
+ cl = tl cl;
+ }
+ if(rcols > ncol)
+ ncol = rcols;
+ }
+ t.currows = nil;
+ t.ncol = ncol;
+ t.cols = array[ncol] of { * => Tablecol(0, Align(Anone, Anone), (0,0)) };
+
+ # Reverse cells just so they are drawn in source order.
+ # Also, trim their contents so they don't end in whitespace.
+ cells : list of ref Tablecell = nil;
+ for(cl := t.cells; cl != nil; cl = tl cl) {
+ c := hd cl;
+ trim_cell(c);
+ cells = c :: cells;
+ }
+ t.cells = cells;
+
+ t.grid = array[t.nrow] of { * => array[t.ncol] of ref Tablecell };
+ # The following arrays keep track of cells that are spanning
+ # multiple rows; rowspancnt[i] is the number of rows left
+ # to be spanned in column i.
+ # When done, cell's (row,col) is upper left grid point.
+ rowspancnt := array[t.ncol] of { * => 0};
+ rowspancell := array[t.ncol] of ref Tablecell;
+
+ ri := 0;
+ ci := 0;
+ for(ri = 0; ri < t.nrow; ri++) {
+ row := t.rows[ri];
+ cl = row.cells;
+ for(ci = 0; ci < t.ncol || cl != nil; ) {
+ if(ci < t.ncol && rowspancnt[ci] > 0) {
+ t.grid[ri][ci] = rowspancell[ci];
+ rowspancnt[ci]--;
+ ci++;
+ }
+ else {
+ if(cl == nil) {
+ ci++;
+ continue;
+ }
+ c := hd cl;
+ cl = tl cl;
+ cspan := c.colspan;
+ rspan := c.rowspan;
+ if(ci+cspan > t.ncol) {
+ # because of row spanning, we calculated
+ # ncol incorrectly; adjust it
+ newncol := ci+cspan;
+ newcols := array[newncol] of Tablecol;
+ newrowspancnt := array[newncol] of { * => 0};
+ newrowspancell := array[newncol] of ref Tablecell;
+ newcols[0:] = t.cols;
+ newrowspancnt[0:] = rowspancnt;
+ newrowspancell[0:] = rowspancell;
+ for(k := t.ncol; k < newncol; k++)
+ newcols[k] = Tablecol(0, Align(Anone, Anone), (0,0));
+ t.cols = newcols;
+ rowspancnt = newrowspancnt;
+ rowspancell = newrowspancell;
+ for(j := 0; j < t.nrow; j++) {
+ newgrr := array[newncol] of ref Tablecell;
+ newgrr[0:] = t.grid[j];
+ for(k = t.ncol; k < newncol; k++)
+ newgrr[k] = nil;
+ t.grid[j] = newgrr;
+ }
+ t.ncol = newncol;
+ }
+ c.row = ri;
+ c.col = ci;
+ for(i := 0; i < cspan; i++) {
+ t.grid[ri][ci] = c;
+ if(rspan > 1) {
+ rowspancnt[ci] = rspan-1;
+ rowspancell[ci] = c;
+ }
+ ci++;
+ }
+ }
+ }
+ }
+ t.flags |= Layout->Lchanged;
+}
+
+# Remove tail of cell content until it isn't whitespace.
+trim_cell(c: ref Tablecell)
+{
+ dropping := 1;
+ while(c.content != nil && dropping) {
+ p := c.content;
+ pprev : ref Item = nil;
+ while(p.next != nil) {
+ pprev = p;
+ p = p.next;
+ }
+ dropping = 0;
+ if(!(p.state&IFnobrk)) {
+ pick q := p {
+ Itext =>
+ s := q.s;
+ (x,y) := S->splitr(s, notwhitespace);
+ if(x == nil)
+ dropping = 1;
+ else if(y != nil)
+ q.s = x;
+ }
+ }
+ if(dropping) {
+ if(pprev == nil)
+ c.content = nil;
+ else
+ pprev.next = nil;
+ }
+ }
+}
+
+roman := array[] of {"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X",
+ "XI", "XII", "XIII", "XIV", "XV"};
+
+listmark(ty: byte, n: int) : string
+{
+ s := "";
+ case int ty {
+ int LTdisc =>
+ s = "•";
+ int LTsquare =>
+ s = "∎";
+ int LTcircle =>
+ s = "∘";
+ int LT1 =>
+ s = string n + ".";
+ int LTa or int LTA =>
+ n--;
+ i := 0;
+ if(n < 0)
+ n = 0;
+ if(n > 25) {
+ n2 := n / 26;
+ n %= 26;
+ if(n2 > 25)
+ n2 = 25;
+ s[i++] = n2 + 'A';
+ }
+ s[i++] = n + 'A';
+ s[i++] = '.';
+ if(ty == LTa)
+ s = S->tolower(s);
+ int LTi or int LTI =>
+ if(n >= len roman) {
+ if(warn)
+ sys->print("warning: unimplemented roman number > %d\n", len roman);
+ n = len roman;
+ }
+ s = roman[n-1];
+ if(ty == LTi)
+ s = S->tolower(s);
+ s += ".";
+ }
+ return s;
+}
+
+# Find map with given name in di.maps.
+# If not there, add one.
+getmap(di: ref Docinfo, name: string) : ref Map
+{
+ m : ref Map;
+ for(ml := di.maps; ml != nil; ml = tl ml) {
+ m = hd ml;
+ if(m.name == name)
+ return m;
+ }
+ m = Map.new(name);
+ di.maps = m :: di.maps;
+ return m;
+}
+
+# attrvalue, when "found" status doesn't matter
+# (because nil ans is sufficient indication)
+aval(tok: ref Token, attid: int) : string
+{
+ (nil, ans) := tok.aval(attid);
+ return ans;
+}
+
+# attrvalue, when ans is a string, but need default
+astrval(tok: ref Token, attid: int, dflt: string) : string
+{
+ (fnd, ans) := tok.aval(attid);
+ if(!fnd)
+ return dflt;
+ else
+ return ans;
+}
+
+# attrvalue, when supposed to convert to int
+# and have default for when not found
+aintval(tok: ref Token, attid: int, dflt: int) : int
+{
+ (fnd, ans) := tok.aval(attid);
+ if(!fnd || ans == "")
+ return dflt;
+ else
+ return toint(ans);
+}
+
+# Like int conversion, but with possible error check (if warning)
+toint(s: string) : int
+{
+ if(warn) {
+ ok := 0;
+ for(i := 0; i < len s; i++) {
+ c := s[i];
+ if(!(c < C->NCTYPE && ctype[c] == C->W))
+ break;
+ }
+ for(; i < len s; i++) {
+ c := s[i];
+ if(c < C->NCTYPE && ctype[c] == C->D)
+ ok = 1;
+ else {
+ ok = 0;
+ break;
+ }
+ }
+ if(!ok || i != len s)
+ sys->print("warning: expected integer, got '%s'\n", s);
+ }
+ return int s;
+}
+
+# attrvalue when need a table to convert strings to ints
+atabval(tok: ref Token, attid: int, tab: array of T->StringInt, dflt: int) : int
+{
+ (fnd, aval) := tok.aval(attid);
+ ans := dflt;
+ if(fnd) {
+ name := S->tolower(aval);
+ (fnd, ans) = T->lookup(tab, name);
+ if(!fnd) {
+ ans = dflt;
+ if(warn)
+ sys->print("warning: name not found in table lookup: %s\n", name);
+ }
+ }
+ return ans;
+}
+
+# like atabval, but when want a byte answer
+atabbval(tok: ref Token, attid: int, tab: array of T->StringInt, dflt: byte) : byte
+{
+ (fnd, aval) := tok.aval(attid);
+ ans := dflt;
+ if(fnd) {
+ name := S->tolower(aval);
+ ians : int;
+ (fnd, ians) = T->lookup(tab, name);
+ if(fnd)
+ ans = byte ians;
+ else if(warn)
+ sys->print("warning: name not found in table lookup: %s\n", name);
+ }
+ return ans;
+}
+
+# special for list types, where "i" and "I" are different,
+# but "square" and "SQUARE" are the same
+listtyval(tok: ref Token, dflt: byte) : byte
+{
+ (fnd, aval) := tok.aval(LX->Atype);
+ ans := dflt;
+ if(fnd) {
+ case aval {
+ "1" => ans = LT1;
+ "A" => ans = LTA;
+ "I" => ans = LTI;
+ "a" => ans = LTa;
+ "i" => ans = LTi;
+ * =>
+ aval = S->tolower(aval);
+ case aval {
+ "circle" => ans = LTcircle;
+ "disc" => ans = LTdisc;
+ "square" => ans = LTsquare;
+ * => if(warn)
+ sys->print("warning: unknown list element type %s\n", aval);
+ }
+ }
+ }
+ return ans;
+}
+
+# attrvalue when value is a URL
+aurlval(tok: ref Token, attid: int, dflt, base: ref Parsedurl) : ref Parsedurl
+{
+ ans := dflt;
+ (fnd, url) := tok.aval(attid);
+ if(fnd && url != nil) {
+ url = S->drop(url, whitespace);
+ ans = U->parse(url);
+ case (ans.scheme) {
+ "javascript" =>
+ ; # don't strip whitespace from the URL
+ * =>
+ # sometimes people put extraneous whitespace in
+ url = stripwhite(url);
+ ans = U->parse(url);
+ if(base != nil)
+ ans = U->mkabs(ans, base);
+ }
+ }
+ return ans;
+}
+
+# remove any whitespace characters from any part of s
+# up to a '#' (assuming s is a url and '#' begins a fragment
+# (can return s if there are no whitespace characters in it)
+stripwhite(s: string) : string
+{
+ j := 0;
+ n := len s;
+ strip := 1;
+ for(i := 0; i < n; i++) {
+ c := s[i];
+ if(c == '#')
+ strip = 0;
+ if(strip && c < C->NCTYPE && ctype[c]==C->W)
+ continue;
+ s[j++] = c;
+ }
+ if(j < n)
+ s = s[0:j];
+ return s;
+}
+
+# Presence of attribute implies true, omission implies false.
+# Boolean attributes can have a value equal to their attribute name.
+# HTML4.01 does not state whether the attribute is true or false
+# if a value is given that doesn't match the attribute name.
+aboolval(tok: ref Token, attid: int): int
+{
+ (fnd, nil) := tok.aval(attid);
+ return fnd;
+}
+
+# attrvalue when mere presence of attr implies value of 1
+aflagval(tok: ref Token, attid: int) : int
+{
+ val := 0;
+ (fnd, sval) := tok.aval(attid);
+ if(fnd) {
+ val = 1;
+ if(sval != "")
+ val = toint(sval);
+ }
+ return val;
+}
+
+# Make an Align (two alignments, horizontal and vertical)
+makealign(tok: ref Token) : Align
+{
+ h := atabbval(tok, LX->Aalign, align_tab, Anone);
+ v := atabbval(tok, LX->Avalign, align_tab, Anone);
+ return Align(h, v);
+}
+
+# Make a Dimen, based on value of attid attr
+makedimen(tok: ref Token, attid: int) : Dimen
+{
+ kind := Dnone;
+ spec := 0;
+ (fnd, wd) := tok.aval(attid);
+ if(fnd)
+ return parsedim(wd);
+ else
+ return Dimen.make(Dnone, 0);
+}
+
+# Parse s as num[.[num]][unit][%|*]
+parsedim(s: string) : Dimen
+{
+ kind := Dnone;
+ spec := 0;
+ (l,r) := S->splitl(s, "^0-9");
+ if(l != "") {
+ # accumulate 1000 * value (to work in fixed point)
+ spec = 1000 * toint(l);
+ if(S->prefix(".", r)) {
+ f : string;
+ (f,r) = S->splitl(r[1:], "^0-9");
+ if(f != "") {
+ mul := 100;
+ for(i := 0; i < len f; i++) {
+ spec = spec + mul * toint(f[i:i+1]);
+ mul = mul / 10;
+ }
+ }
+ }
+ kind = Dpixels;
+ if(r != "") {
+ if(len r >= 2) {
+ Tkdpi := 100; # hack, but matches current tk
+ units := r[0:2];
+ r = r[2:];
+ case units {
+ "pt" => spec = (spec*Tkdpi)/72;
+ "pi" => spec = (spec*12*Tkdpi)/72;
+ "in" => spec = spec*Tkdpi;
+ "cm" => spec = (spec*100*Tkdpi)/254;
+ "mm" => spec = (spec*10*Tkdpi)/254;
+ "em" => spec = spec * 15; # hack, lucidasans 8pt is 15 pixels high
+ * =>
+ if(warn)
+ sys->print("warning: unknown units %s\n", units);
+ }
+ }
+ if(r == "%")
+ kind = Dpercent;
+ else if(r == "*")
+ kind = Drelative;
+ }
+ spec = spec / 1000;
+ }
+ else if(r == "*") {
+ spec = 1;
+ kind = Drelative;
+ }
+ return Dimen.make(kind, spec);
+}
+
+dimlist(tok: ref Token, attid: int) : array of Dimen
+{
+ s := aval(tok, attid);
+ if(s != "") {
+ (nc, cl) := sys->tokenize(s, ", ");
+ if(nc > 0) {
+ d := array[nc] of Dimen;
+ for(k := 0; k < nc; k++) {
+ d[k] = parsedim(hd cl);
+ cl = tl cl;
+ }
+ return d;
+ }
+ }
+ return nil;
+}
+
+stringdim(d: Dimen) : string
+{
+ ans := string d.spec();
+ k := d.kind();
+ if(k == Dpercent)
+ ans += "%";
+ if(k == Drelative)
+ ans += "*";
+ return ans;
+}
+
+stringalign(a: byte) : string
+{
+ s := T->revlookup(align_tab, int a);
+ if(s == nil)
+ s = "none";
+ return s;
+}
+
+stringstate(state: int) : string
+{
+ s := "";
+ if(state&IFbrk) {
+ c := state&(IFcleft|IFcright);
+ clr := "";
+ if(int c) {
+ if(c == (IFcleft|IFcright))
+ clr = " both";
+ else if(c == IFcleft)
+ clr = " left";
+ else
+ clr = " right";
+ }
+ amt := 0;
+ if(state&IFbrksp)
+ amt = 1;
+ s = sys->sprint("brk(%d%s)", amt, clr);
+ }
+ if(state&IFnobrk)
+ s += " nobrk";
+ if(!(state&IFwrap))
+ s += " nowrap";
+ if(state&IFrjust)
+ s += " rjust";
+ if(state&IFcjust)
+ s += " cjust";
+ if(state&IFsmap)
+ s += " smap";
+ indent := (state&IFindentmask)>>IFindentshift;
+ if(indent > 0)
+ s += " indent=" + string indent;
+ hang := state&IFhangmask;
+ if(hang > 0)
+ s += " hang=" + string hang;
+ return s;
+}
+
+Item.newtext(s: string, fnt, fg, voff: int, ul: byte) : ref Item
+{
+ return ref Item.Itext(nil, 0, 0, 0, 0, 0, nil, s, fnt, fg, byte voff, ul);
+}
+
+Item.newrule(align: byte, size, noshade: int, wspec: Dimen) : ref Item
+{
+ return ref Item.Irule(nil, 0, 0, 0, 0, 0, nil, align, byte noshade, size, wspec);
+}
+
+Item.newimage(di: ref Docinfo, src: ref Parsedurl, lowsrc: ref Parsedurl, altrep: string,
+ align: byte, width, height, hspace, vspace, border, ismap, isbkg: int,
+ map: ref Map, name: string, genattr: ref Genattr) : ref Item
+{
+ ci := CImage.new(src, lowsrc, width, height);
+ state := 0;
+ if(ismap)
+ state = IFsmap;
+ if (isbkg)
+ state = IFbkg;
+ return ref Item.Iimage(nil, 0, 0, 0, 0, state, genattr, len di.images,
+ ci, width, height, altrep, map, name, -1, align, byte hspace, byte vspace, byte border);
+}
+
+Item.newformfield(ff: ref Formfield) : ref Item
+{
+ return ref Item.Iformfield(nil, 0, 0, 0, 0, 0, nil, ff);
+}
+
+Item.newtable(t: ref Table) : ref Item
+{
+ return ref Item.Itable(nil, 0, 0, 0, 0, 0, nil, t);
+}
+
+Item.newfloat(it: ref Item, side: byte) : ref Item
+{
+ return ref Item.Ifloat(nil, 0, 0, 0, 0, IFwrap, nil, it, 0, 0, side, byte 0);
+}
+
+Item.newspacer(spkind, font: int) : ref Item
+{
+ return ref Item.Ispacer(nil, 0, 0, 0, 0, 0, nil, spkind, font);
+}
+
+Item.revlist(itl: list of ref Item) : list of ref Item
+{
+ ans : list of ref Item = nil;
+ for( ;itl != nil; itl = tl itl)
+ ans = hd itl :: ans;
+ return ans;
+}
+
+Item.print(it: self ref Item)
+{
+ s := stringstate(it.state);
+ if(s != "")
+ sys->print("%s\n",s);
+ pick a := it {
+ Itext =>
+ sys->print("Text '%s', fnt=%d, fg=%x", a.s, a.fnt, a.fg);
+ Irule =>
+ sys->print("Rule wspec=%s, size=%d, al=%s",
+ stringdim(a.wspec), a.size, stringalign(a.align));
+ Iimage =>
+ src := "";
+ if(a.ci.src != nil)
+ src = a.ci.src.tostring();
+ map := "";
+ if(a.map != nil)
+ map = a.map.name;
+ sys->print("Image src=%s, alt=%s, al=%s, w=%d, h=%d hsp=%d, vsp=%d, bd=%d, map=%s, name=%s",
+ src, a.altrep, stringalign(a.align), a.imwidth, a.imheight,
+ int a.hspace, int a.vspace, int a.border, map, a.name);
+ Iformfield =>
+ ff := a.formfield;
+ if(ff.ftype == Ftextarea)
+ ty := "textarea";
+ else if(ff.ftype == Fselect)
+ ty = "select";
+ else
+ ty = T->revlookup(input_tab, int ff.ftype);
+ sys->print("Formfield %s, fieldid=%d, formid=%d, name=%s, value=%s",
+ ty, ff.fieldid, int ff.form.formid, ff.name, ff.value);
+ Itable =>
+ tab := a.table;
+ sys->print("Table tableid=%d, width=%s, nrow=%d, ncol=%d, ncell=%d, totw=%d, toth=%d\n",
+ tab.tableid, stringdim(tab.width), tab.nrow, tab.ncol, tab.ncell, tab.totw, tab.toth);
+ for(cl := tab.cells; cl != nil; cl = tl cl) {
+ c := hd cl;
+ c.content.printlist(sys->sprint("Cell %d.%d, at (%d,%d)", tab.tableid, c.cellid, c.row, c.col));
+ }
+ sys->print("End of Table %d", tab.tableid);
+ Ifloat =>
+ sys->print("Float, x=%d y=%d, side=%s, it=", a.x, a.y, stringalign(a.side));
+ a.item.print();
+ sys->print("\n\t");
+ Ispacer =>
+ s = "";
+ case a.spkind {
+ ISPnull =>
+ s = "null";
+ ISPvline =>
+ s = "vline";
+ ISPhspace =>
+ s = "hspace";
+ }
+ sys->print("Spacer %s ", s);
+ }
+ sys->print(" w=%d, h=%d, a=%d, anchor=%d\n", it.width, it.height, it.ascent, it.anchorid);
+}
+
+Item.printlist(items: self ref Item, msg: string)
+{
+ sys->print("%s\n", msg);
+ il := items;
+ while(il != nil) {
+ il.print();
+ il = il.next;
+ }
+}
+
+Formfield.new(ftype, fieldid: int, form: ref Form, name, value: string, size, maxlength: int) : ref Formfield
+{
+ return ref Formfield(ftype, fieldid, form, name, value, size,
+ maxlength, 0, 0, byte 0, nil, nil, -1, nil, 0);
+}
+
+Form.new(formid: int, name: string, action: ref Parsedurl, target: string, method: int, events: list of Lex->Attr) : ref Form
+{
+ return ref Form(formid, name, action, target, method, events, 0, 0, nil, FormBuild);
+}
+
+Table.new(tableid: int, align: Align, width: Dimen,
+ border, cellspacing, cellpadding: int, bg: Background, tok: ref Lex->Token) : ref Table
+{
+ return ref Table(tableid,
+ 0, 0, 0, # nrow, ncol, ncell
+ align, width, border, cellspacing, cellpadding, bg,
+ nil, Abottom, -1, # caption, caption_place, caption_lay
+ nil, nil, nil, nil, # currows, cols, rows, cells
+ 0, 0, 0, 0, # totw, toth, caph, availw
+ nil, tok, byte 0); # grid, tabletok, flags
+}
+
+Tablerow.new(align: Align, bg: Background, flags: byte) : ref Tablerow
+{
+ return ref Tablerow(nil, # cells
+ 0, 0, # height, ascent
+ align, # align
+ bg, # background
+ Point(0,0), # pos
+ flags);
+}
+
+Tablecell.new(cellid, rowspan, colspan: int, align: Align, wspec: Dimen,
+ hspec: int, bg: Background, flags: byte) : ref Tablecell
+{
+ return ref Tablecell(cellid,
+ nil, -1, # content, layid
+ rowspan, colspan, align, flags, wspec, hspec, bg,
+ 0, 0, 0, # minw, maxw, ascent
+ 0, 0, # row, col
+ Point(0,0)); # pos
+}
+
+Dimen.kind(d: self Dimen) : int
+{
+ return (d.kindspec & Dkindmask);
+}
+
+Dimen.spec(d: self Dimen) : int
+{
+ return (d.kindspec & Dspecmask);
+}
+
+Dimen.make(kind, spec: int) : Dimen
+{
+ if(spec & Dkindmask) {
+ if(warn)
+ sys->print("warning: dimension spec too big: %d\n", spec);
+ spec = 0;
+ }
+ return Dimen(kind | spec);
+}
+
+Map.new(name: string) : ref Map
+{
+ return ref Map(name, nil);
+}
+
+Docinfo.new() : ref Docinfo
+{
+ ans := ref Docinfo;
+ ans.reset();
+ return ans;
+}
+
+Docinfo.reset(d: self ref Docinfo)
+{
+ d.src = nil;
+ d.base = nil;
+ d.referrer = nil;
+ d.doctitle = "";
+ d.backgrounditem = nil;
+ d.background = (nil, CU->White);
+ d.text = CU->Black;
+ d.link = CU->Blue;
+ d.vlink = CU->Blue;
+ d.alink = CU->Blue;
+ d.target = "_self";
+ d.refresh = "";
+ d.chset = (CU->config).charset;
+ d.lastModified = "";
+ d.scripttype = CU->TextJavascript;
+ d.hasscripts = 0;
+ d.events = nil;
+ d.evmask = 0;
+ d.kidinfo = nil;
+ d.frameid = -1;
+
+ d.anchors = nil;
+ d.dests = nil;
+ d.forms = nil;
+ d.tables = nil;
+ d.maps = nil;
+ d.images = nil;
+}
+
+Kidinfo.new(isframeset: int) : ref Kidinfo
+{
+ ki := ref Kidinfo(isframeset,
+ nil, # src
+ "", # name
+ 0, 0, 0, # marginw, marginh, framebd
+ 0, # flags
+ nil, nil, nil # rows, cols, kidinfos
+ );
+ if(!isframeset) {
+ ki.flags = FRhscrollauto|FRvscrollauto;
+ ki.marginw = FRKIDMARGIN;
+ ki.marginh = FRKIDMARGIN;
+ ki.framebd = 1;
+ }
+ return ki;
+}