summaryrefslogtreecommitdiff
path: root/appl/charon
diff options
context:
space:
mode:
Diffstat (limited to 'appl/charon')
-rw-r--r--appl/charon/build.b2862
-rw-r--r--appl/charon/build.m478
-rw-r--r--appl/charon/charon.b2171
-rw-r--r--appl/charon/charon.m20
-rw-r--r--appl/charon/chutils.b2050
-rw-r--r--appl/charon/chutils.m371
-rw-r--r--appl/charon/common.m23
-rw-r--r--appl/charon/cookiesrv.b595
-rw-r--r--appl/charon/cookiesrv.m12
-rw-r--r--appl/charon/ctype.b70
-rw-r--r--appl/charon/ctype.m24
-rw-r--r--appl/charon/date.b62
-rw-r--r--appl/charon/date.m12
-rw-r--r--appl/charon/event.b273
-rw-r--r--appl/charon/event.m100
-rw-r--r--appl/charon/file.b134
-rw-r--r--appl/charon/ftp.b314
-rw-r--r--appl/charon/gui.b560
-rw-r--r--appl/charon/gui.m48
-rw-r--r--appl/charon/http.b1040
-rw-r--r--appl/charon/img.b3607
-rw-r--r--appl/charon/img.m115
-rw-r--r--appl/charon/jscript.b3025
-rw-r--r--appl/charon/layout.b4828
-rw-r--r--appl/charon/layout.m235
-rw-r--r--appl/charon/lex.b1340
-rw-r--r--appl/charon/lex.m105
-rw-r--r--appl/charon/mkfile92
-rw-r--r--appl/charon/paginate.b511
-rw-r--r--appl/charon/paginate.m16
-rw-r--r--appl/charon/rgb.inc620
-rw-r--r--appl/charon/script.m14
-rw-r--r--appl/charon/transport.m13
-rw-r--r--appl/charon/url.b225
-rw-r--r--appl/charon/url.m30
-rw-r--r--appl/charon/xxx.inc75
-rw-r--r--appl/charon/ycbcr.inc621
37 files changed, 26691 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;
+}
diff --git a/appl/charon/build.m b/appl/charon/build.m
new file mode 100644
index 00000000..24d3250c
--- /dev/null
+++ b/appl/charon/build.m
@@ -0,0 +1,478 @@
+Build: module
+{
+PATH: con "/dis/charon/build.dis";
+
+# Item layout is dictated by desire to have all but formfield and table
+# items allocated in one piece.
+# Also aiming for the 128-byte allocation quantum, which means
+# keeping the total size at 17 32-bit words, including pick tag.
+Item: adt
+{
+ next: cyclic ref Item; # successor in list of items
+ width: int; # width in pixels (0 for floating items)
+ height: int; # height in pixels
+ ascent: int; # ascent (from top to baseline) in pixels
+ anchorid: int; # if nonzero, which anchor we're in
+ state: int; # flags and values (see below)
+ genattr: ref Genattr; # generic attributes and events
+
+ pick {
+ Itext =>
+ s: string; # the characters
+ fnt: int; # style*NumSize+size (see font stuff, below)
+ fg: int; # Pixel (color) for text
+ voff: byte; # Voffbias+vertical offset from baseline, in pixels (+ve == down)
+ ul: byte; # ULnone, ULunder, or ULmid
+ Irule =>
+ align: byte; # alignment spec
+ noshade: byte; # if true, don't shade
+ size: int; # size attr (rule height)
+ wspec: Dimen; # width spec
+ Iimage =>
+ imageid: int; # serial no. of image within its doc
+ ci: ref CharonUtils->CImage; # charon image (has src, actual width, height)
+ imwidth: int; # spec width (actual, if no spec)
+ imheight: int; # spec height (actual, if no spec)
+ altrep: string; # alternate representation, in absence of image
+ map: ref Map; # if non-nil, client side map
+ name: string; # name attribute
+ ctlid: int; # if animated
+ align: byte; # vertical alignment
+ hspace: byte; # in pixels; buffer space on each side
+ vspace: byte; # in pixels; buffer space on top and bottom
+ border: byte; # in pixels: border width to draw around image
+ Iformfield =>
+ formfield: ref Formfield;
+ Itable =>
+ table: ref Table;
+ Ifloat =>
+ item: ref Item; # content of float
+ x: int; # x coord of top (from right, if Aright)
+ y: int; # y coord of top
+ side: byte; # margin it floats to: Aleft or Aright
+ infloats: byte; # true if this has been added to a lay.floats
+ Ispacer =>
+ spkind: int; # ISPnone, etc.
+ fnt: int; # font number
+ }
+
+ newtext: fn(s: string, fnt, fg, voff: int, ul: byte) : ref Item;
+ newrule: fn(align: byte, size, noshade: int, wspec: Dimen) : ref Item;
+ newimage: fn(di: ref Docinfo, src: ref Url->Parsedurl, lowsrc: ref Url->Parsedurl, altrep: string,
+ align: byte, width, height, hspace, vspace, border, ismap, isbkg: int,
+ map: ref Map, name: string, genattr: ref Genattr) : ref Item;
+ newformfield: fn(ff: ref Formfield) : ref Item;
+ newtable: fn(t: ref Table) : ref Item;
+ newfloat: fn(i: ref Item, side: byte) : ref Item;
+ newspacer: fn(spkind, font: int) : ref Item;
+
+ revlist: fn(itl: list of ref Item) : list of ref Item;
+ print: fn(it: self ref Item);
+ printlist: fn(items: self ref Item, msg: string);
+};
+
+# Item state flags and value fields
+IFbrk: con (1<<31); # forced break before this item
+IFbrksp: con (1<<30); # add 1 line space to break (IFbrk set too)
+IFnobrk: con (1<<29); # break not allowed before this item
+IFcleft: con (1<<28); # clear left floats (IFbrk set too)
+IFcright: con (1<<27); # clear right floats (IFbrk set too)
+IFwrap: con (1<<26); # in a wrapping (non-pre) line
+IFhang: con (1<<25); # in a hanging (into left indent) item
+IFrjust: con (1<<24); # right justify current line
+IFcjust: con (1<<23); # center justify current line
+IFsmap: con (1<<22); # image is server-side map
+IFbkg: con (1<<21); # Item.image is a background image
+IFindentshift: con 8;
+IFindentmask: con (255<<IFindentshift); # current indent, in tab stops
+IFhangmask: con 255; # current hang into left indent, in 1/10th tabstops
+
+Voffbias: con 128;
+
+# Spacer kinds. ISPnull has 0 height and width,
+# ISPvline has height/ascent of current font
+# ISPhspace has width of space in current font
+# ISPgeneral used for other purposes (e.g. between list markers and list).
+ISPnull, ISPvline, ISPhspace, ISPgeneral: con iota;
+
+# Generic attributes and events (not many elements will have any of these set)
+Genattr: adt
+{
+ id: string; # document-wide unique id
+ class: string; # space-separated list of classes
+ style: string; # associated style info
+ title: string; # advisory title
+ events: list of Lex->Attr; # attid will be Aonblur, etc., value is script
+ evmask: int; # Aonblur|Aonfocus, etc. when present
+};
+
+
+# Formfield Item: a field from a form
+
+# form field types (ints because often case on them)
+Ftext, Fpassword, Fcheckbox, Fradio, Fsubmit, Fhidden, Fimage,
+ Freset, Ffile, Fbutton, Fselect, Ftextarea: con iota;
+
+Formfield: adt
+{
+ ftype: int; # Ftext, Fpassword, etc.
+ fieldid: int; # serial no. of field within its form
+ form: cyclic ref Form; # containing form
+ name: string; # name attr
+ value: string; # value attr
+ size: int; # size attr
+ maxlength: int; # maxlength attr
+ rows: int; # rows attr
+ cols: int; # cols attr
+ flags: byte; # FFchecked, etc.
+ options: list of ref Option; # for Fselect fields
+ image: cyclic ref Item; # image item, for Fimage fields
+ ctlid: int; # identifies control for this field in layout
+ events: list of Lex->Attr; # same as genattr.events of containing item
+ evmask: int;
+
+ new: fn(ftype, fieldid: int, form: ref Form, name, value: string, size, maxlength: int) : ref Formfield;
+};
+
+# Form flags
+FFchecked: con byte (1<<7);
+FFmultiple: con byte (1<<6);
+
+# Option holds info about an option in a "select" form field
+Option: adt {
+ selected: int; # true if selected initially
+ value: string; # value attr
+ display: string; # display string
+};
+
+# Form holds info about a form
+Form: adt
+{
+ formid: int; # serial no. of form within its doc
+ name: string; # name or id attr (netscape uses name, HTML 4.0 uses id)
+ action: ref Url->Parsedurl; # action attr
+ target: string; # target attribute
+ method: int; # HGet or HPost
+ events: list of Lex->Attr; # attid will be Aonreset or Aonsubmit
+ evmask: int;
+ nfields: int; # number of fields
+ fields: cyclic list of ref Formfield; # field's forms, in input order
+ state: int; # see Form states enum
+
+ new: fn(formid: int, name: string, action: ref Url->Parsedurl, target: string, method: int, events: list of Lex->Attr) : ref Form;
+};
+
+# Form states
+FormBuild, # seen <FORM>
+FormDone, # seen </FORM>
+FormTransferred : con iota; # tx'd to javascript
+
+# Flags used in various table structures
+TFparsing: con byte (1<<7);
+TFnowrap: con byte (1<<6);
+TFisth: con byte (1<<5);
+
+# A Table Item is for a table.
+Table: adt
+{
+ tableid: int; # serial no. of table within its doc
+ nrow: int; # total number of rows
+ ncol: int; # total number of columns
+ ncell: int; # total number of cells
+ align: Align; # alignment spec for whole table
+ width: Dimen; # width spec for whole table
+ border: int; # border attr
+ cellspacing: int; # cellspacing attr
+ cellpadding: int; # cellpadding attr
+ background: Background; # table background
+ caption: cyclic ref Item; # linked list of Items, giving caption
+ caption_place: byte; # Atop or Abottom
+ caption_lay: int; # identifies layout of caption
+ currows: cyclic list of ref Tablerow; # during parsing
+ cols: array of Tablecol; # column specs
+ rows: cyclic array of ref Tablerow; # row specs
+ cells: cyclic list of ref Tablecell; # the unique cells
+ totw: int; # total width
+ toth: int; # total height
+ caph: int; # caption height
+ availw: int; # used for previous 3 sizes
+ grid: cyclic array of array of ref Tablecell;
+ tabletok: ref Lex->Token; # token that started the table
+ flags: byte; # Lchanged
+
+ new: fn(tableid: int, align: Align, width: Dimen,
+ border, cellspacing, cellpadding: int, bg: Background, tok: ref Lex->Token) : ref Table;
+};
+
+# A table column info
+Tablecol: adt
+{
+ width: int;
+ align: Align;
+ pos: Draw->Point;
+};
+
+# A table row spec
+Tablerow: adt
+{
+ cells: cyclic list of ref Tablecell;
+ height: int;
+ ascent: int;
+ align: Align;
+ background: Background;
+ pos: Draw->Point;
+ flags: byte; # 0 or TFparsing
+
+ new: fn(align: Align, bg: Background, flags: byte) : ref Tablerow;
+};
+
+# A Tablecell is one cell of a table.
+# It may span multiple rows and multiple columns.
+# The (row,col) given indexes upper left corner of cell.
+# Try to keep this under 17 words long.
+Tablecell: adt
+{
+ cellid: int; # serial no. of cell within table
+ content: cyclic ref Item; # contents before layout
+ layid: int; # identifies layout of cell
+ rowspan: int; # number of rows spanned by this cell
+ colspan: int; # number of cols spanned by this cell
+ align: Align; # alignment spec
+ flags: byte; # TFparsing, TFnowrap, TFisth
+ wspec: Dimen; # suggested width
+ hspec: int; # suggested height
+ background: Background; # cell background
+ minw: int; # minimum possible width
+ maxw: int; # maximum width
+ ascent: int;
+ row: int;
+ col: int;
+ pos: Draw->Point; # nw corner of cell contents, in cell
+
+ new: fn(cellid, rowspan, colspan: int, align: Align, wspec: Dimen,
+ hspec: int, bg: Background, flags: byte) : ref Tablecell;
+};
+
+# Align holds both a vertical and a horizontal alignment.
+# Usually not all are possible in a given context.
+# Anone means no dimension was specified
+
+# alignment types
+Anone, Aleft, Acenter, Aright, Ajustify, Achar, Atop, Amiddle, Abottom, Abaseline: con byte iota;
+
+Align: adt
+{
+ halign: byte; # one of Anone, Aleft, etc.
+ valign: byte; # one of Anone, Atop, etc.
+};
+
+# A Dimen holds a dimension specification, especially for those
+# cases when a number can be followed by a % or a * to indicate
+# percentage of total or relative weight.
+# Dnone means no dimension was specified
+
+# Dimen
+# To fit in a word, use top bits to identify kind, rest for value
+Dnone: con 0;
+Dpixels: con 1<<29;
+Dpercent: con 2<<29;
+Drelative: con 3<<29;
+Dkindmask: con 3<<29;
+Dspecmask: con ~Dkindmask;
+
+Dimen: adt
+{
+ kindspec: int; # kind | spec
+
+ kind: fn(d: self Dimen) : int;
+ spec: fn(d: self Dimen) : int;
+
+ make: fn(kind, spec: int) : Dimen;
+};
+
+
+# Anchor is for info about hyperlinks that go somewhere
+Anchor: adt
+{
+ index: int; # serial no. of anchor within its doc
+ name: string; # name attr
+ href: ref Url->Parsedurl; # href attr
+ target: string; # target attr
+ events: list of Lex->Attr; # same as genattr.events of containing items
+ evmask: int;
+};
+
+# DestAnchor is for info about hyperlinks that are destinations
+DestAnchor: adt
+{
+ index: int; # serial no. of anchor within its doc
+ name: string; # name attr
+ item: ref Item; # the destination
+};
+
+# Maps (client side)
+Map: adt
+{
+ name: string; # map name
+ areas: list of Area; # hotzones
+
+ new: fn(name: string) : ref Map;
+};
+
+Area: adt
+{
+ shape: string; # rect, circle, or poly
+ href: ref Url->Parsedurl; # associated hypertext link
+ target: string; # associated target frame
+ coords: array of Dimen; # coords for shape
+};
+
+# Background is either an image or a color.
+# If both are set, the image has precedence.
+Background: adt
+{
+ image: ref Item.Iimage; # with state |= IFbkg
+ color: int; # RGB in lower 3 bytes
+};
+
+# Font styles
+FntR, FntI, FntB, FntT, NumStyle: con iota;
+
+# Font sizes
+Tiny, Small, Normal, Large, Verylarge, NumSize: con iota;
+
+NumFnt: con (NumStyle*NumSize);
+DefFnt: con (FntR*NumSize+Normal);
+
+# Lines are needed through some text items, for underlining or strikethrough
+ULnone, ULunder, ULmid: con byte iota;
+
+# List number types
+LTdisc, LTsquare, LTcircle, LT1, LTa, LTA, LTi, LTI: con byte iota;
+
+# Kidinfo flags
+FRnoresize, FRnoscroll, FRhscroll, FRvscroll, FRhscrollauto, FRvscrollauto: con (1<<iota);
+
+# Information about child frame or frameset
+Kidinfo: adt {
+ isframeset: int;
+
+ # fields for "frame"
+ src: ref Url->Parsedurl; # only nil if a "dummy" frame or this is frameset
+ name: string; # always non-empty if this isn't frameset
+ marginw: int;
+ marginh: int;
+ framebd: int;
+ flags: int;
+
+ # fields for "frameset"
+ rows: array of Dimen;
+ cols: array of Dimen;
+ kidinfos: cyclic list of ref Kidinfo;
+
+ new: fn(isframeset: int) : ref Kidinfo;
+};
+
+# Document info (global information about HTML page)
+Docinfo: adt {
+ # stuff from HTTP headers, doc head, and body tag
+ src: ref Url->Parsedurl; # original source of doc
+ base: ref Url->Parsedurl; # base URL of doc
+ referrer: ref Url->Parsedurl; # JavaScript document.referrer
+ doctitle: string; # from <title> element
+ background: Background; # background specification
+ backgrounditem: ref Item; # Image Item for doc background image, or nil
+ text: int; # doc foreground (text) color
+ link: int; # unvisited hyperlink color
+ vlink: int; # visited hyperlink color
+ alink: int; # highlighting hyperlink color
+ target: string; # target frame default
+ refresh: string; # content of <http-equiv=Refresh ...>
+ chset: string; # charset encoding
+ lastModified: string; # last-modified time
+ scripttype: int; # CU->TextJavascript, etc.
+ hasscripts: int; # true if scripts used
+ events: list of Lex->Attr; # event handlers
+ evmask: int;
+ kidinfo: ref Kidinfo; # if a frameset
+ frameid: int; # id of document frame
+
+ # info needed to respond to user actions
+ anchors: list of ref Anchor; # info about all href anchors
+ dests: list of ref DestAnchor; # info about all destination anchors
+ forms: list of ref Form; # info about all forms
+ tables: list of ref Table; # info about all tables
+ maps: list of ref Map; # info about all maps
+ images: list of ref Item; # all image items in doc
+
+ new: fn() : ref Docinfo;
+ reset: fn(f: self ref Docinfo);
+};
+
+# Parsing stuff
+
+# Parsing state
+Pstate: adt {
+ skipping: int; # true when we shouldn't add items
+ skipwhite: int; # true when we should strip leading space
+ curfont: int; # font index for current font
+ curfg: int; # current foreground color
+ curbg: Background; # current background
+ curvoff: int; # current baseline offset
+ curul: byte; # current underline/strike state
+ curjust: byte; # current justify state
+ curanchor: int; # current (href) anchor id (if in one), or 0
+ curstate: int; # current value of item state
+ literal: int; # current literal state
+ inpar: int; # true when in a paragraph-like construct
+ adjsize: int; # current font size adjustment
+ items: ref Item; # dummy head of item list we're building
+ lastit: ref Item; # tail of item list we're building
+ prelastit: ref Item; # item before lastit
+ fntstylestk: list of int; # style stack
+ fntsizestk: list of int; # size stack
+ fgstk: list of int; # text color stack
+ ulstk: list of byte; # underline stack
+ voffstk: list of int; # vertical offset stack
+ listtypestk: list of byte; # list type stack
+ listcntstk: list of int; # list counter stack
+ juststk: list of byte; # justification stack
+ hangstk: list of int; # hanging stack
+
+ new: fn() : ref Pstate;
+};
+
+
+# A source of Items (resulting of HTML parsing).
+# After calling new with a ByteSource (which is past 'gethdr' stage),
+# call getitems repeatedly until get nil. Errors are signalled by exceptions.
+# Possible exceptions raised:
+# EXInternal (start, getitems)
+# exGeterror (getitems)
+# exAbort (getitems)
+ItemSource: adt
+{
+ ts: ref Lex->TokenSource; # source of tokens
+ mtype: int; # media type (TextHtml or TextPlain)
+ doc: ref Docinfo; # global information about page
+ frame: ref Layout->Frame; # containing frame
+ psstk: list of ref Pstate; # local parsing state stack
+ nforms: int; # used to make formids
+ ntables: int; # used to make tableids
+ nanchors: int; # used to make anchor ids
+ nframes: int; # used to make names for frames
+ curform: ref Form; # current form (if in one)
+ curmap: ref Map; # current map (if in one)
+ tabstk: list of ref Table; # table stack
+ kidstk: list of ref Kidinfo; # kidinfo stack
+ reqdurl: ref Url->Parsedurl;
+ reqddata: array of byte;
+ toks: array of ref Lex->Token;
+
+ new: fn(bs: ref CharonUtils->ByteSource, f: ref Layout->Frame, mtype: int) : ref ItemSource;
+ getitems: fn(is: self ref ItemSource) : ref Item;
+};
+
+init: fn(cu: CharonUtils);
+trim_white: fn(data: string): string;
+};
diff --git a/appl/charon/charon.b b/appl/charon/charon.b
new file mode 100644
index 00000000..88ddadbe
--- /dev/null
+++ b/appl/charon/charon.b
@@ -0,0 +1,2171 @@
+implement Charon;
+
+include "common.m";
+include "debug.m";
+
+sys: Sys;
+CU: CharonUtils;
+ ByteSource, MaskedImage, CImage, ImageCache, ReqInfo, Header,
+ ResourceState, config, max, min, X: import CU;
+
+D: Draw;
+ Point, Rect, Font, Image, Display, Screen: import D;
+
+S: String;
+U: Url;
+ Parsedurl: import U;
+L: Layout;
+ Frame, Loc, Control: import L;
+I: Img;
+ ImageSource: import I;
+
+B: Build;
+ Item, Dimen: import B;
+
+E: Events;
+ Event: import E;
+
+J: Script;
+
+G: Gui;
+
+C : Ctype;
+
+include "sh.m";
+
+# package up info related to a navigation command
+GoSpec: adt {
+ kind: int; # GoNormal, etc.
+ url: ref Parsedurl; # destination (absolute)
+ meth: int; # HGet or HPost
+ body: string; # used if HPost
+ target: string; # name of target frame
+ auth: string; # optional auth info
+ histnode: ref HistNode; # if kind is GoHistnode
+
+ newget: fn(kind: int, url: ref Parsedurl, target: string) : ref GoSpec;
+ newpost: fn(url: ref Parsedurl, body, target: string) : ref GoSpec;
+ newspecial: fn(kind: int, histnode: ref HistNode) : ref GoSpec;
+ equal: fn(a: self ref GoSpec, b: ref GoSpec) : int;
+};
+
+GoNormal, GoReplace, GoLink, GoHistnode, GoSettext: con iota;
+
+# Information about a set of frames making up the screen
+DocConfig: adt {
+ framename: string; # nonempty, except possibly for topconfig
+ title: string;
+ initconfig: int; # true unless this is a frameset and some subframe changed
+ gospec: cyclic ref GoSpec;
+ # TODO: add current y pos and form field values
+
+ equal: fn(a: self ref DocConfig, b: ref DocConfig) : int;
+ equalarray: fn(a1: array of ref DocConfig, a2: array of ref DocConfig) : int;
+};
+
+# Information about a particular screen configuration
+HistNode: adt {
+ topconfig: cyclic ref DocConfig; # config of top (whole doc, or frameset root)
+ kidconfigs: cyclic array of ref DocConfig; # configs for kid frames (if a frameset)
+ preds: cyclic list of ref HistNode; # edges in (via normal navigation)
+ succs: cyclic list of ref HistNode; # edges out (via normal navigation)
+ findid : int;
+ findchain : cyclic list of ref HistNode;
+
+ addedge: fn(a: self ref HistNode, b: ref HistNode, atob: int);
+ copy: fn(a: self ref HistNode) : ref HistNode;
+};
+
+History: adt {
+ h: array of ref HistNode; # all visited HistNodes, in LRU order
+ n: int; # h[0:n] is valid part of h
+ findid : int;
+
+ add: fn(h: self ref History, f: ref Frame, g: ref GoSpec, navkind: int);
+ update: fn(h: self ref History, f: ref Frame);
+ find: fn(h: self ref History, k: int) : ref HistNode;
+ print: fn(h: self ref History);
+ histinfo: fn(h: self ref History) : (int, string, string, string);
+ findurl: fn(h: self ref History, s: string) : ref HistNode;
+};
+
+# Authentication strings
+AuthInfo: adt {
+ realm: string;
+ credentials: string;
+};
+
+auths: list of ref AuthInfo = nil;
+
+history : ref History;
+keyfocus: ref Control;
+mouseover: ref B->Anchor;
+mouseoverfr: ref Frame;
+grabctl: ref Control;
+popupctl: ref Control;
+
+SP : con 8; # a spacer for between controls
+SP2 : con 4; # half of SP
+SP3 : con 2;
+pgrp := 0;
+gopgrp := 0;
+dbg := 0;
+warn := 0;
+dbgres := 0;
+doscripts := 0;
+
+top, curframe: ref Frame;
+mainwin: ref Image;
+p0 := Point(0,0);
+
+context: ref Draw->Context;
+opener: chan of string;
+
+sendopener(s: string)
+{
+ if(opener != nil){
+ alt{
+ opener <- = s =>
+ ;
+ * =>
+ ;
+ }
+ }
+}
+
+hasopener(): int
+{
+ return opener != nil;
+}
+
+init(ctxt: ref Draw->Context, argl: list of string)
+{
+ chctxt := ref Context(ctxt, argl, nil, nil, nil);
+ initc(chctxt);
+}
+
+initc(ctxt: ref Context)
+{
+ sys = load Sys Sys->PATH;
+ if (ctxt == nil)
+ fatalerror("bad args\n");
+ opener = ctxt.c;
+ argl := ctxt.args;
+ context = ctxt.ctxt;
+
+ (retval, nil) := sys->stat("/net/tcp");
+ if(retval < 0)
+ sys->bind("#I", "/net", sys->MREPL);
+ (retval, nil) = sys->stat("/net/cs");
+ if(retval < 0)
+ startcs();
+
+ pgrp = sys->pctl(sys->NEWPGRP, nil);
+ CU = load CharonUtils CharonUtils->PATH;
+ if(CU == nil)
+ fatalerror(sys->sprint("Couldn't load %s\n", CharonUtils->PATH));
+
+ ech := chan of ref Event;
+ errpath := CU->init(load Charon SELF, CU, argl, ech, ctxt.cksrv, ctxt.ckclient);
+ if(errpath != "")
+ fatalerror(sys->sprint("Couldn't load %s\n", errpath));
+ ctxt = nil;
+
+ sys = load Sys Sys->PATH;
+ D = load Draw Draw->PATH;
+ S = load String String->PATH;
+ U = load Url Url->PATH;
+ if (U != nil)
+ U->init();
+ E = CU->E;
+ L = CU->L;
+ I = CU->I;
+ B = CU->B;
+ J = CU->J;
+ G = CU->G;
+ C = CU->C;
+
+ dbg = int (CU->config).dbg['d'];
+ warn = dbg || int (CU->config).dbg['w'];
+ dbgres = int (CU->config).dbg['r'];
+ doscripts = (CU->config).doscripts && J != nil;
+ if(dbg && (CU->config).dbgfile != "") {
+ dfile := sys->create((CU->config).dbgfile, sys->OWRITE, 8r666);
+ if(dfile != nil) {
+ sys->dup(dfile.fd, 1);
+ }
+ }
+ curres := ResourceState.cur();
+ newres: ResourceState;
+ if(dbgres) {
+ (CU->startres).print("starting resources");
+ curres = ResourceState.cur();
+ }
+
+ context = G->init(context, CU);
+ if(dbgres) {
+ newres = ResourceState.cur();
+ newres.since(curres).print("difference after G->init (made screen windows)");
+ curres = newres;
+ }
+ mainwin = G->mainwin;
+
+ # L->init() was deferred until after G was inited
+ L->init(CU);
+ if(dbgres) {
+ newres = ResourceState.cur();
+ newres.since(curres).print("difference after L->init (loaded Build, Lex)");
+ curres = newres;
+ }
+ (CU->imcache).init();
+ if(dbgres) {
+ newres = ResourceState.cur();
+ newres.since(curres).print("difference after (CU->imcache).init");
+ curres = newres;
+ }
+ start();
+ if(J != nil)
+ J->frametreechanged(top);
+ startpage := config.starturl;
+ g := GoSpec.newget(GoNormal, CU->makeabsurl(startpage), "_top");
+ if(dbgres) {
+ newres = ResourceState.cur();
+ newres.since(curres).print("difference after initial configure");
+ curres = newres;
+ }
+ spawn plumbwatch();
+ spawn go(g);
+
+ sendopener("B");
+
+Forloop:
+ for(;;) {
+ ev := <- ech;
+
+ if(dbg > 1) {
+ pick de := ev {
+ Emouse =>
+ if(dbg > 2 || de.mtype != E->Mmove)
+ sys->print("%s\n", ev.tostring());
+ * =>
+ sys->print("%s\n", ev.tostring());
+ }
+ }
+ pick e := ev {
+ Ekey =>
+ g = nil;
+ case e.keychar {
+ E->Kdown =>
+ curframe.yscroll(L->CAscrollpage, -1);
+ E->Kup =>
+ curframe.yscroll(L->CAscrollpage, 1);
+ E->Khome =>
+ curframe.yscroll(L->CAscrollpage, -10000);
+ E->Kend =>
+ curframe.yscroll(L->CAscrollpage, 10000);
+ E->Kaup =>
+ curframe.yscroll(L->CAscrollline, -1);
+ E->Kadown =>
+ curframe.yscroll(L->CAscrollline, 1);
+ * =>
+ handlekey(e);
+ }
+ Emouse =>
+ g = handlemouse(e);
+ Ereshape =>
+ mainwin = G->mainwin;
+ redraw(1);
+ curframe = top;
+ g = GoSpec.newspecial(GoHistnode, history.find(0));
+ Equit =>
+ break Forloop;
+ Estop =>
+ if(gopgrp != 0)
+ stop();
+ g = nil;
+ Eback =>
+ g = GoSpec.newspecial(GoHistnode, history.find(-1));
+ Efwd =>
+ g = GoSpec.newspecial(GoHistnode, history.find(1));
+ Eform =>
+ formaction(e.frameid, e.formid, e.ftype, 0);
+ g = nil;
+ Eformfield =>
+ formfieldaction(e.frameid, e.formid, e.fieldid, e.fftype);
+ g = nil;
+ Ego =>
+ case e.gtype {
+ E->EGnormal =>
+ url := CU->makeabsurl(e.url);
+ if (url != nil)
+ g = GoSpec.newget(GoNormal,url, e.target);
+ else
+ g = nil;
+ E->EGreplace =>
+ g = GoSpec.newget(GoReplace, U->parse(e.url), e.target);
+ E->EGreload =>
+ g = GoSpec.newspecial(GoHistnode, history.find(0));
+ E->EGforward =>
+ g = GoSpec.newspecial(GoHistnode, history.find(1));
+ E->EGback =>
+ g = GoSpec.newspecial(GoHistnode, history.find(-1));
+ E->EGdelta =>
+ g = GoSpec.newspecial(GoHistnode, history.find(e.delta));
+ E->EGlocation =>
+ g = GoSpec.newspecial(GoHistnode, history.findurl(e.url));
+ }
+ Esubmit =>
+ if(e.subkind == CU->HGet)
+ g = GoSpec.newget(GoNormal, e.action, e.target);
+ else {
+ g = GoSpec.newpost(e.action, e.data, e.target);
+ }
+ Escroll =>
+ f := findframe(top, e.frameid);
+ if (f != nil)
+ f.scrollabs(e.pt);
+ g = nil;
+ Escrollr =>
+ f := findframe(top, e.frameid);
+ if (f != nil)
+ f.scrollrel(e.pt);
+ g = nil;
+ Esettext =>
+ f := findframe(top, e.frameid);
+ if (f != nil)
+ g = ref GoSpec (GoSettext, e.url, 0, e.text, f.name, "", nil);
+ Elostfocus =>
+ setfocus(nil);
+ g = nil;
+ Edismisspopup =>
+ if (popupctl != nil)
+ setfocus(popupctl.donepopup());
+ popupctl = nil;
+ grabctl = nil;
+ }
+
+ if (g == nil)
+ continue;
+
+ if (g.kind != GoSettext) {
+ if (g.url != nil) {
+ scheme := g.url.scheme;
+ if (scheme == "javascript") {
+ if (doscripts)
+ spawn dojsurl(g);
+ continue;
+ }
+ if (!CU->schemeok(scheme)) {
+ url := g.url.tostring();
+ if (plumbsend(url, "url") == -1)
+ G->setstatus(X("bad URL", "gui")+": "+url);
+ continue;
+ }
+ }
+ }
+
+ if(gopgrp != 0)
+ stop();
+ spawn go(g);
+ }
+ finish();
+}
+
+mkprog(c: Command, ctxt: ref Draw->Context, args: list of string)
+{
+ sys->pctl(Sys->NEWPGRP|Sys->NEWFD, list of {0, 1, 2});
+ c->init(ctxt, args);
+}
+
+start()
+{
+ top = Frame.new();
+ curframe = top;
+ history = ref History(nil, 0, 0);
+
+ keyfocus = nil;
+ mouseover = nil;
+ redraw(1);
+}
+
+redraw(resized: int)
+{
+ im := mainwin;
+ if(resized) {
+# top.r = im.r.inset(2*L->ReliefBd);
+ top.r = im.r;
+ top.cim = mainwin;
+ top.reset();
+ (CU->imcache).resetlimits();
+ }
+ im.clipr = im.r;
+# L->drawrelief(im, top.r.inset(-L->ReliefBd), L->ReliefRaised);
+# L->drawrelief(im, top.r, L->ReliefSunk);
+ L->drawfill(im, top.r, CU->White);
+ G->flush(im.r);
+# im.clipr = top.r;
+}
+
+# Return a Loc representing a control in the frame f
+frameloc(c: ref Control, f: ref Frame) : ref Loc
+{
+ loc := Loc.new();
+ loc.add(L->LEframe, f.r.min);
+ loc.le[loc.n-1].frame = f;
+ if (c != nil) {
+ loc.add(L->LEcontrol, c.r.min);
+ loc.le[loc.n-1].control = c;
+ }
+ return loc;
+}
+
+resetkeyfocus(f: ref Frame)
+{
+ # determine if focus is in frame f or one of its sub-frames
+ if (keyfocus == nil)
+ return;
+
+ for (focusf := keyfocus.f; focusf != nil; focusf = focusf.parent) {
+ if (focusf == f) {
+ keyfocus = nil;
+ break;
+ }
+ }
+ # current focus not in frameset being modified - leave as is
+}
+
+ctlmouse(e: ref Event.Emouse, ctl, grab: ref Control): ref Control
+{
+ ev := E->SEnone;
+ (action, newgrab) := ctl.domouse(e.p, e.mtype, grab);
+ case (action) {
+ L->CAbuttonpush =>
+ if(doscripts && ctl.ff != nil && ctl.ff.evmask)
+ ev = E->SEonclick;
+ else
+ pushaction(ctl, e.p.sub(ctl.r.min));
+ L->CAkeyfocus =>
+ setfocus(ctl);
+ L->CAchanged =>
+ # Select Formfield - selection has changed
+ ev = E->SEonchange;
+ L->CAselected =>
+ # text input Formfield - text selection has changed
+ ev = E->SEonselect;
+ L->CAdopopup =>
+ popupctl = ctl.dopopup();
+ if (popupctl != nil)
+ setfocus(popupctl);
+ L->CAdonepopup =>
+ setfocus(ctl.donepopup());
+ ev = E->SEonchange;
+ popupctl = nil;
+ }
+ if (doscripts && ctl.ff != nil && (ctl.ff.evmask & ev)) {
+ se := ref E->ScriptEvent(ev, ctl.f.id, ctl.ff.form.formid, ctl.ff.fieldid,
+ -1, -1, e.p.x, e.p.y, 1, nil, nil, 0);
+ J->jevchan <-= se;
+ }
+ return newgrab;
+}
+
+mainwinmouse(e: ref Event.Emouse) : (ref GoSpec, ref Control)
+{
+ p := e.p;
+ g : ref GoSpec;
+ ctl : ref Control;
+ newgrab : ref Control;
+ domouseout := 0;
+ loc : ref Loc;
+ if(mouseover != nil)
+ domouseout = 1;
+
+ loc = top.find(p, nil);
+ if(loc != nil) {
+ if(dbg > 1)
+ loc.print("mouse loc");
+ f := loc.lastframe();
+ hasscripts := f.doc.hasscripts;
+ if(e.mtype != E->Mmove)
+ curframe = f;
+ n1 := loc.n-1;
+ case loc.le[n1].kind {
+ L->LEitem =>
+ it := loc.le[n1].item;
+ if (it.anchorid < 0)
+ break;
+
+ a : ref Build->Anchor = nil;
+ for(al := f.doc.anchors; al != nil; al = tl al) {
+ a = hd al;
+ if(a.index == it.anchorid)
+ break;
+ }
+ if (al == nil)
+ break;
+
+ if(dbg > 1)
+ sys->print("in anchor %d, href=%s\n", a.index, a.href.tostring());
+ if(doscripts && a.evmask) {
+ if(a == mouseover) {
+ domouseout = 0; # still over same anchor
+ } else if(e.mtype == E->Mmove) {
+ if(domouseout) {
+ if(mouseover.evmask & E->SEonmouseout) {
+ se := ref E->ScriptEvent(E->SEonmouseout, mouseoverfr.id, -1, -1, mouseover.index, -1, 0, 0, 0, nil, nil, 0);
+ J->jevchan <-= se;
+ }
+ domouseout = 0;
+ }
+ mouseover = a;
+ mouseoverfr = f;
+ if(a.evmask & E->SEonmouseover) {
+ se := ref E->ScriptEvent(E->SEonmouseover, f.id, -1, -1, a.index, -1, e.p.x, e.p.y, 0, nil, nil, 0);
+ J->jevchan <-= se;
+ }
+ }
+ if (e.mtype == E->Mlbuttonup || e.mtype == E->Mldrop) {
+ if(a.evmask & E->SEonclick) {
+ se := ref E->ScriptEvent(E->SEonclick, f.id, -1, -1, a.index, -1, 0, 0, 0, nil, nil, 0);
+ J->jevchan <-= se;
+ break;
+ }
+ ctl = nil;
+ }
+ }
+ if(e.mtype == E->Mlbuttonup || e.mtype == E->Mldrop) {
+ g = anchorgospec(it, a, loc.pos);
+ if (g == nil)
+ break;
+ } else if(e.mtype == E->Mmbuttonup) {
+ g = anchorgospec(it, a, loc.pos);
+ if (g == nil)
+ break;
+ url := g.url.tostring();
+ G->setstatus(url);
+ G->snarfput(url);
+ g = nil;
+ }
+ L->LEcontrol =>
+ ctl = loc.le[n1].control;
+ }
+ }
+ if (ctl != nil)
+ newgrab = ctlmouse(e, ctl, nil);
+ if(newgrab == nil && domouseout && doscripts) {
+ if(mouseover.evmask & E->SEonmouseout) {
+ se := ref E->ScriptEvent(E->SEonmouseout,
+ mouseoverfr.id, -1, -1, mouseover.index, -1, 0, 0, 0, nil, nil, 0);
+ J->jevchan <-= se;
+ }
+ mouseoverfr = nil;
+ mouseover = nil;
+ }
+ return (g, newgrab);
+}
+
+dojsurl(g : ref GoSpec)
+{
+ f := curframe;
+ case g.target {
+ "_top" =>
+ f = top;
+ "_self" =>
+ ; # curframe is already OK
+ "_parent" =>
+ if(f.parent != nil)
+ f = f.parent;
+ "_blank" =>
+ f = top; # we don't create new browsers...
+ * =>
+ # this is recommended "current practice"
+ f = findnamedframe(f, g.target);
+ if(f == nil) {
+ f = findnamedframe(top, g.target);
+ if(f == nil)
+ f = top;
+ }
+ }
+
+ jev := ref E->ScriptEvent (E->SEscript, f.id, -1, -1, -1, -1, 0, 0, 0, g.url.path, chan of string, 0);
+ J->jevchan <-= jev;
+ v := <- jev.reply;
+ if (v != nil) {
+ ev := ref Event.Esettext(f.id, g.url, v);
+ E->evchan <-= ev;
+ }
+}
+
+# If mouse event results in command to navigate somewhere else,
+# return a GoSpec ref, else nil.
+handlemouse(e: ref Event.Emouse): ref GoSpec
+{
+ g: ref GoSpec;
+ ctl := grabctl;
+ if (popupctl != nil)
+ ctl = popupctl;
+ if (ctl != nil)
+ grabctl = ctlmouse(e, ctl, grabctl);
+ else if (e.p.in(mainwin.r))
+ (g, grabctl) = mainwinmouse(e);
+ return g;
+}
+
+setfocus(newc : ref Control)
+{
+ newf, oldf: ref Frame;
+ if (newc != nil)
+ newf = newc.f;
+
+ oldc := keyfocus;
+ if (oldc != nil)
+ oldf = oldc.f;
+
+ if (oldc != nil && oldc != newc)
+ oldc.losefocus(1);
+ if (oldf != nil && oldf != newf)
+ oldf.focus(0, 1);
+ if (newf != nil && newf != oldf)
+ newf.focus(1,1);
+ if (newc != nil && newc != oldc)
+ newc.gainfocus(1);
+ keyfocus = newc;
+}
+
+handlekey(e: ref Event.Ekey)
+{
+ c := keyfocus;
+ if (c == nil)
+ return;
+
+ pick ce := c {
+ Centry =>
+ case c.dokey(e.keychar) {
+ L->CAreturnkey =>
+ if(c.ff != nil) {
+ spawn form_submit(c.f, c.ff.form, p0, c, 1);
+ return;
+ }
+ L->CAtabkey =>
+ # if control in a form - move focus to next focus-able control
+ if (c.ff != nil) {
+ found := 0;
+ form := c.ff.form;
+ nextff : ref B->Formfield;
+ for (ffl := form.fields; ffl != nil; ffl = tl ffl) {
+ ff := hd ffl;
+ if (ff == c.ff) {
+ found = 1;
+ continue;
+ }
+ if (ff.ftype == B->Ftext || ff.ftype == B->Fpassword) {
+ if (nextff == nil || found)
+ nextff = ff;
+ if (found)
+ break;
+ }
+ }
+ if (nextff != nil)
+ formfield_focus(c.f, nextff);
+ }
+ }
+ }
+ return;
+}
+
+fileexist(file: string) :int
+{
+ fd := sys->open(file, sys->OREAD);
+ if (fd == nil)
+ return 0;
+ else
+ return 1;
+}
+
+go(g: ref GoSpec)
+{
+ gopgrp = sys->pctl(sys->NEWPGRP, nil);
+ spawn goproc(g);
+
+ # got to make netget the thread with the gopgrp thread,
+ # since it runs until killed, and killing a pgrp needs an active
+ # thread
+ CU->netget();
+}
+
+goproc(g: ref GoSpec)
+{
+ origkind := g.kind;
+ hn : ref HistNode = nil;
+ doctext := "";
+ case origkind {
+ GoNormal or
+ GoReplace or
+ GoSettext =>
+ ;
+ GoHistnode =>
+ hn = g.histnode;
+ if(hn == nil)
+ return;
+ g = hn.topconfig.gospec;
+ }
+ case g.target {
+ "_top" =>
+ curframe = top;
+ "_self" =>
+ ; # curframe is already OK
+ "_parent" =>
+ if(curframe.parent != nil)
+ curframe = curframe.parent;
+ "_blank" =>
+ curframe = top; # we don't create new browsers...
+ * =>
+ # this is recommended "current practice"
+ curframe = findnamedframe(curframe, g.target);
+ if(curframe == nil) {
+ curframe = findnamedframe(top, g.target);
+ if(curframe == nil)
+ curframe = top;
+ }
+ }
+
+ f := curframe;
+ if(dbg) {
+ sys->print("\n\nGO TO %s\n", g.url.tostring());
+ if(g.target != "_top")
+ sys->print("target frame name=%s\n", f.name);
+ }
+ G->progress <-= (-1, G->Pstart, 0, "");
+ err := "";
+ status := "Done";
+
+ if((origkind == GoNormal || origkind == GoReplace || origkind == GoLink) && g.url.frag != ""
+ && f.doc != nil && f.doc.src != nil && CU->urlequal(g.url, f.doc.src))
+ go_local(f, g.url.frag);
+ else {
+ if (g.kind == GoSettext)
+ settext(g, f, g.body);
+ else
+ err = get(g, f, origkind, hn);
+
+ if(doscripts && J->defaultStatus != "")
+ status = J->defaultStatus;
+ }
+ if(err != nil) {
+ status = err;
+ G->progress <-= (-1, G->Perr, 100, err);
+ } else
+ G->progress <-= (-1, G->Pdone, 0, nil);
+
+ G->setstatus(status);
+ checkrefresh(f);
+}
+
+settext(g : ref GoSpec, f : ref Frame, text : string) : string
+{
+ sdest := g.url.tostring();
+ G->setstatus(X("Fetching", "gui") + " " + sdest);
+ bs := CU->stringreq(text);
+ G->seturl(sdest);
+ history.add(f, g, GoNormal);
+ resetkeyfocus(f);
+ L->layout(f, bs, 0);
+ if (J != nil)
+ J->framedone(f, f.doc.hasscripts);
+ history.update(f);
+ error := "";
+ if(f.kids != nil) {
+ if(J != nil)
+ J->frametreechanged(f);
+ nkids := len f.kids;
+ kdone := chan of (ref Frame, string);
+ for(kl := f.kids; kl != nil; kl = tl kl) {
+ k := hd kl;
+ if(k.src != nil) {
+ gs := GoSpec.newget(GoNormal, k.src, "_self");
+ if(dbg)
+ sys->print("get child frame %s\n", gs.url.tostring());
+ spawn getproc(gs, k, GoNormal, nil, kdone);
+ }
+ }
+ while (nkids--) {
+ (k, e) := <- kdone;
+ if (error != nil)
+ error = e;
+ checkrefresh(k);
+ }
+ }
+
+ if (J != nil) {
+#this code should be split off as it is duplicated from get()
+ # at this point all sub-frames and images have been loaded
+ # Optimise this! so as only do it if a doc in the frameset
+ # has script/event code
+ J->jevchan <-= ref E->ScriptEvent(E->SEonload, f.id, -1, -1, -1, -1, -1, -1, -1, nil, nil, 0);
+ if (doscripts && f.doc.hasscripts) {
+ for(itl := f.doc.images; itl != nil; itl = tl itl) {
+ it := hd itl;
+ if(it.genattr == nil || !it.genattr.evmask)
+ continue;
+ ev := E->SEnone;
+ pick im := it {
+ Iimage =>
+ case im.ci.complete {
+ # correct to equate these two ?
+ Img->Mimnone or
+ Img->Mimerror =>
+ ev = E->SEonerror;
+ Img->Mimdone =>
+ ev = E->SEonload;
+ }
+ if(im.genattr.evmask & ev)
+ J->jevchan <-= ref E->ScriptEvent(ev, f.id, -1, -1, -1, im.imageid, -1, -1, -1, nil, nil, 0);
+ }
+ }
+ }
+ }
+ return error;
+}
+
+getproc(g: ref GoSpec, f: ref Frame, origkind: int, hn: ref HistNode, done : chan of (ref Frame, string))
+{
+ done <-= (f, get(g, f, origkind, hn));
+}
+
+get(g: ref GoSpec, f: ref Frame, origkind: int, hn: ref HistNode) : string
+{
+ curres, newres: ResourceState;
+ if(dbgres) {
+ (CU->imcache).clear();
+ curres = ResourceState.cur();
+ }
+ sdest := g.url.tostring();
+ G->setstatus(X("Fetching", "gui") + " " + sdest);
+ bsmain : ref ByteSource;
+ hdr : ref Header;
+ ri := ref ReqInfo(g.url, g.meth, array of byte g.body, g.auth, g.target);
+ authtried := 0;
+ realm := "";
+ auth := "";
+ error := "";
+ for(nredirs := 0; ; nredirs++) {
+ bsmain = CU->startreq(ri);
+ error = bsmain.err;
+ if(error != "") {
+ CU->freebs(bsmain);
+ return error;
+ }
+ CU->waitreq(bsmain::nil);
+ error = bsmain.err;
+ if(error != "") {
+ CU->freebs(bsmain);
+ return error;
+ }
+ hdr = bsmain.hdr;
+ (use, e, challenge, newurl) := CU->hdraction(bsmain, 1, nredirs);
+ error = e;
+ if(challenge != nil) {
+ if(authtried) {
+ # we already tried once; give up
+ error = "Need authorization";
+ use = 1;
+ }
+ else {
+ (realm, auth) = getauth(challenge);
+ if(auth != "") {
+ ri.auth = auth;
+ authtried = 1;
+ CU->freebs(bsmain);
+ continue;
+ }
+ else {
+ error = "Need authorization";
+ use = 1;
+ }
+ }
+ }
+ if (error == nil) {
+ if (hdr.code != CU->HCOk)
+ error = CU->hcphrase(hdr.code);
+ if(authtried) {
+ # it succeeded; add to auths list so don't have to ask again
+ auths = ref AuthInfo(realm, auth) :: auths;
+ }
+ }
+ if(newurl != nil) {
+ ri.url = newurl;
+ # some sites (e.g., amazon.com) assume that POST turns into
+ # GET on redirect (maybe this is just http 1.0?)
+ ri.method = CU->HGet;
+ CU->freebs(bsmain);
+ continue;
+ }
+ if(use == 0) {
+ CU->freebs(bsmain);
+ return error;
+ }
+ break;
+ }
+ if(dbgres > 1) {
+ newres = ResourceState.cur();
+ newres.since(curres).print("resources to get header");
+ curres = newres;
+ }
+ if(hdr.mtype == CU->TextHtml || hdr.mtype == CU->TextPlain ||
+ I->supported(hdr.mtype)) {
+ G->seturl(sdest);
+ history.add(f, g, origkind);
+ resetkeyfocus(f);
+ srcdata := L->layout(f, bsmain, origkind == GoLink);
+ if (J != nil)
+ J->framedone(f, f.doc.hasscripts);
+ history.update(f);
+ if(dbgres > 1) {
+ newres = ResourceState.cur();
+ newres.since(curres).print("resources to get page and do layout");
+ curres = newres;
+ }
+ if(f.kids != nil) {
+ if(J != nil)
+ J->frametreechanged(f);
+ i := 0;
+ nkids := len f.kids;
+ kdone := chan of (ref Frame, string);
+ for(kl := f.kids; kl != nil; kl = tl kl) {
+ k := hd kl;
+ if(k.src != nil) {
+ if(hn != nil)
+ gs := hn.kidconfigs[i].gospec;
+ else
+ gs = GoSpec.newget(GoNormal, k.src, "_self");
+ if(dbg)
+ sys->print("get child frame %s\n", gs.url.tostring());
+ gokind := GoLink;
+ if (origkind != GoLink)
+ gokind = GoNormal;
+ spawn getproc(gs, k, gokind, nil, kdone);
+ }
+ i++;
+ }
+ while (nkids--) {
+ (k, err) := <- kdone;
+ if (error == nil)
+ # we currently only capture the first error
+ # as we only have one palce to report it
+ error = err;
+ checkrefresh(k);
+ }
+ }
+
+ if (J != nil) {
+ # at this point all sub-frames and images have been loaded
+ J->jevchan <-= ref E->ScriptEvent(E->SEonload, f.id, -1, -1, -1, -1, -1, -1, -1, nil, nil, 0);
+ if (doscripts && f.doc.hasscripts) {
+ for(itl := f.doc.images; itl != nil; itl = tl itl) {
+ it := hd itl;
+ if(it.genattr == nil || !it.genattr.evmask)
+ continue;
+ ev := E->SEnone;
+ pick im := it {
+ Iimage =>
+ case im.ci.complete {
+ # correct to equate these two ?
+ Img->Mimnone or
+ Img->Mimerror =>
+ ev = E->SEonerror;
+ Img->Mimdone =>
+ ev = E->SEonload;
+ }
+ if(im.genattr.evmask & ev)
+ J->jevchan <-= ref E->ScriptEvent(ev, f.id, -1, -1, -1, im.imageid, -1, -1, -1, nil, nil, 0);
+ }
+ }
+ }
+ }
+
+ if(g.url.frag != "")
+ go_local(f, g.url.frag);
+ }
+ else {
+ error = X("Unsupported media type", "gui")+ " "+CU->mnames[hdr.mtype];
+ # Optionally put a save-as dialog up here.
+ if((CU->config).offersave)
+ dosaveas(bsmain);
+ CU->freebs(bsmain);
+ }
+ if(dbgres == 1) {
+ newres = ResourceState.cur();
+ newres.since(curres).print("resources to do page");
+ curres = newres;
+ }
+ return error;
+}
+
+# Scroll frame f so that destination hyperlink loc is at top of view
+go_local(f: ref Frame, loc: string)
+{
+ if(dbg)
+ sys->print("go to local destination %s\n", loc);
+ for(ld := f.doc.dests; ld != nil; ld = tl ld) {
+ d := hd ld;
+ if(d.name == loc) {
+ dloc := f.find(p0, d.item);
+ if(dloc == nil) {
+ if(warn)
+ sys->print("couldn't find item for destination anchor %s\n", loc);
+ return;
+ }
+ p := f.sptolp(dloc.le[dloc.n-1].pos);
+ f.yscroll(L->CAscrollabs, p.y);
+ return;
+ }
+ }
+ # special location names...
+ l := S->tolower(loc);
+ if(l == "top" || l == "home"){
+ f.yscroll(L->CAscrollabs, 0);
+ return;
+ }
+ if(l == "end" || l=="bottom"){
+ f.yscroll(L->CAscrollabs, f.totalr.max.y);
+ return;
+ }
+ if(warn)
+ sys->print("couldn't find destination anchor %s\n", loc);
+}
+
+stripwhite(s: string) : string
+{
+ j := 0;
+ n := len s;
+ for(i := 0; i < n; i++) {
+ c := s[i];
+ if(c < C->NCTYPE && C->ctype[c]==C->W)
+ continue;
+ s[j++] = c;
+ }
+ if(j < n)
+ s = s[0:j];
+ return s;
+}
+
+# If refresh has been set in f (i.e., client pull),
+# pause the appropriate amount of time and then go to new place
+checkrefresh(f: ref Frame)
+{
+ if(f.doc != nil && f.doc.refresh != "") {
+ seconds := 0;
+ url : ref Parsedurl = nil;
+ refresh := stripwhite(f.doc.refresh);
+ (n, l) := sys->tokenize(refresh, ";");
+ if(n > 0) {
+ seconds = int hd l;
+ if(n > 1) {
+ s := hd tl l;
+ if(len s > 4 && S->tolower(s[0:4]) == "url=") {
+ url = U->mkabs(U->parse(s[4:]), f.doc.base);
+ }
+ }
+ }
+ spawn dorefresh(f, seconds, url);
+ }
+}
+
+dorefresh(f: ref Frame, seconds: int, url: ref Parsedurl)
+{
+ sys->sleep(seconds * 1000);
+ e : ref Event;
+ if(url == nil)
+ e = ref Event.Ego(nil, f.name, 0, E->EGreload);
+ else
+ e = ref Event.Ego(url.tostring(), f.name, 0, E->EGnormal);
+ E->evchan <-= e;
+}
+
+# Do depth first search from f, looking for frame with given name.
+findnamedframe(f: ref Frame, name: string) : ref Frame
+{
+ if(f.name == name)
+ return f;
+ for(l := f.kids; l != nil; l = tl l) {
+ k := hd l;
+ a := findnamedframe(k, name);
+ if(a != nil)
+ return a;
+ }
+ return nil;
+}
+
+# Similar, but look for frame id, starting from f
+findframe(f: ref Frame, id: int) : ref Frame
+{
+ if(f.id == id)
+ return f;
+ for(l := f.kids; l != nil; l = tl l) {
+ k := hd l;
+ a := findframe(k, id);
+ if(a != nil)
+ return a;
+ }
+ return nil;
+}
+
+# Return Gospec resulting from button up in anchor a, at offset pos inside item it.
+anchorgospec(it: ref Item, a: ref B->Anchor, p: Point) : ref GoSpec
+{
+ g : ref GoSpec;
+ u := a.href;
+ target := a.target;
+ pick i := it {
+ Iimage =>
+ ci := i.ci;
+ if(ci.mims != nil) {
+ if(i.map != nil) {
+ (u, target) = findhit(i.map, p, ci.width, ci.height);
+ }
+ else if(u != nil && u.scheme != "javascript" && (it.state&B->IFsmap)) {
+ # copy u, add ?x,y
+ x := min(max(p.x-(int i.hspace + int i.border),0),ci.width-1);
+ y := min(max(p.y-(int i.vspace + int i.border),0),ci.height-1);
+ u = ref *a.href;
+ u.query = string x + "," + string y;
+ }
+ }
+ Ifloat =>
+ return anchorgospec(i.item, a, p);
+ }
+
+ if(u != nil)
+ g = GoSpec.newget(GoLink, u, target);
+ return g;
+}
+
+# Control c has been pushed.
+# Find the form it is in and perform required action (reset, or submit).
+pushaction(c: ref Control, pt: Point)
+{
+ pick b := c {
+ Cbutton =>
+ ff := b.ff;
+ f := b.f;
+ if(ff != nil) {
+ case ff.ftype {
+ B->Fsubmit or B->Fimage =>
+ spawn form_submit(c.f, ff.form, pt, c, 1);
+ B->Freset =>
+ spawn form_reset(f, ff.form);
+ }
+ }
+ }
+}
+
+# if onsubmit==1, then raise onsubmit event (if handler present)
+form_submit(fr: ref Frame, frm: ref B->Form, p: Point, submitctl: ref Control, onsubmit: int)
+{
+ submitfield : ref B->Formfield;
+ if (submitctl != nil)
+ submitfield = submitctl.ff;
+
+ if(submitctl != nil && tagof(submitctl) == tagof(Control.Centry)) {
+ # Via CR, so only submit if there is a submit button (first one is the default)
+ firstsubmit : ref B->Formfield;
+ for(l := frm.fields; l != nil; l = tl l) {
+ f := hd l;
+ if (f.ftype == B->Fsubmit) {
+ firstsubmit = f;
+ break;
+ }
+ }
+ if (firstsubmit == nil)
+ return;
+ submitfield = firstsubmit;
+ }
+ if(doscripts && fr.doc.hasscripts && onsubmit && (frm.evmask & E->SEonsubmit)) {
+ c := chan of string;
+ J->jevchan <-= ref E->ScriptEvent(E->SEonsubmit, fr.id, frm.formid, -1, -1, -1, -1, -1, -1, nil, c, 0);
+ if(<-c == nil)
+ return;
+ }
+ v := "";
+ sep := "";
+ radiodone : list of string = nil;
+floop:
+ for(l := frm.fields; l != nil; l = tl l) {
+ f := hd l;
+ if(f.name == "")
+ continue;
+ val := "";
+ c: ref Control;
+ if(f.ctlid >= 0)
+ c = fr.controls[f.ctlid];
+ case f.ftype {
+ B->Ftext or B->Fpassword or B->Ftextarea =>
+ if(c != nil)
+ pick e := c {
+ Centry =>
+ val = e.s;
+ }
+ if(val != "" && f.name == "_ISINDEX_") {
+ # just the index terms after the "?"
+ if(sep != "")
+ v = v + sep;
+ sep = "&";
+ v = v + ucvt(val);
+ break floop;
+ }
+ B->Fcheckbox or B->Fradio =>
+ if(f.ftype == B->Fradio) {
+ # Need the following to catch case where there
+ # is more than one radiobutton with the same name
+ # and value.
+ for(rl := radiodone; rl != nil; rl = tl rl)
+ if(hd rl == f.name)
+ continue floop;
+ }
+ checked := 0;
+ if(c != nil)
+ pick cb := c {
+ Ccheckbox =>
+ checked = cb.flags & L->CFactive;
+ }
+ if(checked) {
+ val = f.value;
+ if(f.ftype == B->Fradio)
+ radiodone = f.name :: radiodone;
+ }
+ else
+ continue;
+ B->Fhidden =>
+ val = f.value;
+ B->Fsubmit =>
+ if(submitctl != nil && f == submitctl.ff && f.name != "_no_name_submit_")
+ val = f.value;
+ else
+ continue;
+ B->Fselect =>
+ if(c != nil)
+ pick s := c {
+ Cselect =>
+ for(i := 0; i < len s.options; i++) {
+ if(s.options[i].selected) {
+ if(sep != "")
+ v = v + sep;
+ sep = "&";
+ v = v + ucvt(f.name) + "=" + ucvt(s.options[i].value);
+ }
+ }
+ continue;
+ }
+ B->Fimage =>
+ if(submitctl != nil && f == submitctl.ff) {
+ if(sep != "")
+ v = v + sep;
+ sep = "&";
+ v = v + ucvt(f.name + ".x") + "=" + ucvt(string max(p.x,0))
+ + sep + ucvt(f.name + ".y") + "=" + ucvt(string max(p.y,0));
+ continue;
+ }
+ }
+# if(val != "") {
+ if(sep != "")
+ v = v + sep;
+ sep = "&";
+ v = v + ucvt(f.name) + "=" + ucvt(val);
+# }
+ }
+ action := ref *frm.action;
+ if (frm.method == CU->HGet) {
+ if (action.query != "" && v != "")
+ action.query += "&";
+ action.query += v;
+ v = "";
+ }
+# action.query = v;
+ E->evchan <-= ref Event.Esubmit(frm.method, action, v, frm.target);
+}
+
+hexdigit := "0123456789ABCDEF";
+urlchars := array [128] of {
+ 'a' to 'z' => byte 1,
+ 'A' to 'Z' => byte 1,
+ '0' to '9' => byte 1,
+ '-' or '/' or '$' or '_' or '@' or '.' or '!' or '*' or '\'' or '(' or ')' => byte 1,
+ * => byte 0
+};
+
+ucvt(s: string): string
+{
+ b := array of byte s;
+ u := "";
+ for(i := 0; i < len b; i++) {
+ c := int b[i];
+ if (c < len urlchars && int urlchars[c])
+ u[len u] = c;
+ else if(c == ' ')
+ u[len u] = '+';
+ else {
+ u[len u] = '%';
+ u[len u] = hexdigit[(c>>4)&15];
+ u[len u] = hexdigit[c&15];
+ }
+ }
+ return u;
+}
+
+form_reset(fr: ref Frame, frm: ref B->Form)
+{
+ if(doscripts && fr.doc.hasscripts && (frm.evmask & E->SEonreset)) {
+ c := chan of string;
+ J->jevchan <-= ref E->ScriptEvent(E->SEonreset, fr.id, frm.formid, -1, -1, -1, -1, -1, -1, nil, c, 0);
+ if(<-c == nil)
+ return;
+ }
+ for(fl := frm.fields; fl != nil; fl = tl fl) {
+ a := hd fl;
+ if(a.ctlid >= 0)
+ fr.controls[a.ctlid].reset();
+ }
+# fr.cim.flush(D->Flushnow);
+}
+
+formaction(frameid, formid, ftype, onsubmit: int)
+{
+ if(dbg > 1)
+ sys->print("formaction %d %d %d %d\n", frameid, formid, ftype, onsubmit);
+ f := findframe(top, frameid);
+ if(f != nil) {
+ d := f.doc;
+ if(d != nil) {
+ for(fl := d.forms; fl != nil; fl = tl fl) {
+ frm := hd fl;
+ if(frm.formid == formid) {
+ if(ftype == E->EFsubmit)
+ spawn form_submit(f, frm, Point(0,0), nil, onsubmit);
+ else
+ spawn form_reset(f, frm);
+ }
+ }
+ }
+ }
+}
+
+formfield_blur(f: ref Frame, ff: ref B->Formfield)
+{
+ if(ff.ftype != B->Fhidden) {
+ c := f.controls[ff.ctlid];
+ if(!(c.flags & L->CFhasfocus))
+ return;
+ # lose focus quietly - don't raise "onblur" event for the given control
+ c.losefocus(0);
+ setfocus(nil);
+ }
+}
+
+formfield_focus(f: ref Frame, ff: ref B->Formfield)
+{
+ if(ff.ftype != B->Fhidden) {
+ c := f.controls[ff.ctlid];
+ if(c.flags & L->CFhasfocus)
+ return;
+ # gain focus quietly - don't raise "onfocus" event for the given control
+ c.gainfocus(0);
+ setfocus(c);
+ }
+}
+
+# simulate a mouse click, but don't trigger onclick event
+formfield_click(f: ref Frame, frm: ref B->Form, ff: ref B->Formfield)
+{
+ c := f.controls[ff.ctlid];
+ case ff.ftype {
+ B->Fcheckbox or
+ B->Fradio or
+ B->Fbutton =>
+ c.domouse(p0, E->Mlbuttonup, nil);
+ B->Fsubmit =>
+ spawn form_submit(f, frm, p0, c, 1);
+ B->Freset =>
+ spawn form_reset(f, frm);
+ }
+}
+
+formfield_select(f: ref Frame, ff: ref B->Formfield)
+{
+ case ff.ftype {
+ B->Ftext or
+ B->Fselect or
+ B->Ftextarea =>
+ ctl := f.controls[ff.ctlid];
+ pick c := ctl {
+ Centry =>
+ c.sel = (0, len c.s);
+ ctl.draw(1);
+ }
+ }
+}
+
+formfieldaction(frameid, formid, fieldid, fftype: int)
+{
+ if(dbg > 1)
+ sys->print("formfieldaction %d %d %d %d\n", frameid, formid, fieldid, fftype);
+ f := findframe(top, frameid);
+ if(f == nil || f.doc == nil)
+ return;
+
+ # find form in frame
+ frm : ref B->Form;
+ for(fl := f.doc.forms; fl != nil; fl = tl fl) {
+ if((hd fl).formid == formid) {
+ frm = hd fl;
+ break;
+ }
+ }
+ if(frm == nil)
+ return;
+
+ # find formfield in form
+ ff : ref B->Formfield;
+ for(ffl := frm.fields; ffl != nil; ffl = tl ffl) {
+ if((hd ffl).fieldid == fieldid) {
+ ff = hd ffl;
+ break;
+ }
+ }
+ if(ff == nil || ff.ctlid < 0)
+ return;
+
+ # perform action
+ case fftype {
+ E->EFFblur =>
+ formfield_blur(f, ff);
+ E->EFFfocus =>
+ formfield_focus(f, ff);
+ E->EFFclick =>
+ formfield_click(f, frm, ff);
+ E->EFFselect =>
+ formfield_select(f, ff);
+ E->EFFredraw =>
+ c := f.controls[ff.ctlid];
+ pick ctl := c {
+ Cselect =>
+ sel := 0;
+ for (i := 0; i < len ctl.options; i++) {
+ if (ctl.options[i].selected) {
+ sel = i;
+ break;
+ }
+ }
+ if (sel > len ctl.options - ctl.nvis)
+ sel = len ctl.options - ctl.nvis;
+ ctl.first = sel;
+ }
+ c.draw(1);
+ }
+}
+
+# Find hit in a local map
+findhit(map: ref B->Map, p: Point, w, h: int) : (ref Parsedurl, string)
+{
+ x := p.x;
+ y := p.y;
+ dflt : ref Parsedurl = nil;
+ dflttarg := "";
+ for(al := map.areas; al != nil; al = tl al) {
+ a := hd al;
+ c := a.coords;
+ nc := len c;
+ x1 := 0;
+ y1 := 0;
+ x2 := 0;
+ y2 := 0;
+ if(nc >= 2) {
+ x1 = d2pix(c[0], w);
+ y1= d2pix(c[1], h);
+ if(nc > 2) {
+ x2 = d2pix(c[2], w);
+ if(nc > 3)
+ y2 = d2pix(c[3], h);
+ }
+ }
+ hit := 0;
+ case a.shape {
+ "rect" or "rectangle" =>
+ if(nc == 4)
+ hit = x1 <= x && x <= x2 &&
+ y1 <= y && y <= y2;
+ "circ" or "circle" =>
+ if(nc == 3) {
+ xd := x - x1;
+ yd := y - y1;
+ hit = xd*xd + yd*yd <= x2*x2;
+ }
+ "poly" or "polygon" =>
+ np := nc / 2;
+ hit = 0;
+ xr := real x;
+ yr := real y;
+ j := np - 1;
+ for(i := 0; i < np; j = i++) {
+ xi := real d2pix(c[2*i], w);
+ yi := real d2pix(c[2*i+1], h);
+ xj := real d2pix(c[2*j], w);
+ yj := real d2pix(c[2*j+1], h);
+ if ((((yi<=yr) && (yr<yj)) ||
+ ((yj<=yr) && (yr<yi))) &&
+ (xr < (xj - xi) * (yr - yi) / (yj - yi) + xi))
+ hit = !hit;
+ }
+ "def" or "default" =>
+ dflt = a.href;
+ dflttarg = a.target;
+ }
+ if(hit)
+ return (a.href, a.target);
+ }
+ return (dflt, dflttarg);
+}
+
+d2pix(d: B->Dimen, tot: int) : int
+{
+ ans := d.spec();
+ if(d.kind() == B->Dpercent)
+ ans = (ans * tot) / 100;
+ return ans;
+}
+GoSpec.newget(kind: int, url: ref Parsedurl, target: string) : ref GoSpec
+{
+ return ref GoSpec(kind, url, CU->HGet, "", target, "", nil);
+}
+
+GoSpec.newpost(url: ref Parsedurl, body, target: string) : ref GoSpec
+{
+ return ref GoSpec(GoNormal, url, CU->HPost, body, target, "", nil);
+}
+
+GoSpec.newspecial(kind: int, hn: ref HistNode) : ref GoSpec
+{
+ return ref GoSpec(kind, nil, 0, "", "", "", hn);
+}
+
+GoSpec.equal(a: self ref GoSpec, b: ref GoSpec) : int
+{
+ if(a.url == nil || b.url == nil)
+ return 0;
+ return CU->urlequal(a.url, b.url) && a.meth == b.meth && a.body == b.body;
+}
+
+DocConfig.equal(a: self ref DocConfig, b: ref DocConfig) : int
+{
+ return a.framename == b.framename && a.gospec.equal(b.gospec);
+}
+
+DocConfig.equalarray(a1: array of ref DocConfig, a2: array of ref DocConfig) : int
+{
+ n := len a1;
+ if(n != len a2)
+ return 0;
+ for(i := 0; i < n; i++) {
+ if(a1[i] == nil || a2[i] == nil)
+ continue;
+ if(!(a1[i]).equal(a2[i]))
+ return 0;
+ }
+ return 1;
+}
+
+# Put b in a.succs (if atob is true) or a.preds (if atob is false)
+# at front of list.
+# If it is already in the list, move it to the front.
+HistNode.addedge(a: self ref HistNode, b: ref HistNode, atob: int)
+{
+ if(atob)
+ oldl := a.succs;
+ else
+ oldl = a.preds;
+ there := 0;
+ for(l := oldl; l != nil; l = tl l)
+ if(hd l == b) {
+ there = 1;
+ break;
+ }
+ if(there)
+ newl := b :: remhnode(oldl, b);
+ else
+ newl = b :: oldl;
+ if(atob)
+ a.succs = newl;
+ else
+ a.preds = newl;
+}
+
+# return copy of l with hn removed (known that hn
+# occurs at most once)
+remhnode(l: list of ref HistNode, hn: ref HistNode) : list of ref HistNode
+{
+ if(l == nil)
+ return nil;
+ hdl := hd l;
+ if(hdl == hn)
+ return tl l;
+ return hdl :: remhnode(tl l, hn);
+}
+
+# Copy of a, with new kidconfigs array (so that it can be changed independent
+# of a), and clear the preds and succs.
+HistNode.copy(a: self ref HistNode) : ref HistNode
+{
+ n := len a.kidconfigs;
+ kc : array of ref DocConfig = nil;
+ if(n > 0) {
+ kc = array[n] of ref DocConfig;
+ for(i := 0; i < n; i++)
+ kc[i] = a.kidconfigs[i];
+ }
+ return ref HistNode(a.topconfig, kc, nil, nil, -1, nil);
+}
+
+# This is called just before layout of f with result of getting g.
+# (we don't yet know doctitle and whether this is a frameset).
+# If navkind is not GoHistnode, update the history graph; but if
+# navkind is GoReplace, replace oldcur with the new HistNode.
+# In any case reorder the history array to put latest last in array.
+History.add(h: self ref History, f: ref Frame, g: ref GoSpec, navkind: int)
+{
+ if(len h.h <= h.n) {
+ newh := array[len h.h + 20] of ref HistNode;
+ newh[0:] = h.h;
+ h.h = newh;
+ }
+ oldcur : ref HistNode;
+ if(h.n > 0)
+ oldcur = h.h[h.n-1];
+ dc := ref DocConfig(f.name, g.url.tostring(), navkind != GoHistnode, g);
+ hnode := ref HistNode(dc, nil, nil, nil, -1, nil);
+ if(f == top) {
+ g.target = "_top";
+ }
+ else if(oldcur != nil) {
+ # oldcur should be a frameset and f should be a kid in it
+ kidpos := -1;
+ for(i := 0; i < len oldcur.kidconfigs; i++) {
+ kc := oldcur.kidconfigs[i];
+ if(kc != nil && kc.framename == f.name) {
+ kidpos = i;
+ break;
+ }
+ }
+ if(kidpos == -1) {
+ if(dbg)
+ sys->print("history botch\n");
+ }
+ else {
+ hnode = oldcur.copy();
+ hnode.kidconfigs[kidpos] = dc;
+ }
+ }
+ # see if equivalent node to hnode is already in history
+ hnodepos := -1;
+ for(i := 0; i < h.n; i++) {
+ if(hnode.topconfig.equal(h.h[i].topconfig)) {
+ if((hnode.kidconfigs==nil && h.h[i].topconfig.initconfig) ||
+ DocConfig.equalarray(hnode.kidconfigs, h.h[i].kidconfigs)) {
+ hnodepos = i;
+ hnode = h.h[i];
+ break;
+ }
+ }
+ }
+ if(hnodepos == -1) {
+ if(navkind == GoReplace && h.n > 0)
+ h.n--;
+ hnodepos = h.n;
+ h.h[h.n++] = hnode;
+ }
+ if(oldcur != nil && hnode != oldcur && navkind != GoHistnode) {
+ oldcur.addedge(hnode, 1);
+ if(navkind != GoReplace)
+ hnode.addedge(oldcur, 0);
+ else if(oldcur.preds != nil)
+ hnode.addedge(hd oldcur.preds, 0);
+ }
+ if(hnodepos != h.n-1) {
+ # move hnode to h.n-1, and shift rest back
+ for(k := hnodepos; k < h.n-1; k++)
+ h.h[k] = h.h[k+1];
+ h.h[h.n-1] = hnode;
+ }
+ G->backbutton(hnode.preds != nil);
+ G->fwdbutton(hnode.succs != nil);
+}
+
+# This is called just after layout of f.
+# Now we can put in correct doctitle, and make kids array if necessary.
+History.update(h: self ref History, f: ref Frame)
+{
+ hnode := h.h[h.n-1];
+ if(f == top) {
+ hnode.topconfig.title = f.doc.doctitle;
+ if(f.kids != nil && hnode.kidconfigs == nil) {
+ kc := array[len f.kids] of ref DocConfig;
+ i := 0;
+ for(l := f.kids; l != nil; l = tl l) {
+ kf := hd l;
+ if(kf.src != nil)
+ kc[i] = ref DocConfig(kf.name, kf.src.tostring(), 1, GoSpec.newget(GoNormal, kf.src, "_self"));
+ i++;
+ }
+ hnode.kidconfigs = kc;
+ }
+ }
+ else {
+ # hnode should be a frameset and f should be a kid in it
+ for(i := 0; i < len hnode.kidconfigs; i++) {
+ kc := hnode.kidconfigs[i];
+ if(kc != nil && kc.framename == f.name) {
+ hnode.kidconfigs[i].title = f.doc.doctitle;
+ return;
+ }
+ }
+ if(dbg)
+ sys->print("history update botch\n");
+ }
+}
+
+# Find the gokind node (-1==Back, 0==Same, +1==Forward)
+# other gokind values come from JavaScript's History.go(delta)
+History.find(h: self ref History, gokind: int) : ref HistNode
+{
+ if(h.n > 0) {
+ cur := h.h[h.n-1];
+ case gokind {
+ 1 =>
+ if(cur.succs != nil)
+ return hd cur.succs;
+ -1 =>
+ if(cur.preds != nil)
+ return hd cur.preds;
+ 0 =>
+ return cur;
+ * =>
+# BUG: follows circularities: gives rise to different behaviour to other
+# browsers but maintains the property of find(n) being equivalent to
+# the user pressing the (forward/back) button n times
+
+ h.findid++;
+ while (gokind != 0 && cur != nil) {
+ hn : list of ref HistNode;
+ if (gokind > 0) {
+ gokind--;
+ hn = cur.succs;
+ } else {
+ gokind++;
+ hn = cur.preds;
+ }
+ if (cur.findid == h.findid)
+ hn = cur.findchain;
+ else
+ cur.findid = h.findid;
+ if (hn != nil) {
+ cur.findchain = tl hn;
+ cur = hd hn;
+ } else
+ cur = nil;
+ }
+ return cur;
+ }
+ }
+ return nil;
+}
+
+# for debugging
+History.print(h: self ref History)
+{
+ sys->print("History\n");
+ for(i := 0; i < h.n; i++) {
+ hn := history.h[i];
+ sys->print("Node %d:\n", i);
+ dc := hn.topconfig;
+ sys->print("\tframe=%s, target=%s, url=%s\n", dc.framename, dc.gospec.target, dc.gospec.url.tostring());
+ if(hn.kidconfigs != nil) {
+ for(j := 0; j < len hn.kidconfigs; j++) {
+ dc = hn.kidconfigs[j];
+ if(dc != nil)
+ sys->print("\t\t%d: frame=%s, target=%s, url=%s\n",
+ j, dc.framename, dc.gospec.target, dc.gospec.url.tostring());
+ }
+ }
+ if(hn.preds != nil)
+ printhnodeindices(h, "Preds", hn.preds);
+ if(hn.succs != nil)
+ printhnodeindices(h, "Succs", hn.succs);
+ }
+ sys->print("\n");
+}
+
+# helpers for JavaScript's History object
+History.histinfo(h: self ref History) : (int, string, string, string)
+{
+ length := 0;
+ current, next, previous : string;
+
+ if(h.n > 0) {
+ hn := h.h[h.n-1];
+ length = len hn.succs + len hn.preds + 1;
+ current = hn.topconfig.gospec.url.tostring();
+ if(hn.succs != nil) {
+ fwd := hd hn.succs;
+ next = fwd.topconfig.gospec.url.tostring();
+ }
+ if(hn.preds != nil) {
+ back := hd hn.preds;
+ previous = back.topconfig.gospec.url.tostring();
+ }
+ }
+ return (length, current, next, previous);
+}
+
+histinfo() : (int, string, string, string)
+{
+ return history.histinfo();
+}
+
+# does URL in hn contain s as a substring?
+isurlsubstring(hn: ref HistNode, s: string) : int
+{
+ url := hn.topconfig.gospec.url.tostring();
+ (l, r) := S->splitstrl(url, s);
+ if(r != nil)
+ return 1;
+ return 0;
+}
+
+# for JavaScript's History.go(location)
+# find nearest history entry whose URL contains s as a substring
+# (search forward and backward from current "in parallel"?)
+History.findurl(h: self ref History, s: string) : ref HistNode
+{
+ if(h.n > 0) {
+ hn := h.h[h.n-1];
+ if(isurlsubstring(hn, s))
+ return hn;
+ fwd := hn.succs;
+ back := hn.preds;
+ while(fwd != nil && back != nil) {
+ if(fwd != nil) {
+ if(isurlsubstring(hd fwd, s))
+ return hd fwd;
+ fwd = tl fwd;
+ }
+ if(back != nil) {
+ if(isurlsubstring(hd back, s))
+ return hd back;
+ back = tl back;
+ }
+ }
+ }
+ return nil;
+}
+
+printhnodeindices(h: ref History, label: string, l: list of ref HistNode)
+{
+ sys->print("\t%s:", label);
+ for( ; l != nil; l = tl l) {
+ hn := hd l;
+ for(i := 0; i < h.n; i++) {
+ if(hn == h.h[i]) {
+ sys->print(" %d", i);
+ break;
+ }
+ }
+ if(i == h.n)
+ sys->print(" ?");
+ }
+ sys->print("\n");
+}
+
+dumphistory()
+{
+ fname := config.userdir + "/history.html";
+ fd := sys->create(fname, sys->OWRITE, 8r600);
+ if(fd == nil) {
+ if(warn)
+ sys->print("can't create history file\n");
+ return;
+ }
+ line := "<HEAD><TITLE>History</TITLE>\n<META HTTP-EQUIV=\"content-type\" CONTENT=\"text/html; charset=utf8\">\n</HEAD>\n<BODY>\n";
+ buf := array[Sys->ATOMICIO] of byte;
+ aline := array of byte line;
+ buf[0:] = aline;
+ bufpos := len aline;
+ for(i := history.n-1; i >= 0; i--) {
+ hn := history.h[i];
+ dc := hn.topconfig;
+ line = "<A HREF=" + dc.gospec.url.tostring() + " TARGET=\"_top\">" + dc.title + "</A><BR>\n";
+ if(hn.kidconfigs != nil) {
+ line += "<UL>";
+ for(j := 0; j < len hn.kidconfigs; j++) {
+ dc = hn.kidconfigs[j];
+ if(dc != nil) {
+ line += "<LI><A HREF=" + dc.gospec.url.tostring() +
+ " TARGET=\"" + dc.framename + "\">" +
+ dc.title + "</A>\n";
+ }
+ }
+ line += "</UL>";
+ }
+ aline = array of byte line;
+ if(bufpos + len aline > Sys->ATOMICIO) {
+ sys->write(fd, buf, bufpos);
+ bufpos = 0;
+ }
+ buf[bufpos:] = aline;
+ bufpos += len aline;
+ }
+ if(bufpos > 0)
+ sys->write(fd, buf, bufpos);
+}
+
+# getauth returns the (realm, credentials), with "" for the credentials
+# if we fail in getting authorization for some reason
+getauth(chal: string) : (string, string)
+{
+ if(len chal < 12 || S->tolower(chal[0:12]) != "basic realm=") {
+ if(dbg || warn)
+ sys->print("unrecognized authorization challenge: %s\n", chal);
+ return ("", "");
+ }
+ realm := chal[12:];
+ if(realm[0] == '"')
+ realm = realm[1:len realm - 1];
+ for(al := auths; al != nil; al = tl al) {
+ a := hd al;
+ if(realm == a.realm)
+ return (realm, a.credentials);
+ }
+
+ c := chan of (int, string);
+ (code, uname, pword) := G->auth(realm);
+ if(code != 1)
+ return (nil, nil);
+ cred := uname + ":" + pword;
+ cred = tobase64(cred);
+ return (realm, cred);
+}
+
+# Convert string to the base64 encoding
+tobase64(a: string) : string
+{
+ n := len a;
+ if(n == 0)
+ return "";
+ out := "";
+ j := 0;
+ i := 0;
+ while(i < n) {
+ x := a[i++] << 16;
+ if(i < n)
+ x |= (a[i++]&255) << 8;
+ if(i < n)
+ x |= (a[i++]&255);
+ out[j++] = c64(x>>18);
+ out[j++] = c64(x>>12);
+ out[j++] = c64(x>> 6);
+ out[j++] = c64(x);
+ }
+ nmod3 := n % 3;
+ if(nmod3 != 0) {
+ out[j-1] = '=';
+ if(nmod3 == 1)
+ out[j-2] = '=';
+ }
+ return out;
+}
+
+c64(c: int) : int
+{
+ v : con "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ return v[c&63];
+}
+
+dosaveas(bsmain: ref ByteSource)
+{
+ (code, ans) := G->prompt("Save as", nil);
+ if (code == -1)
+ return;
+ if(code == 1 && ans != "") {
+ if(ans[0] != '/')
+ ans = config.userdir + "/" + ans;
+ fd := sys->create(ans, sys->OWRITE, 8r644);
+ if(fd == nil) {
+ G->alert(X("Couldn't create", "gui") + " " + ans);
+ return;
+ }
+ G->setstatus(X("Saving", "gui") + " " + bsmain.hdr.actual.tostring());
+ # TODO: should really use a different protocol that
+ # doesn't require getting whole file before proceeding
+ s := "";
+ while(!bsmain.eof) {
+ CU->waitreq(bsmain::nil);
+ if(bsmain.err != "") {
+ s = bsmain.err;
+ break;
+ }
+ }
+ if(s == "") {
+ flen := bsmain.edata;
+ for(i := 0; i < bsmain.edata; ) {
+ n := sys->write(fd, bsmain.data[i:flen], flen-i);
+ if(n <= 0)
+ break;
+ i += n;
+ }
+ if(i != flen)
+ s = "whole file not written";
+ }
+ if(s == "")
+ s = X("Created", "gui") + " " + ans;
+ G->setstatus(X("Created", "gui") + " " + ans);
+ # G->alert(s);
+ }
+ CU->freebs(bsmain);
+}
+
+fatalerror(msg: string)
+{
+ sys->print("Fatal error: %s\n", msg);
+ finish();
+}
+
+pctoloc(mod: string, pc: int) : string
+{
+ ans := sys->sprint("pc=%d", pc);
+ db := load Debug Debug->PATH;
+ if(db == nil)
+ return ans;
+ Sym : import db;
+ db->init();
+ modname := mod;
+ for(i := 0; i < len mod; i++)
+ if(mod[i] == '[') {
+ modname = mod[0:i];
+ break;
+ }
+ sblname := "";
+ case modname {
+ "Build" =>
+ sblname = "build.sbl";
+ "CharonUtils" =>
+ sblname = "chutils.sbl";
+ "Gui" =>
+ sblname = "gui.sbl";
+ "Img" =>
+ sblname = "img.sbl";
+ "Layout" =>
+ sblname = "layout.sbl";
+ "Lex" =>
+ sblname = "lex.sbl";
+ "Test" =>
+ sblname = "test.sbl";
+ }
+ if(sblname == "")
+ return ans;
+ (sym, nil) := db->sym(sblname);
+ if(sym == nil)
+ return ans;
+ src := sym.pctosrc(pc);
+ if(src == nil)
+ return ans;
+ return sys->sprint("%s:%d", src.start.file, src.start.line);
+}
+
+startcs()
+{
+ cs := load Command "/dis/ndb/cs.dis";
+ if (cs == nil) {
+ sys->print("failed to start cs\n");
+ return;
+ }
+ spawn cs->init(nil, nil);
+ sys->sleep(1000);
+}
+
+startcharon(url: string, c: chan of string)
+{
+ ctxt := ref Context;
+ ctxt.ctxt = context;
+ ctxt.args = "charon" :: url :: nil;
+ ctxt.c = c;
+ ctxt.cksrv = CU->CK;
+ ctxt.ckclient = CU->ckclient;
+ ch := load Charon "/dis/charon.dis";
+ fdl := list of {0, 1, 2};
+ if (CU->ckclient != nil)
+ fdl = (CU->ckclient).fd.fd :: fdl;
+ if(ch != nil){
+ sys->pctl(Sys->NEWPGRP|Sys->NEWFD, fdl);
+ ch->initc(ctxt);
+ }
+}
+
+# Kill all processes spawned by us, and exit
+finish()
+{
+ if (CU != nil) {
+ CU->kill(pgrp, 1);
+ if(gopgrp != 0)
+ CU->kill(gopgrp, 1);
+ }
+ if(plumb != nil)
+ plumb->shutdown();
+ sendopener("E");
+ exit;
+}
+
+include "plumbmsg.m";
+ plumb: Plumbmsg;
+ Msg: import plumb;
+
+plumbwatch()
+{
+ plumb = load Plumbmsg Plumbmsg->PATH;
+ if (plumb == nil)
+ return;
+ if (plumb->init(1, (CU->config).plumbport, 0) == -1) {
+ # try to set up plumbing for sending only
+ if (plumb->init(1, nil, 0) == -1)
+ plumb = nil;
+ return;
+ }
+ while ((m := Msg.recv()) != nil) {
+ if (m.kind == "text") {
+ u := CU->makeabsurl(string m.data);
+ if (u != nil)
+ E->evchan <-= ref Event.Ego(u.tostring(), "_top", 0, E->EGnormal);
+ }
+ }
+}
+
+plumbsend(s, dest: string): int
+{
+ if (plumb == nil)
+ return -1;
+ if (dest != nil)
+ dest = "type="+dest;
+ msg := ref Msg((CU->config).plumbport, nil, "", "text", dest, array of byte s);
+ if (msg.send() < 0)
+ return -1;
+ return 0;
+}
+
+stop()
+{
+ stopped := X("Stopped", "gui");
+ G->progress <-= (-1, G->Paborted, 0, stopped);
+ G->setstatus(stopped);
+ CU->abortgo(gopgrp);
+}
+
+gettop(): ref Layout->Frame
+{
+ return top;
+}
diff --git a/appl/charon/charon.m b/appl/charon/charon.m
new file mode 100644
index 00000000..3a6e45c0
--- /dev/null
+++ b/appl/charon/charon.m
@@ -0,0 +1,20 @@
+Charon : module
+{
+ PATH: con "/dis/charon.dis";
+
+ Context: adt {
+ ctxt: ref Draw->Context;
+ args: list of string;
+ c: chan of string;
+ cksrv: Cookiesrv;
+ ckclient: ref Cookiesrv->Client;
+ };
+
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+ initc: fn(ctxt: ref Context);
+ histinfo: fn(): (int, string, string, string);
+ startcharon: fn(url: string, c: chan of string);
+ hasopener: fn(): int;
+ sendopener: fn(s: string);
+ gettop: fn(): ref Layout->Frame;
+};
diff --git a/appl/charon/chutils.b b/appl/charon/chutils.b
new file mode 100644
index 00000000..40da2f5f
--- /dev/null
+++ b/appl/charon/chutils.b
@@ -0,0 +1,2050 @@
+implement CharonUtils;
+
+include "common.m";
+include "transport.m";
+include "date.m";
+include "translate.m";
+
+ date: Date;
+ me: CharonUtils;
+ sys: Sys;
+ D: Draw;
+ S: String;
+ U: Url;
+ T: StringIntTab;
+
+Font : import D;
+Parsedurl: import U;
+convcs : Convcs;
+trans : Translate;
+ Dict : import trans;
+dict : ref Dict;
+
+NCTimeout : con 100000; # free NC slot after 100 seconds
+UBufsize : con 40*1024; # initial buffer size for unknown lengths
+UEBufsize : con 1024; # initial buffer size for unknown lengths, error responses
+
+botchexception := "EXInternal: ByteSource protocol botch";
+bytesourceid := 0;
+crlf : con "\r\n";
+ctype : array of byte; # local ref to C->ctype[]
+dbgproto : int;
+dbg: int;
+netconnid := 0;
+netconns := array[10] of ref Netconn;
+sptab : con " \t";
+
+THTTP, TFTP, TFILE, TMAX: con iota;
+transports := array[TMAX] of Transport;
+tpaths := array [TMAX] of {
+ THTTP => Transport->HTTPPATH,
+ TFTP => Transport->FTPPATH,
+ TFILE => Transport->FILEPATH,
+};
+
+schemes := array [] of {
+ ("http", THTTP),
+ ("https", THTTP),
+ ("ftp", TFTP),
+ ("file", TFILE),
+};
+
+ngchan : chan of (int, list of ref ByteSource, ref Netconn, chan of ref ByteSource);
+
+# must track HTTP methods in chutils.m
+# (upper-case, since that's required in HTTP requests)
+hmeth = array[] of { "GET", "POST" };
+
+# following array must track media type def in chutils.m
+# keep in alphabetical order
+mnames = array[] of {
+ "application/msword",
+ "application/octet-stream",
+ "application/pdf",
+ "application/postscript",
+ "application/rtf",
+ "application/vnd.framemaker",
+ "application/vnd.ms-excel",
+ "application/vnd.ms-powerpoint",
+ "application/x-unknown",
+ "audio/32kadpcm",
+ "audio/basic",
+ "image/cgm",
+ "image/g3fax",
+ "image/gif",
+ "image/ief",
+ "image/jpeg",
+ "image/png",
+ "image/tiff",
+ "image/x-bit",
+ "image/x-bit2",
+ "image/x-bitmulti",
+ "image/x-inferno-bit",
+ "image/x-xbitmap",
+ "model/vrml",
+ "multipart/digest",
+ "multipart/mixed",
+ "text/css",
+ "text/enriched",
+ "text/html",
+ "text/javascript",
+ "text/plain",
+ "text/richtext",
+ "text/sgml",
+ "text/tab-separated-values",
+ "text/xml",
+ "video/mpeg",
+ "video/quicktime"
+};
+
+ncstatenames = array[] of {
+ "free", "idle", "connect", "gethdr", "getdata",
+ "done", "err"
+};
+
+hsnames = array[] of {
+ "none", "information", "ok", "redirect", "request error", "server error"
+};
+
+hcphrase(code: int) : string
+{
+ ans : string;
+ case code {
+ HCContinue => ans = X("Continue", "http");
+ HCSwitchProto => ans = X("Switching Protocols", "http");
+ HCOk => ans = X("Ok", "http");
+ HCCreated => ans = X("Created", "http");
+ HCAccepted => ans = X("Accepted", "http");
+ HCOkNonAuthoritative => ans = X("Non-Authoratative Information", "http");
+ HCNoContent => ans = X("No content", "http");
+ HCResetContent => ans = X("Reset content", "http");
+ HCPartialContent => ans = X("Partial content", "http");
+ HCMultipleChoices => ans = X("Multiple choices", "http");
+ HCMovedPerm => ans = X("Moved permanently", "http");
+ HCMovedTemp => ans = X("Moved temporarily", "http");
+ HCSeeOther => ans = X("See other", "http");
+ HCNotModified => ans = X("Not modified", "http");
+ HCUseProxy => ans = X("Use proxy", "http");
+ HCBadRequest => ans = X("Bad request", "http");
+ HCUnauthorized => ans = X("Unauthorized", "http");
+ HCPaymentRequired => ans = X("Payment required", "http");
+ HCForbidden => ans = X("Forbidden", "http");
+ HCNotFound => ans = X("Not found", "http");
+ HCMethodNotAllowed => ans = X("Method not allowed", "http");
+ HCNotAcceptable => ans = X("Not Acceptable", "http");
+ HCProxyAuthRequired => ans = X("Proxy authentication required", "http");
+ HCRequestTimeout => ans = X("Request timed-out", "http");
+ HCConflict => ans = X("Conflict", "http");
+ HCGone => ans = X("Gone", "http");
+ HCLengthRequired => ans = X("Length required", "http");
+ HCPreconditionFailed => ans = X("Precondition failed", "http");
+ HCRequestTooLarge => ans = X("Request entity too large", "http");
+ HCRequestURITooLarge => ans = X("Request-URI too large", "http");
+ HCUnsupportedMedia => ans = X("Unsupported media type", "http");
+ HCRangeInvalid => ans = X("Requested range not valid", "http");
+ HCExpectFailed => ans = X("Expectation failed", "http");
+ HCServerError => ans = X("Internal server error", "http");
+ HCNotImplemented => ans = X("Not implemented", "http");
+ HCBadGateway => ans = X("Bad gateway", "http");
+ HCServiceUnavailable => ans = X("Service unavailable", "http");
+ HCGatewayTimeout => ans = X("Gateway time-out", "http");
+ HCVersionUnsupported => ans = X("HTTP version not supported", "http");
+ HCRedirectionFailed => ans = X("Redirection failed", "http");
+ * => ans = X("Unknown code", "http");
+ }
+ return ans;
+}
+
+# This array should be kept sorted
+fileexttable := array[] of { T->StringInt
+ ("ai", ApplPostscript),
+ ("au", AudioBasic),
+# ("bit", ImageXBit),
+ ("bit", ImageXInfernoBit),
+ ("bit2", ImageXBit2),
+ ("bitm", ImageXBitmulti),
+ ("eps", ApplPostscript),
+ ("gif", ImageGif),
+ ("gz", ApplOctets),
+ ("htm", TextHtml),
+ ("html", TextHtml),
+ ("jpe", ImageJpeg),
+ ("jpeg", ImageJpeg),
+ ("jpg", ImageJpeg),
+ ("pdf", ApplPdf),
+ ("png", ImagePng),
+ ("ps", ApplPostscript),
+ ("shtml", TextHtml),
+ ("text", TextPlain),
+ ("tif", ImageTiff),
+ ("tiff", ImageTiff),
+ ("txt", TextPlain),
+ ("zip", ApplOctets)
+};
+
+# argl is command line
+# Return string that is empty if all ok, else path of module
+# that failed to load.
+init(ch: Charon, c: CharonUtils, argl: list of string, evc: chan of ref E->Event, cksrv: Cookiesrv, ckc: ref Cookiesrv->Client) : string
+{
+ me = c;
+ sys = load Sys Sys->PATH;
+ startres = ResourceState.cur();
+ D = load Draw Draw->PATH;
+ CH = ch;
+ S = load String String->PATH;
+ if(S == nil)
+ return String->PATH;
+
+ U = load Url Url->PATH;
+ if(U == nil)
+ return Url->PATH;
+ U->init();
+
+ T = load StringIntTab StringIntTab->PATH;
+ if(T == nil)
+ return StringIntTab->PATH;
+
+ trans = load Translate Translate->PATH;
+ if (trans != nil) {
+ trans->init();
+ (dict, nil) = trans->opendict(trans->mkdictname(nil, "charon"));
+ }
+
+ # Now have all the modules needed to process command line
+ # (hereafter can use our loadpath() function to substitute the
+ # build directory version if dbg['u'] is set)
+
+ setconfig(argl);
+ dbg = int config.dbg['d'];
+
+ G = load Gui loadpath(Gui->PATH);
+ if(G == nil)
+ return loadpath(Gui->PATH);
+
+ C = load Ctype loadpath(Ctype->PATH);
+ if(C == nil)
+ return loadpath(Ctype->PATH);
+
+ E = load Events Events->PATH;
+ if(E == nil)
+ return loadpath(Events->PATH);
+
+ J = load Script loadpath(Script->JSCRIPTPATH);
+ # don't report an error loading JavaScript, handled elsewhere
+
+ LX = load Lex loadpath(Lex->PATH);
+ if(LX == nil)
+ return loadpath(Lex->PATH);
+
+ B = load Build loadpath(Build->PATH);
+ if(B == nil)
+ return loadpath(Build->PATH);
+
+ I = load Img loadpath(Img->PATH);
+ if(I == nil)
+ return loadpath(Img->PATH);
+
+ L = load Layout loadpath(Layout->PATH);
+ if(L == nil)
+ return loadpath(Layout->PATH);
+ date = load Date loadpath(Date->PATH);
+ if (date == nil)
+ return loadpath(Date->PATH);
+
+ convcs = load Convcs Convcs->PATH;
+ if (convcs == nil)
+ return loadpath(Convcs->PATH);
+
+
+ # Intialize all modules after loading all, so that each
+ # may cache pointers to the other modules
+ # (G will be initialized in main charon routine, and L has to
+ # be inited after that, because it needs G's display to allocate fonts)
+
+ E->init(evc);
+ I->init(me);
+ err := convcs->init(nil);
+ if (err != nil)
+ return err;
+ if(J != nil) {
+ err = J->init(me);
+ if (err != nil) {
+ # non-fatal: just don't handle javascript
+ J = nil;
+ if (dbg)
+ sys->print("%s\n", err);
+ }
+ }
+ B->init(me);
+ LX->init(me);
+ date->init(me);
+
+ if (config.docookies) {
+ CK = cksrv;
+ ckclient = ckc;
+ if (CK == nil) {
+ path := loadpath(Cookiesrv->PATH);
+ CK = load Cookiesrv path;
+ if (CK == nil)
+ sys->print("cookies: cannot load server %s: %r\n", path);
+ else
+ ckclient = CK->start(config.userdir + "/cookies", 0);
+ }
+ }
+
+ # preload some transports
+ gettransport("http");
+ gettransport("file");
+
+ progresschan = chan of (int, int, int, string);
+ imcache = ref ImageCache;
+ ctype = C->ctype;
+ dbgproto = int config.dbg['p'];
+ ngchan = chan of (int, list of ref ByteSource, ref Netconn, chan of ref ByteSource);
+ return "";
+}
+
+# like startreq() but special case for a string ByteSource
+# which doesn't need an associated netconn
+stringreq(s : string) : ref ByteSource
+{
+ bs := ByteSource.stringsource(s);
+
+ G->progress <-= (bs.id, G->Pstart, 0, "text");
+ anschan := chan of ref ByteSource;
+ ngchan <-= (NGstartreq, bs :: nil, nil, anschan);
+ <-anschan;
+ return bs;
+}
+
+# Make a ByteSource for given request, and make sure
+# that it is on the queue of some Netconn.
+# If don't have a transport for the request's scheme,
+# the returned bs will have err set.
+startreq(req: ref ReqInfo) : ref ByteSource
+{
+ bs := ref ByteSource(
+ bytesourceid++,
+ req, # req
+ nil, # hdr
+ nil, # data
+ 0, # edata
+ "", # err
+ nil, # net
+ 1, # refgo
+ 1, # refnc
+ 0, # eof
+ 0, # lim
+ 0 # seenhdr
+ );
+
+ G->progress <-= (bs.id, G->Pstart, 0, req.url.tostring());
+ anschan := chan of ref ByteSource;
+ ngchan <-= (NGstartreq, bs::nil, nil, anschan);
+ <-anschan;
+ return bs;
+}
+
+# Wait for some ByteSource in current go generation to
+# have a state change that goproc hasn't seen yet.
+waitreq(bsl: list of ref ByteSource) : ref ByteSource
+{
+ anschan := chan of ref ByteSource;
+ ngchan <-= (NGwaitreq, bsl, nil, anschan);
+ return <-anschan;
+}
+
+# Notify netget that goproc is finished with bs.
+freebs(bs: ref ByteSource)
+{
+ anschan := chan of ref ByteSource;
+ ngchan <-= (NGfreebs, bs::nil, nil, anschan);
+ <-anschan;
+}
+
+abortgo(gopgrp: int)
+{
+ if(int config.dbg['d'])
+ sys->print("abort go\n");
+ kill(gopgrp, 1);
+ freegoresources();
+ # renew the channels so that receives/sends by killed threads don't
+ # muck things up
+ ngchan = chan of (int, list of ref ByteSource, ref Netconn, chan of ref ByteSource);
+}
+
+freegoresources()
+{
+ for(i := 0; i < len netconns; i++) {
+ nc := netconns[i];
+ nc.makefree();
+ }
+}
+
+# This runs as a separate thread.
+# It acts as a monitor to synchronize access to the Netconn data
+# structures, as a dispatcher to start runnetconn's as needed to
+# process work on Netconn queues, and as a notifier to let goproc
+# know when any ByteSources have advanced their state.
+netget()
+{
+ msg, n, i: int;
+ bsl : list of ref ByteSource;
+ nc: ref Netconn;
+ waitix := 0;
+ c : chan of ref ByteSource;
+ waitpending : list of (list of ref ByteSource, chan of ref ByteSource);
+ maxconn := config.nthreads;
+ gncs := array[maxconn] of int;
+
+ for(n = 0; n < len netconns; n++)
+ netconns[n] = Netconn.new(n);
+
+ # capture netget chan to prevent abortgo() reset of
+ # ngchan from breaking us (channel hungup) before kill() does its job
+ ngc := ngchan;
+mainloop:
+ for(;;) {
+ (msg,bsl,nc,c) = <- ngc;
+ case msg {
+ NGstartreq =>
+ bs := hd bsl;
+ # bs has req filled in, and is otherwise in its initial state.
+ # Find a suitable Netconn and add bs to its queue of work,
+ # then send nil along c to let goproc continue.
+
+ # if ReqInfo is nil then this is a string ByteSource
+ # in which case we don't need a netconn to service it as we have all
+ # data already
+ if (bs.req == nil) {
+ c <- = nil;
+ continue;
+ }
+
+ if(dbgproto)
+ sys->print("Startreq BS=%d for %s\n", bs.id, bs.req.url.tostring());
+ scheme := bs.req.url.scheme;
+ host := bs.req.url.host;
+ (transport, err) := gettransport(scheme);
+ if(err != "")
+ bs.err = err;
+ else {
+ sport :=bs.req.url.port;
+ if(sport == "")
+ port := transport->defaultport(scheme);
+ else
+ port = int sport;
+ i = 0;
+ freen := -1;
+ for(n = 0; n < len netconns && (i < maxconn || freen == -1); n++) {
+ nc = netconns[n];
+ if(nc.state == NCfree) {
+ if(freen == -1)
+ freen = n;
+ }
+ else if(nc.host == host
+ && nc.port == port && nc.scheme == scheme && i < maxconn) {
+ gncs[i++] = n;
+ }
+ }
+ if(i < maxconn) {
+ # use a new netconn for this bs
+ if(freen == -1) {
+ freen = len netconns;
+ newncs := array[freen+10] of ref Netconn;
+ newncs[0:] = netconns;
+ for(n = freen; n < freen+10; n++)
+ newncs[n] = Netconn.new(n);
+ netconns = newncs;
+ }
+ nc = netconns[freen];
+ nc.host = host;
+ nc.port = port;
+ nc.scheme = scheme;
+ nc.qlen = 0;
+ nc.ngcur = 0;
+ nc.gocur = 0;
+ nc.reqsent = 0;
+ nc.pipeline = 0;
+ nc.connected = 0;
+ }
+ else {
+ # use existing netconn with fewest outstanding requests
+ nc = netconns[gncs[0]];
+ if(maxconn > 1) {
+ minqlen := nc.qlen - nc.gocur;
+ for(i = 1; i < maxconn; i++) {
+ x := netconns[gncs[i]];
+ if(x.qlen-x.gocur < minqlen) {
+ nc = x;
+ minqlen = x.qlen-x.gocur;
+ }
+ }
+ }
+ }
+ if(nc.qlen == len nc.queue) {
+ nq := array[nc.qlen+10] of ref ByteSource;
+ nq[0:] = nc.queue;
+ nc.queue = nq;
+ }
+ nc.queue[nc.qlen++] = bs;
+ bs.net = nc;
+ if(dbgproto)
+ sys->print("Chose NC=%d for BS %d, qlen=%d\n", nc.id, bs.id, nc.qlen);
+ if(nc.state == NCfree || nc.state == NCidle) {
+ if(nc.connected) {
+ nc.state = NCgethdr;
+ if(dbgproto)
+ sys->print("NC %d: starting runnetconn in gethdr state\n", nc.id);
+ }
+ else {
+ nc.state = NCconnect;
+ if(dbgproto)
+ sys->print("NC %d: starting runnetconn in connect state\n", nc.id);
+ }
+ spawn runnetconn(nc, transport);
+ }
+ }
+ c <-= nil;
+
+ NGwaitreq =>
+ # goproc wants to be notified when some ByteSource
+ # changes to a state that the goproc hasn't seen yet.
+ # Send such a ByteSource along return channel c.
+
+ if(dbgproto)
+ sys->print("Waitreq\n");
+
+ for (scanlist := bsl; scanlist != nil; scanlist = tl scanlist) {
+ bs := hd scanlist;
+ if (bs.refnc == 0) {
+ # string ByteSource or completed or error
+ if (bs.err != nil || bs.edata >= bs.lim) {
+ c <-= bs;
+ continue mainloop;
+ }
+ continue;
+ }
+ # netcon based bytesource
+ if ((bs.hdr != nil && !bs.seenhdr && bs.hdr.mtype != UnknownType) || bs.edata > bs.lim) {
+ c <-= bs;
+ continue mainloop;
+ }
+ }
+
+ if(dbgproto)
+ sys->print("Waitpending\n");
+ waitpending = (bsl, c) :: waitpending;
+
+ NGfreebs =>
+ # goproc is finished with bs.
+ bs := hd bsl;
+
+ if(dbgproto)
+ sys->print("Freebs BS=%d\n", bs.id);
+ nc = bs.net;
+ bs.refgo = 0;
+ if(bs.refnc == 0) {
+ bs.free();
+ if(nc != nil)
+ nc.queue[nc.gocur] = nil;
+ }
+ if(nc != nil) {
+ # can be nil if no transport was found
+ nc.gocur++;
+ if(dbgproto)
+ sys->print("NC %d: gocur=%d, ngcur=%d, qlen=%d\n", nc.id, nc.gocur, nc.ngcur, nc.qlen);
+ if(nc.gocur == nc.qlen && nc.ngcur == nc.qlen) {
+ if(!nc.connected)
+ nc.makefree();
+ }
+ }
+ # don't need to check waitpending fro NGwait requests involving bs
+ # the only thread doing a freebs() should be the only thread that
+ # can do a waitreq() on the same bs. Same thread cannot be in both states.
+
+ c <-= nil;
+
+ NGstatechg =>
+ # Some runnetconn is telling us tht it changed the
+ # state of nc. Send a nil along c to let it continue.
+ bs : ref ByteSource;
+ if(dbgproto)
+ sys->print("Statechg NC=%d, state=%s\n",
+ nc.id, ncstatenames[nc.state]);
+ sendtopending : ref ByteSource = nil;
+ pendingchan : chan of ref ByteSource;
+ if(waitpending != nil && nc.gocur < nc.qlen) {
+ bs = nc.queue[nc.gocur];
+ if(dbgproto) {
+ totlen := 0;
+ if(bs.hdr != nil)
+ totlen = bs.hdr.length;
+ sys->print("BS %d: havehdr=%d seenhdr=%d edata=%d lim=%d, length=%d\n",
+ bs.id, bs.hdr != nil, bs.seenhdr, bs.edata, bs.lim, totlen);
+ if(bs.err != "")
+ sys->print (" err=%s\n", bs.err);
+ }
+ if(bs.refgo &&
+ (bs.err != "" ||
+ (bs.hdr != nil && !bs.seenhdr) ||
+ (nc.gocur == nc.ngcur && nc.state == NCdone) ||
+ (bs.edata > bs.lim))) {
+ nwp: list of (list of ref ByteSource, chan of ref ByteSource) = nil;
+ for (waitlist := waitpending; waitlist != nil; waitlist = tl waitlist) {
+ (bslist, anschan) := hd waitlist;
+ if (sendtopending != nil) {
+ nwp = (bslist, anschan) :: nwp;
+ continue;
+ }
+ for (look := bslist; look != nil; look = tl look) {
+ if (bs == hd look) {
+ sendtopending = bs;
+ pendingchan = anschan;
+ break;
+ }
+ }
+ if (sendtopending == nil)
+ nwp = (bslist, anschan) :: nwp;
+ }
+ waitpending = nwp;
+ }
+ }
+ if(nc.state == NCdone || nc.state == NCerr) {
+ if(dbgproto)
+ sys->print("NC %d: runnetconn finishing\n", nc.id);
+ assert(nc.ngcur < nc.qlen);
+ bs = nc.queue[nc.ngcur];
+ bs.refnc = 0;
+ if(bs.refgo == 0) {
+ bs.free();
+ nc.queue[nc.ngcur] = nil;
+ }
+ nc.ngcur++;
+ if(dbgproto)
+ sys->print("NC %d: ngcur=%d\n", nc.id, nc.ngcur);
+ nc.state = NCidle;
+ if(dbgproto)
+ sys->print("NC %d: idle\n", nc.id);
+ if(nc.ngcur < nc.qlen) {
+ if(nc.connected) {
+ nc.state = NCgethdr;
+ if(dbgproto)
+ sys->print("NC %d: starting runnetconn in gethdr state\n", nc.id);
+ }
+ else {
+ nc.state = NCconnect;
+ if(dbgproto)
+ sys->print("NC %d: starting runnetconn in connect state\n", nc.id);
+ }
+ (t, nil) := gettransport(nc.scheme);
+ spawn runnetconn(nc, t);
+ }
+ else if(nc.gocur == nc.qlen && !nc.connected)
+ nc.makefree();
+ }
+ c <-= nil;
+ if(sendtopending != nil) {
+ if(dbgproto)
+ sys->print("Send BS %d to pending waitreq\n", bs.id);
+ pendingchan <-= sendtopending;
+ sendtopending = nil;
+ }
+ }
+ }
+}
+
+# A separate thread, to handle ngcur request of transport.
+# If nc.gen ever goes < gen, we have aborted this go.
+runnetconn(nc: ref Netconn, t: Transport)
+{
+ ach := chan of ref ByteSource;
+ retry := 4;
+# retry := 0;
+ err := "";
+
+ assert(nc.ngcur < nc.qlen);
+ bs := nc.queue[nc.ngcur];
+
+ # dummy loop, just for breaking out of in error cases
+eloop:
+ for(;;) {
+ # Make the connection, if necessary
+ if(nc.state == NCconnect) {
+ t->connect(nc, bs);
+ if(bs.err != "") {
+ if (retry) {
+ retry--;
+ bs.err = "";
+ sys->sleep(100);
+ continue eloop;
+ }
+ break eloop;
+ }
+ nc.state = NCgethdr;
+ }
+ assert(nc.state == NCgethdr && nc.connected);
+ if(nc.scheme == "https")
+ G->progress <-= (bs.id, G->Psslconnected, 0, "");
+ else
+ G->progress <-= (bs.id, G->Pconnected, 0, "");
+
+ t->writereq(nc, bs);
+ nc.reqsent++;
+ if (bs.err != "") {
+ if (retry) {
+ retry--;
+ bs.err = "";
+ nc.state = NCconnect;
+ sys->sleep(100);
+ continue eloop;
+ }
+ break eloop;
+ }
+ # managed to write the request
+ # do not retry if we are doing form POSTs
+ # See RFC1945 section 12.2 "Safe Methods"
+ if (bs.req.method == HPost)
+ retry = 0;
+
+ # Get the header
+ t->gethdr(nc, bs);
+ if(bs.err != "") {
+ if (retry) {
+ retry--;
+ bs.err = "";
+ nc.state = NCconnect;
+ sys->sleep(100);
+ continue eloop;
+ }
+ break eloop;
+ }
+ assert(bs.hdr != nil);
+ G->progress <-= (bs.id, G->Phavehdr, 0, "");
+
+ nc.state = NCgetdata;
+
+ # read enough data to guess media type
+ while (bs.hdr.mtype == UnknownType && ncgetdata(t, nc, bs))
+ bs.hdr.setmediatype(bs.hdr.actual.path, bs.data[:bs.edata]);
+ if (bs.hdr.mtype == UnknownType) {
+ bs.hdr.mtype = TextPlain;
+ bs.hdr.chset = "utf8";
+ }
+ ngchan <-= (NGstatechg,nil,nc,ach);
+ <- ach;
+ while (ncgetdata(t, nc, bs)) {
+ ngchan <-= (NGstatechg,nil,nc,ach);
+ <- ach;
+ }
+ nc.state = NCdone;
+ G->progress <-= (bs.id, G->Phavedata, 100, "");
+ break;
+ }
+ if(bs.err != "") {
+ nc.state = NCerr;
+ nc.connected = 0;
+ G->progress <-= (bs.id, G->Perr, 0, bs.err);
+ }
+ bs.eof = 1;
+ ngchan <-= (NGstatechg, nil, nc, ach);
+ <- ach;
+}
+
+ncgetdata(t: Transport, nc: ref Netconn, bs: ref ByteSource): int
+{
+ hdr := bs.hdr;
+ if (bs.data == nil) {
+ blen := hdr.length;
+ if (blen <= 0) {
+ if(hdr.code == HCOk || hdr.code == HCOkNonAuthoritative)
+ blen = UBufsize;
+ else
+ blen = UEBufsize;
+ }
+ bs.data = array[blen] of byte;
+ }
+ nr := 0;
+ if (hdr.length > 0) {
+ if (bs.edata == hdr.length)
+ return 0;
+ nr = t->getdata(nc, bs);
+ if (nr <= 0)
+ return 0;
+ } else {
+ # don't know data length - keep growing input buffer as needed
+ if (bs.edata == len bs.data) {
+ nd := array [2*len bs.data] of byte;
+ nd[:] = bs.data;
+ bs.data = nd;
+ }
+ nr = t->getdata(nc, bs);
+ if (nr <= 0) {
+ # assume EOF
+ bs.data = bs.data[0:bs.edata];
+ bs.err = "";
+ hdr.length = bs.edata;
+ nc.connected = 0;
+ return 0;
+ }
+ }
+ bs.edata += nr;
+ G->progress <-= (bs.id, G->Phavedata, 100*bs.edata/len bs.data, "");
+ return 1;
+}
+
+Netconn.new(id: int) : ref Netconn
+{
+ return ref Netconn(
+ id, # id
+ "", # host
+ 0, # port
+ "", # scheme
+ sys->Connection(nil, nil, ""), # conn
+ nil, # ssl context
+ 0, # undetermined ssl version
+ NCfree, # state
+ array[10] of ref ByteSource, # queue
+ 0, # qlen
+ 0,0,0, # gocur, ngcur, reqsent
+ 0, # pipeline
+ 0, # connected
+ 0, # tstate
+ nil, # tbuf
+ 0 # idlestart
+ );
+}
+
+Netconn.makefree(nc: self ref Netconn)
+{
+ if(dbgproto)
+ sys->print("NC %d: free\n", nc.id);
+ nc.state = NCfree;
+ nc.host = "";
+ nc.conn.dfd = nil;
+ nc.conn.cfd = nil;
+ nc.conn.dir = "";
+ nc.qlen = 0;
+ nc.gocur = 0;
+ nc.ngcur = 0;
+ nc.reqsent = 0;
+ nc.pipeline = 0;
+ nc.connected = 0;
+ nc.tstate = 0;
+ nc.tbuf = nil;
+ for(i := 0; i < len nc.queue; i++)
+ nc.queue[i] = nil;
+}
+
+ByteSource.free(bs: self ref ByteSource)
+{
+ if(dbgproto)
+ sys->print("BS %d freed\n", bs.id);
+ if(bs.err == "")
+ G->progress <-= (bs.id, G->Pdone, 100, "");
+ else
+ G->progress <-= (bs.id, G->Perr, 0, bs.err);
+ bs.req = nil;
+ bs.hdr = nil;
+ bs.data = nil;
+ bs.err = "";
+ bs.net = nil;
+}
+
+# Return an ByteSource that is completely filled, from string s
+ByteSource.stringsource(s: string) : ref ByteSource
+{
+ a := array of byte s;
+ n := len a;
+ hdr := ref Header(
+ HCOk, # code
+ nil, # actual
+ nil, # base
+ nil, # location
+ n, # length
+ TextHtml, # mtype
+ "utf8", # chset
+ "", # msg
+ "", # refresh
+ "", # chal
+ "", # warn
+ "" # last-modified
+ );
+ bs := ref ByteSource(
+ bytesourceid++,
+ nil, # req
+ hdr, # hdr
+ a, # data
+ n, # edata
+ "", # err
+ nil, # net
+ 1, # refgo
+ 0, # refnc
+ 1, # eof - edata is final
+ 0, # lim
+ 1 # seenhdr
+ );
+ return bs;
+}
+
+MaskedImage.free(mim: self ref MaskedImage)
+{
+ mim.im = nil;
+ mim.mask = nil;
+}
+
+CImage.new(src: ref U->Parsedurl, lowsrc: ref U->Parsedurl, width, height: int) : ref CImage
+{
+ return ref CImage(src, lowsrc, nil, strhash(src.host + "/" + src.path), width, height, nil, nil, 0);
+}
+
+# Return true if Cimages a and b represent the same image.
+# As well as matching the src urls, the specified widths and heights must match too.
+# (Widths and heights are specified if at least one of those is not zero.)
+#
+# BUG: the width/height matching code isn't right. If one has width and height
+# specified, and the other doesn't, should say "don't match", because the unspecified
+# one should come in at its natural size. But we overwrite the width and height fields
+# when the actual size comes in, so we can't tell whether width and height are nonzero
+# because they were specified or because they're their natural size.
+CImage.match(a: self ref CImage, b: ref CImage) : int
+{
+ if(a.imhash == b.imhash) {
+ if(urlequal(a.src, b.src)) {
+ return (a.width == 0 || b.width == 0 || a.width == b.width) &&
+ (a.height == 0 || b.height == 0 || a.height == b.height);
+ # (above is not quite enough: should also check that don't have
+ # situation where one has width set, not height, and the other has reverse,
+ # but it is unusual for an image to have a spec in only one dimension anyway)
+ }
+ }
+ return 0;
+}
+
+# Return approximate number of bytes in image memory used
+# by ci.
+CImage.bytes(ci: self ref CImage) : int
+{
+ tot := 0;
+ for(i := 0; i < len ci.mims; i++) {
+ mim := ci.mims[i];
+ dim := mim.im;
+ if(dim != nil)
+ tot += ((dim.r.max.x-dim.r.min.x)*dim.depth/8) *
+ (dim.r.max.y-dim.r.min.y);
+ dim = mim.mask;
+ if(dim != nil)
+ tot += ((dim.r.max.x-dim.r.min.x)*dim.depth/8) *
+ (dim.r.max.y-dim.r.min.y);
+ }
+ return tot;
+}
+
+# Call this after initial windows have been made,
+# so that resetlimits() will exclude the images for those
+# windows from the available memory.
+ImageCache.init(ic: self ref ImageCache)
+{
+ ic.imhd = nil;
+ ic.imtl = nil;
+ ic.n = 0;
+ ic.memused = 0;
+ ic.resetlimits();
+}
+
+# Call resetlimits when amount of non-image-cache image
+# memory might have changed significantly (e.g., on main window resize).
+ImageCache.resetlimits(ic: self ref ImageCache)
+{
+ res := ResourceState.cur();
+ avail := res.imagelim - (res.image-ic.memused);
+ # (res.image-ic.memused) is used memory not in image cache
+ avail = 8*avail/10; # allow 20% slop for other applications, etc.
+ ic.memlimit = config.imagecachemem;
+ if(ic.memlimit > avail)
+ ic.memlimit = avail;
+# ic.nlimit = config.imagecachenum;
+ ic.nlimit = 10000; # let's try this
+ ic.need(0); # if resized, perhaps need to shed some images
+}
+
+# Look for a CImage matching ci, and if found, move it
+# to the tail position (i.e., MRU)
+ImageCache.look(ic: self ref ImageCache, ci: ref CImage) : ref CImage
+{
+ ans : ref CImage = nil;
+ prev : ref CImage = nil;
+ for(i := ic.imhd; i != nil; i = i.next) {
+ if(i.match(ci)) {
+ if(ic.imtl != i) {
+ # remove from current place in cache chain
+ # and put at tail
+ if(prev != nil)
+ prev.next = i.next;
+ else
+ ic.imhd = i.next;
+ i.next = nil;
+ ic.imtl.next = i;
+ ic.imtl = i;
+ }
+ ans = i;
+ break;
+ }
+ prev = i;
+ }
+ return ans;
+}
+
+# Call this to add ci as MRU of cache chain (should only call if
+# it is known that a ci with same image isn't already there).
+# Update ic.memused.
+# Assume ic.need has been called to ensure that neither
+# memlimit nor nlimit will be exceeded.
+ImageCache.add(ic: self ref ImageCache, ci: ref CImage)
+{
+ ci.next = nil;
+ if(ic.imhd == nil)
+ ic.imhd = ci;
+ else
+ ic.imtl.next = ci;
+ ic.imtl = ci;
+ ic.memused += ci.bytes();
+ ic.n++;
+}
+
+# Delete least-recently-used image in image cache
+# and update memused and n.
+ImageCache.deletelru(ic: self ref ImageCache)
+{
+ ci := ic.imhd;
+ if(ci != nil) {
+ ic.imhd = ci.next;
+ if(ic.imhd == nil) {
+ ic.imtl = nil;
+ ic.memused = 0;
+ }
+ else
+ ic.memused -= ci.bytes();
+ for(i := 0; i < len ci.mims; i++)
+ ci.mims[i].free();
+ ci.mims = nil;
+ ic.n--;
+ }
+}
+
+ImageCache.clear(ic: self ref ImageCache)
+{
+ while(ic.imhd != nil)
+ ic.deletelru();
+}
+
+# Call this just before allocating an Image that will used nbytes
+# of image memory, to ensure that if the image were to be
+# added to the image cache then memlimit and nlimit will be ok.
+# LRU images will be shed if necessary.
+# Return 0 if it will be impossible to make enough memory.
+ImageCache.need(ic: self ref ImageCache, nbytes: int) : int
+{
+ while(ic.n >= ic.nlimit || ic.memused+nbytes > ic.memlimit) {
+ if(ic.imhd == nil)
+ return 0;
+ ic.deletelru();
+ }
+ return 1;
+}
+
+strhash(s: string) : int
+{
+ prime: con 8388617;
+ hash := 0;
+ n := len s;
+ for(i := 0; i < n; i++) {
+ hash = hash % prime;
+ hash = (hash << 7) + s[i];
+ }
+ return hash;
+}
+
+schemeid(s: string): int
+{
+ for (i := 0; i < len schemes; i++) {
+ (n, id) := schemes[i];
+ if (n == s)
+ return id;
+ }
+ return -1;
+}
+
+schemeok(s: string): int
+{
+ return schemeid(s) != -1;
+}
+
+gettransport(scheme: string) : (Transport, string)
+{
+ err := "";
+ transport: Transport = nil;
+ tindex := schemeid(scheme);
+ if (tindex == -1)
+ return (nil, "Unknown scheme");
+ transport = transports[tindex];
+ if (transport == nil) {
+ transport = load Transport loadpath(tpaths[tindex]);
+ if(transport == nil)
+ return (nil, sys->sprint("Can't load transport %s: %r", tpaths[tindex]));
+ transport->init(me);
+ transports[tindex] = transport;
+ }
+ return (transport, err);
+}
+
+# Return new Header with default values for fields
+Header.new() : ref Header
+{
+ return ref Header(
+ HCOk, # code
+ nil, # actual
+ nil, # base
+ nil, # location
+ -1, # length
+ UnknownType, # mtype
+ nil, # chset
+ "", # msg
+ "", # refresh
+ "", # chal
+ "", # warn
+ "" # last-modified
+ );
+}
+
+jpmagic := array[] of {byte 16rFF, byte 16rD8, byte 16rFF, byte 16rE0,
+ byte 0, byte 0, byte 'J', byte 'F', byte 'I', byte 'F', byte 0};
+pngsig := array[] of { byte 137, byte 80, byte 78, byte 71, byte 13, byte 10, byte 26, byte 10 };
+
+# Set the mtype (and possibly chset) fields of h based on (in order):
+# first bytes of file, if unambigous
+# file name extension
+# first bytes of file, even if unambigous (guess)
+# if all else fails, then leave as UnknownType.
+# If it's a text type, also set the chset.
+# (HTTP Transport will try to use Content-Type first, and call this if that
+# doesn't work; other Transports will have to rely on this "guessing" function.)
+Header.setmediatype(h: self ref Header, name: string, first: array of byte)
+{
+ # Look for key signatures at beginning of file (perhaps after whitespace)
+ n := len first;
+ mt := UnknownType;
+ for(i := 0; i < n; i++)
+ if(ctype[int first[i]] != C->W)
+ break;
+ if(n - i >= 6) {
+ s := string first[i:i+6];
+ case S->tolower(s) {
+ "<html " or "<html\t" or "<html>" or "<head>" or "<title" =>
+ mt = TextHtml;
+ "<!doct" =>
+ if(n - i >= 14 && string first[i+6:i+14] == "ype html")
+ mt = TextHtml;
+ "gif87a" or "gif89a" =>
+ if(i == 0)
+ mt = ImageGif;
+ "#defin" =>
+ # perhaps should check more definitively...
+ mt = ImageXXBitmap;
+ }
+
+ if (mt == UnknownType && n > 0) {
+ if (first[0] == jpmagic[0] && n >= len jpmagic) {
+ for(i++; i<len jpmagic; i++)
+ if(jpmagic[i]>byte 0 && first[i]!=jpmagic[i])
+ break;
+ if (i == len jpmagic)
+ mt = ImageJpeg;
+ } else if (first[0] == pngsig[0] && n >= len pngsig) {
+ for(i++; i<len pngsig; i++)
+ if (first[i] != pngsig[i])
+ break;
+ if (i == len pngsig)
+ mt = ImagePng;
+ }
+ }
+ }
+
+ if(mt == UnknownType) {
+ # Try file name extension
+ (nil, file) := S->splitr(name, "/");
+ if(file != "") {
+ (f, ext) := S->splitr(file, ".");
+ if(f != "" && ext != "") {
+ (fnd, val) := T->lookup(fileexttable, S->tolower(ext));
+ if(fnd)
+ mt = val;
+ }
+ }
+ }
+
+# if(mt == UnknownType) {
+# mt = TextPlain;
+# h.chset = "utf8";
+# }
+ h.mtype = mt;
+}
+
+Header.print(h: self ref Header)
+{
+ mtype := "?";
+ if(h.mtype >= 0 && h.mtype < len mnames)
+ mtype = mnames[h.mtype];
+ chset := "?";
+ if(h.chset != nil)
+ chset = h.chset;
+ # sys->print("code=%d (%s) length=%d mtype=%s chset=%s\n",
+ # h.code, hcphrase(h.code), h.length, mtype, chset);
+ if(h.base != nil)
+ sys->print(" base=%s\n", h.base.tostring());
+ if(h.location != nil)
+ sys->print(" location=%s\n", h.location.tostring());
+ if(h.refresh != "")
+ sys->print(" refresh=%s\n", h.refresh);
+ if(h.chal != "")
+ sys->print(" chal=%s\n", h.chal);
+ if(h.warn != "")
+ sys->print(" warn=%s\n", h.warn);
+}
+
+
+mfd : ref sys->FD = nil;
+ResourceState.cur() : ResourceState
+{
+ ms := sys->millisec();
+ main := 0;
+ mainlim := 0;
+ heap := 0;
+ heaplim := 0;
+ image := 0;
+ imagelim := 0;
+ if(mfd == nil)
+ mfd = sys->open("/dev/memory", sys->OREAD);
+ if (mfd == nil)
+ raisex(sys->sprint("can't open /dev/memory: %r"));
+
+ sys->seek(mfd, big 0, Sys->SEEKSTART);
+
+ buf := array[400] of byte;
+ n := sys->read(mfd, buf, len buf);
+ if (n <= 0)
+ raisex(sys->sprint("can't read /dev/memory: %r"));
+
+ (nil, l) := sys->tokenize(string buf[0:n], "\n");
+ # p->cursize, p->maxsize, p->hw, p->nalloc, p->nfree, p->nbrk, poolmax(p), p->name)
+ while(l != nil) {
+ s := hd l;
+ cur_size := int s[0:12];
+ max_size := int s[12:24];
+ case s[7*12:] {
+ "main" =>
+ main = cur_size;
+ mainlim = max_size;
+ "heap" =>
+ heap = cur_size;
+ heaplim = max_size;
+ "image" =>
+ image = cur_size;
+ imagelim = max_size;
+ }
+ l = tl l;
+ }
+
+ return ResourceState(ms, main, mainlim, heap, heaplim, image, imagelim);
+}
+
+ResourceState.since(rnew: self ResourceState, rold: ResourceState) : ResourceState
+{
+ return (rnew.ms - rold.ms,
+ rnew.main - rold.main,
+ rnew.heaplim,
+ rnew.heap - rold.heap,
+ rnew.heaplim,
+ rnew.image - rold.image,
+ rnew.imagelim);
+}
+
+ResourceState.print(r: self ResourceState, msg: string)
+{
+ sys->print("%s:\n\ttime: %d.%#.3ds; memory: main %dk, mainlim %dk, heap %dk, heaplim %dk, image %dk, imagelim %dk\n",
+ msg, r.ms/1000, r.ms % 1000, r.main / 1024, r.mainlim / 1024,
+ r.heap / 1024, r.heaplim / 1024, r.image / 1024, r.imagelim / 1024);
+}
+
+# Decide what to do based on Header and whether this is
+# for the main entity or not, and the number of redirections-so-far.
+# Return tuple contains:
+# (use, error, challenge, redir)
+# and action to do is:
+# If use==1, use the entity else drain its byte source.
+# If error != nil, mesg was put in progress bar
+# If challenge != nil, get auth info and make new request with auth
+# Else if redir != nil, make a new request with redir for url
+#
+# (if challenge or redir is non-nil, use will be 0)
+hdraction(bs: ref ByteSource, ismain: int, nredirs: int) : (int, string, string, ref U->Parsedurl)
+{
+ use := 1;
+ error := "";
+ challenge := "";
+ redir : ref U->Parsedurl = nil;
+
+ h := bs.hdr;
+ assert(h != nil);
+ bs.seenhdr = 1;
+ code := h.code;
+ case code/100 {
+ HSOk =>
+ if(code != HCOk)
+ error = "unexpected code: " + hcphrase(code);
+ HSRedirect =>
+ if(h.location != nil) {
+ redir = h.location;
+ # spec says url should be absolute, but some
+ # sites give relative ones
+ if(redir.scheme == nil)
+ redir = U->mkabs(redir, h.base);
+ if(dbg)
+ sys->print("redirect %s to %s\n", h.actual.tostring(), redir.tostring());
+ if(nredirs >= Maxredir) {
+ redir = nil;
+ error = "probable redirect loop";
+ }
+ else
+ use = 0;
+ }
+ HSError =>
+ if(code == HCUnauthorized && h.chal != "") {
+ challenge = h.chal;
+ use = 0;
+ }
+ else {
+ error = hcphrase(code);
+ use = ismain;
+ }
+ HSServererr =>
+ error = hcphrase(code);
+ use = ismain;
+ * =>
+ error = "unexpected code: " + string code;
+ use = 0;
+
+ }
+ if(error != "")
+ G->progress <-= (bs.id, G->Perr, 0, error);
+ return (use, error, challenge, redir);
+}
+
+# Use event when only care about time stamps on events
+event(s: string, data: int)
+{
+ sys->print("%s: %d %d\n", s, sys->millisec()-startres.ms, data);
+}
+
+kill(pid: int, dogroup: int)
+{
+ msg : array of byte;
+ if(dogroup)
+ msg = array of byte "killgrp";
+ else
+ msg = array of byte "kill";
+ ctl := sys->open("#p/" + string pid + "/ctl", sys->OWRITE);
+ if(ctl != nil)
+ if (sys->write(ctl, msg, len msg) < 0)
+ sys->print("charon: kill write failed (pid %d, grp %d): %r\n", pid, dogroup);
+}
+
+# Read a line up to and including cr/lf (be tolerant and allow missing cr).
+# Look first in buf[bstart:bend], and if that isn't sufficient to get whole line,
+# refill buf from fd as needed.
+# Return values:
+# array of byte: the line, not including cr/lf
+# eof, true if there was no line to get or a read error
+# bstart', bend': new valid portion of buf (after cr/lf).
+getline(fd: ref sys->FD, buf: array of byte, bstart, bend: int) :
+ (array of byte, int, int, int)
+{
+ ans : array of byte = nil;
+ last : array of byte = nil;
+ eof := 0;
+mainloop:
+ for(;;) {
+ for(i := bstart; i < bend; i++) {
+ if(buf[i] == byte '\n') {
+ k := i;
+ if(k > bstart && buf[k-1] == byte '\r')
+ k--;
+ last = buf[bstart:k];
+ bstart = i+1;
+ break mainloop;
+ }
+ }
+ if(bend > bstart)
+ ans = append(ans, buf[bstart:bend]);
+ last = nil;
+ bstart = 0;
+ bend = sys->read(fd, buf, len buf);
+ if(bend <= 0) {
+ eof = 1;
+ bend = 0;
+ break mainloop;
+ }
+ }
+ return (append(ans, last), eof, bstart, bend);
+}
+
+# Append copy of second array to first, return (possibly new)
+# address of the concatenation.
+append(a: array of byte, b: array of byte) : array of byte
+{
+ if(b == nil)
+ return a;
+ na := len a;
+ nb := len b;
+ ans := realloc(a, nb);
+ ans[na:] = b;
+ return ans;
+}
+
+# Return copy of a, but incr bytes bigger
+realloc(a: array of byte, incr: int) : array of byte
+{
+ n := len a;
+ newa := array[n + incr] of byte;
+ if(a != nil)
+ newa[0:] = a;
+ return newa;
+}
+
+# Look (linearly) through a for s; return its index if found, else -1.
+strlookup(a: array of string, s: string) : int
+{
+ n := len a;
+ for(i := 0; i < n; i++)
+ if(s == a[i])
+ return i;
+ return -1;
+}
+
+# Set up config global to defaults, then try to read user-specifiic
+# config data from /usr/<username>/charon/config, then try to
+# override from command line arguments.
+setconfig(argl: list of string)
+{
+ # Defaults, in absence of any other information
+ config.userdir = "";
+ config.srcdir = "/appl/cmd/charon";
+ config.starturl = "file:/services/webget/start.html";
+ config.homeurl = config.starturl;
+ config.change_homeurl = 1;
+ config.helpurl = "file:/services/webget/help.html";
+ config.usessl = SSLV3; # was NOSSL
+ config.devssl = 0;
+ config.custbkurl = "/services/config/bookmarks.html";
+ config.dualbkurl = "/services/config/dualdisplay.html";
+ config.httpproxy = nil;
+ config.noproxydoms = nil;
+ config.buttons = "help,resize,hide,exit";
+ config.framework = "all";
+ config.defaultwidth = 640;
+ config.defaultheight = 480;
+ config.x = -1;
+ config.y = -1;
+ config.nocache = 0;
+ config.maxstale = 0;
+ config.imagelvl = ImgFull;
+ config.imagecachenum = 120;
+ config.imagecachemem = 100000000; # 100Meg, will get lowered later
+ config.docookies = 1;
+ config.doscripts = 1;
+ config.httpminor = 0;
+ config.agentname = "Mozilla/4.08 (Charon; Inferno)";
+ config.nthreads = 4;
+ config.offersave = 1;
+ config.charset = "windows-1252";
+ config.plumbport = "web";
+ config.wintitle = "Charon"; # tkclient->titlebar() title, used by GUI
+ config.dbgfile = "";
+ config.dbg = array[128] of { * => byte 0 };
+
+ # Reading default config file
+ readconf("/services/config/charon.cfg");
+
+ # Try reading user config file
+ user := "";
+ fd := sys->open("/dev/user", sys->OREAD);
+ if(fd != nil) {
+ b := array[40] of byte;
+ n := sys->read(fd, b, len b);
+ if(n > 0)
+ user = string b[0:n];
+ }
+ if(user != "") {
+ config.userdir = "/usr/" + user + "/charon";
+ readconf(config.userdir + "/config");
+ }
+
+ if(argl == nil)
+ return;
+ # Try command line arguments
+ # All should be 'key=val' or '-key' or '-key val', except last which can be url to start
+ for(l := tl argl; l != nil; l = tl l) {
+ s := hd l;
+ if(s == "")
+ continue;
+ if (s[0] != '-')
+ break;
+ a := s[1:];
+ b := "";
+ if(tl l != nil) {
+ b = hd tl l;
+ if(S->prefix("-", b))
+ b = "";
+ else
+ l = tl l;
+ }
+ if(!setopt(a, b)) {
+ if (b != nil)
+ s += " "+b;
+ sys->print("couldn't set option from arg '%s'\n", s);
+ }
+ }
+ if(l != nil) {
+ if (tl l != nil)
+ # usage error
+ sys->print("too many URL's\n");
+ else
+ if(!setopt("starturl", hd l))
+ sys->print("couldn't set starturl from arg '%s'\n", hd l);
+ }
+}
+
+readconf(fname: string)
+{
+ cfgio := sys->open(fname, sys->OREAD);
+ if(cfgio != nil) {
+ buf := array[sys->ATOMICIO] of byte;
+ i := 0;
+ j := 0;
+ aline : array of byte;
+ eof := 0;
+ for(;;) {
+ (aline, eof, i, j) = getline(cfgio, buf, i, j);
+ if(eof)
+ break;
+ line := string aline;
+ if(len line == 0 || line[0]=='#')
+ continue;
+ (key, val) := S->splitl(line, " \t=");
+ if(key != "") {
+ val = S->take(S->drop(val, " \t="), "^#\r\n");
+ if(!setopt(key, val))
+ sys->print("couldn't set option from line '%s'\n", line);
+ }
+ }
+ }
+}
+
+# Set config option named 'key' to val, returning 1 if OK
+setopt(key: string, val: string) : int
+{
+ ok := 1;
+ if(val == "none")
+ val = "";
+ v := int val;
+ case key {
+ "userdir" =>
+ config.userdir = val;
+ "srcdir" =>
+ config.srcdir = val;
+ "starturl" =>
+ if(val != "")
+ config.starturl = val;
+ else
+ ok = 0;
+ "change_homeurl" =>
+ config.change_homeurl = v;
+ "homeurl" =>
+ if(val != "")
+ if(config.change_homeurl) {
+ config.homeurl = val;
+ # order dependent
+ config.starturl = config.homeurl;
+ }
+ else
+ ok = 0;
+ "helpurl" =>
+ if(val != "")
+ config.helpurl = val;
+ else
+ ok = 0;
+ "usessl" =>
+ if(val == "v2")
+ config.usessl |= SSLV2;
+ if(val == "v3")
+ config.usessl |= SSLV3;
+ "devssl" =>
+ if(v == 0)
+ config.devssl = 0;
+ else
+ config.devssl = 1;
+# "custbkurl" =>
+# "dualbkurl" =>
+ "httpproxy" =>
+ if(val != "")
+ config.httpproxy = makeabsurl(val);
+ else
+ config.httpproxy = nil;
+ "noproxy" or "noproxydoms" =>
+ (nil, config.noproxydoms) = sys->tokenize(val, ";, \t");
+ "buttons" =>
+ config.buttons = S->tolower(val);
+ "framework" =>
+ config.framework = S->tolower(val);
+ "defaultwidth" or "width" =>
+ if(v > 200)
+ config.defaultwidth = v;
+ else
+ ok = 0;
+ "defaultheight" or "height" =>
+ if(v > 100)
+ config.defaultheight = v;
+ else
+ ok = 0;
+ "x" =>
+ config.x = v;
+ "y" =>
+ config.y = v;
+ "nocache" =>
+ config.nocache = v;
+ "maxstale" =>
+ config.maxstale = v;
+ "imagelvl" =>
+ config.imagelvl = v;
+ "imagecachenum" =>
+ config.imagecachenum = v;
+ "imagecachemem" =>
+ config.imagecachemem = v;
+ "docookies" =>
+ config.docookies = v;
+ "doscripts" =>
+ config.doscripts = v;
+ "http" =>
+ if(val == "1.1")
+ config.httpminor = 1;
+ else
+ config.httpminor = 0;
+ "agentname" =>
+ config.agentname = val;
+ "nthreads" =>
+ if (v < 1)
+ ok = 0;
+ else
+ config.nthreads = v;
+ "offersave" =>
+ if (v < 1)
+ config.offersave = 0;
+ else
+ config.offersave = 1;
+ "charset" =>
+ config.charset = val;
+ "plumbport" =>
+ config.plumbport = val;
+ "wintitle" =>
+ config.wintitle = val;
+ "dbgfile" =>
+ config.dbgfile = val;
+ "dbg" =>
+ for(i := 0; i < len val; i++) {
+ c := val[i];
+ if(c < len config.dbg)
+ config.dbg[c]++;
+ else {
+ ok = 0;
+ break;
+ }
+ }
+ * =>
+ ok = 0;
+ }
+ return ok;
+}
+
+saveconfig(): int
+{
+ fname := config.userdir + "/config";
+ buf := array [Sys->ATOMICIO] of byte;
+ fd := sys->create(fname, Sys->OWRITE, 8r600);
+ if(fd == nil)
+ return -1;
+
+ nbyte := savealine(fd, buf, "# Charon user configuration\n", 0);
+ nbyte = savealine(fd, buf, "userdir=" + config.userdir + "\n", nbyte);
+ nbyte = savealine(fd, buf, "srcdir=" + config.srcdir +"\n", nbyte);
+ if(config.change_homeurl){
+ nbyte = savealine(fd, buf, "starturl=" + config.starturl + "\n", nbyte);
+ nbyte = savealine(fd, buf, "homeurl=" + config.homeurl + "\n", nbyte);
+ }
+ if(config.httpproxy != nil)
+ nbyte = savealine(fd, buf, "httpproxy=" + config.httpproxy.tostring() + "\n", nbyte);
+ if(config.usessl & SSLV23) {
+ nbyte = savealine(fd, buf, "usessl=v2\n", nbyte);
+ nbyte = savealine(fd, buf, "usessl=v3\n", nbyte);
+ }
+ else {
+ if(config.usessl & SSLV2)
+ nbyte = savealine(fd, buf, "usessl=v2\n", nbyte);
+ if(config.usessl & SSLV3)
+ nbyte = savealine(fd, buf, "usessl=v3\n", nbyte);
+ }
+ if(config.devssl == 0)
+ nbyte = savealine(fd, buf, "devssl=0\n", nbyte);
+ else
+ nbyte = savealine(fd, buf, "devssl=1\n", nbyte);
+ if(config.noproxydoms != nil) {
+ doms := "";
+ doml := config.noproxydoms;
+ while(doml != nil) {
+ doms += hd doml + ",";
+ doml = tl doml;
+ }
+ nbyte = savealine(fd, buf, "noproxy=" + doms + "\n", nbyte);
+ }
+ nbyte = savealine(fd, buf, "defaultwidth=" + string config.defaultwidth + "\n", nbyte);
+ nbyte = savealine(fd, buf, "defaultheight=" + string config.defaultheight + "\n", nbyte);
+ if(config.x >= 0)
+ nbyte = savealine(fd, buf, "x=" + string config.x + "\n", nbyte);
+ if(config.y >= 0)
+ nbyte = savealine(fd, buf, "y=" + string config.y + "\n", nbyte);
+ nbyte = savealine(fd, buf, "nocache=" + string config.nocache + "\n", nbyte);
+ nbyte = savealine(fd, buf, "maxstale=" + string config.maxstale + "\n", nbyte);
+ nbyte = savealine(fd, buf, "imagelvl=" + string config.imagelvl + "\n", nbyte);
+ nbyte = savealine(fd, buf, "imagecachenum=" + string config.imagecachenum + "\n", nbyte);
+ nbyte = savealine(fd, buf, "imagecachemem=" + string config.imagecachemem + "\n", nbyte);
+ nbyte = savealine(fd, buf, "docookies=" + string config.docookies + "\n", nbyte);
+ nbyte = savealine(fd, buf, "doscripts=" + string config.doscripts + "\n", nbyte);
+ nbyte = savealine(fd, buf, "http=" + "1." + string config.httpminor + "\n", nbyte);
+ nbyte = savealine(fd, buf, "agentname=" + string config.agentname + "\n", nbyte);
+ nbyte = savealine(fd, buf, "nthreads=" + string config.nthreads + "\n", nbyte);
+ nbyte = savealine(fd, buf, "charset=" + config.charset + "\n", nbyte);
+ #for(i := 0; i < len config.dbg; i++)
+ #nbyte = savealine(fd, buf, "dbg=" + string config.dbg[i] + "\n", nbyte);
+
+ if(nbyte > 0)
+ sys->write(fd, buf, nbyte);
+
+ return 0;
+}
+
+savealine(fd: ref Sys->FD, buf: array of byte, s: string, n: int): int
+{
+ if(Sys->ATOMICIO < n + len s) {
+ sys->write(fd, buf, n);
+ buf[0:] = array of byte s;
+ return len s;
+ }
+ buf[n:] = array of byte s;
+ return n + len s;
+}
+
+# Make a StringInt table out of a, mapping each string
+# to its index. Check that entries are in alphabetical order.
+makestrinttab(a: array of string) : array of T->StringInt
+{
+ n := len a;
+ ans := array[n] of T->StringInt;
+ for(i := 0; i < n; i++) {
+ ans[i].key = a[i];
+ ans[i].val = i;
+ if(i > 0 && a[i] < a[i-1])
+ raisex("EXInternal: table out of alphabetical order");
+ }
+ return ans;
+}
+
+# Should really move into Url module.
+# Don't include fragment in test, since we are testing if the
+# pointed to docs are the same, not places within docs.
+urlequal(a, b: ref U->Parsedurl) : int
+{
+ return a.scheme == b.scheme
+ && a.host == b.host
+ && a.port == b.port
+ && a.user == b.user
+ && a.passwd == b.passwd
+ && a.path == b.path
+ && a.query == b.query;
+}
+
+# U->makeurl, but add http:// if not an absolute path already
+makeabsurl(s: string) : ref Parsedurl
+{
+ if (s == "")
+ return nil;
+ u := U->parse(s);
+ if (u.scheme != nil)
+ return u;
+ if (s[0] == '/')
+ # try file:
+ s = "file://localhost" + s;
+ else
+ # try http
+ s = "http://" + s;
+ u = U->parse(s);
+ return u;
+}
+
+# Return place to load from, given installed-path name.
+# (If config.dbg['u'] is set, change directory to config.srcdir.)
+loadpath(s: string) : string
+{
+ if(config.dbg['u'] == byte 0)
+ return s;
+ (nil, f) := S->splitr(s, "/");
+ return config.srcdir + "/" + f;
+}
+
+color_tab := array[] of { T->StringInt
+ ("aqua", 16r00FFFF),
+ ("black", Black),
+ ("blue", Blue),
+ ("fuchsia", 16rFF00FF),
+ ("gray", 16r808080),
+ ("green", 16r008000),
+ ("lime", 16r00FF00),
+ ("maroon", 16r800000),
+ ("navy", Navy),
+ ("olive", 16r808000),
+ ("purple", 16r800080),
+ ("red", Red),
+ ("silver", 16rC0C0C0),
+ ("teal", 16r008080),
+ ("white", White),
+ ("yellow", 16rFFFF00)
+};
+# Convert HTML color spec to RGB value, returning dflt if can't.
+# Argument is supposed to be a valid HTML color, or "".
+# Return the RGB value of the color, using dflt if s
+# is "" or an invalid color.
+color(s: string, dflt: int) : int
+{
+ if(s == "")
+ return dflt;
+ s = S->tolower(s);
+ c := s[0];
+ if(c < C->NCTYPE && ctype[c] == C->L) {
+ (fnd, v) := T->lookup(color_tab, s);
+ if(fnd)
+ return v;
+ }
+ if(s[0] == '#')
+ s = s[1:];
+ (v, rest) := S->toint(s, 16);
+ if(rest == "")
+ return v;
+ # s was invalid, so choose a valid one
+ return dflt;
+}
+
+max(a,b: int) : int
+{
+ if(a > b)
+ return a;
+ return b;
+}
+
+min(a,b: int) : int
+{
+ if(a < b)
+ return a;
+ return b;
+}
+
+raisex(e: string)
+{
+ raise e;
+}
+
+assert(i: int)
+{
+ if(!i) {
+ raisex("EXInternal: assertion failed");
+# sys->print("assertion failed\n");
+# s := hmeth[-1];
+ }
+}
+
+getcookies(host, path: string, secure: int): string
+{
+ if (CK == nil || ckclient == nil)
+ return nil;
+ Client: import CK;
+ return ckclient.getcookies(host, path, secure);
+}
+
+setcookie(host, path, cookie: string)
+{
+ if (CK == nil || ckclient == nil)
+ return;
+ Client: import CK;
+ ckclient.set(host, path, cookie);
+}
+
+ex_mkdir(dirname: string): int
+{
+ (ok, nil) := sys->stat(dirname);
+ if(ok < 0) {
+ f := sys->create(dirname, sys->OREAD, sys->DMDIR + 8r777);
+ if(f == nil) {
+ sys->print("mkdir: can't create %s: %r\n", dirname);
+ return 0;
+ }
+ f = nil;
+ }
+ return 1;
+}
+
+stripscript(s: string): string
+{
+ # strip leading whitespace and SGML comment start symbol '<!--'
+ if (s == nil)
+ return nil;
+ cs := "<!--";
+ ci := 0;
+ for (si := 0; si < len s; si++) {
+ c := s[si];
+ if (c == cs[ci]) {
+ if (++ci >= len cs)
+ ci = 0;
+ } else {
+ ci = 0;
+ if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
+ continue;
+ break;
+ }
+ }
+ # strip trailing whitespace and SGML comment terminator '-->'
+ cs = "-->";
+ ci = len cs -1;
+ for (se := len s - 1; se > si; se--) {
+ c := s[se];
+ if (c == cs[ci]) {
+ if (ci-- == 0)
+ ci = len cs -1;
+ } else {
+ ci = len cs - 1;
+ if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
+ continue;
+ break;
+ }
+ }
+ if (se < si)
+ return nil;
+ return s[si:se+1];
+}
+
+# Split a value (guaranteed trimmed) into sep-separated list of one of
+# token
+# token = token
+# token = "quoted string"
+# and put into list of Namevals (lowercase the first token)
+Nameval.namevals(s: string, sep: int) : list of Nameval
+{
+ ans : list of Nameval = nil;
+ n := len s;
+ i := 0;
+ while(i < n) {
+ tok : string;
+ (tok, i) = gettok(s, i, n);
+ if(tok == "")
+ break;
+ tok = S->tolower(tok);
+ val := "";
+ while(i < n && ctype[s[i]] == C->W)
+ i++;
+ if(i == n || s[i] == sep)
+ i++;
+ else if(s[i] == '=') {
+ i++;
+ while(i < n && ctype[s[i]] == C->W)
+ i++;
+ if (i == n)
+ break;
+ if(s[i] == '"')
+ (val, i) = getqstring(s, i, n);
+ else
+ (val, i) = gettok(s, i, n);
+ }
+ else
+ break;
+ ans = Nameval(tok, val) :: ans;
+ }
+ return ans;
+}
+
+gettok(s: string, i,n: int) : (string, int)
+{
+ while(i < n && ctype[s[i]] == C->W)
+ i++;
+ if(i == n)
+ return ("", i);
+ is := i;
+ for(; i < n; i++) {
+ c := s[i];
+ ct := ctype[c];
+ if(!(int (ct&(C->D|C->L|C->U|C->N|C->S))))
+ if(int (ct&(C->W|C->C)) || S->in(c, "()<>@,;:\\\"/[]?={}"))
+ break;
+ }
+ return (s[is:i], i);
+}
+
+# get quoted string; return it without quotes, and index after it
+getqstring(s: string, i,n: int) : (string, int)
+{
+ while(i < n && ctype[s[i]] == C->W)
+ i++;
+ if(i == n || s[i] != '"')
+ return ("", i);
+ is := ++i;
+ for(; i < n; i++) {
+ c := s[i];
+ if(c == '\\')
+ i++;
+ else if(c == '"')
+ return (s[is:i], i+1);
+ }
+ return (s[is:i], i);
+}
+
+# Find value corresponding to key (should be lowercase)
+# and return (1, value) if found or (0, "")
+Nameval.find(l: list of Nameval, key: string) : (int, string)
+{
+ for(; l != nil; l = tl l)
+ if((hd l).key == key)
+ return (1, (hd l).val);
+ return (0, "");
+}
+
+# this should be a converter cache
+getconv(chset : string) : Btos
+{
+ (btos, err) := convcs->getbtos(chset);
+ if (err != nil)
+ sys->print("Converter error: %s\n", err);
+ return btos;
+}
+
+X(s, note : string) : string
+{
+ if (dict == nil)
+ return s;
+ return dict.xlaten(s, note);
+}
diff --git a/appl/charon/chutils.m b/appl/charon/chutils.m
new file mode 100644
index 00000000..8d7d3a8c
--- /dev/null
+++ b/appl/charon/chutils.m
@@ -0,0 +1,371 @@
+CharonUtils: module
+{
+ PATH: con "/dis/charon/chutils.dis";
+
+ # Modules for everyone to share
+ C: Ctype;
+ E: Events;
+ G: Gui;
+ L: Layout;
+ I: Img;
+ B: Build;
+ LX: Lex;
+ J: Script;
+ CH: Charon;
+ CK: Cookiesrv;
+
+ # HTTP methods
+ HGet, HPost : con iota;
+ hmeth: array of string;
+
+ # Media types (must track mnames in chutils.b)
+ ApplMsword, ApplOctets, ApplPdf, ApplPostscript, ApplRtf,
+ ApplFramemaker, ApplMsexcel, ApplMspowerpoint, UnknownType,
+
+ Audio32kadpcm, AudioBasic,
+
+ ImageCgm, ImageG3fax, ImageGif, ImageIef, ImageJpeg, ImagePng, ImageTiff,
+ ImageXBit, ImageXBit2, ImageXBitmulti, ImageXInfernoBit, ImageXXBitmap,
+
+ ModelVrml,
+
+ MultiDigest, MultiMixed,
+
+ TextCss, TextEnriched, TextHtml, TextJavascript, TextPlain, TextRichtext,
+ TextSgml, TextTabSeparatedValues, TextXml,
+
+ VideoMpeg, VideoQuicktime : con iota;
+
+ mnames: array of string;
+
+ # Netconn states
+ NCfree, NCidle, NCconnect, NCgethdr, NCgetdata,
+ NCdone, NCerr : con iota;
+
+ ncstatenames: array of string;
+
+ # Netcomm synch protocol values
+ NGstartreq, NGwaitreq, NGstatechg, NGfreebs : con iota;
+
+ # Colors
+ White: con 16rFFFFFF;
+ Black: con 16r000000;
+ Grey: con 16rdddddd;
+ DarkGrey: con 16r9d9d9d;
+ LightGrey: con 16rededed;
+ Blue: con 16r0000CC;
+ Navy: con 16r000080;
+ Red: con 16rFF0000;
+ Green: con 16r00FF00;
+ DarkRed: con 16r9d0000;
+
+ # Header major status values (code/100)
+ HSNone, HSInformation, HSOk, HSRedirect, HSError, HSServererr : con iota;
+ hsnames: array of string;
+
+ # Individual status code values (HTTP, but use for other transports too)
+ HCContinue: con 100;
+ HCSwitchProto: con 101;
+ HCOk: con 200;
+ HCCreated: con 201;
+ HCAccepted: con 202;
+ HCOkNonAuthoritative: con 203;
+ HCNoContent: con 204;
+ HCResetContent: con 205;
+ HCPartialContent: con 206;
+ HCMultipleChoices: con 300;
+ HCMovedPerm: con 301;
+ HCMovedTemp: con 302;
+ HCSeeOther: con 303;
+ HCNotModified: con 304;
+ HCUseProxy: con 305;
+ HCBadRequest: con 400;
+ HCUnauthorized: con 401;
+ HCPaymentRequired: con 402;
+ HCForbidden: con 403;
+ HCNotFound: con 404;
+ HCMethodNotAllowed: con 405;
+ HCNotAcceptable: con 406;
+ HCProxyAuthRequired: con 407;
+ HCRequestTimeout: con 408;
+ HCConflict: con 409;
+ HCGone: con 410;
+ HCLengthRequired: con 411;
+ HCPreconditionFailed: con 412;
+ HCRequestTooLarge: con 413;
+ HCRequestURITooLarge: con 414;
+ HCUnsupportedMedia: con 415;
+ HCRangeInvalid: con 416;
+ HCExpectFailed: con 419;
+ HCServerError: con 500;
+ HCNotImplemented: con 501;
+ HCBadGateway: con 502;
+ HCServiceUnavailable: con 503;
+ HCGatewayTimeout: con 504;
+ HCVersionUnsupported: con 505;
+ HCRedirectionFailed: con 506;
+
+ # Max number of redirections tolerated
+ Maxredir : con 10;
+
+ # Image Level config options
+ ImgNone, ImgNoAnim, ImgProgressive, ImgFull: con iota;
+
+ # SSL connection version
+ NOSSL, SSLV2, SSLV3, SSLV23: con iota;
+
+ # User Configuration Information (Options)
+ # Debug option letters:
+ # 'd' -> Basic operation info (navigation, etc.)
+ # 'e' -> Events (timing of progress through get/layout/image conversion)
+ # 'h' -> Build layout items from lex tokens
+ # 'i' -> Image conversion
+ # 'l' -> Layout
+ # 'n' -> transport (Network access)
+ # 'o' -> always use old http (http/1.0)
+ # 'p' -> synch Protocol between ByteSource/Netconn
+ # 'r' -> Resource usage
+ # 's' -> Scripts
+ # 't' -> Table layout
+ # 'u' -> use Uninstalled dis modules
+ # 'w' -> Warn about recoverable problems in retrieved pages
+ # 'x -> lex Html tokens
+ Config: adt
+ {
+ userdir: string; # where to find bookmarks, cache, etc.
+ srcdir: string; # where to find charon src (for debugging)
+ starturl: string;# never nil (could be last of command args)
+ change_homeurl: int;
+ homeurl: string;# never nil
+ helpurl: string;
+ usessl: int; # use ssl version 2, 3 or both
+ devssl: int; # use devssl
+ custbkurl: string; # where are customized bookmarks-never nil
+ dualbkurl: string; # where is the dual bookmark page-never nil
+ httpproxy: ref Url->Parsedurl;# nil, if no proxy
+ noproxydoms: list of string; # domains that don't require proxy
+ buttons: string; # customized buttons
+ framework: string; # customized gui framework
+ defaultwidth: int; # of entire browser
+ defaultheight: int; # of entire browser
+ x: int; # initial x position for browser
+ y: int; # initial y position for browser
+ nocache: int; # true if shouldn't retrieve from or store to
+ maxstale: int; # allow cache hit even if exceed expiration by maxstale
+ imagelvl: int; # ImgNone, etc.
+ imagecachenum: int; # imcache.nlimit
+ imagecachemem: int; # imcache.memlimit
+ docookies: int; # allow cookie storage/sending?
+ doscripts: int; # allow scripts to execute?
+ httpminor: int; # use HTTP 1.httpminor
+ agentname: string; # what to send in HTTP header
+ nthreads: int; # number of simultaneous gets allowed
+ offersave: int; # offer to save a file of a type that can't be handled
+ charset: string; # default character set
+ plumbport: string; # from/to plumbing port name (default = "web")
+ wintitle: string;
+ dbgfile: string; # file to write debug messages to
+ dbg: array of byte; # ascii letters for different debugging kinds
+ };
+
+ # Information for fulfilling HTTP request
+ ReqInfo : adt
+ {
+ url: ref Url->Parsedurl; # should be absolute
+ method: int; # HGet or HPost
+ body: array of byte; # used for HPost
+ auth: string; # optional auth info
+ target: string; # target frame name
+ };
+
+ MaskedImage: adt {
+ im: ref Draw->Image; # the image
+ mask: ref Draw->Image; # if non-nil, a mask for the image
+ delay: int; # if animated, delay in millisec before next frame
+ more: int; # true if more frames follow
+ bgcolor: int; # if not -1, restore to this (RGB) color before next frame
+ origin: Draw->Point; # origin of im relative to first frame of an animation
+
+ free: fn(mim: self ref MaskedImage);
+ };
+
+ # Charon Image info.
+ # If this is an animated image then len mims > 1
+ CImage: adt
+ {
+ src: ref Url->Parsedurl; # source of image
+ lowsrc: ref Url->Parsedurl; # for low-resolution devices
+ actual: ref Url->Parsedurl; # what came back as actual source of image
+ imhash: int; # hash of src, for fast comparison
+ width: int;
+ height: int;
+ next: cyclic ref CImage; # next (newer) image in cache
+ mims: array of ref MaskedImage;
+ complete: int; # JavaScript Image.complete
+
+ new: fn(src: ref Url->Parsedurl, lowsrc: ref Url->Parsedurl, width, height: int) : ref CImage;
+ match: fn(a: self ref CImage, b: ref CImage) : int;
+ bytes: fn(ci: self ref CImage) : int;
+ };
+
+ # In-memory cache of CImages
+ ImageCache: adt
+ {
+ imhd: ref CImage; # head (LRU) of cache chain (linked through CImage.next)
+ imtl: ref CImage; # tail MRU) of cache chain
+ n: int; # size of chain
+ memused: int; # current total of image mem used by cached images
+ memlimit: int; # keep memused less than this
+ nlimit: int; # keep n less than this
+
+ init: fn(ic: self ref ImageCache);
+ resetlimits: fn(ic: self ref ImageCache);
+ look: fn(ic: self ref ImageCache, ci: ref CImage) : ref CImage;
+ add: fn(ic: self ref ImageCache, ci: ref CImage);
+ deletelru: fn(ic: self ref ImageCache);
+ clear: fn(ic: self ref ImageCache);
+ need: fn(ic: self ref ImageCache, nbytes: int) : int;
+ };
+
+ # An connection to some host
+ Netconn: adt
+ {
+ id: int; # for debugging
+ host: string; # host name
+ port: int; # port number
+ scheme: string; # Url scheme ("http", "file", etc.)
+ conn: Sys->Connection; # fds, etc.
+ sslx: ref SSL3->Context; # ssl connection
+ vers: int; # ssl version
+ state: int; # NCfree, etc.
+ queue: cyclic array of ref ByteSource;
+ # following are indexes into queue
+ qlen: int; # queue[0:qlen] is queue of requests
+ gocur: int; # go thread currently processing
+ ngcur: int; # ng threads currently processing
+ reqsent: int; # next to send request for
+ pipeline: int; # are requests being pipelined?
+ connected: int; # are we connected to host?
+ tstate: int; # for use by transport
+ tbuf: array of byte; # for use by transport
+ idlestart: int; # timestamp when went Idle
+
+ new: fn(id: int) : ref Netconn;
+ makefree: fn(nc: self ref Netconn);
+ };
+
+ # Info from an HTTP response header
+ Header: adt
+ {
+ code: int; # HC... (detailed response code)
+ actual: ref Url->Parsedurl; # actual request url (may be result of redir)
+ base: ref Url->Parsedurl; # Content-Base or request url
+ location: ref Url->Parsedurl; # Content-Location
+ length: int; # -1 if unknown
+ mtype: int; # TextHtml, etc.
+ chset: string; # charset encoding
+ msg: string; # possible message explaining status
+ refresh:string; # used for server push
+ chal: string; # used if code is HSneedauth
+ warn: string; # should show this to user
+ lastModified: string; # last-modified field
+
+ new: fn() : ref Header;
+ setmediatype: fn(h: self ref Header, name: string, first: array of byte);
+ print: fn(h: self ref Header);
+ };
+
+ # A source of raw bytes (with HTTP info)
+ ByteSource: adt
+ {
+ id: int; # for debugging
+ req: ref ReqInfo;
+ hdr: ref Header; # filled in from headers
+ data: array of byte; # all the data, maybe partially filled
+ edata: int; # data[0:edata] is valid
+ err: string; # there was an error
+ net: cyclic ref Netconn; # servicing fd, etc.
+ refgo: int; # go proc is still using
+ refnc: int; # netconn proc is still using
+
+ # producer sets eof upon finalising data & edata
+ eof: int;
+
+ # consumer changes only these fields:
+ lim: int; # consumer has seen data[0:lim]
+ seenhdr: int; # consumer has seen hdr
+
+ free: fn(bs: self ref ByteSource);
+ stringsource: fn(s: string) : ref ByteSource;
+ };
+
+ # Snapshot of current system resources
+ ResourceState: adt
+ {
+ ms: int; # a millisecond time stamp
+ main: int; # main memory
+ mainlim: int; # max main memory
+ heap: int; # heap memory
+ heaplim: int; # max heap memory
+ image: int; # used image memory
+ imagelim: int; # max image memory
+
+ cur: fn() : ResourceState;
+ since: fn(rnew: self ResourceState, rold: ResourceState) : ResourceState;
+ print: fn(r: self ResourceState, msg: string);
+ };
+
+
+ Nameval: adt {
+ key: string;
+ val: string;
+
+ namevals: fn(s: string, sep: int) : list of Nameval;
+ find: fn(l: list of Nameval, key: string) : (int, string);
+ };
+
+
+ # Globals
+ config: Config;
+ startres: ResourceState;
+ imcache: ref ImageCache;
+ progresschan: chan of (int, int, int, string);
+ gen: int; # go generation number
+ ckclient: ref Cookiesrv->Client;
+
+ init: fn(ch: Charon, me: CharonUtils, argl: list of string, evch: chan of ref E->Event, cksrv: Cookiesrv, ckclient: ref Cookiesrv->Client) : string;
+
+ # Dispatcher functions
+ stringreq: fn(s : string) : ref ByteSource;
+ startreq: fn(req: ref ReqInfo) : ref ByteSource;
+ waitreq: fn(bsl : list of ref ByteSource) : ref ByteSource;
+ freebs: fn(bs: ref ByteSource);
+ abortgo: fn(gopgrp: int);
+ netget: fn();
+
+ # Miscellaneous utility functions
+ kill: fn(pid: int, dogroup: int);
+ getline: fn(fd: ref Sys->FD, buf: array of byte, bstart, bend: int) :
+ (array of byte, int, int, int);
+ saveconfig: fn() : int;
+ strlookup: fn(a: array of string, s: string) : int;
+ realloc: fn(a: array of byte, incr: int) : array of byte;
+ hcphrase: fn(code: int) : string;
+ hdraction: fn(bs: ref ByteSource, ismain: int, nredirs: int) : (int, string, string, ref Url->Parsedurl);
+ makestrinttab: fn(a: array of string) : array of StringIntTab->StringInt;
+ urlequal: fn(a, b: ref Url->Parsedurl) : int;
+ makeabsurl: fn(s: string) : ref Url->Parsedurl;
+ loadpath: fn(s: string) : string;
+ event: fn(s: string, data: int);
+ color: fn(s: string, dflt: int) : int;
+ max: fn(a, b : int) : int;
+ min: fn(a, b : int) : int;
+ raisex: fn(e: string);
+ assert: fn(i: int);
+ stripscript: fn(s: string) : string; # strip HTML comments from Script
+ getconv: fn(chset : string) : Btos;
+ setcookie: fn(host, path, cookie: string);
+ getcookies: fn(host, path: string, secure: int): string;
+ schemeok: fn(scheme: string): int; # is URL scheme supported?
+ X: fn(s, note : string) : string;
+};
diff --git a/appl/charon/common.m b/appl/charon/common.m
new file mode 100644
index 00000000..714d6d6e
--- /dev/null
+++ b/appl/charon/common.m
@@ -0,0 +1,23 @@
+include "sys.m";
+include "draw.m";
+include "string.m";
+include "url.m";
+include "strinttab.m";
+include "ctype.m";
+include "keyring.m";
+include "asn1.m";
+include "pkcs.m";
+include "x509.m";
+include "sslsession.m";
+include "ssl3.m";
+include "convcs.m";
+include "cookiesrv.m";
+include "chutils.m";
+include "lex.m";
+include "script.m";
+include "build.m";
+include "layout.m";
+include "img.m";
+include "event.m";
+include "gui.m";
+include "charon.m";
diff --git a/appl/charon/cookiesrv.b b/appl/charon/cookiesrv.b
new file mode 100644
index 00000000..f50c1ce9
--- /dev/null
+++ b/appl/charon/cookiesrv.b
@@ -0,0 +1,595 @@
+implement Cookiesrv;
+include "sys.m";
+include "bufio.m";
+include "string.m";
+include "daytime.m";
+include "cookiesrv.m";
+
+sys: Sys;
+bufio: Bufio;
+S: String;
+daytime: Daytime;
+
+Iobuf: import bufio;
+
+Cookielist: adt {
+ prev: cyclic ref Cookielist;
+ next: cyclic ref Cookie;
+};
+
+Cookie: adt {
+ name: string;
+ value: string;
+ dom: string;
+ path: string;
+ expire: int; # seconds from epoch, -1 => not set, 0 => expire now
+ secure: int;
+ touched: int;
+ link: cyclic ref Cookielist; # linkage for list of cookies in the same domain
+};
+
+Domain: adt {
+ name: string;
+ doms: cyclic list of ref Domain;
+ cookies: ref Cookielist;
+};
+
+MAXCOOKIES: con 300; # total number of cookies allowed
+LISTMAX: con 20; # max number of cookies per Domain
+PURGENUM: con 30; # number of cookies to delete when freeing up space
+MAXCKLEN: con 4*1024; # max cookie length
+
+ncookies := 0;
+doms: list of ref Domain;
+now: int; # seconds since epoch
+cookiepath: string;
+touch := 0;
+
+start(path: string, saveinterval: int): ref Client
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ if (bufio == nil) {
+ sys->print("cookiesrv: cannot load %s: %r\n", Bufio->PATH);
+ return nil;
+ }
+ S = load String String->PATH;
+ if (S == nil) {
+ sys->print("cookiesrv: cannot load %s: %r\n", String->PATH);
+ return nil;
+ }
+ daytime = load Daytime Daytime->PATH;
+ if (daytime == nil) {
+ sys->print("cookiesrv: cannot load %s: %r\n", Daytime->PATH);
+ return nil;
+ }
+
+ cookiepath = path;
+ now = daytime->now();
+
+ # load the cookie file
+ # order is most recently touched first
+ iob := bufio->open(cookiepath, Sys->OREAD);
+ if (iob != nil) {
+ line: string;
+ while ((line = iob.gets('\n')) != nil) {
+ if (line[len line -1] == '\n')
+ line = line[:len line -1];
+ loadcookie(line);
+ }
+ iob.close();
+ iob = nil;
+ expire();
+ }
+ fdc := chan of ref Sys->FD;
+ spawn server(fdc, saveinterval);
+ fd := <- fdc;
+ if (fd == nil)
+ return nil;
+ return ref Client(fd);
+}
+
+addcookie(ck: ref Cookie, domlist: ref Cookielist)
+{
+ (last, n) := lastlink(domlist);
+ if (n == LISTMAX)
+ rmcookie(last.prev.next);
+ if (ncookies == MAXCOOKIES)
+ rmlru();
+ ck.link = ref Cookielist(domlist, domlist.next);
+ if (domlist.next != nil)
+ domlist.next.link.prev = ck.link;
+ domlist.next = ck;
+ ncookies++;
+}
+
+rmcookie(ck: ref Cookie)
+{
+ nextck := ck.link.next;
+ ck.link.prev.next = nextck;
+ if (nextck != nil)
+ nextck.link.prev = ck.link.prev;
+ ncookies--;
+}
+
+lastlink(ckl: ref Cookielist): (ref Cookielist, int)
+{
+ n := 0;
+ for (nckl := ckl.prev; nckl != nil; nckl = nckl.prev)
+ n++;
+ for (; ckl.next != nil; ckl = ckl.next.link)
+ n++;
+ return (ckl, n);
+}
+
+rmlru()
+{
+ cka := array [ncookies] of ref Cookie;
+ ix := getallcookies(doms, cka, 0);
+ if (ix < PURGENUM)
+ return;
+ mergesort(cka, nil, SORT_TOUCHED);
+ for (n := 0; n < PURGENUM; n++)
+ rmcookie(cka[n]);
+}
+
+getallcookies(dl: list of ref Domain, cka: array of ref Cookie, ix: int): int
+{
+ for (; dl != nil; dl = tl dl) {
+ dom := hd dl;
+ for (ck := dom.cookies.next; ck != nil; ck = ck.link.next)
+ cka[ix++] = ck;
+ ix = getallcookies(dom.doms, cka, ix);
+ }
+ return ix;
+}
+
+isipaddr(s: string): int
+{
+ # assume ipaddr if only numbers and '.'s
+ # should maybe count the dots too (what about IPV6?)
+ return S->drop(s, ".0123456789") == nil;
+}
+
+setcookie(ck: ref Cookie)
+{
+ parent, dom: ref Domain;
+ domain := ck.dom;
+ if (isipaddr(domain))
+ (parent, dom, domain) = getdom(doms, nil, domain);
+ else
+ (parent, dom, domain) = getdom(doms, domain, nil);
+
+ if (dom == nil)
+ dom = newdom(parent, domain);
+
+ for (oldck := dom.cookies.next; oldck != nil; oldck = oldck.link.next) {
+ if (ck.name == oldck.name && ck.path == oldck.path) {
+ rmcookie(oldck);
+ break;
+ }
+ }
+ if (ck.expire > 0 && ck.expire <= now)
+ return;
+ addcookie(ck, dom.cookies);
+}
+
+expire()
+{
+ cka := array [ncookies] of ref Cookie;
+ ix := getallcookies(doms, cka, 0);
+ for (i := 0; i < ix; i++) {
+ ck := cka[i];
+ if (ck.expire > 0 && ck.expire < now)
+ rmcookie(ck);
+ }
+}
+
+newdom(parent: ref Domain, domain: string): ref Domain
+{
+ while (domain != "") {
+ (lhs, rhs) := splitdom(domain);
+ d := ref Domain(rhs, nil, ref Cookielist(nil, nil));
+ if (parent == nil)
+ doms = d :: doms;
+ else
+ parent.doms = d :: parent.doms;
+ parent = d;
+ domain = lhs;
+ }
+ return parent;
+}
+
+getdom(dl: list of ref Domain, lhs, rhs: string): (ref Domain, ref Domain, string)
+{
+ if (rhs == "")
+ (lhs, rhs) = splitdom(lhs);
+ parent: ref Domain;
+ while (dl != nil) {
+ d := hd dl;
+ if (d.name != rhs) {
+ dl = tl dl;
+ continue;
+ }
+ # name matches
+ if (lhs == nil)
+ return (parent, d, rhs);
+ parent = d;
+ (lhs, rhs) = splitdom(lhs);
+ dl = d.doms;
+ }
+ return (parent, nil, lhs+rhs);
+}
+
+# returned list is in shortest to longest domain match order
+getdoms(dl: list of ref Domain, lhs, rhs: string): list of ref Domain
+{
+ if (rhs == "")
+ (lhs, rhs) = splitdom(lhs);
+ for (; dl != nil; dl = tl dl) {
+ d := hd dl;
+ if (d.name == rhs) {
+ if (lhs == nil)
+ return d :: nil;
+ (lhs, rhs) = splitdom(lhs);
+ return d :: getdoms(d.doms, lhs, rhs);
+ }
+ }
+ return nil;
+}
+
+server(fdc: chan of ref Sys->FD, saveinterval: int)
+{
+ sys->pctl(Sys->NEWPGRP|Sys->FORKNS, nil);
+ sys->bind("#s", "/chan", Sys->MBEFORE);
+ fio := sys->file2chan("/chan", "ctl");
+ if (fio == nil) {
+ fdc <-= nil;
+ return;
+ }
+ fd := sys->open("/chan/ctl", Sys->OWRITE);
+ fdc <-= fd;
+ if (fd == nil)
+ return;
+ fd = nil;
+
+ tick := chan of int;
+ spawn ticker(tick, 1*60*1000); # clock tick once a minute
+ tickerpid := <- tick;
+
+ modified := 0;
+ savetime := now + saveinterval;
+
+ for (;;) alt {
+ now = <- tick =>
+ expire();
+ if (saveinterval != 0 && now > savetime) {
+ if (modified) {
+ save();
+ modified = 0;
+ }
+ savetime = now + saveinterval;
+ }
+ (nil, line, nil, rc) := <- fio.write =>
+ now = daytime->now();
+ if (rc == nil) {
+ kill(tickerpid);
+ expire();
+ save();
+ return;
+ }
+ loadcookie(string line);
+ alt {
+ rc <-= (len line, nil) =>
+ ;
+ * =>
+ ;
+ };
+ modified = 1;
+ }
+}
+
+ticker(tick: chan of int, ms: int)
+{
+ tick <-= sys->pctl(0, nil);
+ for (;;) {
+ sys->sleep(ms);
+ tick <-= daytime->now();
+ }
+}
+
+# sort orders
+SORT_TOUCHED, SORT_PATHLEN: con iota;
+
+mergesort(a, b: array of ref Cookie, order: int)
+{
+ if (b == nil)
+ b = array [len a] of ref Cookie;
+ r := len a;
+ if (r > 1) {
+ m := (r-1)/2 + 1;
+ mergesort(a[0:m], b[0:m], order);
+ mergesort(a[m:], b[m:], order);
+ b[0:] = a;
+ for ((i, j, k) := (0, m, 0); i < m && j < r; k++) {
+ if (greater(b[i], b[j], order))
+ a[k] = b[j++];
+ else
+ a[k] = b[i++];
+ }
+ if (i < m)
+ a[k:] = b[i:m];
+ else if (j < r)
+ a[k:] = b[j:r];
+ }
+}
+
+greater(x, y: ref Cookie, order: int): int
+{
+ if (y == nil)
+ return 0;
+ case order {
+ SORT_TOUCHED =>
+ if (x.touched > y.touched)
+ return 1;
+ SORT_PATHLEN =>
+ if (len x.path < len y.path)
+ return 1;
+ }
+ return 0;
+}
+
+cookie2str(ck: ref Cookie): string
+{
+ if (len ck.name +1 > MAXCKLEN)
+ return "";
+ namval := sys->sprint("%s=%s", ck.name, ck.value);
+ if (len namval > MAXCKLEN)
+ namval = namval[:MAXCKLEN];
+ return sys->sprint("%s\t%s\t%d\t%d\t%s", ck.dom, ck.path, ck.expire, ck.secure, namval);
+}
+
+loadcookie(ckstr: string)
+{
+ (n, toks) := sys->tokenize(ckstr, "\t");
+ if (n < 5)
+ return;
+ dom, path, exp, sec, namval: string;
+ (dom, toks) = (hd toks, tl toks);
+ (path, toks) = (hd toks, tl toks);
+ (exp, toks) = (hd toks, tl toks);
+ (sec, toks) = (hd toks, tl toks);
+ (namval, toks) = (hd toks, tl toks);
+
+ # some sanity checks
+ if (dom == "" || path == "" || path[0] != '/')
+ return;
+
+ (name, value) := S->splitl(namval, "=");
+ if (value == nil)
+ return;
+ value = value[1:];
+ ck := ref Cookie(name, value, dom, path, int exp, int sec, touch++, nil);
+ setcookie(ck);
+}
+
+Client.set(c: self ref Client, host, path, cookie: string)
+{
+ ck := parsecookie(host, path, cookie);
+ if (ck == nil)
+ return;
+ b := array of byte cookie2str(ck);
+ sys->write(c.fd, b, len b);
+}
+
+Client.getcookies(nil: self ref Client, host, path: string, secure: int): string
+{
+ dl: list of ref Domain;
+ if (isipaddr(host))
+ dl = getdoms(doms, nil, host);
+ else {
+ # note some domains match hosts
+ # e.g. site X.com has to set a cookie for '.X.com'
+ # to get around the netscape '.' count check
+ # this messes up our domain checking
+ # putting a '.' on the front of host is a safe way of handling this
+# host = "." + host;
+ dl = getdoms(doms, host, nil);
+ }
+ cookies: list of ref Cookie;
+ for (; dl != nil; dl = tl dl) {
+ ckl := (hd dl).cookies;
+ for (ck := ckl.next; ck != nil; ck = ck.link.next) {
+ if (ck.secure && !secure)
+ continue;
+ if (!S->prefix(ck.path, path))
+ continue;
+ ck.touched = touch++;
+ cookies = ck :: cookies;
+ }
+ }
+ if (cookies == nil)
+ return "";
+
+ # sort w.r.t path len and creation order
+ cka := array [len cookies] of ref Cookie;
+ for (i := 0; cookies != nil; cookies = tl cookies)
+ cka[i++] = hd cookies;
+
+ mergesort(cka, nil, SORT_PATHLEN);
+
+ s := sys->sprint("%s=%s", cka[0].name, cka[0].value);
+ for (i = 1; i < len cka; i++)
+ s += sys->sprint("; %s=%s", cka[i].name, cka[i].value);
+ return s;
+}
+
+save()
+{
+ fd := sys->create(cookiepath, Sys->OWRITE, 8r600);
+ if (fd == nil)
+ return;
+ cka := array [ncookies] of ref Cookie;
+ ix := getallcookies(doms, cka, 0);
+ mergesort(cka, nil, SORT_TOUCHED);
+
+ for (i := 0; i < ncookies; i++) {
+ ck := cka[i];
+ if (ck.expire > now)
+ sys->fprint(fd, "%s\n", cookie2str(cka[i]));
+ }
+}
+
+parsecookie(dom, path, cookie: string): ref Cookie
+{
+ defpath := "/";
+ if (path != nil)
+ (defpath, nil) = S->splitr(path, "/");
+
+ (nil, toks) := sys->tokenize(cookie, ";");
+ namval := hd toks;
+ toks = tl toks;
+
+ (name, value) := S->splitl(namval, "=");
+ name = trim(name);
+ if (value != nil && value[0] == '=')
+ value = value[1:];
+ value = trim(value);
+
+ ck := ref Cookie(name, value, dom, defpath, -1, 0, 0, nil);
+ for (; toks != nil; toks = tl toks) {
+ (name, value) = S->splitl(hd toks, "=");
+ if (value != nil && value[0] == '=')
+ value = value[1:];
+ name = trim(name);
+ value = trim(value);
+ case S->tolower(name) {
+ "domain" =>
+ ck.dom = value;
+ "expires" =>
+ ck.expire = date2sec(value);
+ "path" =>
+ ck.path = value;
+ "secure" =>
+ ck.secure = 1;
+ }
+ }
+ if (ckcookie(ck, dom, path))
+ return ck;
+ return nil;
+}
+
+# Top Level Domains as defined in Netscape cookie spec
+tld := array [] of {
+ ".com", ".edu", ".net", ".org", ".gov", ".mil", ".int"
+};
+
+ckcookie(ck: ref Cookie, host, path: string): int
+{
+#dumpcookie(ck, "CKCOOKIE");
+ if (ck == nil)
+ return 0;
+ if (ck.path == "" || ck.dom == "")
+ return 0;
+ if (host == "" || path == "")
+ return 1;
+
+# netscape does no path check on accpeting a cookie
+# any page can set a cookie on any path within its domain.
+# the filtering is done when sending cookies back to the server
+# if (!S->prefix(ck.path, path))
+# return 0;
+
+ if (host == ck.dom)
+ return 1;
+ if (ck.dom[0] != '.' || len host < len ck.dom)
+ return 0;
+
+ ipaddr := S->drop(host, ".0123456789") == nil;
+ if (ipaddr)
+ # ip addresses have to match exactly
+ return 0;
+
+ D := host[len host - len ck.dom:];
+ if (D != ck.dom)
+ return 0;
+
+ # netscape specific policy
+ ndots := 0;
+ for (i := 0; i < len D; i++)
+ if (D[i] == '.')
+ ndots++;
+ for (i = 0; i < len tld; i++) {
+ if (len D >= len tld[i] && D[len D - len tld[i]:] == tld[i]) {
+ if (ndots < 2)
+ return 0;
+ return 1;
+ }
+ }
+ if (ndots < 3)
+ return 0;
+ return 1;
+}
+
+trim(s: string): string
+{
+ is := 0;
+ ie := len s;
+ while(is < ie) {
+ c := s[is];
+ if(!(c == ' ' || c == '\t'))
+ break;
+ is++;
+ }
+ if(is == ie)
+ return "";
+ while(ie > is) {
+ c := s[ie-1];
+ if(!(c == ' ' || c == '\t'))
+ break;
+ ie--;
+ }
+ if(is >= ie)
+ return "";
+ if(is == 0 && ie == len s)
+ return s;
+ return s[is:ie];
+}
+
+kill(pid: int)
+{
+ sys->fprint(sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE), "kill");
+}
+
+date2sec(date: string): int
+{
+ Tm: import daytime;
+ tm := daytime->string2tm(date);
+ if(tm == nil || tm.year < 70 || tm.zone != "GMT")
+ t := -1;
+ else
+ t = daytime->tm2epoch(tm);
+ return t;
+}
+
+dumpcookie(ck: ref Cookie, msg: string)
+{
+ if (msg != nil)
+ sys->print("%s: ", msg);
+ if (ck == nil)
+ sys->print("NIL\n");
+ else {
+ dbgval := ck.value;
+ if (len dbgval > 10)
+ dbgval = dbgval[:10];
+ sys->print("dom[%s], path[%s], name[%s], value[%s], secure=%d\n", ck.dom, ck.path, ck.name, dbgval, ck.secure);
+ }
+}
+
+splitdom(s: string): (string, string)
+{
+ for (ie := len s -1; ie > 0; ie--)
+ if (s[ie] == '.')
+ break;
+ return (s[:ie], s[ie:]);
+}
diff --git a/appl/charon/cookiesrv.m b/appl/charon/cookiesrv.m
new file mode 100644
index 00000000..6cc5f069
--- /dev/null
+++ b/appl/charon/cookiesrv.m
@@ -0,0 +1,12 @@
+Cookiesrv: module {
+ PATH: con "/dis/charon/cookiesrv.dis";
+
+ Client: adt {
+ fd: ref Sys->FD;
+ set: fn(c: self ref Client,host, path, cookie: string);
+ getcookies: fn(c: self ref Client, host, path: string, secure: int): string;
+ };
+
+ # save interval is in minutes
+ start: fn(path: string, saveinterval: int): ref Client;
+};
diff --git a/appl/charon/ctype.b b/appl/charon/ctype.b
new file mode 100644
index 00000000..73c7ea4f
--- /dev/null
+++ b/appl/charon/ctype.b
@@ -0,0 +1,70 @@
+implement Ctype;
+
+include "ctype.m";
+
+ctype = array[NCTYPE] of {
+#0000 0001 0002 0003 0004 0005 0006 0007
+C, C, C, C, C, C, C, C,
+#0008\b 0009 \t 000a \n 000b \v 000c \f 000d \r 000e 000f
+C, W, W, C, C, W, C, C,
+#0010 0011 0012 0013 0014 0015 0016 0017
+C, C, C, C, C, C, C, C,
+#0018 0019 001a 001b 001c 001d 001e 001f
+C, C, C, C, C, C, C, C,
+#0020 0021 ! 0022 " 0023 # 0024 $ 0025 % 0026 & 0027 '
+W, P, P, P, P, P, P, P,
+#0028 ( 0029 ) 002a * 002b + 002c , 002d - 002e . 002f /
+P, P, P, P, P, N, N, P,
+#0030 0 0031 1 0032 2 0033 3 0034 4 0035 5 0036 6 0037 7
+D, D, D, D, D, D, D, D,
+#0038 8 0039 9 003a : 003b ; 003c < 003d = 003e > 003f ?
+D, D, P, P, P, P, P, P,
+#0040 @ 0041 A 0042 B 0043 C 0044 D 0045 E 0046 F 0047 G
+P, U, U, U, U, U, U, U,
+#0048 H 0049 I 004a J 004b K 004c L 004d M 004e N 004f O
+U, U, U, U, U, U, U, U,
+#0050 P 0051 Q 0052 R 0053 S 0054 T 0055 U 0056 V 0057 W
+U, U, U, U, U, U, U, U,
+#0058 X 0059 Y 005a Z 005b [ 005c \ 005d ] 005e ^ 005f _
+U, U, U, P, P, P, P, S,
+#0060 ` 0061 a 0062 b 0063 c 0064 d 0065 e 0066 f 0067 g
+P, L, L, L, L, L, L, L,
+#0068 h 0069 i 006a j 006b k 006c l 006d m 006e n 006f o
+L, L, L, L, L, L, L, L,
+#0070 p 0071 q 0072 r 0073 s 0074 t 0075 u 0076 v 0077 w
+L, L, L, L, L, L, L, L,
+#0078 x 0079 y 007a z 007b { 007c | 007d } 007e ~ 007f 
+L, L, L, P, P, P, P, C,
+#0080 € 0081  0082 ‚ 0083 ƒ 0084 „ 0085 … 0086 † 0087 ‡
+C, C, C, C, C, C, C, C,
+#0088 ˆ 0089 ‰ 008a Š 008b ‹ 008c Œ 008d  008e Ž 008f 
+C, C, C, C, C, C, C, C,
+#0090  0091 ‘ 0092 ’ 0093 “ 0094 ” 0095 • 0096 – 0097 —
+C, C, C, C, C, C, C, C,
+#0098 ˜ 0099 ™ 009a š 009b › 009c œ 009d  009e ž 009f Ÿ
+C, C, C, C, C, C, C, C,
+#00a0   00a1 ¡ 00a2 ¢ 00a3 £ 00a4 ¤ 00a5 ¥ 00a6 ¦ 00a7 §
+P, P, P, P, P, P, P, P,
+#00a8 ¨ 00a9 © 00aa ª 00ab « 00ac ¬ 00ad ­ 00ae ® 00af ¯
+P, P, P, P, P, P, P, P,
+#00b0 ° 00b1 ± 00b2 ² 00b3 ³ 00b4 ´ 00b5 µ 00b6 ¶ 00b7 ·
+P, P, P, P, P, P, P, P,
+#00b8 ¸ 00b9 ¹ 00ba º 00bb » 00bc ¼ 00bd ½ 00be ¾ 00bf ¿
+P, P, P, P, P, P, P, P,
+#00c0 À 00c1 Á 00c2 Â 00c3 Ã 00c4 Ä 00c5 Å 00c6 Æ 00c7 Ç
+U, U, U, U, U, U, U, U,
+#00c8 È 00c9 É 00ca Ê 00cb Ë 00cc Ì 00cd Í 00ce Î 00cf Ï
+U, U, U, U, U, U, U, U,
+#00d0 Ð 00d1 Ñ 00d2 Ò 00d3 Ó 00d4 Ô 00d5 Õ 00d6 Ö 00d7 ×
+U, U, U, U, U, U, U, P,
+#00d8 Ø 00d9 Ù 00da Ú 00db Û 00dc Ü 00dd Ý 00de Þ 00df ß
+U, U, U, U, U, U, U, L,
+#00e0 à 00e1 á 00e2 â 00e3 ã 00e4 ä 00e5 å 00e6 æ 00e7 ç
+L, L, L, L, L, L, L, L,
+#00e8 è 00e9 é 00ea ê 00eb ë 00ec ì 00ed í 00ee î 00ef ï
+L, L, L, L, L, L, L, L,
+#00f0 ð 00f1 ñ 00f2 ò 00f3 ó 00f4 ô 00f5 õ 00f6 ö 00f7 ÷
+L, L, L, L, L, L, L, P,
+#00f8 ø 00f9 ù 00fa ú 00fb û 00fc ü 00fd ý 00fe þ 00ff ÿ
+L, L, L, L, L, L, L, L
+};
diff --git a/appl/charon/ctype.m b/appl/charon/ctype.m
new file mode 100644
index 00000000..d4c86126
--- /dev/null
+++ b/appl/charon/ctype.m
@@ -0,0 +1,24 @@
+Ctype: module
+{
+ PATH: con "/dis/charon/ctype.dis";
+
+ # Classify first NCTYPE chars of Unicode into one of
+ #
+ # W: whitespace
+ # D: decimal digit
+ # L: lowercase letter
+ # U: uppercase letter
+ # N: '.' or '-' (parts of certain kinds of names)
+ # S: '_' (parts of other kinds of names)
+ # P: printable other than all of above
+ # C: control other than whitespace
+ #
+ # These are separate bits, so can test for, e.g., ctype[c]&(U|L),
+ # but only one is set for any particular character,
+ # so can use faster ctype[c]==W too.
+
+ W, D, L, U, N, S, P, C: con byte (1<<iota);
+ NCTYPE: con 256;
+
+ ctype: array of byte;
+};
diff --git a/appl/charon/date.b b/appl/charon/date.b
new file mode 100644
index 00000000..b247d578
--- /dev/null
+++ b/appl/charon/date.b
@@ -0,0 +1,62 @@
+implement Date;
+
+include "common.m";
+include "date.m";
+include "daytime.m";
+ daytime: Daytime;
+ Tm: import daytime;
+
+sys: Sys;
+CU: CharonUtils;
+
+wdayname := array[] of {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+
+monname := array[] of {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+init(cu: CharonUtils)
+{
+ sys = load Sys Sys->PATH;
+ CU = cu;
+ daytime = load Daytime Daytime->PATH;
+ if (daytime==nil)
+ CU->raisex(sys->sprint("EXInternal: can't load Daytime: %r"));
+}
+
+# print dates in the format
+# Wkd, DD Mon YYYY HH:MM:SS GMT
+
+dateconv(t: int): string
+{
+ tm : ref Tm;
+ tm = daytime->gmt(t);
+ return sys->sprint("%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT",
+ wdayname[tm.wday], tm.mday, monname[tm.mon], tm.year+1900,
+ tm.hour, tm.min, tm.sec);
+}
+
+# parse a date and return the seconds since the epoch
+# return 0 for a failure
+#
+# need to handle three formats (we'll be a bit more tolerant)
+# Sun, 06 Nov 1994 08:49:37 GMT (rfc822+rfc1123; preferred)
+# Sunday, 06-Nov-94 08:49:37 GMT (rfc850, obsoleted by rfc1036)
+# Sun Nov 6 08:49:37 1994 (ANSI C's asctime() format; GMT assumed)
+
+date2sec(date : string): int
+{
+ tm := daytime->string2tm(date);
+ if(tm == nil || tm.year < 70 || tm.zone != "GMT")
+ t := 0;
+ else
+ t = daytime->tm2epoch(tm);
+ return t;
+}
+
+now(): int
+{
+ return daytime->now();
+}
diff --git a/appl/charon/date.m b/appl/charon/date.m
new file mode 100644
index 00000000..ea485668
--- /dev/null
+++ b/appl/charon/date.m
@@ -0,0 +1,12 @@
+
+Date: module{
+ PATH : con "/dis/charon/date.dis";
+
+ dateconv: fn(secs :int): string; # returns an http formatted
+ # date representing secs.
+ date2sec: fn(foo:string): int; # parses a date and returns
+ # number of secs since the
+ # epoch that it represents.
+ now: fn(): int; # so don't have to load daytime too
+ init: fn(cu: CharonUtils);
+};
diff --git a/appl/charon/event.b b/appl/charon/event.b
new file mode 100644
index 00000000..79a9a5e1
--- /dev/null
+++ b/appl/charon/event.b
@@ -0,0 +1,273 @@
+implement Events;
+
+include "common.m";
+
+sys: Sys;
+url: Url;
+ Parsedurl: import url;
+
+archan : chan of (ref Event, int, int);
+
+init(ev : chan of ref Event)
+{
+ sys = load Sys Sys->PATH;
+ url = load Url Url->PATH;
+ if (url != nil)
+ url->init();
+ evchan = chan of ref Event;
+ archan = chan of (ref Event, int ,int);
+ spawn eventfilter(evchan, ev);
+}
+
+timer(go, tick : chan of int)
+{
+ go <-= sys->pctl(0, nil);
+ for(;;) {
+ ms := <- go;
+ sys->sleep(ms);
+ tick <-= 1;
+ }
+}
+
+# Handle mouse filtering and auto-repeating.
+# If we are waiting to send to Charon then accept events whilst they are
+# compatible with the pending event (eg. only keep most recent mouse move).
+# Once we have a recv event that isn't compatible with the pending send event
+# stop accepting events until the pending one has been sent.
+# Auto-repeat events are discarded if they cannot be sent or combined with the pending one.
+#
+eventfilter(fromc, toc : chan of ref Event)
+{
+ timergo := chan of int;
+ timertick := chan of int;
+ timeractive := 0;
+ spawn timer(timergo, timertick);
+ timerpid := <-timergo;
+
+ pendingev : ref Event;
+ bufferedev : ref Event;
+ dummyin := chan of ref Event;
+ dummyout := chan of ref Event;
+ inchan := fromc;
+ outchan := dummyout;
+
+ arev : ref Event;
+ aridlems, arms : int;
+
+ for (;;) alt {
+ ev := <- inchan =>
+ outchan = toc;
+ if (pendingev == nil)
+ pendingev = ev;
+ else {
+ # an event is pending - see if we can combine/replace it
+ replace := evreplace(pendingev, ev);
+ if (replace != nil)
+ pendingev = replace;
+ else
+ bufferedev = ev;
+ }
+ if (bufferedev != nil)
+ inchan = dummyin;
+
+ outchan <- = pendingev =>
+ pendingev = bufferedev;
+ bufferedev = nil;
+ inchan = fromc;
+ if (pendingev == nil)
+ outchan = dummyout;
+
+ (arev, aridlems, arms) = <- archan =>
+ if (arev == nil) {
+ if(timeractive) {
+ # kill off old timer action so we don't get nasty
+ # holdovers from past autorepeats.
+ kill(timerpid);
+ spawn timer(timergo, timertick);
+ timerpid = <-timergo;
+ timeractive = 0;
+ }
+ } else if (!timeractive) {
+ timeractive = 1;
+ timergo <-= aridlems;
+ }
+
+ <- timertick =>
+ timeractive = 0;
+ if (arev != nil) {
+ if (pendingev == nil) {
+ pendingev = arev;
+ } else if (bufferedev == nil) {
+ replace := evreplace(pendingev, arev);
+ if (replace != nil)
+ pendingev = replace;
+ else
+ bufferedev = arev;
+ } else {
+ # try and combine with the buffered event
+ replace := evreplace(bufferedev, arev);
+ if (replace != nil)
+ bufferedev = replace;
+ } # else: discard auto-repeat event
+
+ if (bufferedev != nil)
+ inchan = dummyin;
+
+ # kick-start sends (we always have something to send)
+ outchan = toc;
+ timergo <- = arms;
+ timeractive = 1;
+ }
+ }
+}
+
+evreplace(oldev, newev : ref Event) : ref Event
+{
+ pick n := newev {
+ Emouse =>
+ pick o := oldev {
+ Emouse =>
+ if (n.mtype == o.mtype && (n.mtype == Mmove || n.mtype == Mldrag || n.mtype == Mmdrag || n.mtype == Mrdrag))
+ return newev;
+ }
+ Equit =>
+ # always takes precedence
+ return newev;
+ Ego =>
+ pick o := oldev {
+ Ego =>
+ if (n.target == o.target)
+ return newev;
+ }
+ Escroll =>
+ pick o := oldev {
+ Escroll =>
+ if (n.frameid == o.frameid)
+ return newev;
+ }
+ Escrollr =>
+ pick o := oldev {
+ Escrollr =>
+ if (n.frameid == o.frameid)
+ return newev;
+ }
+ Esettext =>
+ pick o := oldev {
+ Esettext =>
+ if (n.frameid == o.frameid)
+ return newev;
+ }
+ Edismisspopup =>
+ if (tagof oldev == tagof Event.Edismisspopup)
+ return newev;
+ * =>
+ return nil;
+ }
+ return nil;
+}
+
+autorepeat(ev : ref Event, idlems, ms : int)
+{
+ archan <- = (ev, idlems, ms);
+}
+
+Event.tostring(ev: self ref Event) : string
+{
+ s := "?";
+ pick e := ev {
+ Ekey =>
+ t : string;
+ case e.keychar {
+ ' ' => t = "<SP>";
+ '\t' => t = "<TAB>";
+ '\n' => t = "<NL>";
+ '\r' => t = "<CR>";
+ '\b' => t = "<BS>";
+ 16r7F => t = "<DEL>";
+ Kup => t = "<UP>";
+ Kdown => t = "<DOWN>";
+ Khome => t = "<HOME>";
+ Kleft => t = "<LEFT>";
+ Kright => t = "<RIGHT>";
+ Kend => t = "<END>";
+ * => t = sys->sprint("%c", e.keychar);
+ }
+ s = sys->sprint("key %d = %s", e.keychar, t);
+ Emouse =>
+ t := "?";
+ case e.mtype {
+ Mmove => t = "move";
+ Mlbuttondown => t = "lbuttondown";
+ Mlbuttonup => t = "lbuttonup";
+ Mldrag => t = "ldrag";
+ Mmbuttondown => t = "mbuttondown";
+ Mmbuttonup => t = "mbuttonup";
+ Mmdrag => t = "mdrag";
+ Mrbuttondown => t = "rbuttondown";
+ Mrbuttonup => t = "rbuttonup";
+ Mrdrag => t = "rdrag";
+ }
+ s = sys->sprint("mouse (%d,%d) %s", e.p.x, e.p.y, t);
+ Emove =>
+ s = sys->sprint("move (%d,%d)", e.p.x, e.p.y);
+ Ereshape =>
+ s = sys->sprint("reshape (%d,%d) (%d,%d)", e.r.min.x, e.r.min.y, e.r.max.x, e.r.max.y);
+ Equit =>
+ s = "quit";
+ Estop =>
+ s = "stop";
+ Eback =>
+ s = "back";
+ Efwd =>
+ s = "fwd";
+ Eform =>
+ case e.ftype {
+ EFsubmit => s = "form submit";
+ EFreset => s = "form reset";
+ }
+ Eformfield =>
+ case e.fftype {
+ EFFblur => s = "formfield blur";
+ EFFfocus => s = "formfield focus";
+ EFFclick => s = "formfield click";
+ EFFselect => s = "formfield select";
+ EFFredraw => s = "formfield redraw";
+ }
+ Ego =>
+ s = "go(";
+ case e.gtype {
+ EGlocation or
+ EGnormal or
+ EGreplace => s += e.url;
+ EGreload => s += "RELOAD";
+ EGforward => s += "FORWARD";
+ EGback => s += "BACK";
+ EGdelta => s += "HISTORY[" + string e.delta + "]";
+ }
+ s += ", " + e.target + ")";
+ Esubmit =>
+ if(e.subkind == CharonUtils->HGet)
+ s = "GET";
+ else
+ s = "POST";
+ s = "submit(" + s;
+ s += ", " + e.action.tostring();
+ s += ", " + e.target + ")";
+ Escroll =>
+ s = "scroll(" + string e.frameid + ", (" + string e.pt.x + ", " + string e.pt.y + "))";
+ Escrollr =>
+ s = "scrollr(" + string e.frameid + ", (" + string e.pt.x + ", " + string e.pt.y + "))";
+ Esettext =>
+ s = "settext(frameid=" + string e.frameid + ", text=" + e.text + ")";
+ Elostfocus =>
+ s = "lostfocus";
+ Edismisspopup =>
+ s = "dismisspopup";
+ }
+ return s;
+}
+
+kill(pid: int)
+{
+ sys->fprint(sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE), "kill");
+}
diff --git a/appl/charon/event.m b/appl/charon/event.m
new file mode 100644
index 00000000..4b9d0528
--- /dev/null
+++ b/appl/charon/event.m
@@ -0,0 +1,100 @@
+Events: module {
+ PATH: con "/dis/charon/event.dis";
+ Event: adt {
+ pick {
+ Ekey =>
+ keychar: int; # Unicode char for pressed key
+ Emouse =>
+ p: Draw->Point; # coords of pointer
+ mtype: int; # Mmove, etc.
+ Emove =>
+ p: Draw->Point; # new top-left of moved window
+ Ereshape =>
+ r: Draw->Rect; # new window place and size
+ Equit =>
+ dummy: int;
+ Estop =>
+ dummy: int;
+ Eback =>
+ dummy: int;
+ Efwd =>
+ dummy: int;
+ Eform =>
+ frameid: int; # which frame is form in
+ formid: int; # which form in the frame
+ ftype: int; # EFsubmit or EFreset
+ Eformfield =>
+ frameid: int; # which frame is form in
+ formid: int; # which form in the frame
+ fieldid: int; # which formfield in the form
+ fftype: int; # EFFblur, EFFfocus, etc.
+ Ego =>
+ url: string; # where to go
+ target: string; # frame to replace
+ delta: int; # History.go(delta)
+ gtype: int;
+ Esubmit =>
+ subkind: int; # CU->HGet or CU->HPost
+ action: ref Url->Parsedurl;
+ data: string;
+ target: string;
+ Escroll or Escrollr =>
+ frameid: int;
+ pt: Draw->Point;
+ Esettext =>
+ frameid: int;
+ url: ref Url->Parsedurl;
+ text: string;
+ Elostfocus => # main window has lost keyboard focus
+ dummy: int;
+ Edismisspopup => # popup window has been dismissed by gui
+ dummy: int;
+ }
+
+ tostring: fn(e: self ref Event) : string;
+ };
+
+ # Events sent to scripting engines
+ ScriptEvent: adt {
+ kind: int;
+ frameid: int;
+ formid: int;
+ fieldid: int;
+ anchorid: int;
+ imageid: int;
+ x: int;
+ y: int;
+ which: int;
+ script: string;
+ reply: chan of string; # onreset/onsubmit reply channel
+ ms: int;
+ };
+
+ # ScriptEvent kinds
+ SEonclick, SEondblclick, SEonkeydown, SEonkeypress, SEonkeyup,
+ SEonmousedown, SEonmouseover, SEonmouseout, SEonmouseup, SEonblur, SEonfocus,
+ SEonchange, SEonload, SEtimeout, SEonabort, SEonerror,
+ SEonreset, SEonresize, SEonselect, SEonsubmit, SEonunload, SEscript, SEinterval, SEnone : con 1 << iota;
+
+ # some special keychars (use Unicode Private Area)
+ Kup, Kdown, Khome, Kleft, Kright, Kend, Kaup, Kadown : con (iota + 16rF000);
+
+ # Mouse event subtypes
+ Mmove, Mlbuttondown, Mlbuttonup, Mldrag, Mldrop,
+ Mmbuttondown, Mmbuttonup, Mmdrag,
+ Mrbuttondown, Mrbuttonup, Mrdrag,
+ Mhold : con iota;
+
+ # Form event subtypes
+ EFsubmit, EFreset : con iota;
+
+ # FormField event subtypes
+ EFFblur, EFFfocus, EFFclick, EFFselect, EFFredraw, EFFnone : con iota;
+
+ # Go event subtypes
+ EGnormal, EGreplace, EGreload, EGforward, EGback, EGdelta, EGlocation: con iota;
+
+ init: fn(evchan : chan of ref Event);
+ autorepeat: fn(ev : ref Event, idlems, ms : int);
+ evchan: chan of ref Event;
+};
diff --git a/appl/charon/file.b b/appl/charon/file.b
new file mode 100644
index 00000000..1a5947c1
--- /dev/null
+++ b/appl/charon/file.b
@@ -0,0 +1,134 @@
+implement Transport;
+
+include "common.m";
+include "transport.m";
+
+# local copies from CU
+sys: Sys;
+U: Url;
+ Parsedurl: import U;
+CU: CharonUtils;
+ Netconn, ByteSource, Header, config : import CU;
+
+dbg := 0;
+
+init(c: CharonUtils)
+{
+ CU = c;
+ sys = load Sys Sys->PATH;
+ U = load Url Url->PATH;
+ if (U != nil)
+ U->init();
+ dbg = int (CU->config).dbg['n'];
+}
+
+connect(nc: ref Netconn, nil: ref ByteSource)
+{
+ nc.connected = 1;
+ nc.state = CU->NCgethdr;
+ return;
+}
+
+writereq(nil: ref Netconn, nil: ref ByteSource)
+{
+ return;
+}
+
+gethdr(nc: ref Netconn, bs: ref ByteSource)
+{
+ u := bs.req.url;
+ f := u.path;
+ hdr := Header.new();
+ nc.conn.dfd = sys->open(f, sys->OREAD);
+ if(nc.conn.dfd == nil) {
+ if(dbg)
+ sys->print("file %d: can't open %s: %r\n", nc.id, f);
+ # Could examine %r to distinguish between NotFound
+ # and Forbidden and other, but string is OS-dependent.
+ hdr.code = CU->HCNotFound;
+ bs.hdr = hdr;
+ nc.connected = 0;
+ return;
+ }
+
+ (ok, statbuf) := sys->fstat(nc.conn.dfd);
+ if(ok < 0) {
+ bs.err = "stat error";
+ return;
+ }
+
+ if (statbuf.mode & Sys->DMDIR) {
+ bs.err = "Directories not implemented";
+ return;
+ }
+
+ # assuming file (not directory)
+ n := int statbuf.length;
+ hdr.length = n;
+ if(n > sys->ATOMICIO)
+ n = sys->ATOMICIO;
+ a := array[n] of byte;
+ n = sys->read(nc.conn.dfd, a, n);
+ if(dbg)
+ sys->print("file %d: initial read %d bytes\n", nc.id, n);
+ if(n < 0) {
+ bs.err = "read error";
+ return;
+ }
+ hdr.setmediatype(f, a[0:n]);
+ hdr.base = hdr.actual = bs.req.url;
+ if(dbg)
+ sys->print("file %d: hdr has mediatype=%s, length=%d\n",
+ nc.id, CU->mnames[hdr.mtype], hdr.length);
+ bs.hdr = hdr;
+ if(n == len a)
+ nc.tbuf = a;
+ else
+ nc.tbuf = a[0:n];
+}
+
+getdata(nc: ref Netconn, bs: ref ByteSource): int
+{
+ dfd := nc.conn.dfd;
+ if (dfd == nil)
+ return -1;
+ if (bs.data == nil || bs.edata >= len bs.data) {
+ closeconn(nc);
+ return 0;
+ }
+ buf := bs.data[bs.edata:];
+ n := len buf;
+ if (nc.tbuf != nil) {
+ # initial overread of header
+ if (n >= len nc.tbuf) {
+ n = len nc.tbuf;
+ buf[:] = nc.tbuf;
+ nc.tbuf = nil;
+ return n;
+ }
+ buf[:] = nc.tbuf[:n];
+ nc.tbuf = nc.tbuf[n:];
+ return n;
+ }
+ n = sys->read(dfd, buf, n);
+ if(dbg > 1)
+ sys->print("ftp %d: read %d bytes\n", nc.id, n);
+ if(n <= 0) {
+ bs.err = sys->sprint("%r");
+ closeconn(nc);
+ }
+ return n;
+}
+
+defaultport(nil: string) : int
+{
+ return 0;
+}
+
+closeconn(nc: ref Netconn)
+{
+ nc.conn.dfd = nil;
+ nc.conn.cfd = nil;
+ nc.conn.dir = "";
+ nc.connected = 0;
+}
diff --git a/appl/charon/ftp.b b/appl/charon/ftp.b
new file mode 100644
index 00000000..9e2f74e6
--- /dev/null
+++ b/appl/charon/ftp.b
@@ -0,0 +1,314 @@
+implement Transport;
+
+include "common.m";
+include "transport.m";
+
+# local copies from CU
+sys: Sys;
+U: Url;
+ Parsedurl: import U;
+S: String;
+CU: CharonUtils;
+ Netconn, ByteSource, Header, config: import CU;
+
+FTPPORT: con 21;
+
+# Return codes
+Extra, Success, Incomplete, TempFail, PermFail : con (1+iota);
+
+cmdbuf := array[200] of byte;
+dbg := 0;
+
+init(c: CharonUtils)
+{
+ CU = c;
+ sys = load Sys Sys->PATH;
+ S = load String String->PATH;
+ U = load Url Url->PATH;
+ if (U != nil)
+ U->init();
+ dbg = int (CU->config).dbg['n'];
+}
+
+connect(nc: ref Netconn, bs: ref ByteSource)
+{
+ port := nc.port;
+ if(port == 0)
+ port = FTPPORT;
+ addr := "tcp!" + nc.host + "!" + string port;
+ if(dbg)
+ sys->print("ftp %d: dialing %s\n", nc.id, addr);
+ err := "";
+ ctlfd : ref sys->FD = nil;
+ rv : int;
+ (rv, nc.conn) = sys->dial(addr, nil);
+ if(rv < 0) {
+ syserr := sys->sprint("%r");
+ if(S->prefix("cs: dialup", syserr))
+ err = syserr[4:];
+ else if(S->prefix("cs: dns: no translation found", syserr))
+ err = "unknown host";
+ else
+ err = sys->sprint("couldn't connect: %s", syserr);
+ }
+ else {
+ if(dbg)
+ sys->print("ftp %d: connected\n", nc.id);
+ ctlfd = nc.conn.dfd;
+ # use cfd to hold control connection so can use dfd to hold data connection
+ nc.conn.cfd = ctlfd;
+ nc.conn.dfd = nil;
+
+ # look for Hello
+ (code, msg) := getreply(nc, ctlfd);
+ if(code != Success)
+ err = "instead of hello: " + msg;
+ else {
+ # logon
+ err = sendrequest(nc, ctlfd, "USER anonymous");
+ if(err == "") {
+ (code, msg) = getreply(nc, ctlfd);
+ if(code == Incomplete) {
+ # need password
+ err = sendrequest(nc, ctlfd, "PASS webget@webget.com");
+ if(err == "")
+ (code, msg) = getreply(nc, ctlfd);
+ }
+ if(err == "") {
+ if(code != Success)
+ err = "login failed: " + msg;
+
+ # image type
+ err = sendrequest(nc, ctlfd, "TYPE I");
+ if(err == "") {
+ (code, msg) = getreply(nc, ctlfd);
+ if(code != Success)
+ err = "can't set type I: " + msg;
+ }
+ }
+ }
+ }
+ }
+ if(err == "") {
+ nc.connected = 1;
+ nc.state = CU->NCgethdr;
+ }
+ else {
+ if(dbg)
+ sys->print("ftp %d: connection failed: %s\n", nc.id, err);
+ bs.err = err;
+ closeconn(nc);
+ }
+}
+
+# Ask ftp server on ctlfd for passive port and dial it
+dialdata(nc: ref Netconn, ctlfd: ref sys->FD) : string
+{
+ # put in passive mode
+ err := sendrequest(nc, ctlfd, "PASV");
+ (code, msg) := getreply(nc, ctlfd);
+ if(code != Success)
+ return "can't use passive mode: " + msg;
+ (paddr, pport) := passvap(msg);
+ if(paddr == "")
+ return "passive mode protocol botch: " + msg;
+ # dial data port
+ daddr := "tcp!" + paddr + "!" + pport;
+ if(dbg)
+ sys->print("ftp %d: dialing data %s", nc.id, daddr);
+ (ok, dnet) := sys->dial(daddr, nil);
+ if(ok < 0)
+ return "data dial error";
+ nc.conn.dfd = dnet.dfd;
+ return "";
+}
+
+writereq(nc: ref Netconn, bs: ref ByteSource)
+{
+ ctlfd := nc.conn.cfd;
+ CU->assert(ctlfd != nil);
+ err := dialdata(nc, ctlfd);
+ if(err == "") {
+ # tell remote to send file
+ err = sendrequest(nc, ctlfd, "RETR " + bs.req.url.path);
+ }
+ if(err != "") {
+ if(dbg)
+ sys->print("ftp %d: error: %s\n", nc.id, err);
+ bs.err = err;
+ closeconn(nc);
+ }
+}
+
+gethdr(nc: ref Netconn, bs: ref ByteSource)
+{
+ hdr := Header.new();
+ bs.hdr = hdr;
+ err := "";
+ ctlfd := nc.conn.cfd;
+ dfd := nc.conn.dfd;
+ CU->assert(ctlfd != nil && dfd != nil);
+ (code, msg) := getreply(nc, ctlfd);
+ if(code != Extra) {
+ if(dbg)
+ sys->print("ftp %d: retrieve failed: %s\n",
+ nc.id, msg);
+ hdr.code = CU->HCNotFound;
+ hdr.msg = "Not found";
+ }
+ else {
+ hdr.code = CU->HCOk;
+
+ # try to guess media type before returning header
+ buf := array[sys->ATOMICIO] of byte;
+ n := sys->read(dfd, buf, len buf);
+ if(dbg)
+ sys->print("ftp %d: read %d bytes\n", nc.id, n);
+ if(n < 0)
+ err = "error reading data";
+ else {
+ if(n > 0)
+ nc.tbuf = buf[0:n];
+ else
+ nc.tbuf = nil;
+ hdr.setmediatype(bs.req.url.path, nc.tbuf);
+ hdr.actual = bs.req.url;
+ hdr.base = hdr.actual;
+ hdr.length = -1;
+ hdr.msg = "Ok";
+ }
+ }
+ if(err != "") {
+ if(dbg)
+ sys->print("ftp %d: error %s\n", nc.id, err);
+ bs.err = err;
+ closeconn(nc);
+ }
+}
+
+getdata(nc: ref Netconn, bs: ref ByteSource): int
+{
+ dfd := nc.conn.dfd;
+ CU->assert(dfd != nil);
+ if (bs.data == nil || bs.edata >= len bs.data) {
+ closeconn(nc);
+ return 0;
+ }
+ buf := bs.data[bs.edata:];
+ n := len buf;
+ if (nc.tbuf != nil) {
+ # initial overread of header
+ if (n >= len nc.tbuf) {
+ n = len nc.tbuf;
+ buf[:] = nc.tbuf;
+ nc.tbuf = nil;
+ return n;
+ }
+ buf[:] = nc.tbuf[:n];
+ nc.tbuf = nc.tbuf[n:];
+ return n;
+ }
+ n = sys->read(dfd, buf, n);
+ if(dbg > 1)
+ sys->print("ftp %d: read %d bytes\n", nc.id, n);
+ if(n <= 0) {
+ bs.err = "eof";
+ closeconn(nc);
+ }
+ return n;
+}
+
+# Send ftp request cmd along fd; return "" if OK else error string.
+sendrequest(nc: ref Netconn, fd: ref sys->FD, cmd: string) : string
+{
+ if(dbg > 1)
+ sys->print("ftp %d: send request: %s\n", nc.id, cmd);
+ cmd = cmd + "\r\n";
+ buf := array of byte cmd;
+ n := len buf;
+ if(sys->write(fd, buf, n) != n)
+ return sys->sprint("write error: %r");
+ return "";
+}
+
+# Get reply to ftp request along fd.
+# Reply may be more than one line ("commentary")
+# but ends with a line that has a status code in the first
+# three characters (a number between 100 and 600)
+# followed by a blank and a possible message.
+# If OK, return the hundreds digit of the status (which will
+# mean one of Extra, Success, etc.), and the whole
+# last line; else return (-1, "").
+getreply(nc: ref Netconn, fd: ref sys->FD) : (int, string)
+{
+ # Reply might contain more than one line,
+ # because there might be "commentary" lines.
+ i := 0;
+ j := 0;
+ aline: array of byte;
+ eof := 0;
+ for(;;) {
+ (aline, eof, i, j) = CU->getline(fd, cmdbuf, i, j);
+ if(eof)
+ break;
+ line := string aline;
+ n := len line;
+ if(n == 0)
+ break;
+ if(dbg > 1)
+ sys->print("ftp %d: got reply: %s\n", nc.id, line);
+ rv := int line;
+ if(rv >= 100 && rv < 600) {
+ # if line is like '123-stuff'
+ # then there will be more lines until
+ # '123 stuff'
+ if(len line<4 || line[3]==' ')
+ return (rv/100, line);
+ }
+ }
+ return (-1, "");
+}
+
+# Parse reply to PASSV to find address and port numbers.
+# This is AI because extant agents aren't good at following
+# the standard.
+passvap(s: string) : (string, string)
+{
+ addr := "";
+ port := "";
+ (nil, v) := S->splitl(s, "(");
+ if(v != "")
+ s = v[1:];
+ else
+ (nil, s) = S->splitl(s, "0123456789");
+ if(s != "") {
+ (n, l) := sys->tokenize(s, ",");
+ if(n >= 6) {
+ addr = hd l + ".";
+ l = tl l;
+ addr += hd l + ".";
+ l = tl l;
+ addr += hd l + ".";
+ l = tl l;
+ addr += hd l;
+ l = tl l;
+ p1 := int hd l;
+ p2 := int hd tl l;
+ port = string (((p1&255)<<8)|(p2&255));
+ }
+ }
+ return (addr, port);
+}
+
+defaultport(nil: string) : int
+{
+ return FTPPORT;
+}
+
+closeconn(nc: ref Netconn)
+{
+ nc.conn.dfd = nil;
+ nc.conn.cfd = nil;
+ nc.conn.dir = "";
+ nc.connected = 0;
+}
diff --git a/appl/charon/gui.b b/appl/charon/gui.b
new file mode 100644
index 00000000..d433bf82
--- /dev/null
+++ b/appl/charon/gui.b
@@ -0,0 +1,560 @@
+# Gui implementation for running under wm (tk window manager)
+implement Gui;
+
+include "common.m";
+include "tk.m";
+include "tkclient.m";
+
+include "dialog.m";
+ dialog: Dialog;
+
+sys: Sys;
+
+D: Draw;
+ Font,Point, Rect, Image, Screen, Display: import D;
+
+CU: CharonUtils;
+
+E: Events;
+ Event: import E;
+
+tk: Tk;
+
+tkclient: Tkclient;
+
+WINDOW, CTLS, PROG, STATUS, BORDER, EXIT: con 1 << iota;
+REQD: con ~0;
+
+cfg := array[] of {
+ (REQD, "entry .ctlf.url -bg white -font /fonts/lucidasans/unicode.7.font -height 16"),
+ (REQD, "button .ctlf.back -bd 1 -command {send gctl back} -state disabled -text {back} -font /fonts/lucidasans/unicode.7.font"),
+ (REQD, "button .ctlf.stop -bd 1 -command {send gctl stop} -state disabled -text {stop} -font /fonts/lucidasans/unicode.7.font"),
+ (REQD, "button .ctlf.fwd -bd 1 -command {send gctl fwd} -state disabled -text {next} -font /fonts/lucidasans/unicode.7.font"),
+ (REQD, "label .status.status -bd 1 -font /fonts/lucidasans/unicode.6.font -height 14 -anchor w"),
+ (REQD, "button .ctlf.exit -bd 1 -bitmap exit.bit -command {send wm_title exit}"),
+ (REQD, "frame .f -bd 0"),
+ (BORDER, ".f configure -bd 2 -relief sunken"),
+ (CTLS|EXIT, "frame .ctlf"),
+ (STATUS, "frame .status -bd 0"),
+ (STATUS, "frame .statussep -bg black -height 1"),
+ (STATUS, "button .status.snarf -text snarf -command {send gctl snarfstatus} -font /fonts/charon/plain.small.font"),
+
+ (CTLS, "bind .ctlf.url <Key-\n> {send gctl go}"),
+ (CTLS, "bind .ctlf.url <Key-\u0003> {send gctl copyurl}"),
+ (CTLS, "bind .ctlf.url <Key-\u0016> {send gctl pasteurl}"),
+
+# (PROG, "canvas .prog -bd 0 -height 20"),
+# (PROG, "bind .prog <ButtonPress-1> {send gctl b1p %X %Y}"),
+ (CTLS, "pack .ctlf.back .ctlf.stop .ctlf.fwd -side left -anchor w -fill y"),
+ (CTLS, "pack .ctlf.url -side left -padx 2 -fill x -expand 1"),
+ (EXIT, "pack .ctlf.exit -side right -anchor e"),
+ (CTLS|EXIT, "pack .ctlf -side top -fill x"),
+ (REQD, "pack .f -side top -fill both -expand 1"),
+# (PROG, "pack .prog -side bottom -fill x"),
+ (STATUS, "pack .status.snarf -side right"),
+ (STATUS, "pack .status.status -side right -fill x -expand 1"),
+ (STATUS, "pack .statussep -side top -fill x"),
+ (STATUS, "pack .status -side bottom -fill x"),
+ (CTLS|EXIT, "pack propagate .ctlf 0"),
+ (STATUS, "pack propagate .status 0"),
+};
+
+framebinds := array[] of {
+ "bind .f <Key> {send gctl k %s}",
+ "bind .f <FocusOut> {send gctl focusout}",
+ "bind .f <ButtonPress-1> {grab set .f;send gctl b1p %X %Y}",
+ "bind .f <Double-ButtonPress-1> {send gctl b1p %X %Y}",
+ "bind .f <ButtonRelease-1> {grab release .f;send gctl b1r %X %Y}",
+ "bind .f <Motion-Button-1> {send gctl b1d %X %Y}",
+ "bind .f <ButtonPress-2> {send gctl b2p %X %Y}",
+ "bind .f <Double-ButtonPress-2> {send gctl b2p %X %Y}",
+ "bind .f <ButtonRelease-2> {send gctl b2r %X %Y}",
+ "bind .f <Motion-Button-2> {send gctl b2d %X %Y}",
+ "bind .f <ButtonPress-3> {send gctl b3p %X %Y}",
+ "bind .f <Double-ButtonPress-3> {send gctl b3p %X %Y}",
+ "bind .f <ButtonRelease-3> {send gctl b3r %X %Y}",
+ "bind .f <Motion-Button-3> {send gctl b3d %X %Y}",
+ "bind .f <Motion> {send gctl m %X %Y}",
+};
+
+tktop: ref Tk->Toplevel;
+mousegrabbed := 0;
+offset: Point;
+ZP: con Point(0,0);
+popup: ref Popup;
+popuptk: ref Tk->Toplevel;
+gctl: chan of string;
+drawctxt: ref Draw->Context;
+
+realwin: ref Draw->Image;
+mask: ref Draw->Image;
+
+init(ctxt: ref Draw->Context, cu: CharonUtils): ref Draw->Context
+{
+ sys = load Sys Sys->PATH;
+ D = load Draw Draw->PATH;
+ CU = cu;
+ E = cu->E;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ if(tkclient == nil)
+ CU->raisex(sys->sprint("EXInternal: can't load module Tkclient: %r"));
+ tkclient->init();
+
+ wmctl: chan of string;
+ buttons := parsebuttons((CU->config).buttons);
+ winopts := parsewinopts((CU->config).framework);
+
+ (tktop, wmctl) = tkclient->toplevel(ctxt, "", (CU->config).wintitle, buttons);
+
+ ctxt = tktop.ctxt.ctxt;
+ drawctxt = ctxt;
+ display = ctxt.display;
+
+ gctl = chan of string;
+ tk->namechan(tktop, gctl, "gctl");
+ tk->cmd(tktop, "pack propagate . 0");
+ filtertkcmds(tktop, winopts, cfg);
+ tkcmds(tktop, framebinds);
+ w := (CU->config).defaultwidth;
+ h := (CU->config).defaultheight;
+ tk->cmd(tktop, ". configure -width " + string w + " -height " + string h);
+ tk->cmd(tktop, "update");
+ tkclient->onscreen(tktop, nil);
+ tkclient->startinput(tktop, "kbd"::"ptr"::nil);
+ makewins();
+ mask = display.opaque;
+ progress = chan of Progressmsg;
+ pidc := chan of int;
+ spawn progmon(pidc);
+ <- pidc;
+ spawn evhandle(tktop, wmctl, E->evchan);
+ return ctxt;
+}
+
+parsebuttons(s: string): int
+{
+ b := 0;
+ (nil, toks) := sys->tokenize(s, ",");
+ for (;toks != nil; toks = tl toks) {
+ case hd toks {
+ "help" =>
+ b |= Tkclient->Help;
+ "resize" =>
+ b |= Tkclient->Resize;
+ "hide" =>
+ b |= Tkclient->Hide;
+ "plain" =>
+ b = Tkclient->Plain;
+ }
+ }
+ return b | Tkclient->Help;
+}
+
+parsewinopts(s: string): int
+{
+ b := WINDOW;
+ (nil, toks) := sys->tokenize(s, ",");
+ for (;toks != nil; toks = tl toks) {
+ case hd toks {
+ "status" =>
+ b |= STATUS;
+ "controls" or "ctls" =>
+ b |= CTLS;
+ "progress" or "prog" =>
+ b |= PROG;
+ "border" =>
+ b |= BORDER;
+ "exit" =>
+ b |= EXIT;
+ "all" =>
+ # note: "all" doesn't include 'EXIT' !
+ b |= WINDOW | STATUS | CTLS | PROG | BORDER;
+ }
+ }
+ return b;
+}
+
+filtertkcmds(top: ref Tk->Toplevel, filter: int, cmds: array of (int, string))
+{
+ for (i := 0; i < len cmds; i++) {
+ (val, cmd) := cmds[i];
+ if (val & filter) {
+ if ((e := tk->cmd(top, cmd)) != nil && e[0] == '!')
+ sys->print("tk error on '%s': %s\n", cmd, e);
+ }
+ }
+}
+
+tkcmds(top: ref Tk->Toplevel, cmds: array of string)
+{
+ for (i := 0; i < len cmds; i++)
+ if ((e := tk->cmd(top, cmds[i])) != nil && e[0] == '!')
+ sys->print("tk error on '%s': %s\n", cmds[i], e);
+}
+
+clientr(t: ref Tk->Toplevel, wname: string): Rect
+{
+ bd := int tk->cmd(t, wname + " cget -borderwidth");
+ x := bd + int tk->cmd(t, wname + " cget -actx");
+ y := bd + int tk->cmd(t, wname + " cget -acty");
+ w := int tk->cmd(t, wname + " cget -actwidth");
+ h := int tk->cmd(t, wname + " cget -actheight");
+ return Rect((x,y),(x+w,y+h));
+}
+
+progmon(pidc: chan of int)
+{
+ pidc <-= sys->pctl(0, nil);
+ for (;;) {
+ msg := <- progress;
+#prprog(msg);
+ # just handle stop button for now
+ if (msg.bsid == -1) {
+ case (msg.state) {
+ Pstart => stopbutton(1);
+ * => stopbutton(0);
+ }
+ }
+ }
+}
+
+st2s := array [] of {
+ Punused => "unused",
+ Pstart => "start",
+ Pconnected => "connected",
+ Psslconnected => "sslconnected",
+ Phavehdr => "havehdr",
+ Phavedata => "havedata",
+ Pdone => "done",
+ Perr => "error",
+ Paborted => "aborted",
+};
+
+prprog(m:Progressmsg)
+{
+ sys->print("%d %s %d%% %s\n", m.bsid, st2s[m.state], m.pcnt, m.s);
+}
+
+
+r2s(r: Rect): string
+{
+ return sys->sprint("%d %d %d %d", r.min.x, r.min.y, r.max.x, r.max.y);
+}
+
+winpos(t: ref Tk->Toplevel): Point
+{
+ return (int tk->cmd(t, ". cget -actx"), int tk->cmd(t, ". cget -acty"));
+}
+
+evhandle(t: ref Tk->Toplevel, wmctl: chan of string, evchan: chan of ref Event)
+{
+ for(;;) {
+ ev: ref Event = nil;
+ dismisspopup := 1;
+ alt {
+ s := <-gctl =>
+ (nil, l) := sys->tokenize(s, " ");
+ case hd l {
+ "focusout" =>
+ ev = ref Event.Elostfocus;
+ "b1p" or "b1r" or "b1d" or
+ "b2p" or "b2r" or "b2d" or
+ "b3p" or "b3r" or "b3d" or
+ "m" =>
+ l = tl l;
+ pt := Point(int hd l, int hd tl l);
+ pt = pt.sub(offset);
+ mtype := s2mtype(s);
+ dismisspopup = 0;
+ if(mtype == E->Mlbuttondown) {
+ tk->cmd(t, "focus .f");
+ pu := popup;
+ if (pu != nil && !pu.r.contains(pt))
+ dismisspopup = 1;
+ pu = nil;
+ }
+ ev = ref Event.Emouse(pt, mtype);
+ "k" =>
+ dismisspopup = 0;
+ k := int hd tl l;
+ if(k != 0)
+ ev = ref Event.Ekey(k);
+ "back" =>
+ ev = ref Event.Eback;
+ "stop" =>
+ ev = ref Event.Estop;
+ "fwd" =>
+ ev = ref Event.Efwd;
+ "go" =>
+ url := tk->cmd(tktop, ".ctlf.url get");
+ if (url != nil)
+ ev = ref Event.Ego(url, nil, 0, E->EGnormal);
+ "copyurl" =>
+ url := tk->cmd(tktop, ".ctlf.url get");
+ snarfput(url);
+ "pasteurl" =>
+ url := tk->quote(tkclient->snarfget());
+ tk->cmd(tktop, ".ctlf.url delete 0 end");
+ tk->cmd(tktop, ".ctlf.url insert end " + url);
+ tk->cmd(tktop, "update");
+ "snarfstatus" =>
+ url := tk->cmd(tktop, ".status.status cget -text");
+ tkclient->snarfput(url);
+ }
+ s := <-t.ctxt.ctl or
+ s = <-t.wreq or
+ s = <-wmctl =>
+ case s {
+ "exit" =>
+ hidewins();
+ ev = ref Event.Equit(0);
+ "task" =>
+ if (cancelpopup())
+ evchan <-= ref Event.Edismisspopup;
+ tkclient->wmctl(t, s);
+ if(tktop.image == nil)
+ realwin = nil;
+ "help" =>
+ ev = ref Event.Ego((CU->config).helpurl, nil, 0, E->EGnormal);
+ * =>
+ if (s[0] == '!' && cancelpopup())
+ evchan <-= ref Event.Edismisspopup;
+ oldimg := t.image;
+ e := tkclient->wmctl(t, s);
+ if(s[0] == '!' && e == nil){
+ if(t.image != oldimg){
+ oldimg = nil;
+ makewins();
+ ev = ref Event.Ereshape(mainwin.r);
+ }
+ offset = tk->rect(tktop, ".f", 0).min;
+ }
+ }
+ s := <-t.ctxt.kbd =>
+ tk->keyboard(t, s);
+ s := <-t.ctxt.ptr =>
+ tk->pointer(t, *s);
+ }
+ if (dismisspopup) {
+ if (cancelpopup()) {
+ evchan <-= ref Event.Edismisspopup;
+ }
+ }
+ if (ev != nil)
+ evchan <-= ev;
+ }
+}
+
+s2mtype(s: string): int
+{
+ mtype := E->Mmove;
+ if(s[0] == 'm')
+ mtype = E->Mmove;
+ else {
+ case s[1] {
+ '1' =>
+ case s[2] {
+ 'p' => mtype = E->Mlbuttondown;
+ 'r' => mtype = E->Mlbuttonup;
+ 'd' => mtype = E->Mldrag;
+ }
+ '2' =>
+ case s[2] {
+ 'p' => mtype = E->Mmbuttondown;
+ 'r' => mtype = E->Mmbuttonup;
+ 'd' => mtype = E->Mmdrag;
+ }
+ '3' =>
+ case s[2] {
+ 'p' => mtype = E->Mrbuttondown;
+ 'r' => mtype = E->Mrbuttonup;
+ 'd' => mtype = E->Mrdrag;
+ }
+ }
+ }
+ return mtype;
+}
+
+makewins()
+{
+ if(tktop.image == nil)
+ return;
+ screen := Screen.allocate(tktop.image, display.transparent, 0);
+ offset = tk->rect(tktop, ".f", 0).min;
+ r := tk->rect(tktop, ".f", Tk->Local);
+ realwin = screen.newwindow(r, D->Refnone, D->White);
+ realwin.origin(ZP, r.min);
+ if(realwin == nil)
+ CU->raisex(sys->sprint("EXFatal: can't initialize windows: %r"));
+
+ mainwin = display.newimage(realwin.r, realwin.chans, 0, D->White);
+ if(mainwin == nil)
+ CU->raisex(sys->sprint("EXFatal: can't initialize windows: %r"));
+}
+
+hidewins()
+{
+ tk->cmd(tktop, ". unmap");
+}
+
+snarfput(s: string)
+{
+ tkclient->snarfput(s);
+}
+
+setstatus(s: string)
+{
+ tk->cmd(tktop, ".status.status configure -text " + tk->quote(s));
+ tk->cmd(tktop, "update");
+}
+
+seturl(s: string)
+{
+ tk->cmd(tktop, ".ctlf.url delete 0 end");
+ tk->cmd(tktop, ".ctlf.url insert 0 " + tk->quote(s));
+ tk->cmd(tktop, "update");
+}
+
+auth(realm: string): (int, string, string)
+{
+ user := prompt(realm + " username?", nil).t1;
+ passwd := prompt("password?", nil).t1;
+ if(user == nil)
+ return (0, nil, nil);
+ return (1, user, passwd);
+}
+
+alert(msg: string)
+{
+sys->print("ALERT:%s\n", msg);
+ return;
+}
+
+confirm(msg: string): int
+{
+sys->print("CONFIRM:%s\n", msg);
+ return -1;
+}
+
+prompt(msg, dflt: string): (int, string)
+{
+ if(dialog == nil){
+ dialog = load Dialog Dialog->PATH;
+ dialog->init();
+ }
+ return (1, dialog->getstring(drawctxt, mainwin, msg));
+ # return (-1, "");
+}
+
+stopbutton(enable: int)
+{
+ state: string;
+ if (enable) {
+ tk->cmd(tktop, ".ctlf.stop configure -bg red -activebackground red -activeforeground white");
+ state = "normal";
+ } else {
+ tk->cmd(tktop, ".ctlf.stop configure -bg #dddddd");
+ state = "disabled";
+ }
+ tk->cmd(tktop, ".ctlf.stop configure -state " + state + ";update");
+}
+
+backbutton(enable: int)
+{
+ state: string;
+ if (enable) {
+ tk->cmd(tktop, ".ctlf.back configure -bg lime -activebackground lime -activeforeground red");
+ state = "normal";
+ } else {
+ tk->cmd(tktop, ".ctlf.back configure -bg #dddddd");
+ state = "disabled";
+ }
+ tk->cmd(tktop, ".ctlf.back configure -state " + state + ";update");
+}
+
+fwdbutton(enable: int)
+{
+ state: string;
+ if (enable) {
+ tk->cmd(tktop, ".ctlf.fwd configure -bg lime -activebackground lime -activeforeground red");
+ state = "normal";
+ } else {
+ tk->cmd(tktop, ".ctlf.fwd configure -bg #dddddd");
+ state = "disabled";
+ }
+ tk->cmd(tktop, ".ctlf.fwd configure -state " + state + ";update");
+}
+
+flush(r: Rect)
+{
+ if(realwin != nil) {
+ oclipr := mainwin.clipr;
+ mainwin.clipr = r;
+ realwin.draw(r, mainwin, nil, r.min);
+ mainwin.clipr = oclipr;
+ }
+}
+
+clientfocus()
+{
+ tk->cmd(tktop, "focus .f");
+ tk->cmd(tktop, "update");
+}
+
+exitcharon()
+{
+ hidewins();
+ E->evchan <-= ref Event.Equit(0);
+}
+
+getpopup(r: Rect): ref Popup
+{
+ return nil;
+# cancelpopup();
+## img := screen.newwindow(r, D->White);
+# img := display.newimage(r, screen.image.chans, 0, D->White);
+# if (img == nil)
+# return nil;
+# winr := r.addpt(offset); # race for offset
+#
+# pos := "-x " + string winr.min.x + " -y " + string winr.min.y;
+# (top, nil) := tkclient->toplevel(drawctxt, pos, nil, Tkclient->Plain);
+# tk->namechan(top, gctl, "gctl");
+# tk->cmd(top, "frame .f -bd 0 -bg white -width " + string r.dx() + " -height " + string r.dy());
+# tkcmds(top, framebinds);
+# tk->cmd(top, "pack .f; update");
+# tkclient->onscreen(tktop, "onscreen");
+# tkclient->startinput(tktop, "kbd"::"ptr"::nil);
+# win := screen.newwindow(winr, D->Refbackup, D->White);
+# if (win == nil)
+# return nil;
+# win.origin(r.min, winr.min);
+#
+# popuptk = top;
+# popup = ref Popup(r, img, win);
+## XXXX need to start a thread to feed mouse/kbd events from popup,
+## but we need to know when to tear it down.
+# return popup;
+}
+
+cancelpopup(): int
+{
+ popuptk = nil;
+ pu := popup;
+ if (pu == nil)
+ return 0;
+ pu.image = nil;
+ pu.window = nil;
+ pu = nil;
+ popup = nil;
+ return 1;
+}
+
+Popup.flush(p: self ref Popup, r: Rect)
+{
+ win := p.window;
+ img := p.image;
+ if (win != nil && img != nil)
+ win.draw(r, img, nil, r.min);
+}
diff --git a/appl/charon/gui.m b/appl/charon/gui.m
new file mode 100644
index 00000000..b295aa47
--- /dev/null
+++ b/appl/charon/gui.m
@@ -0,0 +1,48 @@
+Gui: module {
+ PATH: con "/dis/charon/gui.dis";
+
+ Progressmsg : adt {
+ bsid : int;
+ state : int;
+ pcnt : int;
+ s : string;
+ };
+
+ # clients should never capture Popup.image
+ # other than during drawing operations
+ Popup: adt {
+ r: Draw->Rect;
+ image: ref Draw->Image;
+ window: ref Draw->Image;
+
+ flush: fn(p: self ref Popup, r: Draw->Rect);
+ };
+
+ # Progress states
+ Punused, Pstart, Pconnected, Psslconnected, Phavehdr,
+ Phavedata, Pdone, Perr, Paborted : con iota;
+
+ display: ref Draw->Display;
+ mainwin: ref Draw->Image;
+ progress: chan of Progressmsg;
+
+ init: fn(ctxt: ref Draw->Context, cu: CharonUtils): ref Draw->Context;
+
+ snarfput: fn(s: string);
+ setstatus: fn(s: string);
+ seturl: fn(s: string);
+ auth: fn(realm: string) : (int, string, string);
+ alert: fn(msg: string);
+ confirm: fn(msg: string) : int;
+ prompt: fn(msg, dflt: string) : (int, string);
+ backbutton: fn(enable : int);
+ fwdbutton: fn (enable : int);
+
+ flush: fn (r : Draw->Rect);
+ clientfocus: fn();
+
+ getpopup: fn(r: Draw->Rect): ref Popup;
+ cancelpopup: fn(): int;
+
+ exitcharon: fn();
+};
diff --git a/appl/charon/http.b b/appl/charon/http.b
new file mode 100644
index 00000000..27c7d695
--- /dev/null
+++ b/appl/charon/http.b
@@ -0,0 +1,1040 @@
+implement Transport;
+
+include "common.m";
+include "transport.m";
+include "date.m";
+
+#D: Date;
+# sslhs: SSLHS;
+ssl3: SSL3;
+Context: import ssl3;
+# Inferno supported cipher suites:
+ssl_suites := array [] of {
+ byte 0, byte 16r03, # RSA_EXPORT_WITH_RC4_40_MD5
+ byte 0, byte 16r04, # RSA_WITH_RC4_128_MD5
+ byte 0, byte 16r05, # RSA_WITH_RC4_128_SHA
+ byte 0, byte 16r06, # RSA_EXPORT_WITH_RC2_CBC_40_MD5
+ byte 0, byte 16r07, # RSA_WITH_IDEA_CBC_SHA
+ byte 0, byte 16r08, # RSA_EXPORT_WITH_DES40_CBC_SHA
+ byte 0, byte 16r09, # RSA_WITH_DES_CBC_SHA
+ byte 0, byte 16r0A, # RSA_WITH_3DES_EDE_CBC_SHA
+
+ byte 0, byte 16r0B, # DH_DSS_EXPORT_WITH_DES40_CBC_SHA
+ byte 0, byte 16r0C, # DH_DSS_WITH_DES_CBC_SHA
+ byte 0, byte 16r0D, # DH_DSS_WITH_3DES_EDE_CBC_SHA
+ byte 0, byte 16r0E, # DH_RSA_EXPORT_WITH_DES40_CBC_SHA
+ byte 0, byte 16r0F, # DH_RSA_WITH_DES_CBC_SHA
+ byte 0, byte 16r10, # DH_RSA_WITH_3DES_EDE_CBC_SHA
+ byte 0, byte 16r11, # DHE_DSS_EXPORT_WITH_DES40_CBC_SHA
+ byte 0, byte 16r12, # DHE_DSS_WITH_DES_CBC_SHA
+ byte 0, byte 16r13, # DHE_DSS_WITH_3DES_EDE_CBC_SHA
+ byte 0, byte 16r14, # DHE_RSA_EXPORT_WITH_DES40_CBC_SHA
+ byte 0, byte 16r15, # DHE_RSA_WITH_DES_CBC_SHA
+ byte 0, byte 16r16, # DHE_RSA_WITH_3DES_EDE_CBC_SHA
+
+ byte 0, byte 16r17, # DH_anon_EXPORT_WITH_RC4_40_MD5
+ byte 0, byte 16r18, # DH_anon_WITH_RC4_128_MD5
+ byte 0, byte 16r19, # DH_anon_EXPORT_WITH_DES40_CBC_SHA
+ byte 0, byte 16r1A, # DH_anon_WITH_DES_CBC_SHA
+ byte 0, byte 16r1B, # DH_anon_WITH_3DES_EDE_CBC_SHA
+
+ byte 0, byte 16r1C, # FORTEZZA_KEA_WITH_NULL_SHA
+ byte 0, byte 16r1D, # FORTEZZA_KEA_WITH_FORTEZZA_CBC_SHA
+ byte 0, byte 16r1E, # FORTEZZA_KEA_WITH_RC4_128_SHA
+};
+
+ssl_comprs := array [] of {byte 0};
+
+# local copies from CU
+sys: Sys;
+U: Url;
+ Parsedurl: import U;
+S: String;
+C: Ctype;
+T: StringIntTab;
+CU: CharonUtils;
+ Netconn, ByteSource, Header, config, Nameval : import CU;
+
+ctype: array of byte; # local copy of C->ctype
+
+HTTPD: con 80; # Default IP port
+HTTPSD: con 443; # Default IP port for HTTPS
+
+# For Inferno, won't be able to read more than this at one go anyway
+BLOCKSIZE: con 1460;
+
+# HTTP/1.1 Spec says 5, but we've seen more than that in non-looping redirs
+# MAXREDIR: con 10;
+
+# tstate bits
+THTTP_1_0, TPersist, TProxy, TSSL: con (1<<iota);
+
+# Header fields (in order: general, request, response, entity)
+HCacheControl, HConnection, HDate, HPragma, HTransferEncoding,
+ HUpgrade, HVia,
+ HKeepAlive, # extension
+HAccept, HAcceptCharset, HAcceptEncoding, HAcceptLanguage,
+ HAuthorization, HExpect, HFrom, HHost, HIfModifiedSince,
+ HIfMatch, HIfNoneMatch, HIfRange, HIfUnmodifiedSince,
+ HMaxForwards, HProxyAuthorization, HRange, HReferer,
+ HUserAgent,
+ HCookie, # extension
+HAcceptRanges, HAge, HLocation, HProxyAuthenticate, HPublic,
+ HRetryAfter, HServer, HSetProxy, HVary, HWarning,
+ HWWWAuthenticate,
+ HContentDisposition, HSetCookie, HRefresh, # extensions
+ HWindowTarget, HPICSLabel, # more extensions
+HAllow, HContentBase, HContentEncoding, HContentLanguage,
+ HContentLength, HContentLocation, HContentMD5,
+ HContentRange, HContentType, HETag, HExpires,
+ HLastModified,
+ HXReqTime, HXRespTime, HXUrl, # our extensions, for cached entities
+ NumHfields: con iota;
+
+# (track above enumeration)
+hdrnames := array[] of {
+ "Cache-Control",
+ "Connection",
+ "Date",
+ "Pragma",
+ "Transfer-Encoding",
+ "Upgrade",
+ "Via",
+ "Keep-Alive",
+ "Accept",
+ "Accept-Charset",
+ "Accept-Encoding",
+ "Accept-Language",
+ "Authorization",
+ "Expect",
+ "From",
+ "Host",
+ "If-Modified-Since",
+ "If-Match",
+ "If-None-Match",
+ "If-Range",
+ "If-Unmodified-Since",
+ "Max-Forwards",
+ "Proxy-Authorization",
+ "Range",
+ "Refererer",
+ "User-Agent",
+ "Cookie",
+ "Accept-Ranges",
+ "Age",
+ "Location",
+ "Proxy-Authenticate",
+ "Public",
+ "Retry-After",
+ "Server",
+ "Set-Proxy",
+ "Vary",
+ "Warning",
+ "WWW-Authenticate",
+ "Content-Disposition",
+ "Set-Cookie",
+ "Refresh",
+ "Window-Target",
+ "PICS-Label",
+ "Allow",
+ "Content-Base",
+ "Content-Encoding",
+ "Content-Language",
+ "Content-Length",
+ "Content-Location",
+ "Content-MD5",
+ "Content-Range",
+ "Content-Type",
+ "ETag",
+ "Expires",
+ "Last-Modified",
+ "X-Req-Time",
+ "X-Resp-Time",
+ "X-Url"
+};
+
+# For fast lookup; track above, and keep sorted and lowercase
+hdrtable := array[] of { T->StringInt
+ ("accept", HAccept),
+ ("accept-charset", HAcceptCharset),
+ ("accept-encoding", HAcceptEncoding),
+ ("accept-language", HAcceptLanguage),
+ ("accept-ranges", HAcceptRanges),
+ ("age", HAge),
+ ("allow", HAllow),
+ ("authorization", HAuthorization),
+ ("cache-control", HCacheControl),
+ ("connection", HConnection),
+ ("content-base", HContentBase),
+ ("content-disposition", HContentDisposition),
+ ("content-encoding", HContentEncoding),
+ ("content-language", HContentLanguage),
+ ("content-length", HContentLength),
+ ("content-location", HContentLocation),
+ ("content-md5", HContentMD5),
+ ("content-range", HContentRange),
+ ("content-type", HContentType),
+ ("cookie", HCookie),
+ ("date", HDate),
+ ("etag", HETag),
+ ("expect", HExpect),
+ ("expires", HExpires),
+ ("from", HFrom),
+ ("host", HHost),
+ ("if-modified-since", HIfModifiedSince),
+ ("if-match", HIfMatch),
+ ("if-none-match", HIfNoneMatch),
+ ("if-range", HIfRange),
+ ("if-unmodified-since", HIfUnmodifiedSince),
+ ("keep-alive", HKeepAlive),
+ ("last-modified", HLastModified),
+ ("location", HLocation),
+ ("max-forwards", HMaxForwards),
+ ("pics-label", HPICSLabel),
+ ("pragma", HPragma),
+ ("proxy-authenticate", HProxyAuthenticate),
+ ("proxy-authorization", HProxyAuthorization),
+ ("public", HPublic),
+ ("range", HRange),
+ ("referer", HReferer),
+ ("refresh", HRefresh),
+ ("retry-after", HRetryAfter),
+ ("server", HServer),
+ ("set-cookie", HSetCookie),
+ ("set-proxy", HSetProxy),
+ ("transfer-encoding", HTransferEncoding),
+ ("upgrade", HUpgrade),
+ ("user-agent", HUserAgent),
+ ("vary", HVary),
+ ("via", HVia),
+ ("warning", HWarning),
+ ("window-target", HWindowTarget),
+ ("www-authenticate", HWWWAuthenticate),
+ ("x-req-time", HXReqTime),
+ ("x-resp-time", HXRespTime),
+ ("x-url", HXUrl)
+};
+
+HTTP_Header: adt {
+ startline: string;
+
+ # following four fields only filled in if this is a response header
+ protomajor: int;
+ protominor: int;
+ code: int;
+ reason: string;
+ iossl: int; # true for ssl
+
+ vals: array of string;
+ cookies: list of string;
+
+ new: fn() : ref HTTP_Header;
+ read: fn(h: self ref HTTP_Header, fd: ref sys->FD, sslx: ref SSL3->Context, buf: array of byte) : (string, int, int);
+ write: fn(h: self ref HTTP_Header, fd: ref sys->FD, sslx: ref SSL3->Context) : int;
+ usessl: fn(h: self ref HTTP_Header);
+ addval: fn(h: self ref HTTP_Header, key: int, val: string);
+ getval: fn(h: self ref HTTP_Header, key: int) : string;
+};
+
+mediatable: array of T->StringInt;
+
+agent : string;
+dbg := 0;
+warn := 0;
+sptab : con " \t";
+
+init(cu: CharonUtils)
+{
+ CU = cu;
+ sys = load Sys Sys->PATH;
+ S = load String String->PATH;
+ U = load Url Url->PATH;
+ if (U != nil)
+ U->init();
+ C = cu->C;
+ T = load StringIntTab StringIntTab->PATH;
+# D = load Date CU->loadpath(Date->PATH);
+# if (D == nil)
+# CU->raise(sys->sprint("EXInternal: can't load Date: %r"));
+# D->init(cu);
+ ctype = C->ctype;
+ # sslhs = nil; # load on demand
+ ssl3 = nil; # load on demand
+ mediatable = CU->makestrinttab(CU->mnames);
+ agent = (CU->config).agentname;
+ dbg = int (CU->config).dbg['n'];
+ warn = dbg || int (CU->config).dbg['w'];
+}
+
+connect(nc: ref Netconn, bs: ref ByteSource)
+{
+ if(nc.scheme == "https")
+ nc.tstate |= TSSL;
+ if(config.httpminor == 0)
+ nc.tstate |= THTTP_1_0;
+ dialhost := nc.host;
+ dialport := string nc.port;
+ if(nc.scheme != "https" && config.httpproxy != nil && need_proxy(nc.host)) {
+ nc.tstate |= TProxy;
+ dialhost = config.httpproxy.host;
+ if(config.httpproxy.port != "")
+ dialport = config.httpproxy.port;
+ }
+ addr := "tcp!" + dialhost + "!" + dialport;
+ err := "";
+ if(dbg)
+ sys->print("http %d: dialing %s\n", nc.id, addr);
+ rv: int;
+ (rv, nc.conn) = sys->dial(addr, nil);
+ if(rv < 0) {
+ syserr := sys->sprint("%r");
+ if(S->prefix("cs: dialup", syserr))
+ err = syserr[4:];
+ else if(S->prefix("cs: dns: no translation found", syserr))
+ err = "unknown host";
+ else
+ err = sys->sprint("couldn't connect: %s", syserr);
+ }
+ else {
+ if(dbg)
+ sys->print("http %d: connected\n", nc.id);
+ if(nc.tstate&TSSL) {
+ #if(sslhs == nil) {
+ # sslhs = load SSLHS SSLHS->PATH;
+ # if(sslhs == nil)
+ # err = sys->sprint("can't load SSLHS: %r");
+ # else
+ # sslhs->init(2);
+ #}
+ #if(err == "")
+ # (err, nc.conn) = sslhs->client(nc.conn.dfd, addr);
+ if(nc.tstate&TProxy) # tunelling SSL through proxy
+ err = tunnel_ssl(nc);
+ vers := 0;
+ if(err == "") {
+ if(ssl3 == nil) {
+ m := load SSL3 SSL3->PATH;
+ if(m == nil)
+ err = "can't load SSL3 module";
+ else if((err = m->init()) == nil)
+ ssl3 = m;
+ }
+ if(config.usessl == CU->NOSSL)
+ err = "ssl is configured off";
+ else if((config.usessl & CU->SSLV23) == CU->SSLV23)
+ vers = 23;
+ else if(config.usessl & CU->SSLV2)
+ vers = 2;
+ else if(config.usessl & CU->SSLV3)
+ vers = 3;
+ }
+ if(err == "") {
+ nc.sslx = ssl3->Context.new();
+ if(config.devssl)
+ nc.sslx.use_devssl();
+ info := ref SSL3->Authinfo(ssl_suites, ssl_comprs, nil,
+ 0, nil, nil, nil);
+vers = 3;
+ (err, nc.vers) = nc.sslx.client(nc.conn.dfd, addr, vers, info);
+ }
+ }
+ }
+ if(err == "") {
+ nc.connected = 1;
+ nc.state = CU->NCgethdr;
+ }
+ else {
+ if(dbg)
+ sys->print("http %d: connection failed: %s\n", nc.id, err);
+ bs.err = err;
+#constate("connect", nc.conn);
+ closeconn(nc);
+ }
+}
+
+constate(msg: string, conn: Sys->Connection)
+{
+ fd := conn.dfd;
+ fdfd := -1;
+ if (fd != nil)
+ fdfd = fd.fd;
+ sys->print("connstate(%s, %d) ", msg, fdfd);
+ sfd := sys->open(conn.dir + "/status", Sys->OREAD);
+ if (sfd == nil) {
+ sys->print("cannot open %s/status: %r\n", conn.dir);
+ return;
+ }
+ buf := array [1024] of byte;
+ n := sys->read(sfd, buf, len buf);
+ s := sys->sprint("error: %r");
+ if (n > 0)
+ s = string buf[:n];
+ sys->print("%s status: %s\n", conn.dir, s);
+}
+
+tunnel_ssl(nc: ref Netconn) : string
+{
+ httpvers: string;
+ if(nc.state&THTTP_1_0)
+ httpvers = "1.0";
+ else
+ httpvers = "1.1";
+ req := "CONNECT " + nc.host + ":" + string nc.port + " HTTP/" + httpvers;
+ n := sys->fprint(nc.conn.dfd, "%s\r\n\r\n", req);
+ if(n < 0)
+ return sys->sprint("proxy: %r");
+ buf := array [Sys->ATOMICIO] of byte;
+ n = sys->read(nc.conn.dfd, buf, Sys->ATOMICIO);
+ if(n < 0)
+ return sys->sprint("proxy: %r");;
+ resp := string buf[0:n];
+ (m, s) := sys->tokenize(resp, " ");
+
+ if(m < 2)
+ return "proxy: " + resp;
+ if(hd tl s != "200"){
+ (nil, e) := sys->tokenize(resp, "\n\r");
+ return hd e;
+ }
+ return "";
+}
+
+need_proxy(h: string) : int
+{
+ doms := config.noproxydoms;
+ lh := len h;
+ for(; doms != nil; doms = tl doms) {
+ dom := hd doms;
+ ld := len dom;
+ if(lh >= ld && h[lh-ld:] == dom)
+ return 0; # domain is on the no proxy list
+ }
+ return 1;
+}
+
+writereq(nc: ref Netconn, bs: ref ByteSource)
+{
+ #
+ # Prepare the request
+ #
+ req := bs.req;
+ u := ref *req.url;
+ requ, httpvers: string;
+ #if(nc.tstate&TProxy)
+ if((nc.tstate&TProxy) && !(nc.tstate&TSSL)) {
+ u.frag = nil;
+ requ = u.tostring();
+ } else {
+ requ = u.path;
+ if(u.query != "")
+ requ += "?" + u.query;
+ }
+ if(nc.tstate&THTTP_1_0)
+ httpvers = "1.0";
+ else
+ httpvers = "1.1";
+ reqhdr := HTTP_Header.new();
+ if(nc.tstate&TSSL)
+ reqhdr.usessl();
+ reqhdr.startline = CU->hmeth[req.method] + " " + requ + " HTTP/" + httpvers;
+ if(u.port != "")
+ reqhdr.addval(HHost, u.host+ ":" + u.port);
+ else
+ reqhdr.addval(HHost, u.host);
+ reqhdr.addval(HUserAgent, agent);
+ reqhdr.addval(HAccept, "*/*; *");
+# if(cr != nil && (cr.status == CRRevalidate || cr.status == CRMustRevalidate)) {
+# if(cr.etag != "")
+# reqhdr.addval(HIfNoneMatch, cr.etag);
+# else
+# reqhdr.addval(HIfModifiedSince, D->dateconv(cr.notafter));
+# }
+ if(req.auth != "")
+ reqhdr.addval(HAuthorization, "Basic " + req.auth);
+ if(req.method == CU->HPost) {
+ reqhdr.addval(HContentLength, string (len req.body));
+ reqhdr.addval(HContentType, "application/x-www-form-urlencoded");
+ }
+ if((CU->config).docookies > 0) {
+ cookies := CU->getcookies(u.host, u.path, nc.tstate&TSSL);
+ if (cookies != nil)
+ reqhdr.addval(HCookie, cookies);
+ }
+ #
+ # Issue the request
+ #
+ err := "";
+ if(dbg > 1) {
+ sys->print("http %d: writing request:\n", nc.id);
+ reqhdr.write(sys->fildes(1), nil);
+ }
+ rv := reqhdr.write(nc.conn.dfd, nc.sslx);
+ if(rv >= 0 && req.method == CU->HPost) {
+ if(dbg > 1)
+ sys->print("http %d: writing body:\n%s\n", nc.id, string req.body);
+ if((nc.tstate&TSSL) && nc.sslx != nil)
+ rv = nc.sslx.write(req.body, len req.body);
+ else
+ rv = sys->write(nc.conn.dfd, req.body, len req.body);
+ }
+ if(rv < 0) {
+ err = "error writing to host";
+#constate("writereq", nc.conn);
+ }
+ if(err != "") {
+ if(dbg)
+ sys->print("http %d: error: %s", nc.id, err);
+ bs.err = err;
+ closeconn(nc);
+ }
+}
+
+
+gethdr(nc: ref Netconn, bs: ref ByteSource)
+{
+ resph := HTTP_Header.new();
+ if(nc.tstate&TSSL)
+ resph.usessl();
+ hbuf := array[8000] of byte;
+ (err, i, j) := resph.read(nc.conn.dfd, nc.sslx, hbuf);
+ if(err != "") {
+#constate("gethdr", nc.conn);
+ if(!(nc.tstate&THTTP_1_0)) {
+ # try switching to http 1.0
+ if(dbg)
+ sys->print("http %d: switching to HTTP/1.0\n", nc.id);
+ nc.tstate |= THTTP_1_0;
+ }
+ }
+ else {
+ if(dbg) {
+ sys->print("http %d: got response header:\n", nc.id);
+ resph.write(sys->fildes(1), nil);
+ sys->print("http %d: %d bytes remaining from read\n", nc.id, j-i);
+ }
+ if(resph.protomajor == 1) {
+ if(!(nc.tstate&THTTP_1_0) && resph.protominor == 0) {
+ nc.tstate |= THTTP_1_0;
+ if(dbg)
+ sys->print("http %d: switching to HTTP/1.0\n", nc.id);
+ }
+ }
+ else if(warn)
+ sys->print("warning: unimplemented major protocol %d.%d\n",
+ resph.protomajor, resph.protominor);
+ if(j > i)
+ nc.tbuf = hbuf[i:j];
+ else
+ nc.tbuf = nil;
+ bs.hdr = hdrconv(resph, bs.req.url);
+ if(bs.hdr.length == 0 && (nc.tstate&THTTP_1_0))
+ closeconn(nc);
+ }
+ if(err != "") {
+ if(dbg)
+ sys->print("http %d: error %s\n", nc.id, err);
+ bs.err = err;
+ closeconn(nc);
+ }
+}
+
+# returns number of bytes transferred to bs.data
+# 0 => EOF
+# -1 => error
+getdata(nc: ref Netconn, bs: ref ByteSource): int
+{
+ if (bs.data == nil || bs.edata >= len bs.data) {
+ if(nc.tstate&THTTP_1_0) {
+ # hmm - when do non-eof'd HTTP1.1 connections close?
+ closeconn(nc);
+ }
+ return 0;
+ }
+ buf := bs.data[bs.edata:];
+ n := len buf;
+ if (nc.tbuf != nil) {
+ # initial overread of header
+ if (n >= len nc.tbuf) {
+ n = len nc.tbuf;
+ buf[:] = nc.tbuf;
+ nc.tbuf = nil;
+ return n;
+ }
+ buf[:] = nc.tbuf[:n];
+ nc.tbuf = nc.tbuf[n:];
+ return n;
+ }
+ if ((nc.tstate&TSSL) && nc.sslx != nil)
+ n = nc.sslx.read(buf, n);
+ else
+ n = sys->read(nc.conn.dfd, buf, n);
+ if(dbg > 1)
+ sys->print("http %d: read %d bytes\n", nc.id, n);
+ if (n <= 0) {
+#constate("getdata", nc.conn);
+ closeconn(nc);
+ if(n < 0)
+ bs.err = sys->sprint("%r");
+ }
+#else
+#sys->write(sys->fildes(1), buf[:n], n);
+ return n;
+ }
+
+#getdata(nc: ref Netconn, bs: ref ByteSource)
+#{
+# buf := bs.data;
+# n := 0;
+# if(nc.tbuf != nil) {
+# # initial data from overread of header
+# # Note: can have more data in nc.tbuf than was
+# # reported by the HTTP header
+# n = len nc.tbuf;
+# if (n > bs.hdr.length) {
+# n = bs.hdr.length;
+# nc.tbuf = nc.tbuf[0:n];
+# }
+# if(len buf <= n) {
+# if(warn && len buf < n)
+# sys->print("more initial data than specified length\n");
+# bs.data = nc.tbuf;
+# }
+# else
+# buf[0:] = nc.tbuf[:n];
+# nc.tbuf = nil;
+# }
+# if(n == 0) {
+# if((nc.tstate&TSSL) && nc.sslx != nil)
+# n = nc.sslx.read(buf[bs.edata:], len buf - bs.edata);
+# else
+# n = sys->read(nc.conn.dfd, buf[bs.edata:], len buf - bs.edata);
+# }
+# if(dbg > 1)
+# sys->print("http %d: read %d bytes\n", nc.id, n);
+# if(n <= 0) {
+# closeconn(nc);
+# if(n < 0)
+# bs.err = sys->sprint("%r");
+# }
+# else {
+# bs.edata += n;
+# if(bs.edata == len buf && bs.hdr.length != 100000000) {
+# if(nc.tstate&THTTP_1_0) {
+# closeconn(nc);
+# }
+# }
+# }
+# if(bs.err != "") {
+# if(dbg)
+# sys->print("http %d: error %s\n", nc.id, bs.err);
+# closeconn(nc);
+# }
+#}
+
+hdrconv(hh: ref HTTP_Header, u: ref Parsedurl) : ref Header
+{
+ hdr := Header.new();
+ hdr.code = hh.code;
+ hdr.actual = u;
+ s := hh.getval(HContentBase);
+ if(s != "")
+ hdr.base = U->parse(s);
+ else
+ hdr.base = hdr.actual;
+ s = hh.getval(HLocation);
+ if(s != "")
+ hdr.location = U->parse(s);
+ s = hh.getval(HContentLength);
+ if(s != "")
+ hdr.length = int s;
+ else
+ hdr.length = -1;
+ s = hh.getval(HContentType);
+ if(s != "")
+ setmtype(hdr, s);
+ hdr.msg = hh.reason;
+ hdr.refresh = hh.getval(HRefresh);
+ hdr.chal = hh.getval(HWWWAuthenticate);
+ s = hh.getval(HContentEncoding);
+ if(s != "") {
+ if(warn)
+ sys->print("warning: unhandled content encoding: %s\n", s);
+ # force "save as" dialog
+ hdr.mtype = CU->UnknownType;
+ }
+ hdr.warn = hh.getval(HWarning);
+ hdr.lastModified = hh.getval(HLastModified);
+ if((CU->config).docookies > 0) {
+ for (ckl := hh.cookies; ckl != nil; ckl = tl ckl)
+ CU->setcookie(u.host, u.path, hd ckl);
+ }
+ return hdr;
+}
+
+# Set hdr's media type and chset (if a text type).
+# If can't set media type, leave it alone (caller will guess).
+setmtype(hdr: ref CU->Header, s: string)
+{
+ (ty, parms) := S->splitl(S->tolower(s), ";");
+ (fnd, val) := T->lookup(mediatable, trim(ty));
+ if(fnd) {
+ hdr.mtype = val;
+ if(len parms > 0 && val >= CU->TextCss && val <= CU->TextXml) {
+ nvs := Nameval.namevals(parms[1:], ';');
+ s: string;
+ (fnd, s) = Nameval.find(nvs, "charset");
+ if(fnd)
+ hdr.chset = s;
+ }
+ }
+ else {
+ if(warn)
+ sys->print("warning: unknown media type in %s\n", s);
+ }
+}
+
+# Remove leading and trailing whitespace from s.
+trim(s: string) : string
+{
+ is := 0;
+ ie := len s;
+ while(is < ie) {
+ if(ctype[s[is]] != C->W)
+ break;
+ is++;
+ }
+ if(is == ie)
+ return "";
+ while(ie > is) {
+ if(ctype[s[ie-1]] != C->W)
+ break;
+ ie--;
+ }
+ if(is >= ie)
+ return "";
+ if(is == 0 && ie == len s)
+ return s;
+ return s[is:ie];
+}
+
+# If s is in double quotes, remove them
+remquotes(s: string) : string
+{
+ n := len s;
+ if(n >= 2 && s[0] == '"' && s[n-1] == '"')
+ return s[1:n-1];
+ return s;
+}
+
+HTTP_Header.new() : ref HTTP_Header
+{
+ return ref HTTP_Header("", 0, 0, 0, "", 0, array[NumHfields] of { * => "" }, nil);
+}
+
+HTTP_Header.usessl(h: self ref HTTP_Header)
+{
+ h.iossl = 1;
+}
+
+HTTP_Header.addval(h: self ref HTTP_Header, key: int, val: string)
+{
+ if (key == HSetCookie) {
+ h.cookies = val :: h.cookies;
+ return;
+ }
+ oldv := h.vals[key];
+ if(oldv != "") {
+ # check that hdr type allows list of things
+ case key {
+ HAccept or HAcceptCharset or HAcceptEncoding
+ or HAcceptLanguage or HAcceptRanges
+ or HCacheControl or HConnection or HContentEncoding
+ or HContentLanguage or HIfMatch or HIfNoneMatch
+ or HPragma or HPublic or HUpgrade or HVia
+ or HWarning or HWWWAuthenticate or HExpect =>
+ val = oldv + ", " + val;
+ HCookie =>
+ val = oldv + "; " + val;
+ * =>
+ if(warn)
+ sys->print("warning: multiple %s headers not allowed\n", hdrnames[key]);
+ }
+ }
+ h.vals[key] = val;
+}
+
+HTTP_Header.getval(h: self ref HTTP_Header, key: int) : string
+{
+ return h.vals[key];
+}
+
+# Read into supplied buf.
+# Returns (ok, start of non-header bytes, end of non-header bytes)
+# If bytes > 127 appear, assume Latin-1
+#
+# Header values added will always be trimmed (see trim() above).
+HTTP_Header.read(h: self ref HTTP_Header, fd: ref sys->FD, sslx: ref SSL3->Context, buf: array of byte) : (string, int, int)
+{
+ i := 0;
+ j := 0;
+ aline : array of byte = nil;
+ eof := 0;
+ if(h.iossl && sslx != nil) {
+ (aline, eof, i, j) = ssl_getline(sslx, buf, i, j);
+ }
+ else {
+ (aline, eof, i, j) = CU->getline(fd, buf, i, j);
+ }
+ if(aline == nil) {
+ return ("header read got immediate eof", 0, 0);
+ }
+ h.startline = latin1tostring(aline);
+ if(dbg > 1)
+ sys->print("header read, startline=%s\n", h.startline);
+ (vers, srest) := S->splitl(h.startline, " ");
+ if(len srest > 0)
+ srest = srest[1:];
+ (scode, reason) := S->splitl(srest, " ");
+ ok := 1;
+ if(len vers >= 8 && vers[0:5] == "HTTP/") {
+ (smaj, vrest) := S->splitl(vers[5:], ".");
+ if(smaj == "" || len vrest <= 1)
+ ok = 0;
+ else {
+ h.protomajor = int smaj;
+ if(h.protomajor < 1)
+ ok = 0;
+ else
+ h.protominor = int vrest[1:];
+ }
+ if(len scode != 3)
+ ok = 0;
+ else {
+ h.code = int scode;
+ if(h.code < 100)
+ ok = 0;
+ }
+ if(len reason > 0)
+ reason = reason[1:];
+ h.reason = reason;
+ }
+ else
+ ok = 0;
+ if(!ok)
+ return (sys->sprint("header read failed to parse start line '%s'\n", string aline), 0, 0);
+
+ prevkey := -1;
+ while(len aline > 0) {
+ if(h.iossl && sslx != nil) {
+ (aline, eof, i, j) = ssl_getline(sslx, buf, i, j);
+ }
+ else {
+ (aline, eof, i, j) = CU->getline(fd, buf, i, j);
+ }
+ if(eof)
+ return ("header doesn't end with blank line", 0, 0);
+ if(len aline == 0)
+ break;
+ line := latin1tostring(aline);
+ if(dbg > 1)
+ sys->print("%s\n", line);
+ if(ctype[line[0]] == C->W) {
+ if(prevkey < 0) {
+ if(warn)
+ sys->print("warning: header continuation line at beginning: %s\n", line);
+ }
+ else
+ h.vals[prevkey] = h.vals[prevkey] + " " + trim(line);
+ }
+ else {
+ (nam, val) := S->splitl(line, ":");
+ if(val == nil) {
+ if(warn)
+ sys->print("warning: header line has no colon: %s\n", line);
+ }
+ else {
+ (fnd, key) := T->lookup(hdrtable, S->tolower(nam));
+ if(!fnd) {
+ if(warn)
+ sys->print("warning: unknown header field: %s\n", line);
+ }
+ else {
+ h.addval(key, trim(val[1:]));
+ prevkey = key;
+ }
+ }
+ }
+ }
+ return ("", i, j);
+}
+
+# Write in big hunks. Convert to Latin1.
+# Return last sys->write return value.
+HTTP_Header.write(h: self ref HTTP_Header, fd: ref sys->FD, sslx: ref SSL3->Context) : int
+{
+ # Expect almost all responses will fit in this sized buf
+ buf := array[sys->ATOMICIO] of byte;
+ i := 0;
+ buflen := len buf;
+ need := len h.startline + 2 + 2;
+ if(need > buflen) {
+ buf = CU->realloc(buf, need-buflen);
+ buflen = len buf;
+ }
+ i = copyaslatin1(buf, h.startline, i, 1);
+ for(key := 0; key < NumHfields; key++) {
+ val := h.vals[key];
+ if(val != "") {
+ # 4 extra for this line, 2 for final cr/lf
+ need = len val + len hdrnames[key] + 4 + 2;
+ if(i + need > buflen) {
+ buf = CU->realloc(buf, i+need-buflen);
+ buflen = len buf;
+ }
+ i = copyaslatin1(buf, hdrnames[key], i, 0);
+ buf[i++] = byte ':';
+ buf[i++] = byte ' ';
+ # perhaps should break up really long lines,
+ # but we aren't expecting any
+ i = copyaslatin1(buf, val, i, 1);
+ }
+ }
+ buf[i++] = byte '\r';
+ buf[i++] = byte '\n';
+ n := 0;
+ for(k := 0; k < i; ) {
+ if(h.iossl && sslx != nil) {
+ n = sslx.write(buf[k:], i-k);
+ }
+ else {
+ n = sys->write(fd, buf[k:], i-k);
+ }
+ if(n <= 0)
+ break;
+ k += n;
+ }
+ return n;
+}
+
+# For latin1tostring, so don't have to keep allocating it
+lbuf := array[300] of byte;
+
+# Assume we call this on 'lines', so they won't be too long
+latin1tostring(a: array of byte) : string
+{
+ imax := len lbuf - 1;
+ i := 0;
+ n := len a;
+ for(k := 0; k < n; k++) {
+ b := a[k];
+ if(b < byte 128)
+ lbuf[i++] = b;
+ else
+ i += sys->char2byte(int b, lbuf, i);
+ if(i >= imax) {
+ if(imax > 1000) {
+ if(warn)
+ sys->print("warning: header line too long\n");
+ break;
+ }
+ lbuf = CU->realloc(lbuf, 100);
+ imax = len lbuf - 1;
+ }
+ }
+ ans := string lbuf[0:i];
+ return ans;
+}
+
+# Copy s into a[i:], converting to Latin1.
+# Add cr/lf if addcrlf is true.
+# Assume caller has checked that a has enough room.
+copyaslatin1(a: array of byte, s: string, i: int, addcrlf: int) : int
+{
+ ns := len s;
+ for(k := 0; k < ns; k++) {
+ c := s[k];
+ if(c < 256)
+ a[i++] = byte c;
+ else {
+ if(warn)
+ sys->print("warning: non-latin1 char in header ignored: '%c'\n", c);
+ }
+ }
+ if(addcrlf) {
+ a[i++] = byte '\r';
+ a[i++] = byte '\n';
+ }
+ return i;
+}
+
+defaultport(scheme: string) : int
+{
+ if(scheme == "https")
+ return HTTPSD;
+ return HTTPD;
+}
+
+closeconn(nc: ref Netconn)
+{
+ nc.conn.dfd = nil;
+ nc.conn.cfd = nil;
+ nc.conn.dir = "";
+ nc.connected = 0;
+ nc.sslx = nil;
+}
+
+ssl_getline(sslx: ref SSL3->Context, buf: array of byte, bstart, bend: int)
+ :(array of byte, int, int, int)
+{
+ ans : array of byte = nil;
+ last : array of byte = nil;
+ eof := 0;
+mainloop:
+ for(;;) {
+ for(i := bstart; i < bend; i++) {
+ if(buf[i] == byte '\n') {
+ k := i;
+ if(k > bstart && buf[k-1] == byte '\r')
+ k--;
+ last = buf[bstart:k];
+ bstart = i+1;
+ break mainloop;
+ }
+ }
+ if(bend > bstart)
+ ans = append(ans, buf[bstart:bend]);
+ last = nil;
+ bstart = 0;
+ bend = sslx.read(buf, len buf);
+ if(bend <= 0) {
+ eof = 1;
+ bend = 0;
+ break mainloop;
+ }
+ }
+ return (append(ans, last), eof, bstart, bend);
+}
+
+# Append copy of second array to first, return (possibly new)
+# address of the concatenation.
+append(a: array of byte, b: array of byte) : array of byte
+{
+ if(b == nil)
+ return a;
+ na := len a;
+ nb := len b;
+ ans := realloc(a, nb);
+ ans[na:] = b;
+ return ans;
+}
+
+# Return copy of a, but incr bytes bigger
+realloc(a: array of byte, incr: int) : array of byte
+{
+ n := len a;
+ newa := array[n + incr] of byte;
+ if(a != nil)
+ newa[0:] = a;
+ return newa;
+}
+
diff --git a/appl/charon/img.b b/appl/charon/img.b
new file mode 100644
index 00000000..fd8b625d
--- /dev/null
+++ b/appl/charon/img.b
@@ -0,0 +1,3607 @@
+implement Img;
+
+include "common.m";
+
+# headers for png support
+include "filter.m";
+include "crc.m";
+
+# big tables in separate files
+include "rgb.inc";
+include "ycbcr.inc";
+
+include "xxx.inc";
+
+# local copies from CU
+sys: Sys;
+CU: CharonUtils;
+ Header, ByteSource, MaskedImage, ImageCache, ResourceState: import CU;
+D: Draw;
+ Chans, Point, Rect, Image, Display: import D;
+E: Events;
+ Event: import E;
+G: Gui;
+
+# channel descriptions
+CRGB: con 0; # three channels, R, G, B, no map
+CY: con 1; # one channel, luminance
+CRGB1: con 2; # one channel, map present
+CYCbCr: con 3; # three channels, Y, Cb, Cr, no map
+
+dbg := 0;
+dbgev := 0;
+warn := 0;
+progressive := 0;
+display: ref D->Display;
+
+inflate: Filter;
+crc: Crc;
+CRCstate: import crc;
+
+init(cu: CharonUtils)
+{
+ sys = load Sys Sys->PATH;
+ CU = cu;
+ D = load Draw Draw->PATH;
+ G = cu->G;
+ crc = load Crc Crc->PATH;
+ inflate = load Filter "/dis/lib/inflate.dis";
+ inflate->init();
+ init_tabs();
+}
+
+# Return true if mtype is an image type we can handle
+supported(mtype: int) : int
+{
+ case mtype {
+ CU->ImageJpeg or
+ CU->ImageGif or
+ CU->ImageXXBitmap or
+ CU->ImageXInfernoBit or
+ CU->ImagePng =>
+ return 1;
+ }
+ return 0;
+}
+
+# w,h passed in are specified width and height.
+# Result will be resampled if they don't match the dimensions
+# in the decoded picture (if only one of w,h is specified, the other
+# dimension is scaled by the same factor).
+ImageSource.new(bs: ref ByteSource, w, h: int) : ref ImageSource
+{
+ dbg = int (CU->config).dbg['i'];
+ warn = (int (CU->config).dbg['w']) || dbg;
+ dbgev = int (CU->config).dbg['e'];
+ display = G->display;
+ mtype := CU->UnknownType;
+ if(bs.hdr != nil)
+ mtype = bs.hdr.mtype;
+ is := ref ImageSource(
+ w,h, # width, height
+ 0,0, # origw, origh
+ mtype, # mtype
+ 0, # i
+ 0, # curframe
+ bs, # bs
+ nil, # ghdr
+ nil, # jhdr
+ "" # err
+ );
+ return is;
+}
+
+ImageSource.free(is: self ref ImageSource)
+{
+ is.bs = nil;
+ is.gstate = nil;
+ is.jstate = nil;
+}
+
+ImageSource.getmim(is: self ref ImageSource) : (int, ref MaskedImage)
+{
+ if(dbg)
+ sys->print("img: getmim\n");
+ if(dbgev)
+ CU->event("IMAGE_GETMIM", is.width*is.height);
+ ans : ref MaskedImage = nil;
+ ret := Mimnone;
+prtype := 0;
+ {
+ if(is.bs.hdr == nil)
+ return (Mimnone, nil);
+ # temporary hack: wait until whole file is here first
+ if(is.bs.eof) {
+ if(is.mtype == CU->UnknownType) {
+ u := is.bs.req.url;
+ h := is.bs.hdr;
+ h.setmediatype(u.path, is.bs.data);
+ is.mtype = h.mtype;
+ }
+ case is.mtype {
+ CU->ImageJpeg =>
+ ans = getjpegmim(is);
+ CU->ImageGif =>
+ ans = getgifmim(is);
+ CU->ImageXXBitmap =>
+ ans = getxbitmapmim(is);
+ CU->ImageXInfernoBit =>
+ ans = getbitmim(is);
+ CU->ImagePng =>
+ ans = getpngmim(is);
+ * =>
+ is.err = sys->sprint("unsupported image type %s", (CU->mnames)[is.mtype]);
+ ret = Mimerror;
+ ans = nil;
+ }
+ if(ans != nil)
+ ret = Mimdone;
+ }
+ else {
+ # slow down the spin-waiting for this image
+ sys->sleep(100);
+ }
+ }exception ex{
+ "exImageerror*" =>
+ ret = Mimerror;
+ if(dbg)
+ sys->print("getmim got err: %s\n", is.err);
+ }
+ if(dbg)
+ sys->print("img: getmim returns (%d,%x)\n", ret, ans);
+ if(dbgev)
+ CU->event("IMAGE_GETMIM_END", 0);
+ is.bs.lim = is.i;
+ return (ret, ans);
+}
+
+# Raise exImagerror exception
+imgerror(is: ref ImageSource, msg: string)
+{
+ is.err = msg;
+ if(dbg)
+ sys->print("Image error: %s\n", msg);
+ CU->raisex("exImageerror:");
+}
+
+# Get next char or raise exception if cannot
+getc(is: ref ImageSource) : int
+{
+ if(is.i >= len is.bs.data) {
+ imgerror(is, "premature eof");
+ }
+ return int is.bs.data[is.i++];
+}
+
+# Unget the last character.
+# When called before any other getting routines, we
+# know the buffer still has that character in it.
+ungetc(is: ref ImageSource)
+{
+ if(is.i == 0)
+ CU->raisex("EXInternal: ungetc past beginning of buffer");
+ is.i--;
+}
+
+# Like ungetc, but ungets two bytes (gotten in order b1, another char).
+# Now the bytes could have spanned a boundary, if we were unlucky,
+# so we have to be prepared to put b1 in front of current buffer.
+ungetc2(is: ref ImageSource, nil: byte)
+{
+ if(is.i < 2) {
+ if(is.i != 1)
+ CU->raisex("EXInternal: ungetc2 past beginning of buffer");
+ is.i = 0;
+ }
+ else
+ is.i -= 2;
+}
+
+# Get 2 bytes and return the 16-bit value, little-endian order.
+getlew(is: ref ImageSource) : int
+{
+ c0 := getc(is);
+ c1 := getc(is);
+ return c0 + (c1<<8);
+}
+
+# Get 2 bytes and return the 16-bit value, big-endian order.
+getbew(is: ref ImageSource) : int
+{
+ c0 := getc(is);
+ c1 := getc(is);
+ return (c0<<8) + c1;
+}
+
+# Copy next n bytes of input into buf
+# or raise exception if cannot.
+read(is: ref ImageSource, buf: array of byte, n: int)
+{
+ ok := 0;
+ if(is.i +n < len is.bs.data) {
+ buf[0:] = is.bs.data[is.i:is.i+n];
+ is.i += n;
+ }
+ else
+ imgerror(is, "premature eof");
+}
+
+# Caller needs n bytes.
+# Return an (array, index into array) where at least
+# the next n bytes can be found.
+# There might be a "premature eof" exception.
+getn(is: ref ImageSource, n: int) : (array of byte, int)
+{
+ a := is.bs.data;
+ i := is.i;
+ if(i + n <= len a)
+ is.i += n;
+ else
+ imgerror(is, "premature eof");
+ return (a, i);
+}
+
+# display.newimage with some defaults; throw exception if fails
+newimage(is: ref ImageSource, w, h: int) : ref Image
+{
+ if(!(CU->imcache).need(w*h))
+ imgerror(is, "out of memory");
+ im := display.newimage(((0,0),(w,h)), D->CMAP8, 0, D->White);
+ if(im == nil)
+ imgerror(is, "out of memory");
+ return im;
+}
+
+newimage24(is: ref ImageSource, w, h: int) : ref Image
+{
+ if(!(CU->imcache).need(w*h*3))
+ imgerror(is, "out of memory");
+ im := display.newimage(((0,0),(w,h)), D->RGB24, 0, D->White);
+ if(im == nil)
+ imgerror(is, "out of memory");
+ return im;
+}
+
+newimagegrey(is: ref ImageSource, w, h: int) : ref Image
+{
+ if(!(CU->imcache).need(w*h))
+ imgerror(is, "out of memory");
+ im := display.newimage(((0,0),(w,h)), D->GREY8, 0, D->White);
+ if(im == nil)
+ imgerror(is, "out of memory");
+ return im;
+}
+
+
+newmi(im: ref Image) : ref MaskedImage
+{
+ return ref MaskedImage(im, nil, 0, 0, -1, Point(0,0));
+}
+
+# Call this after origw and origh are set to set the width and height
+# to our desired (rescaled) answer dimensions.
+# If only one of the dimensions is specified, the other is scaled by
+# the same factor.
+setdims(is: ref ImageSource)
+{
+ sw := is.origw;
+ sh := is.origh;
+ dw := is.width;
+ dh := is.height;
+ if(dw == 0 && dh == 0) {
+ dw = sw;
+ dh = sh;
+ }
+ else if(dw == 0 || dh == 0) {
+ if(dw == 0) {
+ dw = int ((real sw) * (real dh/real sh));
+ if(dw == 0)
+ dw = 1;
+ }
+ else {
+ dh = int ((real sh) * (real dw/real sw));
+ if(dh == 0)
+ dh = 1;
+ }
+ }
+ is.width = dw;
+ is.height = dh;
+}
+
+# for debugging
+printarray(a: array of int, name: string)
+{
+ sys->print("%s:", name);
+ for(i := 0; i < len a; i++) {
+ if((i%10)==0)
+ sys->print("\n%5d: ", i);
+ sys->print("%6d", a[i]);
+ }
+ sys->print("\n");
+}
+
+################# XBitmap ###################
+
+getxbitmaphdr(is: ref ImageSource)
+{
+ fnd: int;
+ (fnd, is.origw) = getxbitmapdefine(is);
+ if(fnd)
+ (fnd, is.origh) = getxbitmapdefine(is);
+ if(!fnd)
+ imgerror(is, "xbitmap starts badly");
+ # now, optional x_hot, y_hot
+ (fnd, nil) = getxbitmapdefine(is);
+ if(fnd)
+ (fnd, nil) = getxbitmapdefine(is);
+ # now expect 'static char x...x_bits[] = {'
+ get_to_char(is, '{');
+}
+
+getxbitmapmim(is: ref ImageSource) : ref MaskedImage
+{
+ getxbitmaphdr(is);
+ setdims(is);
+ bytesperline := (is.origw+7) / 8;
+ pixels := array[is.origw*is.origh] of byte;
+ pixi := 0;
+ for(i := 0; i < is.origh; i++) {
+ for(j := 0; j < bytesperline; j++) {
+ v := get_hexbyte(is);
+ kend := 7;
+ if(j == bytesperline-1)
+ kend = (is.origw-1)%8;
+ for(k := 0; k <= kend; k++) {
+ if(v & (1<<k))
+ pixels[pixi] = byte D->Black;
+ else
+ pixels[pixi] = byte D->White;
+ pixi++;
+ }
+ }
+ }
+ if(is.width != is.origw || is.height != is.origh)
+ pixels = resample(pixels, is.origw, is.origh, is.width, is.height);
+ im := newimage(is, is.width, is.height);
+ im.writepixels(im.r, pixels);
+ return newmi(im);
+}
+
+# get a line, which should be of form
+# '#define fieldname val'
+# and return (found, integer rep of val)
+getxbitmapdefine(is: ref ImageSource) : (int, int)
+{
+ fnd := 0;
+ n := 0;
+ c := getc(is);
+ if(c == '#') {
+ get_to_char(is, ' ');
+ get_to_char(is, ' ');
+ c = getc(is);
+ while(c >= '0' && c <= '9') {
+ fnd = 1;
+ n = n*10 + c - '0';
+ c = getc(is);
+ }
+ }
+ else
+ ungetc(is);
+ get_to_char(is, '\n');
+ return (fnd, n);
+}
+
+# read fd until get char cterm
+# (raise exception if eof hit first)
+get_to_char(is: ref ImageSource, cterm: int)
+{
+ for(;;) {
+ if(getc(is) == cterm)
+ return;
+ }
+}
+
+# read fd until get xDD, were DD are hex digits.
+# (raise exception if not hex digits or if eof hit first)
+get_hexbyte(is: ref ImageSource) : int
+{
+ get_to_char(is, 'x');
+ n1 := hexdig(getc(is));
+ n2 := hexdig(getc(is));
+ if(n1 < 0 || n2 < 0)
+ imgerror(is, "X Bitmap expected hex digits");
+ return (n1<<4) + n2;
+}
+
+hexdig(c: int) : int
+{
+ if('0' <= c && c <= '9')
+ c -= '0';
+ else if('a' <= c && c <= 'f')
+ c += 10 - 'a';
+ else if('A' <= c && c <= 'F')
+ c += 10 - 'A';
+ else
+ c = -1;
+ return c;
+}
+
+################# GIF ###################
+
+# GIF flags
+TRANSP: con 1;
+INPUT: con 2;
+DISPMASK: con 7<<2;
+HASCMAP: con 16r80;
+INTERLACED: con 16r40;
+
+Entry: adt
+{
+ prefix: int;
+ exten: int;
+};
+
+getgifhdr(is: ref ImageSource)
+{
+ if(dbg)
+ sys->print("img: getgifhdr\n");
+ h := ref Gifstate;
+ (buf, i) := getn(is, 6);
+ vers := string buf[i:i+6];
+ if(vers!="GIF87a" && vers!="GIF89a")
+ imgerror(is, "unknown GIF version " + vers);
+ is.origw = getlew(is);
+ is.origh = getlew(is);
+ h.fields = getc(is);
+ h.bgrnd = getc(is);
+ h.aspect = getc(is);
+ setdims(is);
+ if(dbg)
+ sys->print("img: getgifhdr has vers=%s, origw=%d, origh=%d, w=%d, h=%d, fields=16r%x, bgrnd=%d, aspect=%d\n",
+ vers, is.origw, is.origh, is.width, is.height, h.fields, h.bgrnd, h.aspect);
+ h.flags = 0;
+ h.delay = 0;
+ h.trindex = byte 0;
+ h.tbl = array[4096] of GifEntry;
+ for(i = 0; i < 258; i++) {
+ h.tbl[i].prefix = -1;
+ h.tbl[i].exten = i;
+ }
+ h.globalcmap = nil;
+ h.cmap = nil;
+ if(h.fields & HASCMAP)
+ h.globalcmap = gifreadcmap(is, (h.fields&7)+1);
+ is.gstate = h;
+ if(warn && h.aspect != 0)
+ sys->print("warning: non-standard aspect ratio in GIF image ignored\n");
+ if(!gifgettoimage(is))
+ imgerror(is, "GIF file has no image");
+}
+
+gifgettoimage(is: ref ImageSource) : int
+{
+ if(dbg)
+ sys->print("img: gifgettoimage\n");
+ h := is.gstate;
+loop:
+ for(;;) {
+ # some GIFs omit Trailer
+ if(is.i >= len is.bs.data)
+ break;
+ case c := getc(is) {
+ 16r2C => # Image Descriptor
+ return 1;
+
+ 16r21 => # Extension
+ hsize := 0;
+ hasdata := 0;
+
+ case getc(is){
+ 16r01 => # Plain Text Extension
+ hsize = 14;
+ hasdata = 1;
+ if(dbg)
+ sys->print("gifgettoimage: text extension\n");
+ 16rF9 => # Graphic Control Extension
+ getc(is); # blocksize (should be 4)
+ h.flags = getc(is);
+ h.delay = getlew(is);
+ h.trindex = byte getc(is);
+ getc(is); # block terminator (should be 0)
+ # set minimum delay
+ if (h.delay < 20)
+ h.delay = 20;
+ if(dbg)
+ sys->print("gifgettoimage: graphic control flags=16r%x, delay=%d, trindex=%d\n",
+ h.flags, h.delay, int h.trindex);
+ 16rFE => # Comment Extension
+ if(dbg)
+ sys->print("gifgettoimage: comment extension\n");
+ hasdata = 1;
+ 16rFF => # Application Extension
+ if(dbg)
+ sys->print("gifgettoimage: application extension\n");
+ hsize = getc(is);
+ # standard says this must be 11, but Adobe likes to put out 10-byte ones,
+ # so we pay attention to the field.
+ hasdata = 1;
+ * =>
+ imgerror(is, "GIF unknown extension");
+ }
+ if(hsize > 0)
+ getn(is, hsize);
+ if(hasdata) {
+ for(;;) {
+ if((nbytes := getc(is)) == 0)
+ break;
+ (a, i) := getn(is, nbytes);
+ if(dbg)
+ sys->print("extension data: '%s'\n", string a[i:i+nbytes]);
+ }
+ }
+
+ 16r3B => # Trailer
+ # read to end of data
+ getn(is, len is.bs.data - is.i);
+ break loop;
+
+ * =>
+ if(c == 0)
+ continue; # FIX for some buggy gifs
+ imgerror(is, "GIF unknown block type " + string c);
+ }
+ }
+ return 0;
+}
+
+getgifmim(is: ref ImageSource) : ref MaskedImage
+{
+ if(is.gstate == nil)
+ getgifhdr(is);
+
+ # At this point, should just have read Image Descriptor marker byte
+ h := is.gstate;
+ left :=getlew(is);
+ top := getlew(is);
+ width := getlew(is);
+ height := getlew(is);
+ h.fields = getc(is);
+ totpix := width*height;
+ h.cmap = h.globalcmap;
+ if(dbg)
+ sys->print("getgifmim, left=%d, top=%d, width=%d, height=%d, fields=16r%x\n",
+ left, top, width, height, h.fields);
+ if(dbgev)
+ CU->event("IMAGE_GETGIFMIM", 0);
+ if(h.fields & HASCMAP)
+ h.cmap = gifreadcmap(is, (h.fields&7)+1);
+ if(h.cmap == nil)
+ imgerror(is, "GIF needs colormap");
+
+ # now decode the image
+ c, incode: int;
+
+ codesize := getc(is);
+ if(codesize > 8)
+ imgerror(is, "GIF bad codesize");
+ if(len h.cmap!=3*(1<<codesize)
+ && len h.cmap != 3*(1<<(codesize-1)) # peculiar GIF bitmap files
+ && (codesize!=2 || len h.cmap!=3*2)){ # peculiar GIF bitmap files II
+ if (warn)
+ sys->print("warning: GIF codesize = %d doesn't match cmap len = %d\n", codesize, len h.cmap);
+ #imgerror(is, "GIF codesize doesn't match color map");
+ }
+
+ CTM :=1<<codesize;
+ EOD := CTM+1;
+
+ pic := array[totpix] of byte;
+ pici := 0;
+ data : array of byte = nil;
+ datai := 0;
+ dataend := 0;
+
+ nbits := 0;
+ sreg := 0;
+ stack := array[4096] of byte;
+ stacki: int;
+ fc := 0;
+ tbl := h.tbl;
+
+Decode:
+ for(;;) {
+ csize := codesize+1;
+ csmask := ((1<<csize) - 1);
+ nentry := EOD+1;
+ maxentry := csmask;
+ first := 1;
+ ocode := -1;
+
+ for(;; ocode = incode) {
+ while(nbits < csize) {
+ if(datai == dataend) {
+ nbytes := getc(is);
+ if (nbytes == 0)
+ # Block Terminator
+ break Decode;
+ (data, datai) = getn(is, nbytes);
+ dataend = datai+nbytes;
+ }
+ c = int data[datai++];
+ sreg |= c<<nbits;
+ nbits += 8;
+ }
+ code := sreg & csmask;
+ sreg >>= csize;
+ nbits -= csize;
+
+ if(code == EOD) {
+ nbytes := getc(is);
+ if(nbytes != 0 && warn)
+ sys->print("warning: unexpected data past EOD\n");
+ break Decode;
+ }
+
+ if(code == CTM)
+ continue Decode;
+
+ stacki = len stack-1;
+
+ incode = code;
+
+ # special case for KwKwK
+ if(code == nentry) {
+ stack[stacki--] = byte fc;
+ code = ocode;
+ }
+
+ if(code > nentry)
+ imgerror(is, "GIF bad code");
+
+ for(c=code; c>=0; c=tbl[c].prefix)
+ stack[stacki--] = byte tbl[c].exten;
+
+ nb := len stack-(stacki+1);
+ if(pici+nb > len pic) {
+ # this common error is harmless
+ # we have to keep reading to keep the blocks in sync
+ ;
+ }
+ else {
+ pic[pici:] = stack[stacki+1:];
+ pici += nb;
+ }
+
+ fc = int stack[stacki+1];
+
+ if(first) {
+ first = 0;
+ continue;
+ }
+ early:=0; # peculiar tiff feature here for reference
+ if(nentry == maxentry-early) {
+ if(csize >= 12)
+ continue;
+ csize++;
+ maxentry = (1<<csize);
+ csmask = maxentry - 1;
+ if(csize < 12)
+ maxentry--;
+ }
+ tbl[nentry].prefix = ocode;
+ tbl[nentry].exten = fc;
+ nentry++;
+ }
+ }
+ while(pici < len pic) {
+ # shouldn't happen, but sometimes get buggy gifs
+ pic[pici++] = byte 0;
+ }
+
+ if(h.fields & INTERLACED) {
+ if(dbg)
+ sys->print("getgifmim uninterlacing\n");
+ if(dbgev)
+ CU->event("IMAGE_GETGIFMIM_INTERLACE_START", 0);
+ # (TODO: Could un-interlace in place.
+ # Decompose permutation into cycles,
+ # then need one double-copy of a line
+ # per cycle).
+ ipic := array[totpix] of byte;
+ # Group 1: every 8th row, starting with row 0
+ pici = 0;
+ ipici := 0;
+ ipiclim := totpix-width;
+ w2 := width+width;
+ w4 := w2+w2;
+ w8 := w4+w4;
+ startandby := array[4] of {(0,w8), (w4,w8), (w2,w4), (width,w2)};
+ for(k := 0; k < 4; k++) {
+ (start, by) := startandby[k];
+ for(ipici=start; ipici <= ipiclim; ipici += by) {
+ ipic[ipici:] = pic[pici:pici+width];
+ pici += width;
+ }
+ }
+ pic = ipic;
+ if(dbgev)
+ CU->event("IMAGE_GETGIFMIM_INTERLACE_END", 0);
+ }
+ if(is.width != is.origw || is.height != is.origh) {
+ if (is.width < 0)
+ is.width = 0;
+ if (is.height < 0)
+ is.height = 0;
+ # need to resample, using same factors as original image
+ wscale := real is.width / real is.origw;
+ hscale := real is.height / real is.origh;
+ owidth := width;
+ oheight := height;
+ width = int (wscale * real width);
+ if(width == 0)
+ width = 1;
+ height = int (hscale * real height);
+ if(height == 0)
+ height = 1;
+ left = int (wscale * real left);
+ top = int (hscale * real top);
+ pic = resample(pic, owidth, oheight, width, height);
+ }
+ mask : ref Image;
+ if(h.flags & TRANSP) {
+ if(dbg)
+ sys->print("getgifmim making mask, trindex=%d\n", int h.trindex);
+ if(dbgev)
+ CU->event("IMAGE_GETGIFMIM_MASK_START", 0);
+ # make a 1-bit deep bitmap for mask
+ # expect most mask bits will be 1
+ bytesperrow := (width+7)/8;
+ trpix := h.trindex;
+ mpic := array[bytesperrow*height] of byte;
+ mpici := 0;
+ pici = 0;
+ for(y := 0; y < height; y++) {
+ v := byte 16rFF;
+ k := 0;
+ for(x := 0; x < width; x++) {
+ if(pic[pici++] == trpix)
+ v &= ~(byte 16r80>>k);
+ if(++k == 8) {
+ k = 0;
+ mpic[mpici++] = v;
+ v = byte 16rFF;
+ }
+ }
+ if(k != 0)
+ mpic[mpici++] = v;
+ }
+ if(!(CU->imcache).need(bytesperrow*height))
+ imgerror(is, "out of memory");
+ mask = display.newimage(((0,0),(width,height)), D->GREY1, 0, D->Opaque);
+ if(mask == nil)
+ imgerror(is, "out of memory");
+ mask.writepixels(mask.r, mpic);
+ mpic = nil;
+ if(dbgev)
+ CU->event("IMAGE_GETGIFMIM_MASK_END", 0);
+ }
+ if(dbgev)
+ CU->event("IMAGE_GETGIFMIM_REMAP_START", 0);
+ pic24 := remap24(pic, h.cmap);
+# remap1(pic, width, height, h.cmap);
+ if(dbgev)
+ CU->event("IMAGE_GETGIFMIM_REMAP_END", 0);
+ bgcolor := -1;
+ i := h.bgrnd;
+ if(i >= 0 && 3*i+2 < len h.cmap) {
+ bgcolor = ((int h.cmap[3*i])<<16)
+ | ((int h.cmap[3*i+1])<<8)
+ | (int h.cmap[3*i+2]);
+ }
+ im := newimage24(is, width, height);
+ im.writepixels(im.r, pic24);
+ if(is.curframe == 0) {
+ # make sure first frame fills up whole rectangle
+ if(is.width != width || is.height != height || left != 0 || top != 0) {
+ r := Rect((left,top),(left+width,top+height));
+ pix := D->White;
+ if(bgcolor != -1)
+ pix = (bgcolor<<8) | 16rFF;
+ newim := display.newimage(((0,0),(is.width,is.height)), D->RGB24, 0, pix);
+ if(newim == nil)
+ imgerror(is, "out of memory");
+ newim.draw(r, im, mask, (0,0));
+ im = newim;
+ if(mask != nil) {
+ newmask := display.newimage(((0,0),(is.width,is.height)), D->GREY1, 0, D->Opaque);
+ if(newmask == nil)
+ imgerror(is, "out of memory");
+ newmask.draw(r, mask, nil, (0,0));
+ mask = newmask;
+ }
+ left = 0;
+ top = 0;
+ }
+ }
+ pic = nil;
+ mi := newmi(im);
+ mi.mask = mask;
+ mi.delay = h.delay*10; # convert centiseconds to milliseconds
+ mi.origin = Point(left, top);
+ dispmeth := (h.flags>>2)&7;
+ if(dispmeth == 2) {
+ # reset to background color after displaying this frame
+ mi.bgcolor = bgcolor;
+ }
+ else if(dispmeth == 3) {
+ # Supposed to "reset to previous", which appears to
+ # mean the previous frame that didn't have a "reset to previous".
+ # Signal this special case to layout by setting bgcolor to -2.
+ mi.bgcolor = -2;
+ }
+ if(gifgettoimage(is)) {
+ mi.more = 1;
+ is.curframe++;
+ # have to reinitialize table for next time
+ for(i = 0; i < 258; i++) {
+ h.tbl[i].prefix = -1;
+ h.tbl[i].exten = i;
+ }
+ }
+ if(dbgev)
+ CU->event("IMAGE_GETGIFMIM_END", 0);
+ return mi;
+}
+
+# Read a GIF colormap, where bpe is number of bits in an entry.
+# Raises a 'premature eof' exception if can't get the whole map.
+gifreadcmap(is: ref ImageSource, bpe: int) : array of byte
+{
+ size := 3*(1<<bpe);
+ map := array[size] of byte;
+ if(dbg > 1)
+ sys->print("gifreadcmap wants %d bytes\n", size);
+ read(is, map, size);
+ return map;
+}
+
+################# JPG ###################
+
+# Constants, all preceded by byte 16rFF
+SOF: con 16rC0; # Start of Frame
+SOF2: con 16rC2; # Start of Frame; progressive Huffman
+JPG: con 16rC8; # Reserved for JPEG extensions
+DHT: con 16rC4; # Define Huffman Tables
+DAC: con 16rCC; # Arithmetic coding conditioning
+RST: con 16rD0; # Restart interval termination
+RST7: con 16rD7; # Restart interval termination (highest value)
+SOI: con 16rD8; # Start of Image
+EOI: con 16rD9; # End of Image
+SOS: con 16rDA; # Start of Scan
+DQT: con 16rDB; # Define quantization tables
+DNL: con 16rDC; # Define number of lines
+DRI: con 16rDD; # Define restart interval
+DHP: con 16rDE; # Define hierarchical progression
+EXP: con 16rDF; # Expand reference components
+APPn: con 16rE0; # Reserved for application segments
+JPGn: con 16rF0; # Reserved for JPEG extensions
+COM: con 16rFE; # Comment
+
+NBUF: con 16*1024;
+
+
+jpegcolorspace: con CYCbCr;
+
+zerobytes := array[64] of { * => byte 0 };
+zeroints := array[64] of { * => 0 };
+
+getjpeghdr(is: ref ImageSource)
+{
+ if(dbg)
+ sys->print("getjpeghdr\n");
+ h := ref Jpegstate(
+ 0, 0, # sr, cnt
+ 0, # Nf
+ nil, # comp
+ byte 0, # mode,
+ 0, 0, # X, Y
+ nil, # qt
+ nil, nil, # dcht, acht
+ 0, # Ns
+ nil, # scomp
+ 0, 0, # Ss, Se
+ 0, 0, # Ah, Al
+ 0, 0, # ri, nseg
+ nil, # nblock
+ nil, nil, # dccoeff, accoeff
+ 0, 0, 0, 0 # nacross, ndown, Hmax, Vmax
+ );
+ is.jstate = h;
+ if(jpegmarker(is) != SOI)
+ imgerror(is, "Jpeg expected SOI marker");
+ (m, n) := jpegtabmisc(is);
+ if(!(m == SOF || m == SOF2))
+ imgerror(is, "Jpeg expected Frame marker");
+ nil = getc(is); # sample precision
+ h.Y = getbew(is);
+ h.X = getbew(is);
+ h.Nf = getc(is);
+ if(dbg)
+ sys->print("start of frame, Y=%d, X=%d, Nf=%d\n", h.Y, h.X, h.Nf);
+ h.comp = array[h.Nf] of Framecomp;
+ h.nblock = array[h.Nf] of int;
+ for(i:=0; i<h.Nf; i++) {
+ h.comp[i].C = getc(is);
+ (H, V) := nibbles(getc(is));
+ h.comp[i].H = H;
+ h.comp[i].V = V;
+ h.comp[i].Tq = getc(is);
+ h.nblock[i] =H*V;
+ if(dbg)
+ sys->print("comp[%d]: C=%d, H=%d, V=%d, Tq=%d\n",
+ i, h.comp[i].C, H, V, h.comp[i].Tq);
+ }
+ h.mode = byte m;
+ is.origw = h.X;
+ is.origh = h.Y;
+ setdims(is);
+ if(n != 6+3*h.Nf)
+ imgerror(is, "Jpeg bad SOF length");
+}
+
+jpegmarker(is: ref ImageSource) : int
+{
+ if(getc(is) != 16rFF)
+ imgerror(is, "Jpeg expected marker");
+ return getc(is);
+}
+
+# Consume tables and miscellaneous marker segments,
+# returning the marker id and length of the first non-such-segment
+# (after having consumed the marker).
+# May raise "premature eof" or other exception.
+jpegtabmisc(is: ref ImageSource) : (int, int)
+{
+ h := is.jstate;
+ m, n : int;
+Loop:
+ for(;;) {
+ h.nseg++;
+ m = jpegmarker(is);
+ n = 0;
+ if(m != EOI)
+ n = getbew(is) - 2;
+ if(dbg > 1)
+ sys->print("jpegtabmisc reading segment, got m=%x, n=%d\n", m, n);
+ case m {
+ SOF or SOF2 or SOS or EOI =>
+ break Loop;
+
+ APPn+0 =>
+ if(h.nseg==1 && n >= 6) {
+ (buf, i) := getn(is, 6);
+ n -= 6;
+ if(string buf[i:i+4]=="JFIF") {
+ vers0 := int buf[i+5];
+ vers1 := int buf[i+6];
+ if(vers0>1 || vers1>2)
+ imgerror(is, "Jpeg unimplemented version");
+ }
+ }
+
+ APPn+1 to APPn+15 =>
+ ;
+
+ DQT =>
+ jpegquanttables(is, n);
+ n = 0;
+
+ DHT =>
+ jpeghuffmantables(is, n);
+ n = 0;
+
+ DRI =>
+ h.ri =getbew(is);
+ n -= 2;
+
+ COM =>
+ ;
+
+ * =>
+ imgerror(is, "Jpeg unexpected marker");
+ }
+ if(n > 0)
+ getn(is, n);
+ }
+ return (m, n);
+}
+
+# Consume huffman tables, raising exception on error.
+jpeghuffmantables(is: ref ImageSource, n: int)
+{
+ if(dbg)
+ sys->print("jpeghuffmantables\n");
+ h := is.jstate;
+ if(h.dcht == nil) {
+ h.dcht = array[4] of ref Huffman;
+ h.acht = array[4] of ref Huffman;
+ }
+ for(l:= 0; l < n; )
+ l += jpeghuffmantable(is);
+ if(l != n)
+ imgerror(is, "Jpeg huffman table bad length");
+}
+
+jpeghuffmantable(is: ref ImageSource) : int
+{
+ t := ref Huffman;
+ h := is.jstate;
+ (Tc, th) := nibbles(getc(is));
+ if(dbg > 1)
+ sys->print("jpeghuffmantable, Tc=%d, th=%d\n", Tc, th);
+ if(Tc > 1)
+ imgerror(is, "Jpeg unknown Huffman table class");
+ if(th>3 || (h.mode==byte SOF && th>1))
+ imgerror(is, "Jpeg unknown Huffman table index");
+ if(Tc == 0)
+ h.dcht[th] = t;
+ else
+ h.acht[th] = t;
+
+ # flow chart C-2
+ (b, bi) := getn(is, 16);
+ numcodes := array[16] of int;
+ nsize := 0;
+ for(i:=0; i<16; i++)
+ nsize += (numcodes[i] = int b[bi+i]);
+ t.size = array[nsize+1] of int;
+ k := 0;
+ for(i=1; i<=16; i++) {
+ n :=numcodes[i-1];
+ for(j:=0; j<n; j++)
+ t.size[k++] = i;
+ }
+ t.size[k] = 0;
+
+ # initialize HUFFVAL
+ t.val = array[nsize] of int;
+ (b, bi) = getn(is, nsize);
+ for(i=0; i<nsize; i++)
+ t.val[i] = int b[bi++];
+
+ # flow chart C-3
+ t.code = array[nsize+1] of int;
+ k = 0;
+ code := 0;
+ si := t.size[0];
+ for(;;) {
+ do
+ t.code[k++] = code++;
+ while(t.size[k] == si);
+ if(t.size[k] == 0)
+ break;
+ do {
+ code <<= 1;
+ si++;
+ } while(t.size[k] != si);
+ }
+
+ # flow chart F-25
+ t.mincode = array[17] of int;
+ t.maxcode = array[17] of int;
+ t.valptr = array[17] of int;
+ i = 0;
+ j := 0;
+ F25:
+ for(;;) {
+ for(;;) {
+ i++;
+ if(i > 16)
+ break F25;
+ if(numcodes[i-1] != 0)
+ break;
+ t.maxcode[i] = -1;
+ }
+ t.valptr[i] = j;
+ t.mincode[i] = t.code[j];
+ j += int numcodes[i-1]-1;
+ t.maxcode[i] = t.code[j];
+ j++;
+ }
+
+ # create byte-indexed fast path tables
+ t.value = array[256] of int;
+ t.shift = array[256] of int;
+ maxcode := t.maxcode;
+ # stupid startup algorithm: just run machine for each byte value
+ Bytes:
+ for(v:=0; v<256; v++){
+ cnt := 7;
+ m := 1<<7;
+ code = 0;
+ sr := v;
+ i = 1;
+ for(;;i++){
+ if(sr & m)
+ code |= 1;
+ if(code <= maxcode[i])
+ break;
+ code <<= 1;
+ m >>= 1;
+ if(m == 0){
+ t.shift[v] = 0;
+ t.value[v] = -1;
+ continue Bytes;
+ }
+ cnt--;
+ }
+ t.shift[v] = 8-cnt;
+ t.value[v] = t.val[t.valptr[i]+(code-t.mincode[i])];
+ }
+ if(dbg > 2) {
+ sys->print("Huffman table %d:\n", th);
+ printarray(t.size, "size");
+ printarray(t.code, "code");
+ printarray(t.val, "val");
+ printarray(t.mincode, "mincode");
+ printarray(t.maxcode, "maxcode");
+ printarray(t.value, "value");
+ printarray(t.shift, "shift");
+ }
+
+ return nsize+17;
+}
+
+jpegquanttables(is: ref ImageSource, n: int)
+{
+ if(dbg)
+ sys->print("jpegquanttables\n");
+ h := is.jstate;
+ if(h.qt == nil)
+ h.qt = array[4] of array of int;
+ for(l:=0; l<n; )
+ l += jpegquanttable(is);
+ if(l != n)
+ imgerror(is, "Jpeg quant table bad length");
+}
+
+jpegquanttable(is: ref ImageSource): int
+{
+ (pq, tq) := nibbles(getc(is));
+ if(dbg)
+ sys->print("jpegquanttable pq=%d tq=%d\n", pq, tq);
+ if(pq > 1)
+ imgerror(is, "Jpeg unknown quantization table class");
+ if(tq > 3)
+ imgerror(is, "Jpeg bad quantization table index");
+ q := array[64] of int;
+ is.jstate.qt[tq] = q;
+ for(i:=0; i<64; i++) {
+ if(pq == 0)
+ q[i] =getc(is);
+ else
+ q[i] = getbew(is);
+ }
+ if(dbg > 2)
+ printarray(q, "quant table");
+ return 1+(64*(1+pq));;
+}
+
+# Have just read Frame header.
+# Now expect:
+# ((tabl/misc segment(s))* (scan header) (entropy coded segment)+)+ EOI
+getjpegmim(is: ref ImageSource) : ref MaskedImage
+{
+ if(dbg)
+ sys->print("getjpegmim\n");
+ if(dbgev)
+ CU->event("IMAGE_GETJPGMIM", is.width*is.height);
+ getjpeghdr(is);
+ h := is.jstate;
+ chans: array of array of byte = nil;
+ for(;;) {
+ (m, n) := jpegtabmisc(is);
+ if(m == EOI)
+ break;
+ if(m != SOS)
+ imgerror(is, "Jpeg expected start of scan");
+ h.Ns = getc(is);
+ if(dbg)
+ sys->print("start of scan, Ns=%d\n", h.Ns);
+ scomp := array[h.Ns] of Scancomp;
+ for(i := 0; i < h.Ns; i++) {
+ scomp[i].C = getc(is);
+ (scomp[i].tdc, scomp[i].tac) = nibbles(getc(is));
+ }
+ h.scomp = scomp;
+ h.Ss = getc(is);
+ h.Se = getc(is);
+ (h.Ah, h.Al) = nibbles(getc(is));
+ if(n != 4+h.Ns*2)
+ imgerror(is, "Jpeg SOS header wrong length");
+
+ if(h.mode == byte SOF) {
+ if(chans != nil)
+ imgerror(is, "Jpeg baseline has > 1 scan");
+ chans = jpegbaselinescan(is);
+ }
+ else
+ jpegprogressivescan(is);
+ }
+ if(h.mode == byte SOF2)
+ chans = jprogressiveIDCT(is);
+ if(chans == nil)
+ imgerror(is, "jpeg has no image");
+ width := is.width;
+ height := is.height;
+ if(width != h.X || height != h.Y) {
+ for(k := 0; k < len chans; k++)
+ chans[k] = resample(chans[k], h.X, h.Y, width, height);
+ }
+ if(dbgev)
+ CU->event("IMAGE_JPG_REMAP", 0);
+ if(len chans == 1) {
+ im := newimagegrey(is, width, height);
+ im.writepixels(im.r, chans[0]);
+ return newmi(im);
+# remapgrey(chans[0], width, height);
+ } else {
+ if (len chans == 3) {
+ r := remapYCbCr(chans);
+ im := newimage24(is, width, height);
+ im.writepixels(im.r, r);
+ return newmi(im);
+ }
+ remaprgb(chans, width, height, jpegcolorspace);
+ }
+ if(dbgev)
+ CU->event("IMAGE_JPG_REMAP_END", 0);
+ im := newimage(is, width, height);
+ im.writepixels(im.r, chans[0]);
+ if(dbgev)
+ CU->event("IMAGE_GETJPGMIM_END", 0);
+ return newmi(im);
+}
+
+remapYCbCr(chans: array of array of byte): array of byte
+{
+ Y := chans[0];
+ Cb := chans[1];
+ Cr := chans[2];
+
+ rgb := array [3*len Y] of byte;
+ bix := 0;
+ for (i := 0; i < len Y; i++) {
+ y := int Y[i];
+ cb := int Cb[i];
+ cr := int Cr[i];
+ r := y + Cr2r[cr];
+ g := y - Cr2g[cr] - Cb2g[cb];
+ b := y + Cb2b[cb];
+
+ rgb[bix++] = clampb[b+CLAMPBOFF];
+ rgb[bix++] = clampb[g+CLAMPBOFF];
+ rgb[bix++] = clampb[r+CLAMPBOFF];
+ }
+ return rgb;
+}
+
+zig := array[64] of {
+ 0, 1, 8, 16, 9, 2, 3, 10, 17, # 0-7
+ 24, 32, 25, 18, 11, 4, 5, # 8-15
+ 12, 19, 26, 33, 40, 48, 41, 34, # 16-23
+ 27, 20, 13, 6, 7, 14, 21, 28, # 24-31
+ 35, 42, 49, 56, 57, 50, 43, 36, # 32-39
+ 29, 22, 15, 23, 30, 37, 44, 51, # 40-47
+ 58, 59, 52, 45, 38, 31, 39, 46, # 48-55
+ 53, 60, 61, 54, 47, 55, 62, 63 # 56-63
+};
+
+jpegbaselinescan(is: ref ImageSource) : array of array of byte
+{
+ if(dbg)
+ sys->print("jpegbaselinescan\n");
+ if(dbgev)
+ CU->event("IMAGE_JPGBASELINESCAN", 0);
+ h := is.jstate;
+ Ns := h.Ns;
+ if(Ns != h.Nf)
+ imgerror(is, "Jpeg baseline needs Ns==Nf");
+ if(!(Ns==3 || Ns==1))
+ imgerror(is, "Jpeg baseline needs Ns==1 or 3");
+
+ res := ResourceState.cur();
+ heapavail := res.heaplim - res.heap;
+
+ # check heap availability for
+ # chans: (3+Ns)*4 + (Ns*(3*4+h.X*h.Y)) bytes
+ # Td, Ta, data, H, V, DC: 6 arrays of (3+Ns)*4 bytes
+ #
+ heapavail -= (3+Ns)*28 + (Ns*(12 + h.X * h.Y));
+ if(heapavail <= 0) {
+ if(dbg)
+ sys->print("jpegbaselinescan: no memory for chans et al.\n");
+ imgerror(is, "not enough memory");
+ }
+
+ chans := array[h.Nf] of array of byte;
+ for(k:=0; k<h.Nf; k++)
+ chans[k] = array[h.X*h.Y] of byte;
+
+ # build per-component arrays
+ Td := array[Ns] of int;
+ Ta := array[Ns] of int;
+ data := array[Ns] of array of array of int;
+ H := array[Ns] of int;
+ V := array[Ns] of int;
+ DC := array[Ns] of int;
+
+ # compute maximum H and V
+ Hmax := 0;
+ Vmax := 0;
+ for(comp:=0; comp<Ns; comp++) {
+ if(h.comp[comp].H > Hmax)
+ Hmax = h.comp[comp].H;
+ if(h.comp[comp].V > Vmax)
+ Vmax = h.comp[comp].V;
+ }
+ if(dbg > 1)
+ sys->print("Hmax=%d, Vmax=%d\n", Hmax, Vmax);
+
+ # initialize data structures
+ allHV1 := 1;
+ for(comp=0; comp<Ns; comp++) {
+ # JPEG requires scan components to be in same order as in frame,
+ # so if both have 3 we know scan is Y Cb Cr and there's no need to
+ # reorder
+ Td[comp] = h.scomp[comp].tdc;
+ Ta[comp] = h.scomp[comp].tac;
+ H[comp] = h.comp[comp].H;
+ V[comp] = h.comp[comp].V;
+ nblock := H[comp]*V[comp];
+ if(nblock != 1)
+ allHV1 = 0;
+
+ # data[comp]: needs (3+nblock)*4 + nblock*(3+8*8)*4 bytes
+ heapavail -= 272*nblock + 12;
+ if(heapavail <= 0){
+ if(dbg)
+ sys->print("jpegbaselinescan: no memory for data\n");
+ imgerror(is, "not enough memory");
+ }
+
+ data[comp] = array[nblock] of array of int;
+ DC[comp] = 0;
+ for(m:=0; m<nblock; m++)
+ data[comp][m] = array[8*8] of int;
+ if(dbg > 2)
+ sys->print("scan comp %d: H=%d, V=%d, nblock=%d, Td=%d, Ta=%d\n",
+ comp, H[comp], V[comp], nblock, Td[comp], Ta[comp]);
+ }
+
+ ri := h.ri;
+
+ h.cnt = 0;
+ h.sr = 0;
+ nacross := ((h.X+(8*Hmax-1))/(8*Hmax));
+ nmcu := ((h.Y+(8*Vmax-1))/(8*Vmax))*nacross;
+ if(dbg)
+ sys->print("nacross=%d, nmcu=%d\n", nacross, nmcu);
+ for(mcu:=0; mcu<nmcu; ) {
+ if(dbg > 2)
+ sys->print("mcu %d\n", mcu);
+ for(comp=0; comp<Ns; comp++) {
+ if(dbg > 2)
+ sys->print("comp %d\n", comp);
+ dcht := h.dcht[Td[comp]];
+ acht := h.acht[Ta[comp]];
+ qt := h.qt[h.comp[comp].Tq];
+
+ for(block:=0; block<H[comp]*V[comp]; block++) {
+ if(dbg > 2)
+ sys->print("block %d\n", block);
+ # F-22
+ t := jdecode(is, dcht);
+ diff := jreceive(is, t);
+ DC[comp] += diff;
+ if(dbg > 2)
+ sys->print("t=%d, diff=%d, DC=%d\n", t, diff, DC[comp]);
+
+ # F-23
+ zz := data[comp][block];
+ zz[0:] = zeroints;
+ zz[0] = qt[0]*DC[comp];
+ k = 1;
+
+ for(;;) {
+ rs := jdecode(is, acht);
+ (rrrr, ssss) := nibbles(rs);
+ if(ssss == 0){
+ if(rrrr != 15)
+ break;
+ k += 16;
+ }else{
+ k += rrrr;
+ z := jreceive(is, ssss);
+ zz[zig[k]] = z*qt[k];
+ if(k == 63)
+ break;
+ k++;
+ }
+ }
+
+ idct(zz);
+ }
+ }
+
+ # rotate colors to RGB and assign to bytes
+ if(Ns == 1) # very easy
+ colormap1(h, chans[0], data[0][0], mcu, nacross);
+ else if(allHV1) # fairly easy
+ colormapall1(h, chans, data[0][0], data[1][0], data[2][0], mcu, nacross);
+ else # miserable general case
+ colormap(h, chans, data[0], data[1], data[2], mcu, nacross, Hmax, Vmax, H, V);
+
+ # process restart marker, if present
+ mcu++;
+ if(ri>0 && mcu<nmcu && mcu%ri==0){
+ jrestart(is, mcu);
+ for(comp=0; comp<Ns; comp++)
+ DC[comp] = 0;
+ }
+ }
+ if(dbgev)
+ CU->event("IMAGE_JPGBASELINESCAN_END", 0);
+ return chans;
+}
+
+jrestart(is: ref ImageSource, mcu: int)
+{
+ h := is.jstate;
+ ri := h.ri;
+ restart := mcu/ri-1;
+ rst, nskip: int;
+ nskip = 0;
+ do {
+ do{
+ rst = jnextborm(is);
+ nskip++;
+ }while(rst>=0 && rst!=16rFF);
+ if(rst == 16rFF){
+ rst = jnextborm(is);
+ nskip++;
+ }
+ } while(rst>=0 && (rst&~7)!= RST);
+ if(nskip != 2 || rst < 0 || ((rst&7) != (restart&7)))
+ imgerror(is, "Jpeg restart problem");
+ h.cnt = 0;
+ h.sr = 0;
+}
+
+jpegprogressivescan(is: ref ImageSource)
+{
+ if(dbgev)
+ CU->event("IMAGE_JPGPROGSCAN", 0);
+ h := is.jstate;
+ if(h.dccoeff == nil)
+ jprogressiveinit(is, h);
+
+ c := h.scomp[0].C;
+ comp := -1;
+ for(i:=0; i<h.Nf; i++)
+ if(h.comp[i].C == c)
+ comp = i;
+ if(comp == -1)
+ imgerror(is, "Jpeg bad component index in scan header");
+
+ if(h.Ss == 0)
+ jprogressivedc(is, comp);
+ else if(h.Ah == 0)
+ jprogressiveac(is, comp);
+ else
+ jprogressiveacinc(is, comp);
+ if(dbgev)
+ CU->event("IMAGE_JPGPROGSCAN_END", 0);
+}
+
+jprogressiveIDCT(is: ref ImageSource): array of array of byte
+{
+ if(dbgev)
+ CU->event("IMAGE_JPGPROGIDCT", 0);
+ h := is.jstate;
+ Nf := h.Nf;
+
+ res := ResourceState.cur();
+ heapavail := res.heaplim - res.heap;
+
+ # check heap availability for
+ # H, V, data, blockno: 4 arrays of (3+Nf)*4 bytes
+ # chans: (3+Nf)*4 + (Nf*(3*4+h.X*h.Y)) bytes
+ #
+ heapavail -= (3+Nf)*20 + (Nf*(12 + h.X * h.Y));
+ if(heapavail <= 0) {
+ if(dbg)
+ sys->print("jprogressiveIDCT: no memory for chans et al.\n");
+ imgerror(is, "not enough memory");
+ }
+ H := array[Nf] of int;
+ V := array[Nf] of int;
+
+ allHV1 := 1;
+
+ data := array[Nf] of array of array of int;
+ for(comp:=0; comp<Nf; comp++){
+ H[comp] = h.comp[comp].H;
+ V[comp] = h.comp[comp].V;
+ nblock := h.nblock[comp];
+ if(nblock != 1)
+ allHV1 = 0;
+
+ # data[comp]: needs (3+nblock)*4 + nblock*(3+8*8)*4 bytes
+ heapavail -= 272*nblock + 12;
+ if(heapavail <= 0){
+ if(dbg)
+ sys->print("jprogressiveIDCT: no memory for data\n");
+ imgerror(is, "not enough memory");
+ }
+
+ data[comp] = array[nblock] of array of int;
+ for(m:=0; m<nblock; m++)
+ data[comp][m] = array[8*8] of int;
+ }
+
+ chans := array[h.Nf] of array of byte;
+ for(k:=0; k<h.Nf; k++)
+ chans[k] = array[h.X*h.Y] of byte;
+
+ blockno := array[Nf] of {* => 0};
+ nmcu := h.nacross*h.ndown;
+ for(mcu:=0; mcu<nmcu; mcu++){
+ for(comp=0; comp<Nf; comp++){
+ dccoeff := h.dccoeff[comp];
+ accoeff := h.accoeff[comp];
+ bn := blockno[comp];
+ for(block:=0; block<h.nblock[comp]; block++){
+ zz := data[comp][block];
+ zz[0:] = zeroints;
+ zz[0] = dccoeff[bn];
+
+ for(k=1; k<64; k++)
+ zz[zig[k]] = accoeff[bn][k];
+
+ idct(zz);
+ bn++;
+ }
+ blockno[comp] = bn;
+ }
+
+ # rotate colors to RGB and assign to bytes
+ if(Nf == 1) # very easy
+ colormap1(h, chans[0], data[0][0], mcu, h.nacross);
+ else if(allHV1) # fairly easy
+ colormapall1(h, chans, data[0][0], data[1][0], data[2][0], mcu, h.nacross);
+ else # miserable general case
+ colormap(h, chans, data[0], data[1], data[2], mcu, h.nacross, h.Hmax, h.Vmax, H, V);
+ }
+ return chans;
+}
+
+jprogressiveinit(is: ref ImageSource, h: ref Jpegstate)
+{
+ Ns := h.Ns;
+ Nf := h.Nf;
+ if((Ns!=3 && Ns!=1) || Ns!=Nf)
+ imgerror(is, "Jpeg image must have 1 or 3 components");
+
+ # compute maximum H and V
+ h.Hmax = 0;
+ h.Vmax = 0;
+ for(comp:=0; comp<Nf; comp++){
+ if(h.comp[comp].H > h.Hmax)
+ h.Hmax = h.comp[comp].H;
+ if(h.comp[comp].V > h.Vmax)
+ h.Vmax = h.comp[comp].V;
+ }
+ h.nacross = ((h.X+(8*h.Hmax-1))/(8*h.Hmax));
+ h.ndown = ((h.Y+(8*h.Vmax-1))/(8*h.Vmax));
+ nmcu := h.nacross*h.ndown;
+
+ res := ResourceState.cur();
+ heapavail := res.heaplim - res.heap;
+
+ # check heap availability for
+ # h.dccoeff: (3+Nf)*4 bytes
+ # h.accoeff: (3+Nf)*4 bytes
+ heapavail -= (3+Nf)*8;
+ if(heapavail <= 0) {
+ if(dbg)
+ sys->print("jprogressiveinit: no memory for coeffs\n");
+ imgerror(is, "not enough memory");
+ }
+
+ h.dccoeff = array[Nf] of array of int;
+ h.accoeff = array[Nf] of array of array of int;
+ for(k:=0; k<Nf; k++){
+ n := h.nblock[k]*nmcu;
+
+ # check heap availability for
+ # h.dccoeff[k]: (3+n)*4 bytes
+ # h.accoeff[k]: (3+n)*4 + n*(3+64)*4 bytes
+ heapavail -= 276*n + 24;
+ if(heapavail <= 0){
+ if(dbg)
+ sys->print("jprogressiveinit: no memory for coeff arrays\n");
+ imgerror(is, "not enough memory");
+ }
+
+ h.dccoeff[k] = array[n] of {* => 0};
+ h.accoeff[k] = array[n] of array of int;
+ for(j:=0; j<n; j++)
+ h.accoeff[k][j] = array[64] of {* => 0};
+ }
+}
+
+jprogressivedc(is: ref ImageSource, comp: int)
+{
+ h := is.jstate;
+ Ns := h.Ns;
+ Ah := h.Ah;
+ Al := h.Al;
+ if(Ns!=h.Nf)
+ imgerror(is, "Jpeg progressive with Nf!=Ns in DC scan");
+
+ # build per-component arrays
+ Td := array[Ns] of int;
+ DC := array[Ns] of int;
+
+ # initialize data structures
+ h.cnt = 0;
+ h.sr = 0;
+ for(comp=0; comp<Ns; comp++) {
+ # JPEG requires scan components to be in same order as in frame,
+ # so if both have 3 we know scan is Y Cb Cr and there's no need to
+ # reorder
+ Td[comp] = h.scomp[comp].tdc;
+ DC[comp] = 0;
+ }
+
+ ri := h.ri;
+
+ nmcu := h.nacross*h.ndown;
+ blockno := array[Ns] of {* => 0};
+ for(mcu:=0; mcu<nmcu; ){
+ for(comp=0; comp<Ns; comp++){
+ dcht := h.dcht[Td[comp]];
+ qt := h.qt[h.comp[comp].Tq][0];
+ dc := h.dccoeff[comp];
+ bn := blockno[comp];
+
+ for(block:=0; block<h.nblock[comp]; block++) {
+ if(Ah == 0) {
+ t := jdecode(is, dcht);
+ diff := jreceive(is, t);
+ DC[comp] += diff;
+ dc[bn] = qt*DC[comp]<<Al;
+ } else
+ dc[bn] |= qt*jreceivebit(is)<<Al;
+ bn++;
+ }
+ blockno[comp] = bn;
+ }
+
+ # process restart marker, if present
+ mcu++;
+ if(ri>0 && mcu<nmcu && mcu%ri==0){
+ jrestart(is, mcu);
+ for(comp=0; comp<Ns; comp++)
+ DC[comp] = 0;
+ }
+ }
+}
+
+jprogressiveac(is: ref ImageSource, comp: int)
+{
+ h := is.jstate;
+ Ns := h.Ns;
+ Al := h.Al;
+ if(Ns != 1)
+ imgerror(is, "Jpeg illegal Ns>1 in progressive AC scan");
+ Ss := h.Ss;
+ Se := h.Se;
+ H := h.comp[comp].H;
+ V := h.comp[comp].V;
+
+ nacross := h.nacross*H;
+ ndown := h.ndown*V;
+ q := 8*h.Hmax/H;
+ nhor := (h.X+q-1)/q;
+ q = 8*h.Vmax/V;
+ nver := (h.Y+q-1)/q;
+
+ # initialize data structures
+ h.cnt = 0;
+ h.sr = 0;
+ Ta := h.scomp[0].tac;
+
+ ri := h.ri;
+
+ eobrun := 0;
+ acht := h.acht[Ta];
+ qt := h.qt[h.comp[comp].Tq];
+ nmcu := nacross*ndown;
+ mcu := 0;
+ for(y:=0; y<nver; y++) {
+ for(x:=0; x<nhor; x++) {
+ # Figure G-3
+ if(eobrun > 0){
+ --eobrun;
+ continue;
+ }
+
+ # arrange blockno to be in same sequence as
+ # original scan calculation.
+ tmcu := x/H + (nacross/H)*(y/V);
+ blockno := tmcu*H*V + H*(y%V) + x%H;
+ acc := h.accoeff[comp][blockno];
+ k := Ss;
+ for(;;) {
+ rs := jdecode(is, acht);
+ (rrrr, ssss) := nibbles(rs);
+ if(ssss == 0) {
+ if(rrrr < 15) {
+ eobrun = 0;
+ if(rrrr > 0)
+ eobrun = jreceiveEOB(is, rrrr)-1;
+ break;
+ }
+ k += 16;
+ }
+ else {
+ k += rrrr;
+ z := jreceive(is, ssss);
+ acc[k] = z*qt[k]<<Al;
+ if(k == Se)
+ break;
+ k++;
+ }
+ }
+ }
+
+ # process restart marker, if present
+ mcu++;
+ if(ri>0 && mcu<nmcu && mcu%ri==0) {
+ jrestart(is, mcu);
+ eobrun = 0;
+ }
+ }
+}
+
+jprogressiveacinc(is: ref ImageSource, comp: int)
+{
+ h := is.jstate;
+ Ns := h.Ns;
+ if(Ns != 1)
+ imgerror(is, "Jpeg illegal Ns>1 in progressive AC scan");
+ Ss := h.Ss;
+ Se := h.Se;
+ H := h.comp[comp].H;
+ V := h.comp[comp].V;
+ Al := h.Al;
+
+ nacross := h.nacross*H;
+ ndown := h.ndown*V;
+ q := 8*h.Hmax/H;
+ nhor := (h.X+q-1)/q;
+ q = 8*h.Vmax/V;
+ nver := (h.Y+q-1)/q;
+
+ # initialize data structures
+ h.cnt = 0;
+ h.sr = 0;
+ Ta := h.scomp[0].tac;
+ ri := h.ri;
+
+ eobrun := 0;
+ ac := h.accoeff[comp];
+ acht := h.acht[Ta];
+ qt := h.qt[h.comp[comp].Tq];
+ nmcu := nacross*ndown;
+ mcu := 0;
+ pending := 0;
+ nzeros := -1;
+ for(y:=0; y<nver; y++){
+ for(x:=0; x<nhor; x++){
+ # Figure G-7
+
+ # arrange blockno to be in same sequence as
+ # original scan calculation.
+ tmcu := x/H + (nacross/H)*(y/V);
+ blockno := tmcu*H*V + H*(y%V) + x%H;
+ acc := ac[blockno];
+ if(eobrun > 0){
+ if(nzeros > 0)
+ imgerror(is, "Jpeg zeros pending at block start");
+ for(k:=Ss; k<=Se; k++)
+ jincrement(is, acc, k, qt[k]<<Al);
+ --eobrun;
+ continue;
+ }
+
+ for(k:=Ss; k<=Se; ){
+ if(nzeros >= 0){
+ if(acc[k] != 0)
+ jincrement(is, acc, k, qt[k]<<Al);
+ else if(nzeros-- == 0)
+ acc[k] = pending;
+ k++;
+ continue;
+ }
+ rs := jdecode(is, acht);
+ (rrrr, ssss) := nibbles(rs);
+ if(ssss == 0){
+ if(rrrr < 15){
+ eobrun = 0;
+ if(rrrr > 0)
+ eobrun = jreceiveEOB(is, rrrr)-1;
+ while(k <= Se){
+ jincrement(is, acc, k, qt[k]<<Al);
+ k++;
+ }
+ break;
+ }
+ for(i:=0; i<16; k++){
+ jincrement(is, acc, k, qt[k]<<Al);
+ if(acc[k] == 0)
+ i++;
+ }
+ continue;
+ }else if(ssss != 1)
+ imgerror(is, "Jpeg ssss!=1 in progressive increment");
+ nzeros = rrrr;
+ pending = jreceivebit(is);
+ if(pending == 0)
+ pending = -1;
+ pending *= qt[k]<<Al;
+ }
+ }
+
+ # process restart marker, if present
+ mcu++;
+ if(ri>0 && mcu<nmcu && mcu%ri==0){
+ jrestart(is, mcu);
+ eobrun = 0;
+ nzeros = -1;
+ }
+ }
+}
+
+jincrement(is: ref ImageSource, acc: array of int, k, Pt: int)
+{
+ if(acc[k] == 0)
+ return;
+ b := jreceivebit(is);
+ if(b != 0)
+ if(acc[k] < 0)
+ acc[k] -= Pt;
+ else
+ acc[k] += Pt;
+}
+
+jc1: con 2871; # 1.402 * 2048
+jc2: con 705; # 0.34414 * 2048
+jc3: con 1463; # 0.71414 * 2048
+jc4: con 3629; # 1.772 * 2048
+
+# Fills in pixels (x,y) for x = minx=8*(mcu%nacross), minx+1, ..., minx+7 (or h.X-1, if less)
+# and for y = miny=8*(mcu/nacross), miny+1, ..., miny+7 (or h.Y-1, if less)
+colormap1(h: ref Jpegstate, pic: array of byte, data: array of int, mcu, nacross: int)
+{
+ minx := 8*(mcu%nacross);
+ dx := 8;
+ if(minx+dx > h.X)
+ dx = h.X-minx;
+ miny := 8*(mcu/nacross);
+ dy := 8;
+ if(miny+dy > h.Y)
+ dy = h.Y-miny;
+ pici := miny*h.X+minx;
+ k := 0;
+ for(y:=0; y<dy; y++) {
+ for(x:=0; x<dx; x++)
+ pic[pici+x] = clampb[(data[k+x]+128)+CLAMPBOFF];
+ pici += h.X;
+ k += 8;
+ }
+}
+
+# Fills in same pixels as colormap1
+colormapall1(h: ref Jpegstate, chans: array of array of byte, data0, data1, data2: array of int, mcu, nacross: int)
+{
+ rpic := chans[0];
+ gpic := chans[1];
+ bpic := chans[2];
+ minx := 8*(mcu%nacross);
+ dx := 8;
+ if(minx+dx > h.X)
+ dx = h.X-minx;
+ miny := 8*(mcu/nacross);
+ dy := 8;
+ if(miny+dy > h.Y)
+ dy = h.Y-miny;
+ pici := miny*h.X+minx;
+ k := 0;
+ for(y:=0; y<dy; y++) {
+ for(x:=0; x<dx; x++){
+ if(jpegcolorspace == CYCbCr) {
+ rpic[pici+x] = clampb[data0[k+x]+128+CLAMPBOFF];
+ gpic[pici+x] = clampb[data1[k+x]+128+CLAMPBOFF];
+ bpic[pici+x] = clampb[data2[k+x]+128+CLAMPBOFF];
+ }
+ else { # RGB
+ Y := (data0[k+x]+128) << 11;
+ Cb := data1[k+x];
+ Cr := data2[k+x];
+ r := Y+jc1*Cr;
+ g := Y-jc2*Cb-jc3*Cr;
+ b := Y+jc4*Cb;
+ rpic[pici+x] = clampb[(r>>11)+CLAMPBOFF];
+ gpic[pici+x] = clampb[(g>>11)+CLAMPBOFF];
+ bpic[pici+x] = clampb[(b>>11)+CLAMPBOFF];
+ }
+ }
+ pici += h.X;
+ k += 8;
+ }
+}
+
+# Fills in pixels (x,y) for x = minx=8*Hmax*(mcu%nacross), minx+1, ..., minx+8*Hmax-1 (or h.X-1, if less)
+# and for y = miny=8*Vmax*(mcu/nacross), miny+1, ..., miny+8*Vmax-1 (or h.Y-1, if less)
+colormap(h: ref Jpegstate, chans: array of array of byte, data0, data1, data2: array of array of int, mcu, nacross, Hmax, Vmax: int, H, V: array of int)
+{
+ rpic := chans[0];
+ gpic := chans[1];
+ bpic := chans[2];
+ minx := 8*Hmax*(mcu%nacross);
+ dx := 8*Hmax;
+ if(minx+dx > h.X)
+ dx = h.X-minx;
+ miny := 8*Vmax*(mcu/nacross);
+ dy := 8*Vmax;
+ if(miny+dy > h.Y)
+ dy = h.Y-miny;
+ pici := miny*h.X+minx;
+ H0 := H[0];
+ H1 := H[1];
+ H2 := H[2];
+ if(dbg > 2)
+ sys->print("colormap, minx=%d, miny=%d, dx=%d, dy=%d, pici=%d, H0=%d, H1=%d, H2=%d\n",
+ minx, miny, dx, dy, pici, H0, H1, H2);
+ for(y:=0; y<dy; y++) {
+ t := y*V[0];
+ b0 := H0*(t/(8*Vmax));
+ y0 := 8*((t/Vmax)&7);
+ t = y*V[1];
+ b1 := H1*(t/(8*Vmax));
+ y1 := 8*((t/Vmax)&7);
+ t = y*V[2];
+ b2 := H2*(t/(8*Vmax));
+ y2 := 8*((t/Vmax)&7);
+ x0 := 0;
+ x1 := 0;
+ x2 := 0;
+ for(x:=0; x<dx; x++) {
+ if(jpegcolorspace == CYCbCr) {
+ rpic[pici+x] = clampb[data0[b0][y0+x0++*H0/Hmax] + 128 + CLAMPBOFF];
+ gpic[pici+x] = clampb[data1[b1][y1+x1++*H1/Hmax] + 128 + CLAMPBOFF];
+ bpic[pici+x] = clampb[data2[b2][y2+x2++*H2/Hmax] + 128 + CLAMPBOFF];
+ }
+ else { # RGB
+ Y := (data0[b0][y0+x0++*H0/Hmax]+128) << 11;
+ Cb := data1[b1][y1+x1++*H1/Hmax];
+ Cr := data2[b2][y2+x2++*H2/Hmax];
+ r := Y+jc1*Cr;
+ g := Y-jc2*Cb-jc3*Cr;
+ b := Y+jc4*Cb;
+ rpic[pici+x] = clampb[(r>>11)+CLAMPBOFF];
+ gpic[pici+x] = clampb[(g>>11)+CLAMPBOFF];
+ bpic[pici+x] = clampb[(b>>11)+CLAMPBOFF];
+ }
+ if(x0*H0/Hmax >= 8){
+ x0 = 0;
+ b0++;
+ }
+ if(x1*H1/Hmax >= 8){
+ x1 = 0;
+ b1++;
+ }
+ if(x2*H2/Hmax >= 8){
+ x2 = 0;
+ b2++;
+ }
+ }
+ pici += h.X;
+ }
+}
+
+# decode next 8-bit value from entropy-coded input. chart F-26
+jdecode(is: ref ImageSource, t: ref Huffman): int
+{
+ h := is.jstate;
+ maxcode := t.maxcode;
+ if(h.cnt < 8)
+ jnextbyte(is);
+ # fast lookup
+ code := (h.sr>>(h.cnt-8))&16rFF;
+ v := t.value[code];
+ if(v >= 0){
+ h.cnt -= t.shift[code];
+ return v;
+ }
+
+ h.cnt -= 8;
+ if(h.cnt == 0)
+ jnextbyte(is);
+ h.cnt--;
+ cnt := h.cnt;
+ m := 1<<cnt;
+ sr := h.sr;
+ code <<= 1;
+ i := 9;
+ for(;;i++){
+ if(sr & m)
+ code |= 1;
+ if(code <= maxcode[i])
+ break;
+ code <<= 1;
+ m >>= 1;
+ if(m == 0){
+ sr = jnextbyte(is);
+ m = 16r80;
+ cnt = 8;
+ }
+ cnt--;
+ }
+ h.cnt = cnt;
+ return t.val[t.valptr[i]+(code-t.mincode[i])];
+}
+
+# load next byte of input
+jnextbyte(is: ref ImageSource): int
+{
+ b :=getc(is);
+
+ if(b == 16rFF) {
+ b2 :=getc(is);
+ if(b2 != 0) {
+ if(b2 == int DNL)
+ imgerror(is, "Jpeg DNL marker unimplemented");
+ # decoder is reading into marker; satisfy it and restore state
+ ungetc2(is, byte b);
+ }
+ }
+ h := is.jstate;
+ h.cnt += 8;
+ h.sr = (h.sr<<8)| b;
+ return b;
+}
+
+# like jnextbyte, but look for marker too
+jnextborm(is: ref ImageSource): int
+{
+ b :=getc(is);
+
+ if(b == 16rFF)
+ return b;
+ h := is.jstate;
+ h.cnt += 8;
+ h.sr = (h.sr<<8)| b;
+ return b;
+}
+
+# return next s bits of input, MSB first, and level shift it
+jreceive(is: ref ImageSource, s: int): int
+{
+ h := is.jstate;
+ while(h.cnt < s)
+ jnextbyte(is);
+ h.cnt -= s;
+ v := h.sr >> h.cnt;
+ m := (1<<s);
+ v &= m-1;
+ # level shift
+ if(v < (m>>1))
+ v += ~(m-1)+1;
+ return v;
+}
+
+# return next s bits of input, decode as EOB
+jreceiveEOB(is: ref ImageSource, s: int): int
+{
+ h := is.jstate;
+ while(h.cnt < s)
+ jnextbyte(is);
+ h.cnt -= s;
+ v := h.sr >> h.cnt;
+ m := (1<<s);
+ v &= m-1;
+ # level shift
+ v += m;
+ return v;
+}
+
+# return next bit of input
+jreceivebit(is: ref ImageSource): int
+{
+ h := is.jstate;
+ if(h.cnt < 1)
+ jnextbyte(is);
+ h.cnt--;
+ return (h.sr >> h.cnt) & 1;
+}
+
+
+nibbles(c: int) : (int, int)
+{
+ return (c>>4, c&15);
+
+}
+
+# Scaled integer implementation.
+# inverse two dimensional DCT, Chen-Wang algorithm
+# (IEEE ASSP-32, pp. 803-816, Aug. 1984)
+# 32-bit integer arithmetic (8 bit coefficients)
+# 11 mults, 29 adds per DCT
+#
+# coefficients extended to 12 bit for IEEE1180-1990
+# compliance
+
+W1: con 2841; # 2048*sqrt(2)*cos(1*pi/16)
+W2: con 2676; # 2048*sqrt(2)*cos(2*pi/16)
+W3: con 2408; # 2048*sqrt(2)*cos(3*pi/16)
+W5: con 1609; # 2048*sqrt(2)*cos(5*pi/16)
+W6: con 1108; # 2048*sqrt(2)*cos(6*pi/16)
+W7: con 565; # 2048*sqrt(2)*cos(7*pi/16)
+
+W1pW7: con 3406; # W1+W7
+W1mW7: con 2276; # W1-W7
+W3pW5: con 4017; # W3+W5
+W3mW5: con 799; # W3-W5
+W2pW6: con 3784; # W2+W6
+W2mW6: con 1567; # W2-W6
+
+R2: con 181; # 256/sqrt(2)
+
+idct(b: array of int)
+{
+ # transform horizontally
+ for(y:=0; y<8; y++){
+ eighty := y<<3;
+ # if all non-DC components are zero, just propagate the DC term
+ if(b[eighty+1]==0)
+ if(b[eighty+2]==0 && b[eighty+3]==0)
+ if(b[eighty+4]==0 && b[eighty+5]==0)
+ if(b[eighty+6]==0 && b[eighty+7]==0){
+ v := b[eighty]<<3;
+ b[eighty+0] = v;
+ b[eighty+1] = v;
+ b[eighty+2] = v;
+ b[eighty+3] = v;
+ b[eighty+4] = v;
+ b[eighty+5] = v;
+ b[eighty+6] = v;
+ b[eighty+7] = v;
+ continue;
+ }
+ # prescale
+ x0 := (b[eighty+0]<<11)+128;
+ x1 := b[eighty+4]<<11;
+ x2 := b[eighty+6];
+ x3 := b[eighty+2];
+ x4 := b[eighty+1];
+ x5 := b[eighty+7];
+ x6 := b[eighty+5];
+ x7 := b[eighty+3];
+ # first stage
+ x8 := W7*(x4+x5);
+ x4 = x8 + W1mW7*x4;
+ x5 = x8 - W1pW7*x5;
+ x8 = W3*(x6+x7);
+ x6 = x8 - W3mW5*x6;
+ x7 = x8 - W3pW5*x7;
+ # second stage
+ x8 = x0 + x1;
+ x0 -= x1;
+ x1 = W6*(x3+x2);
+ x2 = x1 - W2pW6*x2;
+ x3 = x1 + W2mW6*x3;
+ x1 = x4 + x6;
+ x4 -= x6;
+ x6 = x5 + x7;
+ x5 -= x7;
+ # third stage
+ x7 = x8 + x3;
+ x8 -= x3;
+ x3 = x0 + x2;
+ x0 -= x2;
+ x2 = (R2*(x4+x5)+128)>>8;
+ x4 = (R2*(x4-x5)+128)>>8;
+ # fourth stage
+ b[eighty+0] = (x7+x1)>>8;
+ b[eighty+1] = (x3+x2)>>8;
+ b[eighty+2] = (x0+x4)>>8;
+ b[eighty+3] = (x8+x6)>>8;
+ b[eighty+4] = (x8-x6)>>8;
+ b[eighty+5] = (x0-x4)>>8;
+ b[eighty+6] = (x3-x2)>>8;
+ b[eighty+7] = (x7-x1)>>8;
+ }
+ # transform vertically
+ for(x:=0; x<8; x++){
+ # if all non-DC components are zero, just propagate the DC term
+ if(b[x+8*1]==0)
+ if(b[x+8*2]==0 && b[x+8*3]==0)
+ if(b[x+8*4]==0 && b[x+8*5]==0)
+ if(b[x+8*6]==0 && b[x+8*7]==0){
+ v := (b[x+8*0]+32)>>6;
+ b[x+8*0] = v;
+ b[x+8*1] = v;
+ b[x+8*2] = v;
+ b[x+8*3] = v;
+ b[x+8*4] = v;
+ b[x+8*5] = v;
+ b[x+8*6] = v;
+ b[x+8*7] = v;
+ continue;
+ }
+ # prescale
+ x0 := (b[x+8*0]<<8)+8192;
+ x1 := b[x+8*4]<<8;
+ x2 := b[x+8*6];
+ x3 := b[x+8*2];
+ x4 := b[x+8*1];
+ x5 := b[x+8*7];
+ x6 := b[x+8*5];
+ x7 := b[x+8*3];
+ # first stage
+ x8 := W7*(x4+x5) + 4;
+ x4 = (x8+W1mW7*x4)>>3;
+ x5 = (x8-W1pW7*x5)>>3;
+ x8 = W3*(x6+x7) + 4;
+ x6 = (x8-W3mW5*x6)>>3;
+ x7 = (x8-W3pW5*x7)>>3;
+ # second stage
+ x8 = x0 + x1;
+ x0 -= x1;
+ x1 = W6*(x3+x2) + 4;
+ x2 = (x1-W2pW6*x2)>>3;
+ x3 = (x1+W2mW6*x3)>>3;
+ x1 = x4 + x6;
+ x4 -= x6;
+ x6 = x5 + x7;
+ x5 -= x7;
+ # third stage
+ x7 = x8 + x3;
+ x8 -= x3;
+ x3 = x0 + x2;
+ x0 -= x2;
+ x2 = (R2*(x4+x5)+128)>>8;
+ x4 = (R2*(x4-x5)+128)>>8;
+ # fourth stage
+ b[x+8*0] = (x7+x1)>>14;
+ b[x+8*1] = (x3+x2)>>14;
+ b[x+8*2] = (x0+x4)>>14;
+ b[x+8*3] = (x8+x6)>>14;
+ b[x+8*4] = (x8-x6)>>14;
+ b[x+8*5] = (x0-x4)>>14;
+ b[x+8*6] = (x3-x2)>>14;
+ b[x+8*7] = (x7-x1)>>14;
+ }
+}
+
+################# Remap colors and Dither ##############
+
+closest_rgbpix(r, g, b: int) : int
+{
+ pix := int closestrgb[((r>>4)<<8)+((g>>4)<<4)+(b>>4)];
+ # If white is the closest but original r,g,b wasn't white,
+ # look for another color, because web page designer probably
+ # cares more about contrast than actual color
+ if(pix == 0 && !(r == 255 && g ==255 && b == 255)) {
+ bestdist := 1000000;
+ for(i := 1; i < 256; i++) {
+ dr := r-rgbvmap_r[i];
+ dg := g-rgbvmap_g[i];
+ db := b-rgbvmap_b[i];
+ d := dr*dr + dg*dg + db*db;
+ if(d < bestdist) {
+ bestdist = d;
+ pix = i;
+ }
+ }
+ }
+ return pix;
+}
+
+CLAMPBOFF: con 300;
+NCLAMPB: con CLAMPBOFF+256+CLAMPBOFF;
+CLAMPNOFF: con 64;
+NCLAMPN: con CLAMPNOFF+256+CLAMPNOFF;
+
+clampb: array of byte; # clamps byte values
+clampn_b: array of int; # clamps byte values, then shifts >> 4
+clampn_g: array of int; # clamps byte values, then masks off lower 4 bits
+clampn_r: array of int; # clamps byte values, masks off lower 4 bits, then shifts <<4
+
+init_tabs()
+{
+ clampn_b = array[NCLAMPN] of int;
+ clampn_g = array[NCLAMPN] of int;
+ clampn_r = array[NCLAMPN] of int;
+ for(j:=0; j<CLAMPNOFF; j++) {
+ clampn_b[j] = 0;
+ clampn_g[j] = 0;
+ clampn_r[j] = 0;
+ }
+ for(j=0; j<256; j++) {
+ t := j>>4;
+ clampn_b[CLAMPNOFF+j] = t;
+ clampn_g[CLAMPNOFF+j] = t<<4;
+ clampn_r[CLAMPNOFF+j] = t<<8;
+ }
+ for(j=0; j<CLAMPNOFF; j++) {
+ clampn_b[CLAMPNOFF+256+j] = 16r0F;
+ clampn_g[CLAMPNOFF+256+j] = 16rF0;
+ clampn_r[CLAMPNOFF+256+j] = 16rF00;
+ }
+ clampb = array[NCLAMPB] of byte;
+ for(j=0; j<CLAMPBOFF; j++)
+ clampb[j] = byte 0;
+ for(j=0; j<256; j++)
+ clampb[CLAMPBOFF+j] = byte j;
+ for(j=0; j<CLAMPBOFF; j++)
+ clampb[CLAMPBOFF+256+j] = byte 16rFF;
+}
+
+# could account for mask in alpha rather than having separate mask
+remap24(pic: array of byte, cmap: array of byte): array of byte
+{
+ cmap_r := array[256] of byte;
+ cmap_g := array[256] of byte;
+ cmap_b := array[256] of byte;
+ i := 0;
+ for(j := 0; j < 256 && i < len cmap; j++) {
+ cmap_r[j] = cmap[i++];
+ cmap_g[j] = cmap[i++];
+ cmap_b[j] = cmap[i++];
+ }
+ # in case input has bad indices
+ for( ; j < 256; j++) {
+ cmap_r[j] = byte 0;
+ cmap_g[j] = byte 0;
+ cmap_b[j] = byte 0;
+ }
+ pic24 := array [3 * len pic] of byte;
+ ix24 := 0;
+ for (i = 0; i < len pic; i++) {
+ c := int pic[i];
+ pic24[ix24++] = cmap_b[c];
+ pic24[ix24++] = cmap_g[c];
+ pic24[ix24++] = cmap_r[c];
+ }
+ return pic24;
+}
+
+# Remap pixels of pic[] into the closest colors in the rgbv map,
+# and do error diffusion of the result.
+# pic is a one-channel image whose rgb values are given by looking
+# up values in cmap.
+remap1(pic: array of byte, dx, dy: int, cmap: array of byte)
+{
+ if(dbg)
+ sys->print("remap1, pic len %d, dx=%d, dy=%d\n", len pic, dx, dy);
+ cmap_r := array[256] of int;
+ cmap_g := array[256] of int;
+ cmap_b := array[256] of int;
+ i := 0;
+ for(j := 0; j < 256 && i < len cmap; j++) {
+ cmap_r[j] = int cmap[i++];
+ cmap_g[j] = int cmap[i++];
+ cmap_b[j] = int cmap[i++];
+ }
+ # in case input has bad indices
+ for( ; j < 256; j++) {
+ cmap_r[j] = 0;
+ cmap_g[j] = 0;
+ cmap_b[j] = 0;
+ }
+ # modified floyd steinberg, coefficients (1 0) 3/16, (0, 1) 3/16, (1, 1) 7/16
+ ered := array[dx+1] of { * => 0 };
+ egrn := array[dx+1] of int;
+ eblu := array[dx+1] of int;
+ egrn[0:] = ered;
+ eblu[0:] = ered;
+ p := 0;
+ for(y:=0; y<dy; y++) {
+ er := 0;
+ eg := 0;
+ eb := 0;
+ for(x:=0; x<dx; ) {
+ x1 := x+1;
+ in := int pic[p];
+ r := cmap_r[in]+ered[x];
+ g := cmap_g[in]+egrn[x];
+ b := cmap_b[in]+eblu[x];
+ col := int (closestrgb[clampn_r[r+CLAMPNOFF]
+ +clampn_g[g+CLAMPNOFF]
+ +clampn_b[b+CLAMPNOFF]]);
+ pic[p++] = byte 255 - byte col;
+
+ r -= rgbvmap_r[col];
+ t := (3*r)>>4;
+ ered[x] = t+er;
+ ered[x1] += t;
+ er = r-3*t;
+
+ g -= rgbvmap_g[col];
+ t = (3*g)>>4;
+ egrn[x] = t+eg;
+ egrn[x1] += t;
+ eg = g-3*t;
+
+ b -= rgbvmap_b[col];
+ t = (3*b)>>4;
+ eblu[x] = t+eb;
+ eblu[x1] += t;
+ eb = b-3*t;
+
+ x = x1;
+ }
+ }
+}
+
+# Remap pixels of pic[] into the closest greyscale colors in the rgbv map,
+# and do error diffusion of the result.
+# pic is a one-channel greyscale image.
+remapgrey(pic: array of byte, dx, dy: int)
+{
+ if(dbg)
+ sys->print("remapgrey, pic len %d, dx=%d, dy=%d\n", len pic, dx, dy);
+ # modified floyd steinberg, coefficients (1 0) 3/16, (0, 1) 3/16, (1, 1) 7/16
+ e := array[dx+1] of {* => 0 };
+ p := 0;
+ for(y:=0; y<dy; y++){
+ eb := 0;
+ for(x:=0; x<dx; ) {
+ x1 := x+1;
+ b := int pic[p]+e[x];
+ b1 := clampn_b[b+CLAMPNOFF];
+ col := 255-17*b1;
+ pic[p++] = byte col;
+
+ b -= rgbvmap_b[col];
+ t := (3*b)>>4;
+ e[x] = t+eb;
+ e[x1] += t;
+ eb = b-3*t;
+ x = x1;
+ }
+ }
+}
+
+# Remap pixels of chans into the closest colors in the rgbv map,
+# and do error diffusion of the result.
+# chans is a 3-channel image whose channels are either (y,cb,cr) or
+# (r,g,b), depending on whether colorspace is CYCbCr or CRGB.
+# Variable names use r,g,b (historical).
+remaprgb(chans: array of array of byte, dx, dy, colorspace: int)
+{
+ if(dbg)
+ sys->print("remaprgb, pic len %d, dx=%d, dy=%d\n", len chans[0], dx, dy);
+ rpic := chans[0];
+ gpic := chans[1];
+ bpic := chans[2];
+ pic := chans[0];
+ # modified floyd steinberg, coefficients (1 0) 3/16, (0, 1) 3/16, (1, 1) 7/16
+ ered := array[dx+1] of { * => 0 };
+ egrn := array[dx+1] of int;
+ eblu := array[dx+1] of int;
+ egrn[0:] = ered;
+ eblu[0:] = ered;
+ closest: array of byte;
+ map0, map1, map2: array of int;
+ if(colorspace == CRGB) {
+ closest = closestrgb;
+ map0 = rgbvmap_r;
+ map1 = rgbvmap_g;
+ map2 = rgbvmap_b;
+ }
+ else {
+ closest = closestycbcr;
+ map0 = rgbvmap_y;
+ map1 = rgbvmap_cb;
+ map2 = rgbvmap_cr;
+ }
+ p := 0;
+ for(y:=0; y<dy; y++ ) {
+ er := 0;
+ eg := 0;
+ eb := 0;
+ for(x:=0; x<dx; ) {
+ x1 := x + 1;
+ r := int rpic[p]+ered[x];
+ g := int gpic[p]+egrn[x];
+ b := int bpic[p]+eblu[x];
+ # Errors can be uncorrectable if converting from YCbCr,
+ # since we can't guarantee that an extremal value of one of
+ # the components selects a color with an extremal value.
+ # If we don't, the errors accumulate without bound. This
+ # doesn't happen in RGB because the closest table can guarantee
+ # a color on the edge of the gamut, producing a zero error in
+ # that component. For the rotation YCbCr space, there may be
+ # no color that can guarantee zero error at the edge.
+ # Therefore we must clamp explicitly rather than by assuming
+ # an upper error bound of CLAMPOFF. The performance difference
+ # is miniscule anyway.
+ if(r < 0)
+ r = 0;
+ else if(r > 255)
+ r = 255;
+ if(g < 0)
+ g = 0;
+ else if(g > 255)
+ g = 255;
+ if(b < 0)
+ b = 0;
+ else if(b > 255)
+ b = 255;
+ col := int (closest[(b>>4)+16*((g>>4)+(r&16rF0))]);
+ pic[p++] = byte (255-col);
+# col := int (pic[p++] = closest[(b>>4)+16*((g>>4)+16*(r>>4))]);
+
+ r -= map0[col];
+ t := (3*r)>>4;
+ ered[x] = t+er;
+ ered[x1] += t;
+ er = r-3*t;
+
+ g -= map1[col];
+ t = (3*g)>>4;
+ egrn[x] = t+eg;
+ egrn[x1] += t;
+ eg = g-3*t;
+
+ b -= map2[col];
+ t = (3*b)>>4;
+ eblu[x] = t+eb;
+ eblu[x1] += t;
+ eb = b-3*t;
+
+ x = x1;
+ }
+ }
+}
+
+# Given src array, representing sw*sh pixel values, resample them into
+# the returned array, with dimensions dw*dh.
+#
+# Quick and dirty resampling: just interpolate.
+# This lets us resample arrays of pixels indices (e.g., result of gif decoding).
+# The filter-based resampling methods need conversion to rgb or grayscale.
+# Also, although the results won't look good, people really shouldn't be
+# asking the browser to resample except for special purposes (like the common
+# case of resizing a 1x1 image to make a spacer).
+resample(src: array of byte, sw, sh: int, dw, dh: int) : array of byte
+{
+ if(dbgev)
+ CU->event("IMAGE_RESAMPLE_START", 0);
+ if(src == nil || sw == 0 || sh == 0 || dw == 0 || dh == 0)
+ return src;
+ xfac := real sw / real dw;
+ yfac := real sh / real dh;
+ totpix := dw*dh;
+ dst := array[totpix] of byte;
+ dindex := 0;
+
+ # precompute index in src row corresponding to each index in dst row
+ sindices := array[dw] of int;
+ dx := 0.0;
+ for(x := 0; x < dw; x++) {
+ sx := int dx;
+ dx += xfac;
+ if(sx >= sw)
+ sx = sw-1;
+ sindices[x] = sx;
+ }
+ dy := 0.0;
+ for(y := 0; y < dh; y++) {
+ sy := int dy;
+ dy += yfac;
+ if(sy >= sh)
+ sy = sh-1;
+ soffset := sy * sw;
+ for(x = 0; x < dw; x++)
+ dst[dindex++] = src[soffset + sindices[x]];
+ }
+ if(dbgev)
+ CU->event("IMAGE_RESAMPLE_END", 0);
+ return dst;
+}
+
+################# BIT ###################
+
+getbitmim(is: ref ImageSource) : ref MaskedImage
+{
+ if(dbg)
+ sys->print("img getbitmim: w=%d h=%d len=%d\n",
+ is.width, is.height, len is.bs.data);
+
+ im := getbitimage(is, display, is.bs.data);
+ if(im == nil)
+ imgerror(is, "out of memory");
+ is.i = is.bs.edata; # getbitimage should do this too!
+ is.width = im.r.max.x;
+ is.height = im.r.max.y;
+ return newmi(im);
+}
+
+
+NMATCH: con 3; # shortest match possible
+NCBLOCK: con 6000; # size of compressed blocks
+drawld2chan := array[] of {
+0 => Draw->GREY1,
+1 => Draw->GREY2,
+2 => Draw->GREY4,
+3 => Draw->CMAP8
+};
+
+getbitimage(is: ref ImageSource, disp: ref Display, d: array of byte): ref Image
+{
+ compressed := 0;
+
+ if(len d < 5*12)
+ imgerror(is, "bad bit format");
+
+ if(string d[:11] == "compressed\n"){
+ if(dbg)
+ sys->print("img: bit compressed\n");
+ compressed = 1;
+ d = d[11:];
+ }
+
+ #
+ # distinguish new channel descriptor from old ldepth.
+ # channel descriptors have letters as well as numbers,
+ # while ldepths are a single digit formatted as %-11d
+ #
+ new := 0;
+ for(m := 0; m < 10; m++){
+ if(d[m] != byte ' '){
+ new = 1;
+ break;
+ }
+ }
+ if(d[11] != byte ' ')
+ imgerror(is, "bad bit format");
+ chans: Chans;
+ if(new){
+ s := string d[0:11];
+ chans = Chans.mk(s);
+ if(chans.desc == 0)
+ imgerror(is, sys->sprint("bad channel string %s", s));
+ }else{
+ ld := int( d[10] - byte '0' );
+ if(ld < 0 || ld > 3)
+ imgerror(is, "bad bit ldepth");
+ chans = drawld2chan[ld];
+ }
+
+ xmin := int string d[ 1*12 : 2*12 ];
+ ymin := int string d[ 2*12 : 3*12 ];
+ xmax := int string d[ 3*12 : 4*12 ];
+ ymax := int string d[ 4*12 : 5*12 ];
+ if( (xmin > xmax) || (ymin > ymax) )
+ imgerror(is, "bad bit rectangle");
+
+ if(dbg)
+ sys->print("img: bit: chans=%s, xmin=%d, ymin=%d, xmax=%d, ymax=%d\n",
+ chans.text(), xmin, ymin, xmax, ymax);
+
+ r := Rect( (xmin, ymin), (xmax, ymax) );
+ im := disp.newimage(r, chans, 0, D->Black);
+ if(im == nil)
+ return nil;
+
+ if (!compressed){
+ if(!new)
+ for(j:=5*12; j<len d; j++)
+ d[j] ^= byte 16rFF;
+ im.writepixels(im.r, d[5*12:]);
+ return im;
+ }
+
+ # see /libdraw/readimage.c, /libdraw/creadimage.c, and
+ # /libmemdraw/cload.c for reference implementation
+ # of bit compression
+
+ bpl := D->bytesperline(r, im.depth);
+ a := array[(ymax-ymin)*bpl] of byte;
+ ai := 0; #index into uncompressed data array a
+ di := 5*12; #index into compressed data
+ while(ymin < ymax){
+ y := int string d[ di : di + 1*12 ];
+ n := int string d[ di + 1*12 : di + 2*12 ];
+ di += 2*12;
+
+ if (y <= ymin || ymax < y)
+ imgerror(is, "bad compressed bit y-max");
+ if (n <= 0 || NCBLOCK < n)
+ imgerror(is, "bad compressed bit count");
+
+ # no input-stream error checking :-(
+ u := di;
+ while(di < u+n){
+ c := int d[di++];
+ if (c >= 128){
+ # copy as is
+ cnt := c-128 + 1;
+
+ # check for overrun of index di within d?
+
+ a[ai:] = d[di:di+cnt];
+ if(!new)
+ for(j:=0; j<cnt; j++)
+ a[ai+j] ^= byte 16rFF;
+ di += cnt;
+ ai += cnt;
+ }
+ else {
+ # copy a run/match
+ offs := int(d[di++]) + ((c&3)<<8) + 1;
+ cnt := (c>>2) + NMATCH;
+
+ # simply: a[ai:ai+cnt] = a[ai-offs:ai-offs+cnt];
+ for(i:=0; i<cnt; i++)
+ a[ai+i] = a[ai-offs+i];
+ ai += cnt;
+ }
+ }
+ ymin = y;
+ }
+ im.writepixels(im.r, a);
+ return im;
+}
+
+################# PNG ###################
+
+Rawimage: adt {
+ r: Draw->Rect;
+ cmap: array of byte;
+ transp: int; # transparency flag (only for nchans=1)
+ trindex: byte; # transparency index
+ nchans: int;
+ chans: array of array of byte;
+ chandesc:int;
+
+ fields: int; # defined by format
+};
+
+Chunk: adt {
+ size : int;
+ typ: string;
+ crc_state: ref CRCstate;
+};
+
+Png: adt {
+ depth: int;
+ filterbpp: int;
+ colortype: int;
+ compressionmethod: int;
+ filtermethod: int;
+ interlacemethod: int;
+ # tRNS
+ PLTEsize: int;
+ tRNS: array of byte;
+ # state for managing unpacking
+ alpha: int;
+ done: int;
+ error: string;
+ row, rowstep, colstart, colstep: int;
+ phase: int;
+ phasecols: int;
+ phaserows: int;
+ rowsize: int;
+ rowbytessofar: int;
+ thisrow: array of byte;
+ lastrow: array of byte;
+};
+
+# currently do not support transparency
+# hence no mask is set
+#
+# need to re-jig this code
+# for example there is no point in mapping up a 2 or 4 bit greyscale image
+# to 8 bit luminance to then remap it to the inferno palette when
+# the draw device will do that for us anyway!
+
+getpngmim(is: ref ImageSource) : ref MaskedImage
+{
+ chunk := ref Chunk;
+ png := ref Png;
+ raw := ref Rawimage;
+
+ chunk.crc_state = crc->init(0, int 16rffffffff);
+# Check it's a PNG
+ if (!png_signature(is))
+ imgerror(is, "PNG not a PNG");
+# Get the IHDR
+ if (!png_chunk_header(is, chunk))
+ imgerror(is, "PNG duff header");
+ if (chunk.typ != "IHDR")
+ imgerror(is, "PNG IHDR must come first");
+ if (chunk.size != 13)
+ imgerror(is, "PNG IHDR wrong size");
+ raw.r.max.x = png_int(is, chunk.crc_state);
+ if (raw.r.max.x <= 0)
+ imgerror(is, "PNG invalid width");
+ raw.r.max.y = png_int(is, chunk.crc_state);
+ if (raw.r.max.y <= 0)
+ imgerror(is, "PNG invalid height");
+ png.depth = png_byte(is, chunk.crc_state);
+ case png.depth {
+ 1 or 2 or 4 or 8 or 16 =>
+ ;
+ * =>
+ imgerror(is, "PNG invalid depth");
+ }
+ png.colortype = png_byte(is, chunk.crc_state);
+
+ okcombo : int;
+
+ case png.colortype {
+ 0 =>
+ okcombo = 1;
+ raw.nchans = 1;
+ raw.chandesc = CY;
+ png.alpha = 0;
+ 2 =>
+ okcombo = (png.depth == 8 || png.depth == 16);
+ raw.nchans = 3;
+ raw.chandesc = CRGB;
+ png.alpha = 0;
+ 3 =>
+ okcombo = (png.depth != 16);
+ raw.nchans = 1;
+ raw.chandesc = CRGB1;
+ png.alpha = 0;
+ 4 =>
+ okcombo = (png.depth == 8 || png.depth == 16);
+ raw.nchans = 1;
+ raw.chandesc = CY;
+ png.alpha = 1;
+ 6 =>
+ okcombo = (png.depth == 8 || png.depth == 16);
+ raw.nchans = 3;
+ raw.chandesc = CRGB;
+ png.alpha = 1;
+ * =>
+ imgerror(is, "PNG invalid colortype");
+ }
+ if (!okcombo)
+ imgerror(is, "PNG invalid depth/colortype combination");
+ png.compressionmethod = png_byte(is, chunk.crc_state);
+ if (png.compressionmethod != 0)
+ imgerror(is, "PNG invalid compression method " + string png.compressionmethod);
+ png.filtermethod = png_byte(is, chunk.crc_state);
+ if (png.filtermethod != 0)
+ imgerror(is, "PNG invalid filter method");
+ png.interlacemethod = png_byte(is, chunk.crc_state);
+ if (png.interlacemethod != 0 && png.interlacemethod != 1)
+ imgerror(is, "PNG invalid interlace method");
+# sys->print("width %d height %d depth %d colortype %d interlace %d\n",
+# raw.r.max.x, raw.r.max.y, png.depth, png.colortype, png.interlacemethod);
+ if (!png_crc_and_check(is, chunk))
+ imgerror(is, "PNG invalid CRC");
+# Stash some detail in raw
+ raw.r.min = Point(0, 0);
+ raw.transp = 0;
+ raw.chans = array[raw.nchans] of array of byte;
+ {
+ for (r:= 0; r < raw.nchans; r++)
+ raw.chans[r] = array[raw.r.max.x * raw.r.max.y] of byte;
+ }
+# Get the next chunk
+ seenPLTE := 0;
+ seenIDAT := 0;
+ seenLastIDAT := 0;
+ inflateFinished := 0;
+ seenIEND := 0;
+ seentRNS := 0;
+ rq: chan of ref Filter->Rq;
+
+ png.error = nil;
+ rq = nil;
+ while (png.error == nil) {
+ if (!png_chunk_header(is, chunk)) {
+ if (!seenIEND)
+ png.error = "duff header";
+ break;
+ }
+ if (seenIEND) {
+ png.error = "rubbish at eof";
+ break;
+ }
+ case (chunk.typ) {
+ "IEND" =>
+ seenIEND = 1;
+ "PLTE" =>
+ if (seenPLTE) {
+ png.error = "too many PLTEs";
+ break;
+ }
+ if (seentRNS) {
+ png.error = "tRNS before PLTE";
+ break;
+ }
+ if (seenIDAT) {
+ png.error = "PLTE too late";
+ break;
+ }
+ if (chunk.size % 3 || chunk.size < 1 * 3 || chunk.size > 256 * 3) {
+ png.error = "PLTE strange size";
+ break;
+ }
+ if (png.colortype == 0 || png.colortype == 4) {
+ png.error = "superfluous PLTE";
+ break;
+ }
+ raw.cmap = array[256 * 3] of byte;
+ png.PLTEsize = chunk.size / 3;
+ if (!png_bytes(is, chunk.crc_state, raw.cmap, chunk.size)) {
+ png.error = "eof in PLTE";
+ break;
+ }
+# {
+# x: int;
+# sys->print("Palette:\n");
+# for (x = 0; x < chunk.size; x += 3)
+# sys->print("%3d: (%3d, %3d, %3d)\n",
+# x / 3, int raw.cmap[x], int raw.cmap[x + 1], int raw.cmap[x + 2]);
+# }
+ seenPLTE = 1;
+ "tRNS" =>
+ if (seenIDAT) {
+ png.error = "tRNS too late";
+ break;
+ }
+ case png.colortype {
+ 0 =>
+ if (chunk.size != 2) {
+ png.error = "tRNS wrong size";
+ break;
+ }
+ level := png_ushort(is, chunk.crc_state);
+ if (level < 0) {
+ png.error = "eof in tRNS";
+ break;
+ }
+ if (png.depth != 16) {
+ raw.transp = 1;
+ raw.trindex = byte level;
+ }
+ 2 =>
+ # a legitimate coding, but we can't use the information
+ if (!png_skip_bytes(is, chunk.crc_state, chunk.size))
+ png.error = "eof in skipped tRNS chunk";
+ break;
+ 3 =>
+ if (!seenPLTE) {
+ png.error = "tRNS too early";
+ break;
+ }
+ if (chunk.size > png.PLTEsize) {
+ png.error = "tRNS too big";
+ break;
+ }
+ png.tRNS = array[png.PLTEsize] of byte;
+ for (x := chunk.size; x < png.PLTEsize; x++)
+ png.tRNS[x] = byte 255;
+ if (!png_bytes(is, chunk.crc_state, png.tRNS, chunk.size)) {
+ png.error = "eof in tRNS";
+ break;
+ }
+# {
+# sys->print("tRNS:\n");
+# for (x = 0; x < chunk.size; x++)
+# sys->print("%3d: (%3d)\n", x, int png.tRNS[x]);
+# }
+ if (png.error == nil) {
+ # analyse the tRNS chunk to see if it contains a single transparent index
+ # translucent entries are treated as opaque
+ for (x = 0; x < chunk.size; x++)
+ if (png.tRNS[x] == byte 0) {
+ raw.trindex = byte x;
+ if (raw.transp) {
+ raw.transp = 0;
+ break;
+ }
+ raw.transp = 1;
+ }
+# if (raw.transp)
+# sys->print("selected index %d\n", int raw.trindex);
+ }
+ 4 or 6 =>
+ png.error = "tRNS invalid when alpha present";
+ }
+ seentRNS = 1;
+ "IDAT" =>
+ if (seenLastIDAT) {
+ png.error = "non contiguous IDATs";
+ break;
+ }
+ if (inflateFinished) {
+ png.error = "too many IDATs";
+ break;
+ }
+ remaining := 0;
+ if (!seenIDAT) {
+ # open channel to inflate filter
+ if (!processdatainit(png, raw))
+ break;
+ rq = inflate->start(nil);
+ png_skip_bytes(is, chunk.crc_state, 2);
+ remaining = chunk.size - 2;
+ }
+ else
+ remaining = chunk.size;
+ while (remaining && png.error == nil) {
+ pick m := <- rq {
+ Fill =>
+# sys->print("Fill(%d) remaining %d\n", len m.buf, remaining);
+ toget := len m.buf;
+ if (toget > remaining)
+ toget = remaining;
+ if (!png_bytes(is, chunk.crc_state, m.buf, toget)) {
+ m.reply <-= -1;
+ png.error = "eof during IDAT";
+ break;
+ }
+ m.reply <-= toget;
+ remaining -= toget;
+ Result =>
+# sys->print("Result(%d)\n", len m.buf);
+ m.reply <-= 0;
+ processdata(png, raw, m.buf);
+ Info =>
+# sys->print("Info(%s)\n", m.msg);
+ Finished =>
+ inflateFinished = 1;
+# sys->print("Finished\n");
+ Error =>
+ imgerror(is, "PNG inflate error\n");
+ }
+ }
+ seenIDAT = 1;
+ * =>
+ # skip the blighter
+ if (!png_skip_bytes(is, chunk.crc_state, chunk.size))
+ png.error = "eof in skipped chunk";
+ }
+ if (png.error != nil)
+ break;
+ if (!png_crc_and_check(is, chunk))
+ imgerror(is, "PNG invalid CRC");
+ if (chunk.typ != "IDAT" && seenIDAT)
+ seenLastIDAT = 1;
+ }
+ # can only get here if IEND was last chunk, or png.error set
+
+ if (png.error == nil && !seenIDAT) {
+ png.error = "no IDAT!";
+ inflateFinished = 1;
+ }
+ while (rq != nil && !inflateFinished) {
+ pick m := <-rq {
+ Fill =>
+# sys->print("Fill(%d)\n", len m.buf);
+ png.error = "eof in zlib stream";
+ m.reply <-= -1;
+ inflateFinished = 1;
+ Result =>
+# sys->print("Result(%d)\n", len m.buf);
+ if (png.error != nil) {
+ m.reply <-= -1;
+ inflateFinished = 1;
+ }
+ else {
+ m.reply <-= 0;
+ processdata(png, raw, m.buf);
+ }
+ Info =>
+# sys->print("Info(%s)\n", m.msg);
+ Finished =>
+# sys->print("Finished\n");
+ inflateFinished = 1;
+ break;
+ Error =>
+ png.error = "inflate error\n";
+ inflateFinished = 1;
+ }
+
+ }
+ if (png.error == nil && !png.done)
+ png.error = "insufficient data";
+ if (png.error != nil)
+ imgerror(is, "PNG " + png.error);
+
+ width := raw.r.dx();
+ height := raw.r.dy();
+ case raw.chandesc {
+ CY =>
+ remapgrey(raw.chans[0], width, height);
+ CRGB =>
+ remaprgb(raw.chans, width, height, CRGB);
+ CRGB1 =>
+ remap1(raw.chans[0], width, height, raw.cmap);
+ }
+ pixels := raw.chans[0];
+ is.origw = width;
+ is.origh = height;
+ setdims(is);
+ if(is.width != is.origw || is.height != is.origh)
+ pixels = resample(pixels, is.origw, is.origh, is.width, is.height);
+ im := newimage(is, is.width, is.height);
+ im.writepixels(im.r, pixels);
+ mi := newmi(im);
+# mi.mask = display.newimage(im.r, D->GREY1, 0, D->Black);
+ return mi;
+}
+
+phase2stepping(phase: int): (int, int, int, int)
+{
+ case phase {
+ 0 =>
+ return (0, 1, 0, 1);
+ 1 =>
+ return (0, 8, 0, 8);
+ 2 =>
+ return (0, 8, 4, 8);
+ 3 =>
+ return (4, 8, 0, 4);
+ 4 =>
+ return (0, 4, 2, 4);
+ 5 =>
+ return (2, 4, 0, 2);
+ 6 =>
+ return (0, 2, 1, 2);
+ 7 =>
+ return (1, 2, 0, 1);
+ * =>
+ return (-1, -1, -1, -1);
+ }
+}
+
+processdatainitphase(png: ref Png, raw: ref Rawimage)
+{
+ (png.row, png.rowstep, png.colstart, png.colstep) = phase2stepping(png.phase);
+ if (raw.r.max.x > png.colstart)
+ png.phasecols = (raw.r.max.x - png.colstart + png.colstep - 1) / png.colstep;
+ else
+ png.phasecols = 0;
+ if (raw.r.max.y > png.row)
+ png.phaserows = (raw.r.max.y - png.row + png.rowstep - 1) / png.rowstep;
+ else
+ png.phaserows = 0;
+ png.rowsize = png.phasecols * (raw.nchans + png.alpha) * png.depth;
+ png.rowsize = (png.rowsize + 7) / 8;
+ png.rowsize++; # for the filter byte
+ png.rowbytessofar = 0;
+ png.thisrow = array[png.rowsize] of byte;
+ png.lastrow = array[png.rowsize] of byte;
+# sys->print("init phase %d: r (%d, %d, %d) c (%d, %d, %d) (%d)\n",
+# png.phase, png.row, png.rowstep, png.phaserows,
+# png.colstart, png.colstep, png.phasecols, png.rowsize);
+}
+
+processdatainit(png: ref Png, raw: ref Rawimage): int
+{
+ if (raw.nchans != 1&& raw.nchans != 3) {
+ png.error = "only 1 or 3 channels supported";
+ return 0;
+ }
+# if (png.interlacemethod != 0) {
+# png.error = "only progressive supported";
+# return 0;
+# }
+ if (png.colortype == 3 && raw.cmap == nil) {
+ png.error = "PLTE chunk missing";
+ return 0;
+ }
+ png.done = 0;
+ png.filterbpp = (png.depth * (raw.nchans + png.alpha) + 7) / 8;
+ png.phase = png.interlacemethod;
+
+ processdatainitphase(png, raw);
+
+ return 1;
+}
+
+upconvert(out: array of byte, outstride: int, in: array of byte, pixels: int, bpp: int)
+{
+ b: byte;
+ bits := pixels * bpp;
+ lim := bits / 8;
+ mask := byte ((1 << bpp) - 1);
+ outx := 0;
+ inx := 0;
+ for (x := 0; x < lim; x++) {
+ b = in[inx];
+ for (s := 8 - bpp; s >= 0; s -= bpp) {
+ pixel := (b >> s) & mask;
+ ucp := pixel;
+ for (y := bpp; y < 8; y += bpp)
+ ucp |= pixel << y;
+ out[outx] = ucp;
+ outx += outstride;
+ }
+ inx++;
+ }
+ residue := (bits % 8) / bpp;
+ if (residue) {
+ b = in[inx];
+ for (s := 8 - bpp; s >= 0; s -= bpp) {
+ pixel := (b >> s) & mask;
+ ucp := pixel;
+ for (y := bpp; y < 8; y += bpp)
+ ucp |= pixel << y;
+ out[outx] = ucp;
+ outx += outstride;
+ if (--residue <= 0)
+ break;
+ }
+ }
+}
+
+# expand (1 or 2 or 4) bit to 8 bit without scaling (for palletized stuff)
+
+expand(out: array of byte, outstride: int, in: array of byte, pixels: int, bpp: int)
+{
+ b: byte;
+ bits := pixels * bpp;
+ lim := bits / 8;
+ mask := byte ((1 << bpp) - 1);
+ outx := 0;
+ inx := 0;
+ for (x := 0; x < lim; x++) {
+ b = in[inx];
+ for (s := 8 - bpp; s >= 0; s -= bpp) {
+ out[outx] = (b >> s) & mask;
+ outx += outstride;
+ }
+ inx++;
+ }
+ residue := (bits % 8) / bpp;
+ if (residue) {
+ b = in[inx];
+ for (s := 8 - bpp; s >= 0; s -= bpp) {
+ out[outx] = (b >> s) & mask;
+ outx += outstride;
+ if (--residue <= 0)
+ break;
+ }
+ }
+}
+
+copybytes(out: array of byte, outstride: int, in: array of byte, instride: int, pixels: int)
+{
+ inx := 0;
+ outx := 0;
+ for (x := 0; x < pixels; x++) {
+ out[outx] = in[inx];
+ inx += instride;
+ outx += outstride;
+ }
+}
+
+outputrow(png: ref Png, raw: ref Rawimage, row: array of byte)
+{
+ offset := png.row * raw.r.max.x;
+ case raw.nchans {
+ 1 =>
+ case (png.depth) {
+ * =>
+ png.error = "depth not supported";
+ return;
+ 1 or 2 or 4 =>
+ if (raw.chandesc == CRGB1)
+ expand(raw.chans[0][offset + png.colstart:], png.colstep, row, png.phasecols, png.depth);
+ else
+ upconvert(raw.chans[0][offset + png.colstart:], png.colstep, row, png.phasecols, png.depth);
+ 8 or 16 =>
+ # might have an Alpha channel to ignore!
+ stride := (png.alpha + 1) * png.depth / 8;
+ copybytes(raw.chans[0][offset + png.colstart:], png.colstep, row, stride, png.phasecols);
+ }
+ 3 =>
+ case (png.depth) {
+ * =>
+ png.error = "depth not supported (2)";
+ return;
+ 8 or 16 =>
+ # split rgb into three channels
+ bytespc := png.depth / 8;
+ stride := (3 + png.alpha) * bytespc;
+ copybytes(raw.chans[0][offset + png.colstart:], png.colstep, row, stride, png.phasecols);
+ copybytes(raw.chans[1][offset + png.colstart:], png.colstep, row[bytespc:], stride, png.phasecols);
+ copybytes(raw.chans[2][offset + png.colstart:], png.colstep, row[bytespc * 2:], stride, png.phasecols);
+ }
+ }
+}
+
+filtersub(png: ref Png)
+{
+ subx := 1;
+ for (x := int png.filterbpp + 1; x < png.rowsize; x++) {
+ png.thisrow[x] += png.thisrow[subx];
+ subx++;
+ }
+}
+
+filterup(png: ref Png)
+{
+ if (png.row == 0)
+ return;
+ for (x := 1; x < png.rowsize; x++)
+ png.thisrow[x] += png.lastrow[x];
+}
+
+filteraverage(png: ref Png)
+{
+ for (x := 1; x < png.rowsize; x++) {
+ a: int;
+ if (x > png.filterbpp)
+ a = int png.thisrow[x - png.filterbpp];
+ else
+ a = 0;
+ if (png.row != 0)
+ a += int png.lastrow[x];
+ png.thisrow[x] += byte (a / 2);
+ }
+}
+
+filterpaeth(png: ref Png)
+{
+ a, b, c: byte;
+ p, pa, pb, pc: int;
+ for (x := 1; x < png.rowsize; x++) {
+ if (x > png.filterbpp)
+ a = png.thisrow[x - png.filterbpp];
+ else
+ a = byte 0;
+ if (png.row == 0) {
+ b = byte 0;
+ c = byte 0;
+ } else {
+ b = png.lastrow[x];
+ if (x > png.filterbpp)
+ c = png.lastrow[x - png.filterbpp];
+ else
+ c = byte 0;
+ }
+ p = int a + int b - int c;
+ pa = p - int a;
+ if (pa < 0)
+ pa = -pa;
+ pb = p - int b;
+ if (pb < 0)
+ pb = -pb;
+ pc = p - int c;
+ if (pc < 0)
+ pc = -pc;
+ if (pa <= pb && pa <= pc)
+ png.thisrow[x] += a;
+ else if (pb <= pc)
+ png.thisrow[x] += b;
+ else
+ png.thisrow[x] += c;
+ }
+}
+
+phaseendcheck(png: ref Png, raw: ref Rawimage): int
+{
+ if (png.row >= raw.r.max.y || png.rowsize <= 1) {
+ # this phase is over
+ if (png.phase == 0) {
+ png.done = 1;
+ }
+ else {
+ png.phase++;
+ if (png.phase > 7)
+ png.done = 1;
+ else
+ processdatainitphase(png, raw);
+ }
+ return 1;
+ }
+ return 0;
+}
+
+processdata(png: ref Png, raw: ref Rawimage, buf: array of byte)
+{
+#sys->print("processdata(%d)\n", len buf);
+ if (png.error != nil)
+ return;
+ i := 0;
+ while (i < len buf) {
+ if (png.done) {
+ png.error = "too much data";
+ return;
+ }
+ if (phaseendcheck(png, raw))
+ continue;
+ tocopy := (png.rowsize - png.rowbytessofar);
+ if (tocopy > (len buf - i))
+ tocopy = len buf - i;
+ png.thisrow[png.rowbytessofar :] = buf[i : i + tocopy];
+ i += tocopy;
+ png.rowbytessofar += tocopy;
+ if (png.rowbytessofar >= png.rowsize) {
+ # a new row has arrived
+ # apply filter here
+#sys->print("phase %d row %d\n", png.phase, png.row);
+ case int png.thisrow[0] {
+ 0 =>
+ ;
+ 1 =>
+ filtersub(png);
+ 2 =>
+ filterup(png);
+ 3 =>
+ filteraverage(png);
+ 4 =>
+ filterpaeth(png);
+ * =>
+# sys->print("implement filter method %d\n", int png.thisrow[0]);
+ png.error = "filter method unsupported";
+ return;
+ }
+ # output row
+ if (png.row >= raw.r.max.y) {
+ png.error = "too much data";
+ return;
+ }
+ outputrow(png, raw, png.thisrow[1 :]);
+ png.row += png.rowstep;
+ save := png.lastrow;
+ png.lastrow = png.thisrow;
+ png.thisrow = save;
+ png.rowbytessofar = 0;
+ }
+ }
+ phaseendcheck(png, raw);
+}
+
+png_signature(is: ref ImageSource): int
+{
+ sig := array[8] of { byte 137, byte 80, byte 78, byte 71, byte 13, byte 10, byte 26, byte 10 };
+ x: int;
+ for (x = 0; x < 8; x++)
+ if (png_getb(is) != int sig[x])
+ return 0;
+ return 1;
+}
+
+png_getb(is: ref ImageSource) : int
+{
+ if(is.i >= len is.bs.data)
+ return -1;
+ return int is.bs.data[is.i++];
+}
+
+png_bytes(is: ref ImageSource, crc_state: ref CRCstate, buf: array of byte, n: int): int
+{
+ if (is.i +n > len is.bs.data) {
+ is.i = len is.bs.data;
+ return 0;
+ }
+ if (buf == nil) {
+ is.i += n;
+ return 1;
+ }
+ buf[0:] = is.bs.data[is.i:is.i+n];
+ is.i += n;
+ if (crc_state != nil)
+ crc->crc(crc_state, buf, n);
+ return 1;
+}
+
+png_skip_bytes(is: ref ImageSource, crc_state: ref CRCstate, n: int): int
+{
+ buf := array[1024] of byte;
+ while (n) {
+ thistime: int = 1024;
+ if (thistime > n)
+ thistime = n;
+ if (!png_bytes(is, crc_state, buf, thistime))
+ return 0;
+ n -= thistime;
+ }
+ return 1;
+}
+
+png_get_4(is: ref ImageSource, crc_state: ref CRCstate, signed: int): (int, int)
+{
+ buf := array[4] of byte;
+ if (!png_bytes(is, crc_state, buf, 4))
+ return (0, 0);
+ if (signed && int buf[0] & 16r80)
+ return (0, 0);
+ r:int = (int buf[0] << 24) | (int buf[1] << 16) | (int buf[2] << 8) | (int buf[3]);
+# sys->print("got int %d\n", r);
+ return (1, r);
+}
+
+png_int(is: ref ImageSource, crc_state: ref CRCstate): int
+{
+ ok, r: int;
+ (ok, r) = png_get_4(is, crc_state, 1);
+ if (ok)
+ return r;
+ return -1;
+}
+
+png_ushort(is: ref ImageSource, crc_state: ref CRCstate): int
+{
+ buf := array[2] of byte;
+ if (!png_bytes(is, crc_state, buf, 2))
+ return -1;
+ return (int buf[0] << 8) | int buf[1];
+}
+
+png_crc_and_check(is: ref ImageSource, chunk: ref Chunk): int
+{
+ crc, ok: int;
+ (ok, crc) = png_get_4(is, nil, 0);
+ if (!ok)
+ return 0;
+# sys->print("crc: computed %.8ux expected %.8ux\n", chunk.crc_state.crc, crc);
+ if (chunk.crc_state.crc != crc)
+ return 1;
+ return 1;
+}
+
+png_byte(is: ref ImageSource, crc_state: ref CRCstate): int
+{
+ buf := array[1] of byte;
+ if (!png_bytes(is, crc_state, buf, 1))
+ return -1;
+# sys->print("got byte %d\n", int buf[0]);
+ return int buf[0];
+}
+
+png_type(is: ref ImageSource, crc_state: ref CRCstate): string
+{
+ x: int;
+ buf := array[4] of byte;
+ if (!png_bytes(is, crc_state, buf, 4))
+ return nil;
+ for (x = 0; x < 4; x++) {
+ c: int;
+ c = int buf[x];
+ if ((c < 65 || c > 90 && c < 97) || c > 122)
+ return nil;
+ }
+ return string buf;
+}
+
+png_chunk_header(is: ref ImageSource, chunk: ref Chunk): int
+{
+ chunk.size = png_int(is, nil);
+ if (chunk.size < 0)
+ return 0;
+ crc->reset(chunk.crc_state);
+ chunk.typ = png_type(is, chunk.crc_state);
+ if (chunk.typ == nil)
+ return 0;
+# sys->print("%s(%d)\n", chunk.typ, chunk.size);
+ return 1;
+}
diff --git a/appl/charon/img.m b/appl/charon/img.m
new file mode 100644
index 00000000..15a80a16
--- /dev/null
+++ b/appl/charon/img.m
@@ -0,0 +1,115 @@
+Img: module {
+ PATH : con "/dis/charon/img.dis";
+
+ Mimerror, Mimnone, Mimpartial, Mimdone: con iota + 1;
+
+ # Getmim returns image and possible mask;
+ # the int returned is either Mimnone, Mimpartial or Mimdone, depending on
+ # how much of returned mim is filled in.
+ # if the image is animated, successive calls to getmim return subsequent frames.
+ # Errors are indicated by returning Mimerror, with the err field non empty.
+ # Should call free() when don't intend to call getmim any more
+ ImageSource: adt
+ {
+ width: int;
+ height: int;
+ origw: int;
+ origh: int;
+ mtype: int;
+ i: int;
+ curframe: int;
+ bs: ref CharonUtils->ByteSource;
+ gstate: ref Gifstate;
+ jstate: ref Jpegstate;
+ err: string;
+
+ new: fn(bs: ref CharonUtils->ByteSource, w, h: int) : ref ImageSource;
+ getmim: fn(is: self ref ImageSource) : (int, ref CharonUtils->MaskedImage);
+ free: fn(is: self ref ImageSource);
+ };
+
+ # Following are private to implementation
+ Jpegstate: adt
+ {
+ # variables in i/o routines
+ sr: int; # shift register, right aligned
+ cnt: int; # # bits in right part of sr
+
+ Nf: int;
+ comp: array of Framecomp;
+ mode: byte;
+ X,Y: int;
+ qt: array of array of int; # quantization tables
+ dcht: array of ref Huffman;
+ acht: array of ref Huffman;
+ Ns: int;
+ scomp: array of Scancomp;
+ Ss: int;
+ Se: int;
+ Ah: int;
+ Al: int;
+ ri: int;
+ nseg: int;
+ nblock: array of int;
+
+ # progressive scan
+ dccoeff: array of array of int;
+ accoeff: array of array of array of int; # only need 8 bits plus quantization
+ nacross: int;
+ ndown: int;
+ Hmax: int;
+ Vmax: int;
+ };
+
+ Huffman: adt
+ {
+ bits: array of int;
+ size: array of int;
+ code: array of int;
+ val: array of int;
+ mincode: array of int;
+ maxcode: array of int;
+ valptr: array of int;
+ # fast lookup
+ value: array of int;
+ shift: array of int;
+ };
+
+ Framecomp: adt # Frame component specifier from SOF marker
+ {
+ C: int;
+ H: int;
+ V: int;
+ Tq: int;
+ };
+
+ Scancomp: adt # Frame component specifier from SOF marker
+ {
+ C: int;
+ tdc: int;
+ tac: int;
+ };
+
+ Gifstate: adt
+ {
+ fields: int;
+ bgrnd: int;
+ aspect: int;
+ flags: int;
+ delay: int;
+ trindex: byte;
+ tbl: array of GifEntry;
+ globalcmap: array of byte;
+ cmap: array of byte;
+ };
+
+ GifEntry: adt
+ {
+ prefix: int;
+ exten: int;
+ };
+
+ init: fn(cu: CharonUtils);
+ supported: fn(mtype: int) : int;
+ closest_rgbpix: fn(r, g, b: int) : int;
+};
diff --git a/appl/charon/jscript.b b/appl/charon/jscript.b
new file mode 100644
index 00000000..ad35f295
--- /dev/null
+++ b/appl/charon/jscript.b
@@ -0,0 +1,3025 @@
+implement JScript;
+
+include "common.m";
+include "ecmascript.m";
+
+ES: Ecmascript;
+ Exec, Obj, Call, Prop, Val, Ref, RefVal, Builtin, ReadOnly: import ES;
+me: ESHostobj;
+
+# local copies from CU
+sys: Sys;
+CU: CharonUtils;
+
+D: Draw;
+S: String;
+T: StringIntTab;
+C: Ctype;
+B: Build;
+ Item: import B;
+CH: Charon;
+L: Layout;
+ Frame, Control: import L;
+U: Url;
+ Parsedurl: import U;
+E: Events;
+ Event, ScriptEvent: import E;
+
+G : Gui;
+
+JScript: module
+{
+ # First, conform to Script interface
+ defaultStatus: string;
+ jevchan: chan of ref ScriptEvent;
+ versions: array of string;
+
+ init: fn(cu: CharonUtils): string;
+ frametreechanged: fn(top: ref Layout->Frame);
+ havenewdoc: fn(f: ref Layout->Frame);
+ evalscript: fn(f: ref Layout->Frame, s: string) : (string, string, string);
+ framedone: fn(f : ref Layout->Frame, hasscripts : int);
+
+ #
+ # implement the host object interface, too
+ #
+ get: fn(ex: ref Exec, o: ref Obj, property: string): ref Val;
+ put: fn(ex: ref Exec, o: ref Obj, property: string, val: ref Val);
+ canput: fn(ex: ref Exec, o: ref Obj, property: string): ref Val;
+ hasproperty: fn(ex: ref Exec, o: ref Obj, property: string): ref Val;
+ delete: fn(ex: ref Exec, o: ref Obj, property: string);
+ defaultval: fn(ex: ref Exec, o: ref Obj, tyhint: int): ref Val;
+ call: fn(ex: ref Exec, func, this: ref Obj, args: array of ref Val, eval: int): ref Ref;
+ construct: fn(ex: ref Exec, func: ref Obj, args: array of ref Val): ref Obj;
+};
+
+versions = array [] of {
+ "javascript",
+ "javascript1.0",
+ "javascript1.1",
+ "javascript1.2",
+ "javascript1.3",
+};
+
+# Call init() before calling anything else.
+# It makes a global object (a Window) for the browser's top level frame,
+# and also puts a navaigator object in it. The document can't be filled
+# in until the first document gets loaded.
+#
+# This module keeps track of the correspondence between the Script Window
+# objects and the corresponding Layout Frames, using the ScriptWin adt to
+# build a tree mirroring the structure. The root of the tree never changes
+# after first being set (but changing its document essentially resets all of the
+# other data structures). After charon has built its top-level window, it
+# should call frametreechanged(top).
+#
+# When a frame gets reset or gets some frame children added, call frametreechanged(f),
+# where f is the changed frame. This module will update its ScriptWin tree as needed.
+#
+# Whenever the document in a (Layout) Frame f changes, call havenewdoc(f)
+# after the frame's doc field is set. This causes this module to initialize the document
+# object in the corresponding window object.
+#
+# From within the build process, call evalscript(containing frame, script) to evaluate
+# global code fragments as needed. The return value is two strings: a possible error
+# description, and HTML that is the result of a document.write (so it should be spliced
+# in at the point where the <SCRIPT> element occurred). evalscript() also handles
+# the job of synching up the Docinfo data (on the Build side) with the document object
+# (on the Script side).
+#
+# For use by other external routines, the xfertoscriptobjs() and xferfromscriptobjs()
+# functions that do the just-described synch-up are available for external callers.
+
+# Adt for keeping track of correspondence between Image objects
+# and their corresponding Build items.
+ScriptImg: adt
+{
+ item: ref Build->Item.Iimage;
+ obj: ref Obj;
+};
+
+ScriptForm : adt {
+ form : ref Build->Form;
+ obj : ref Obj;
+ ix : int; # index in document.forms array
+ fields : list of (ref Build->Formfield, ref Obj);
+};
+
+# Adt for keeping track of correspondence between Window
+# objects and their corresponding Frames.
+
+ScriptWin: adt
+{
+ frame: ref Layout->Frame;
+ ex: ref Exec; # ex.global is frame's Window obj
+ locobj: ref Obj; # Location object for window
+ val : ref Val; # val of ex.global - used to side-effect entry in parent.frames[]
+
+ parent: ref ScriptWin;
+ forms: list of ref ScriptForm; # no guaranteed order
+ kids: cyclic list of ref ScriptWin;
+ imgs: list of ref ScriptImg;
+ newloc: string; # url to go to after script finishes executing
+ newloctarg: string; # target frame for newloc
+ docwriteout: string;
+ inbuild: int;
+ active: int; # frame or sub-frame has scripts
+ error: int;
+ imgrelocs: list of ref Obj;
+
+ new: fn(f: ref Layout->Frame, ex: ref Exec, loc: ref Obj, par: ref ScriptWin) : ref ScriptWin;
+ addkid: fn(sw: self ref ScriptWin, f: ref Layout->Frame);
+ dummy: fn(): ref ScriptWin;
+# findbyframe: fn(sw: self ref ScriptWin, f: ref Layout->Frame) : ref ScriptWin;
+ findbyframeid: fn(sw: self ref ScriptWin, fid: int) : ref ScriptWin;
+ findbydoc: fn(sw: self ref ScriptWin, d: ref Build->Docinfo) : ref ScriptWin;
+ findbyobj: fn(sw : self ref ScriptWin, obj : ref Obj) : ref ScriptWin;
+ findbyname: fn(sw : self ref ScriptWin, name : string) : ref ScriptWin;
+};
+
+opener: ref ScriptWin;
+winclose: int;
+
+# Helper adts for initializing objects.
+# Methods go in prototype, properties go in objects
+
+MethSpec: adt
+{
+ name: string;
+ args: array of string;
+};
+
+
+IVundef, IVnull, IVtrue, IVfalse, IVnullstr, IVzero, IVzerostr, IVarray: con iota;
+
+PropSpec: adt
+{
+ name: string;
+ attr: int;
+ initval: int; # one of IVnull, etc.
+};
+
+ObjSpec: adt
+{
+ name: string;
+ methods: array of MethSpec;
+ props: array of PropSpec;
+};
+
+MimeSpec: adt
+{
+ description: string;
+ suffixes: string;
+ ty: string;
+};
+
+# Javascript 1.1 (Netscape 3) client objects
+
+objspecs := array[] of {
+ ObjSpec("Anchor",
+ nil,
+ array[] of {PropSpec
+ ("name", ReadOnly, IVnullstr) }
+ ),
+ ObjSpec("Applet",
+ nil,
+ nil
+ ),
+ ObjSpec("document",
+ array[] of {MethSpec
+ ("close", nil),
+ ("open", array[] of { "mimetype", "replace" }),
+ ("write", array[] of { "string" }),
+ ("writeln", array[] of { "string" }) },
+ array[] of {PropSpec
+ ("alinkColor", 0, IVnullstr),
+ ("anchors", ReadOnly, IVarray),
+ ("applets", ReadOnly, IVarray),
+ ("bgColor", 0, IVnullstr),
+ ("cookie", 0, IVnullstr),
+ ("domain", 0, IVnullstr),
+ ("embeds", ReadOnly, IVarray),
+ ("fgColor", 0, IVnullstr),
+ ("forms", ReadOnly, IVarray),
+ ("images", ReadOnly, IVarray),
+ ("lastModified", ReadOnly, IVnullstr),
+ ("linkColor", 0, IVnullstr),
+ ("links", ReadOnly, IVarray),
+ ("location", 0, IVnullstr),
+ ("plugins", ReadOnly, IVarray),
+ ("referrer", ReadOnly, IVnullstr),
+ ("title", ReadOnly, IVnullstr),
+ ("URL", ReadOnly, IVnullstr),
+ ("vlinkColor", 0, IVnullstr) }
+ ),
+ ObjSpec("Form",
+ array[] of {MethSpec
+ ("reset", nil),
+ ("submit", nil) },
+ array[] of {PropSpec
+ ("action", 0, IVnullstr),
+ ("elements", ReadOnly, IVarray),
+ ("encoding", 0, IVnullstr),
+ ("length", ReadOnly, IVzero),
+ ("method", 0, IVnullstr),
+ ("name", 0, IVnullstr),
+ ("target", 0, IVnullstr) }
+ ),
+ # This is merge of Netscape objects (to save code & data space):
+ # Button, Checkbox, Hidden, Radio, Reset, Select, Text, and Textarea
+ ObjSpec("FormField",
+ array[] of {MethSpec
+ ("blur", nil),
+ ("click", nil),
+ ("focus", nil),
+ ("select", nil) },
+ array[] of {PropSpec
+ ("checked", 0, IVundef),
+ ("defaultChecked", 0, IVundef),
+ ("defaultValue", 0, IVundef),
+ ("form", ReadOnly, IVundef),
+ ("length", 0, IVundef),
+ ("name", 0, IVnullstr),
+ ("options", 0, IVundef),
+ ("type", ReadOnly, IVundef),
+ ("selectedIndex", 0, IVundef),
+ ("value", 0, IVnullstr) }
+ ),
+ ObjSpec("History",
+ array[] of {MethSpec
+ ("back", nil),
+ ("forward", nil),
+ ("go", array[] of { "location-or-delta" }) },
+ array[] of {PropSpec
+ ("current", ReadOnly, IVnullstr),
+ ("length", ReadOnly, IVzero),
+ ("next", ReadOnly, IVnullstr),
+ ("previous", ReadOnly, IVnullstr) }
+ ),
+ ObjSpec("Image",
+ nil,
+ array[] of {PropSpec
+ ("border", ReadOnly, IVzerostr),
+ ("complete", ReadOnly, IVfalse),
+ ("height", ReadOnly, IVzerostr),
+ ("hspace", ReadOnly, IVzerostr),
+ ("lowsrc", 0, IVnullstr),
+ ("name", ReadOnly, IVnullstr),
+ ("src", 0, IVnullstr),
+ ("vspace", ReadOnly, IVzerostr),
+ ("width", ReadOnly, IVzerostr) }
+ ),
+ ObjSpec("Link",
+ nil,
+ array[] of {PropSpec
+ ("hash", 0, IVnullstr),
+ ("host", 0, IVnullstr),
+ ("hostname", 0, IVnullstr),
+ ("href", 0, IVnullstr),
+ ("pathname", 0, IVnullstr),
+ ("port", 0, IVnullstr),
+ ("protocol", 0, IVnullstr),
+ ("search", 0, IVnullstr),
+ ("target", 0, IVnullstr) }
+ ),
+ ObjSpec("Location",
+ array[] of {MethSpec
+ ("reload", array[] of { "forceGet" }),
+ ("replace", array[] of { "URL" }) },
+ array[] of {PropSpec
+ ("hash", 0, IVnullstr),
+ ("host", 0, IVnullstr),
+ ("hostname", 0, IVnullstr),
+ ("href", 0, IVnullstr),
+ ("pathname", 0, IVnullstr),
+ ("port", 0, IVnullstr),
+ ("protocol", 0, IVnullstr),
+ ("search", 0, IVnullstr) }
+ ),
+ ObjSpec("MimeType",
+ nil,
+ array[] of {PropSpec
+ ("description", ReadOnly, IVnullstr),
+ ("enabledPlugin", ReadOnly, IVnull),
+ ("suffixes", ReadOnly, IVnullstr),
+ ("type", ReadOnly, IVnullstr) }
+ ),
+ ObjSpec("Option",
+ nil,
+ array[] of {PropSpec
+ ("defaultSelected", 0, IVfalse),
+ ("index", 0, IVundef),
+ ("selected", 0, IVfalse),
+ ("text", 0, IVnullstr),
+ ("value", 0, IVnullstr) }
+ ),
+ ObjSpec("navigator",
+ array[] of {MethSpec
+ ("javaEnabled", nil),
+ ("plugins.refresh", nil),
+ ("taintEnabled", nil) },
+ array[] of {PropSpec
+ ("appCodeName", ReadOnly, IVnullstr),
+ ("appName", ReadOnly, IVnullstr),
+ ("appVersion", ReadOnly, IVnullstr),
+ ("mimeTypes", ReadOnly, IVarray),
+ ("platform", ReadOnly, IVnullstr),
+ ("plugins", ReadOnly, IVarray),
+ ("userAgent", ReadOnly, IVnullstr) }
+ ),
+ ObjSpec("Plugin",
+ nil,
+ array[] of {PropSpec
+ ("description", 0, IVnullstr),
+ ("filename", 0, IVnullstr),
+ ("length", 0, IVzero),
+ ("name", 0, IVnullstr) }
+ ),
+ ObjSpec("Screen",
+ nil,
+ array[] of {PropSpec
+ ("availHeight", ReadOnly, IVzero),
+ ("availWidth", ReadOnly, IVzero),
+ ("availLeft", ReadOnly, IVzero),
+ ("availTop", ReadOnly, IVzero),
+ ("colorDepth", ReadOnly, IVzero),
+ ("pixelDepth", ReadOnly, IVzero),
+ ("height", ReadOnly, IVzero),
+ ("width", ReadOnly, IVzero) }
+ ),
+ ObjSpec("Window",
+ array[] of {MethSpec
+ ("alert", array[] of { "msg" }),
+ ("blur", nil),
+ ("clearInterval", array[] of { "intervalid" }),
+ ("clearTimeout", array[] of { "timeoutid" }),
+ ("close", nil),
+ ("confirm", array[] of { "msg" }),
+ ("focus", nil),
+ ("moveBy", array[] of { "dx", "dy" }),
+ ("moveTo", array[] of { "x", "y" }),
+ ("open", array[] of { "url", "winname", "winfeatures" }),
+ ("prompt", array[] of { "msg", "inputdflt" }),
+ ("resizeBy", array[] of { "dh", "dw" }),
+ ("resizeTo", array[] of { "width", "height" }),
+ ("scroll", array[] of { "x", "y" }),
+ ("scrollBy", array[] of { "dx", "dy" }),
+ ("scrollTo", array[] of { "x", "y" }),
+ ("setInterval", array[] of { "code", "msec" }),
+ ("setTimeout", array[] of { "expr", "msec" }) },
+ array[] of {PropSpec
+ ("closed", ReadOnly, IVfalse),
+ ("defaultStatus", 0, IVnullstr),
+ ("document", 0, IVnull),
+ ("frames", ReadOnly, IVnull), # array, really
+ ("history", 0, IVnull), # array, really
+ ("length", ReadOnly, IVzero),
+ ("location", 0, IVnullstr),
+# ("Math", ReadOnly, IVnull),
+ ("name", 0, IVnullstr),
+ ("navigator", ReadOnly, IVnull),
+ ("offscreenBuffering", 0, IVnullstr),
+ ("opener", 0, IVnull),
+ ("parent", ReadOnly, IVnull),
+ ("screen", 0, IVnull),
+ ("self", ReadOnly, IVnull),
+ ("status", 0, IVnullstr),
+ ("top", ReadOnly, IVnull),
+ ("window", ReadOnly, IVnull) }
+ )
+};
+
+# Currently supported charon mime types
+mimespecs := array[] of {
+ MimeSpec("HTML",
+ "htm,html",
+ "text/html"
+ ),
+ MimeSpec("Plain text",
+ "txt,text",
+ "text/plain"
+ ),
+ MimeSpec("Gif Image",
+ "gif",
+ "image/gif"
+ ),
+ MimeSpec("Jpeg Image",
+ "jpeg,jpg,jpe",
+ "image/jpeg"
+ ),
+ MimeSpec("X Bitmap Image",
+ "",
+ "image/x-xbitmap"
+ )
+};
+
+# charon's 's' debug flag:
+# 1: basic syntax and runtime errors
+# 2: 'event' logging and DOM actions
+# 3: print parsed code and ops as executed
+# 4: print value of expression statements and abort on runtime errors
+dbg := 0;
+dbgdom := 0;
+
+top: ref ScriptWin;
+createdimages : list of ref Obj;
+nullstrval: ref Val;
+zeroval: ref Val;
+zerostrval: ref Val;
+
+# Call this after charon's main (top) frame has been built
+init(cu: CharonUtils) : string
+{
+ 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;
+ B = cu->B;
+ L = cu->L;
+ E = cu->E;
+ CH = cu->CH;
+ G = cu->G;
+ dbg = int (CU->config).dbg['s'];
+ if (dbg > 1)
+ dbgdom = 1;
+ ES = load Ecmascript Ecmascript->PATH;
+ if(ES == nil)
+ return sys->sprint("could not load module %s: %r", Ecmascript->PATH);
+ err := ES->init();
+ if (err != nil)
+ return sys->sprint("ecmascript error: %s", err);
+
+ me = load ESHostobj SELF;
+ if(me == nil)
+ return sys->sprint("jscript: could not load self as a ESHostobj: %r");
+ if(dbg >= 3) {
+ ES->debug['p'] = 1; # print parsed code
+ ES->debug['e'] = 1; # prinv ops as they are executed
+ if(dbg >= 4) {
+ ES->debug['e'] = 2;
+ ES->debug['v'] = 1; # print value of expression statements
+ ES->debug['r'] = 1; # print and abort if runtime errors
+ }
+ }
+
+ # some constant values, for initialization
+ nullstrval = ES->strval("");
+ zeroval = ES->numval(0.);
+ zerostrval = ES->strval("0");
+ jevchan = chan of ref ScriptEvent;
+ spawn jevhandler();
+ return nil;
+}
+
+doneevent := ScriptEvent(-1,0,0,0,0,0,0,0,0,nil,nil,0);
+
+# Used to receive and act upon ScriptEvents from main charon thread.
+# Want to queue the events up, so that the main thread doesn't have
+# to wait, and spawn off a do_on, one at a time, so that they don't
+# interfere with each other.
+# When do_on is finished, it must send a copy of doneevent
+# so that jevhandler knows it can spawn another do_on.
+jevhandler()
+{
+ q := array[10] of ref ScriptEvent;
+ qhead := 0;
+ qtail := 0;
+ spawnok := 1;
+ for(;;) {
+ jev := <- jevchan;
+ if(jev.kind == -1)
+ spawnok = 1;
+ else
+ q[qtail++] = jev;
+ jev = nil;
+
+ # remove next event to process, if ok to and there is one
+ if(spawnok && qhead < qtail)
+ jev = q[qhead++];
+
+ # adjust queue to make sure there is room for next event
+ if(qhead == qtail) {
+ qhead = 0;
+ qtail = 0;
+ }
+ if(qtail == len q) {
+ if(qhead > 0) {
+ q[0:] = q[qhead:qtail];
+ qtail -= qhead;
+ qhead = 0;
+ }
+ else {
+ newq := array[len q + 10] of ref ScriptEvent;
+ newq[0:] = q;
+ q = newq;
+ }
+ }
+
+ # process next event, if any
+ if(jev != nil) {
+ spawnok = 0;
+ spawn do_on(jev);
+ }
+ }
+}
+
+# Create an execution context for the frame.
+# The global object of the frame is the frame's Window object.
+# Return the execution context and the Location object for the window.
+makeframeex(f : ref Layout->Frame) : (ref Exec, ref Obj)
+{
+ winobj := mkhostobj(nil, "Window");
+ ex := ES->mkexec(winobj);
+ winobj.prototype = ex.objproto;
+ winobj.prototype = mkprototype(ex, specindex("Window"));
+
+ navobj := mknavobj(ex);
+ reinitprop(winobj, "navigator", ES->objval(navobj));
+
+ histobj := mkhostobj(ex, "History");
+ (length, current, next, previous) := CH->histinfo();
+ reinitprop(histobj, "current", ES->strval(current));
+ reinitprop(histobj, "length", ES->numval(real length));
+ reinitprop(histobj, "next", ES->strval(next));
+ reinitprop(histobj, "previous", ES->strval(previous));
+ ES->put(ex, winobj, "history", ES->objval(histobj));
+
+ locobj := mkhostobj(ex, "Location");
+ src : ref U->Parsedurl;
+ di := f.doc;
+ if (di != nil && di.src != nil) {
+ src = di.src;
+ reinitprop(locobj, "hash", ES->strval("#" + src.frag));
+ reinitprop(locobj, "host", ES->strval(src.host + ":" + src.port));
+ reinitprop(locobj, "hostname", ES->strval(src.host));
+ reinitprop(locobj, "href", ES->strval(src.tostring()));
+ reinitprop(locobj, "pathname", ES->strval(src.path));
+ reinitprop(locobj, "port", ES->strval(src.port));
+ reinitprop(locobj, "protocol", ES->strval(src.scheme + ":"));
+ reinitprop(locobj, "search", ES->strval("?" + src.query));
+ }
+ ES->put(ex, winobj, "location", ES->objval(locobj));
+
+ scrobj := mkhostobj(ex, "Screen");
+ scr := (CU->G->display).image;
+ scrw := D->(scr.r.dx)();
+ scrh := D->(scr.r.dy)();
+ reinitprop(scrobj, "availHeight", ES->numval(real scrh));
+ reinitprop(scrobj, "availWidth", ES->numval(real scrw));
+ reinitprop(scrobj, "availLeft", ES->numval(real scr.r.min.x));
+ reinitprop(scrobj, "availTop", ES->numval(real scr.r.min.y));
+ reinitprop(scrobj, "colorDepth", ES->numval(real scr.depth));
+ reinitprop(scrobj, "pixelDepth", ES->numval(real scr.depth));
+ reinitprop(scrobj, "height", ES->numval(real scrh));
+ reinitprop(scrobj, "width", ES->numval(real scrw));
+ ES->put(ex, winobj, "screen", ES->objval(scrobj));
+
+ # make the non-core constructor objects
+# improto := mkprototype(ex, specindex("Image"));
+ o := ES->biinst(winobj, Builtin("Image", "Image", array[] of {"width", "height"}, 2),
+ ex.funcproto, me);
+ o.construct = o.call;
+
+ o = ES->biinst(winobj, Builtin("Option", "Option", array[] of {"text", "value", "defaultSelected", "selected"}, 4),
+ ex.funcproto, me);
+ o.construct = o.call;
+ defaultStatus = "";
+ return (ex, locobj);
+}
+
+mknavobj(ex: ref Exec) : ref Obj
+{
+ navobj := mkhostobj(ex, "navigator");
+ reinitprop(navobj, "appCodeName", ES->strval("Mozilla"));
+ reinitprop(navobj, "appName", ES->strval("Netscape"));
+# reinitprop(navobj, "appVersion", ES->strval("3.0 (Inferno, U)"));
+# reinitprop(navobj, "userAgent", ES->strval("Mozilla/3.0 (Inferno; U)"));
+ reinitprop(navobj, "appVersion", ES->strval("4.08 (Charon; Inferno)"));
+ reinitprop(navobj, "userAgent", ES->strval("Mozilla/4.08 (Charon; Inferno)"));
+
+ omty := getobj(ex, navobj, "mimeTypes");
+ for(i := 0; i < len mimespecs; i++) {
+ sp := mimespecs[i];
+ v := mkhostobj(ex, "MimeType");
+ reinitprop(v, "description", ES->strval(sp.description));
+ reinitprop(v, "suffixes", ES->strval(sp.suffixes));
+ reinitprop(v, "type", ES->strval(sp.ty));
+ arrayput(ex, omty, i, sp.ty, ES->objval(v));
+ }
+ return navobj;
+}
+
+# Something changed in charon's frame tree
+frametreechanged(t: ref Layout->Frame)
+{
+ rebuild : ref ScriptWin;
+ if (top == nil) {
+ (ex, loc) := makeframeex(t);
+ top = ScriptWin.new(t, ex, loc, nil);
+ rebuild = top;
+ } else {
+ rebuild = top.findbyframeid(t.id);
+ # t could be new frame - need to look for parent
+ while (rebuild == nil && t.parent != nil) {
+ t = t.parent;
+ rebuild = top.findbyframeid(t.id);
+ }
+ # if we haven't found it by now, it's not in the official Frame
+ # hierarchy, so ignore it
+ }
+ if (rebuild != nil)
+ wininstant(rebuild);
+}
+
+# Frame f has just been reset, then given a new doc field
+# (with initial values for src, base, refresh, chset).
+# We'll defer doing any actual building of the script objects
+# until an evalscript; that way, pages that don't use scripts
+# incur minimum penalties).
+havenewdoc(f: ref Layout->Frame)
+{
+ sw := top.findbyframeid(f.id);
+ if(sw != nil) {
+ sw.inbuild = 1;
+ sw.forms = nil;
+ (sw.ex, sw.locobj) = makeframeex(f);
+ if (sw.val != nil)
+ # global object is referenced via parent.frames array
+ sw.val.obj = sw.ex.global;
+ wininstant(sw);
+ }
+}
+
+# returns (error, output, value)
+# error: error message
+# output: result of any document.writes
+# value: value of last statement executed (used for handling "javascript:" URL scheme)
+#
+evalscript(f: ref Layout->Frame, s: string) : (string, string, string)
+{
+ if (top.error)
+ return("scripts disabled for this document", "", "");
+ sw := top.findbyframeid(f.id);
+ if (sw == nil)
+ return("cannot find script window", "", "");
+ if (sw.ex == nil)
+ return("script window has no execution context", "", "");
+ if(sw == nil || sw.ex == nil)
+ return ("", "", "");
+
+ ex := sw.ex;
+ sw.docwriteout = "";
+ expval := "";
+ createdimages = nil;
+ {
+ xfertoscriptobjs(f, 1);
+ if(s != "") {
+ ex.error = nil;
+ c := ES->eval(ex, s);
+ if (c.kind == ES->CThrow && dbg) {
+ sys->print("unhandled error:\n\tvalue:%s\n\treason:%s\n",
+ ES->toString(ex, c.val), ex.error);
+ sys->print("%s\n", s);
+ }
+ if (c.kind == ES->CNormal && c.val != nil) {
+ if (ES->isstr(c.val))
+ expval = c.val.str;
+ }
+ xferfromscriptobjs(f, 1);
+ checknewlocs(top);
+ checkopener();
+ }
+ w := sw.docwriteout;
+ sw.docwriteout = nil;
+ return("", w, expval);
+ }exception exc{
+ "*" =>
+ if(dbg) {
+ sys->print("fatal error %q executing evalscript: %s\nscript=", exc, ex.error);
+ sa := array of byte s;
+ sys->write(sys->fildes(1), sa, len sa);
+ sys->print("\n");
+ }
+ top.error = 1;
+ emsg := "Fatal error processing script\n\nScript processing suspended for this page";
+ G->alert(emsg);
+ w := sw.docwriteout;
+ sw.docwriteout = nil;
+ return (ex.error, w, "");
+ }
+}
+
+xfertoscriptobjs(f: ref Layout->Frame, inbuild: int)
+{
+ sw := top.findbyframeid(f.id);
+ if(sw == nil)
+ return;
+ ex := sw.ex;
+ ow := ex.global;
+ di := f.doc;
+
+ for(el := di.events; el != nil; el = tl el) {
+ e := hd el;
+ hname := "";
+ dhname := "";
+ case e.attid {
+ Lex->Aonblur =>
+ hname = "onblur";
+ di.evmask |= E->SEonblur;
+ Lex->Aonerror =>
+ hname = "onerror";
+ di.evmask |= E->SEonerror;
+ Lex->Aonfocus =>
+ hname = "onfocus";
+ di.evmask |= E->SEonfocus;
+ Lex->Aonload =>
+ hname = "onload";
+ di.evmask |= E->SEonload;
+ Lex->Aonresize =>
+ hname = "onresize";
+ di.evmask |= E->SEonresize;
+ Lex->Aonunload =>
+ hname = "onunload";
+ di.evmask |= E->SEonunload;
+ Lex->Aondblclick =>
+ dhname = "ondblclick";
+ di.evmask |= E->SEondblclick;
+ Lex->Aonkeydown =>
+ dhname = "onkeydown";
+ di.evmask |= E->SEonkeydown;
+ Lex->Aonkeypress =>
+ dhname = "onkeypress";
+ di.evmask |= E->SEonkeypress;
+ Lex->Aonkeyup =>
+ dhname = "onkeyup";
+ di.evmask |= E->SEonkeyup;
+ Lex->Aonmousedown =>
+ dhname = "onmousedown";
+ di.evmask |= E->SEonmousedown;
+ Lex->Aonmouseup =>
+ dhname = "onmouseup";
+ di.evmask |= E->SEonmouseup;
+ }
+ if(hname != "")
+ puthandler(ex, ow, hname, e.value);
+ if(dhname != ""){
+ od := getobj(ex, ow, "document");
+ if(od == nil) {
+ reinitprop(ow, "document", docinstant(ex, f));
+ od = getobj(ex, ow, "document");
+ }
+ puthandler(ex, od, dhname, e.value);
+ }
+ }
+ di.events = nil;
+
+ od := getobj(ex, ow, "document");
+ if(od == nil) {
+ reinitprop(ow, "document", docinstant(ex, f));
+ od = getobj(ex, ow, "document");
+ CU->assert(od != nil);
+ }
+ else if(inbuild) {
+ docfill(ex, od, f);
+ ES->put(ex, od, "location", ES->objval(sw.locobj));
+ }
+ for(frml := sw.forms; frml != nil; frml = tl frml) {
+ frm := hd frml;
+ for (fldl := frm.fields; fldl != nil; fldl = tl fldl) {
+ (fld, ofield) := hd fldl;
+ if (ofield == nil)
+ continue;
+ if(fld.ctlid >= 0 && fld.ctlid < len f.controls) {
+ pick c := f.controls[fld.ctlid] {
+ Centry =>
+ reinitprop(ofield, "value", ES->strval(c.s));
+ Ccheckbox =>
+ cv := ES->false;
+ if(c.flags&Layout->CFactive)
+ cv = ES->true;
+ reinitprop(ofield, "checked", cv);
+ Cselect =>
+ for(i := 0; i < len c.options; i++) {
+ if(c.options[i].selected) {
+ reinitprop(ofield, "selectedIndex", ES->numval(real i));
+ # hack for common mistake in scripts
+ # (implemented by other browsers)
+ opts := getobj(ex, ofield, "options");
+ if (opts != nil)
+ reinitprop(opts, "selectedIndex", ES->numval(real i));
+ }
+ }
+ }
+ }
+ }
+ }
+ for(sil := sw.imgs; sil != nil; sil = tl sil) {
+ si := hd sil;
+ if(si.item.ci.complete != 0)
+ reinitprop(si.obj, "complete", ES->true);
+ }
+}
+
+xferfromscriptobjs(f: ref Layout->Frame, inbuild: int)
+{
+ sw := top.findbyframeid(f.id);
+ if(sw == nil)
+ return;
+ ex := sw.ex;
+ ow := ex.global;
+ od := getobj(ex, ow, "document");
+ if(od != nil) {
+ if(inbuild) {
+ di := f.doc;
+ di.doctitle = strxfer(ex, od, "title", di.doctitle);
+ di.background.color = colorxfer(ex, od, "bgColor", di.background.color);
+ di.text = colorxfer(ex, od, "fgColor", di.text);
+ di.alink = colorxfer(ex, od, "alinkColor", di.alink);
+ di.link = colorxfer(ex, od, "linkColor", di.link);
+ di.vlink = colorxfer(ex, od, "vlinkColor", di.vlink);
+ if(createdimages != nil) {
+ for(oil := createdimages; oil != nil; oil = tl oil) {
+ oi := hd oil;
+ vsrc := ES->get(ex, oi, "src");
+ if(ES->isstr(vsrc)) {
+ u := U->parse(vsrc.str);
+ if(u.path != "") {
+ u = U->mkabs(u, di.base);
+ it := Item.newimage(di, u, nil, "", B->Anone,
+ 0, 0, 0, 0, 0, 0, 0, nil, nil, nil);
+ di.images = it :: di.images;
+ }
+ }
+ }
+ }
+ }
+ else {
+ for (ol := sw.imgrelocs; ol != nil; ol = tl ol) {
+ oi := hd ol;
+ vnewsrc := ES->get(ex, oi, "src");
+ if(ES->isstr(vnewsrc) && vnewsrc.str != nil) {
+ for(sil := sw.imgs; sil != nil; sil = tl sil) {
+ si := hd sil;
+ if(si.obj == oi) {
+ f.swapimage(si.item, vnewsrc.str);
+ break;
+ }
+ }
+ }
+ }
+ sw.imgrelocs = nil;
+ }
+ }
+}
+
+# Check ScriptWin tree for non-empty newlocs.
+# When found, generate a go event to the new place.
+# If found, don't recurse into children, because those
+# child frames are about to go away anyway.
+# Otherwise, recurse into all kids -- this might generate
+# multiple go events.
+# BUG: if multiple events are generated, later ones will
+# interrupt (STOP!) loading of pages specified by preceding
+# events. To fix, need to queue them up, probably in
+# main charon module.
+checknewlocs(sw: ref ScriptWin)
+{
+ if(sw.newloc != "") {
+ E->evchan <-= ref Event.Ego(sw.newloc, sw.newloctarg, 0, E->EGnormal);
+ sw.newloc = "";
+ }
+ else {
+ for(l := sw.kids; l != nil; l = tl l)
+ checknewlocs(hd l);
+ }
+}
+
+checkopener()
+{
+ if(opener != nil && opener.newloc != "") {
+ CH->sendopener(sys->sprint("L %s", opener.newloc)); # just location for now
+ opener.newloc = "";
+ }
+ if(winclose)
+ G->exitcharon();
+}
+
+# if e.anchorid >= 0 => target is Link
+# if e.fieldid > 0 => target is FormField (and e.formid > 0)
+# if e.formid > 0 => target is Form (e.fieldid == -1)
+# if e.imageid >= 0 => target is Image
+# otherwise => target is window
+do_on(e: ref ScriptEvent)
+{
+ if(dbgdom)
+ sys->print("do_on %d, frameid=%d, formid=%d, fieldid=%d, anchorid=%d, imageid=%d, x=%d, y=%d, which=%d\n",
+ e.kind, e.frameid, e.formid, e.fieldid, e.anchorid, e.imageid, e.x, e.y, e.which);
+ if (top.error) {
+ if (dbgdom)
+ sys->print("do_on() previous error prevents processing\n");
+ if (e.reply != nil)
+ e.reply <-= nil;
+ jevchan <-= ref doneevent;
+ return;
+ }
+ sw := top.findbyframeid(e.frameid);
+ # BUG FIX: Frame can be reset by Charon main thread
+ # between us getting its ref and using it
+ # WARNING - xferfromscriptobjs() will not update non-ref-type members of frame adt
+ # (currently not a problem) as updates will go to our copy
+ f : ref Frame;
+ if (sw != nil && !sw.inbuild) {
+ f = ref *sw.frame;
+ if (f.doc == nil)
+ f = nil;
+ }
+ if (f == nil) {
+ if(e.reply != nil)
+ e.reply <-= nil;
+ jevchan <-= ref doneevent;
+ if (dbgdom)
+ sys->print("do_on() failed to find frame %d\n", e.frameid);
+ return;
+ }
+ ex := sw.ex;
+ ow := ex.global;
+ od := getobj(ex, ow, "document");
+ sw.docwriteout = nil;
+
+{
+ # event target types
+ TAnchor, TForm, TFormField, TImage, TDocument, TWindow, Tnone: con iota;
+ ttype := Tnone;
+ target, oform: ref Obj;
+ if(e.anchorid >= 0) {
+ ttype = TAnchor;
+ target = getanchorobj(ex, e.frameid, e.anchorid);
+ } else if(e.formid > 0) {
+ oform = getformobj(ex, e.frameid, e.formid);
+ if(e.fieldid > 0) {
+ ttype = TFormField;
+ target = getformfieldobj(e.frameid, e.formid, e.fieldid);
+ } else {
+ ttype = TForm;
+ target = oform;
+ }
+ } else if(e.imageid >= 0) {
+ ttype = TImage;
+ target = getimageobj(ex, e.frameid, e.imageid);
+ } else if(e.kind == E->SEondblclick || e.kind == E->SEonkeydown ||
+ e.kind == E->SEonkeypress || e.kind == E->SEonkeyup ||
+ e.kind == E->SEonmousedown || e.kind == E->SEonmouseup){
+ ttype = TDocument;
+ target = od;
+ } else {
+ ttype = TWindow;
+ target = ow;
+ }
+ if(target != nil) {
+ oscript: ref Obj;
+ scrname := "";
+ case e.kind {
+ E->SEonabort =>
+ scrname = "onabort";
+ E->SEonblur =>
+ scrname = "onblur";
+ E->SEonchange =>
+ scrname = "onchange";
+ E->SEonclick =>
+ scrname = "onclick";
+ E->SEondblclick =>
+ scrname = "ondblclick";
+ E->SEonerror =>
+ scrname = "onerror";
+ E->SEonfocus =>
+ scrname = "onfocus";
+ E->SEonkeydown =>
+ scrname = "onkeydown";
+ E->SEonkeypress =>
+ scrname = "onkeypress";
+ E->SEonkeyup =>
+ scrname = "onkeyup";
+ E->SEonload =>
+ scrname = "onload";
+ E->SEonmousedown =>
+ scrname = "onmousedown";
+ E->SEonmouseout =>
+ scrname = "onmouseout";
+ E->SEonmouseover =>
+ scrname = "onmouseover";
+ E->SEonmouseup =>
+ scrname = "onmouseup";
+ E->SEonreset =>
+ scrname = "onreset";
+ E->SEonresize =>
+ scrname = "onresize";
+ E->SEonselect =>
+ scrname = "onselect";
+ E->SEonsubmit =>
+ scrname = "onsubmit";
+ E->SEonunload =>
+ scrname = "onunload";
+ E->SEtimeout or
+ E->SEinterval =>
+ oscript = dotimeout(ex, target, e);
+ E->SEscript =>
+ # TODO - handle document text from evalscript
+ # need to determine if document is 'open' or not.
+ (err, nil, val) := evalscript(f, e.script);
+ if (e.reply != nil)
+ e.reply <- = val;
+ e.reply = nil;
+ }
+ if(scrname != "")
+ oscript = getobj(ex, target, scrname);
+ if(oscript != nil) {
+ xfertoscriptobjs(f, 0);
+ if(dbgdom)
+ sys->print("calling script\n");
+ # establish scope chain per Rhino p. 287 (3rd edition)
+ oldsc := ex.scopechain;
+ sc := ow :: nil;
+ if(ttype != TWindow) {
+ sc = od :: sc;
+ if(ttype == TFormField)
+ sc = oform :: sc;
+ if(ttype != TDocument)
+ sc = target :: sc;
+ }
+ ex.scopechain = sc;
+ v := ES->call(ex, oscript, target, nil, 1).val;
+ # 'fix' for onsubmit
+ # JS references state that if the handler returns false
+ # then the action is cancelled.
+ # other browsers interpret this as "if and only if the handler
+ # returns false."
+ # When a function completes normally without returning a value
+ # its value is 'undefined', toBoolean(undefined) = false
+ if (v == ES->undefined)
+ v = ES->true;
+ else
+ v = ES->toBoolean(ex, v);
+ ex.scopechain = oldsc;
+ # onreset/onsubmit reply channel
+ if(e.reply != nil) {
+ ans : string;
+ if(v == ES->true)
+ ans = "true";
+ e.reply <-= ans;
+ e.reply = nil;
+ }
+ xferfromscriptobjs(f, 0);
+ checknewlocs(top);
+ checkopener();
+
+ if (ttype == TFormField && e.kind == E->SEonclick && v == ES->true)
+ E->evchan <-= ref Event.Eformfield(e.frameid, e.formid, e.fieldid, E->EFFclick);
+ if (ttype == TAnchor && e.kind == E->SEonclick && v == ES->true) {
+ gohref := getstr(ex, target, "href");
+ gotarget := getstr(ex, target, "target");
+ if (gotarget == "")
+ gotarget = "_self";
+ E->evchan <-= ref Event.Ego(gohref, gotarget, 0, E->EGnormal);
+ }
+ }
+ }
+ if(e.reply != nil)
+ e.reply <-= nil;
+ checkdocwrite(top);
+ jevchan <-= ref doneevent;
+}
+exception exc{
+ "*" =>
+ if (exc == "throw") {
+ # ignore ecmascript runtime errors
+ if(dbgdom)
+ sys->print("error executing 'on' handler: %s\n", ex.error);
+ } else {
+ # fatal error
+ top.error = 1;
+ emsg := "Fatal error in script ("+exc+"):\n" + ex.error + "\n";
+ G->alert(emsg);
+ }
+ if(e.reply != nil)
+ e.reply <-= nil;
+ jevchan <-= ref doneevent;
+ return;
+}
+}
+
+xferframeset(sw : ref ScriptWin)
+{
+ if (!sw.inbuild)
+ xfertoscriptobjs(sw.frame, 1);
+ for (k := sw.kids; k != nil; k = tl k)
+ xferframeset(hd k);
+}
+
+framedone(f : ref Frame, hasscripts : int)
+{
+ sw := top.findbyframeid(f.id);
+ if (sw != nil) {
+ if (!top.active && hasscripts) {
+ # need to transfer entire frame tree
+ # as one frame can reference objects in another
+ xferframeset(top);
+ }
+ sw.active |= hasscripts;
+ top.active |= hasscripts;
+ if (top.active)
+ xfertoscriptobjs(f, 1);
+ sw.inbuild = 0;
+ }
+}
+
+checkdocwrite(sw : ref ScriptWin) : int
+{
+ if (sw.inbuild)
+ return 0;
+
+ if (sw.docwriteout != nil) {
+ # The URL is bogus - not sure what the correct value should be
+ ev := ref Event.Esettext(sw.frame.id, sw.frame.src, sw.docwriteout);
+ sw.docwriteout = "";
+ E->evchan <- = ev;
+ return 1;
+ }
+ for (k := sw.kids; k != nil; k = tl k)
+ if (checkdocwrite(hd k))
+ break;
+ return 0;
+}
+
+#
+# interface for host objects
+#
+get(ex: ref Exec, o: ref Obj, property: string): ref Val
+{
+ if(o.class == "document" && property == "cookie") {
+ ans := "";
+ target := top.findbyobj(o);
+ if(target != nil) {
+ url := target.frame.doc.src;
+ ans = CU->getcookies(url.host, url.path, url.scheme == "https");
+ }
+ return ES->strval(ans);
+ }
+ if(o.class == "Window" && property == "opener"){
+ if(!CH->hasopener() || top.ex.global != o)
+ v := ES->undefined;
+ else{
+ if(opener == nil)
+ opener = ScriptWin.dummy();
+ v = ES->objval(opener.ex.global);
+ }
+ reinitprop(o, "opener", v);
+ }
+ return ES->get(ex, o, property);
+}
+
+
+put(ex: ref Exec, o: ref Obj, property: string, val: ref Val)
+{
+ if(dbgdom)
+ sys->print("put property %s in cobj of class %s\n", property, o.class);
+
+ url : ref Parsedurl;
+ target : ref ScriptWin;
+ str := ES->toString(ex, val);
+ ev := E->SEnone;
+
+ case o.class {
+ "Array" =>
+ # we 'host' the Formfield.options array so as we can
+ # track changes to the options list
+ vformfield := ES->get(ex, o, "@PRIVformfield");
+ if (!ES->isobj(vformfield))
+ # not one of our 'options' arrays
+ break;
+ ix := prop2index(property);
+ if (property != "length" && ix == -1)
+ # not a property that affects us
+ break;
+ oformfield := vformfield.obj;
+ oform := getobj(ex, oformfield, "form");
+ if (oform == nil)
+ break;
+ ES->put(ex, o, property, val);
+ if (ES->isobj(val) && val.obj.class == "Option") {
+ ES->put(ex, val.obj, "@PRIVformfield", vformfield);
+ ES->put(ex, val.obj, "form", ES->objval(oform));
+ reinitprop(val.obj, "index", ES->numval(real ix));
+ }
+ updateffopts(ex, oform, oformfield, ix);
+ return;
+ "Window" =>
+ case property {
+ "defaultStatus" or
+ "status" =>
+ if(ES->isstr(val)) {
+ if(property == "defaultStatus")
+ defaultStatus = val.str;
+ G->setstatus(val.str);
+ }
+ "location" =>
+ target = top.findbyobj(o);
+ if (target == nil)
+ break;
+ url = U->parse(str);
+ # TODO: be more defensive
+ url = U->mkabs(url, target.frame.doc.base);
+ "name" =>
+ sw := top.findbyobj(o);
+ if (sw == nil)
+ break;
+ name := ES->toString(ex, val);
+ if (sw.parent != nil) {
+ w := sw.parent.ex.global;
+ v := sw.val;
+ if (sw.frame.name != nil)
+ ES->delete(ex, w, sw.frame.name);
+ ES->varinstant(w, 0, name, ref RefVal(v));
+ }
+ # Window.name is used for determining TARGET of <A> etc.
+ # update Charon's Frame info so as new name gets used properly
+ sw.frame.name = name;
+ "offscreenBuffering" =>
+ if(ES->isstr(val) || val == ES->true || val == ES->false){
+ }
+ "onblur" =>
+ ev = E->SEonblur;
+ "onerror" =>
+ ev = E->SEonerror;
+ "onfocus" =>
+ ev = E->SEonfocus;
+ "onload" =>
+ ev = E->SEonload;
+ "onresize" =>
+ ev = E->SEonresize;
+ "onunload" =>
+ ev = E->SEonunload;
+ "opener" =>
+ ;
+ }
+ if(ev != E->SEnone) {
+ target = top.findbyobj(o);
+ if(target == nil)
+ break;
+ di := target.frame.doc;
+ if(!ES->isobj(val) || val.obj.call == nil)
+ di.evmask &= ~ev;
+ else
+ di.evmask |= ev;
+ }
+ "Link" =>
+ case property {
+ "onclick" =>
+ ev = E->SEonclick;
+ "ondblclick" =>
+ ev = E->SEondblclick;
+ "onkeydown" =>
+ ev = E->SEonkeydown;
+ "onkeypress" =>
+ ev = E->SEonkeypress;
+ "onkeyup" =>
+ ev = E->SEonkeyup;
+ "onmousedown" =>
+ ev = E->SEonmousedown;
+ "onmouseout" =>
+ ev = E->SEonmouseout;
+ "onmouseover" =>
+ ev = E->SEonmouseover;
+ "onmouseup" =>
+ ev = E->SEonmouseup;
+ }
+ if(ev != E->SEnone) {
+ vframeid := ES->get(ex, o, "@PRIVframeid");
+ vanchorid := ES->get(ex, o, "@PRIVanchorid");
+ if(!ES->isnum(vframeid) || !ES->isnum(vanchorid))
+ break;
+ frameid := ES->toInt32(ex, vframeid);
+ anchorid := ES->toInt32(ex, vanchorid);
+ target = top.findbyframeid(frameid);
+ if(target == nil)
+ break;
+ anchor: ref B->Anchor;
+ for(al := target.frame.doc.anchors; al != nil; al = tl al) {
+ a := hd al;
+ if(a.index == anchorid) {
+ anchor = a;
+ break;
+ }
+ }
+ if(anchor == nil)
+ break;
+ if(!ES->isobj(val) || val.obj.call == nil)
+ anchor.evmask &= ~ev;
+ else
+ anchor.evmask |= ev;
+ }
+ "Location" =>
+ target = top.findbyobj(o);
+ if (target == nil) {
+ break;
+ }
+ url = ref *target.frame.doc.src;
+ case property {
+ "hash" =>
+ if (str != nil && str[0] == '#')
+ str = str[1:];
+ url.frag = str;
+ "host" =>
+ # host:port
+ (h, p) := S->splitl(str, ":");
+ if (p != nil)
+ p = p[1:];
+ if (h != nil)
+ url.host = h;
+ if (p != nil)
+ url.port = p;
+ "hostname" =>
+ url.host = str;
+ "href" or
+ "pathname" =>
+ url = U->mkabs(U->parse(str), target.frame.doc.base);
+ "port" =>
+ url.port = str;
+ "protocol" =>
+ url.scheme = S->tolower(str);
+ "search" =>
+ url.query = str;
+ * =>
+ url = nil;
+ }
+ "Image" =>
+ case property {
+ "src" or
+ "lowsrc" =>
+ # making URLs absolute matches Netscape
+ target = top.findbyobj(o);
+ if(target == nil)
+ break;
+ url = U->mkabs(U->parse(str), target.frame.doc.base);
+ val = ES->strval(url.tostring());
+ target.imgrelocs = o :: target.imgrelocs;
+ url = nil;
+ "onabort" =>
+ ev = E->SEonabort;
+ "ondblclick" =>
+ ev = E->SEondblclick;
+ "onkeydown" =>
+ ev = E->SEonkeydown;
+ "onkeypress" =>
+ ev = E->SEonkeypress;
+ "onkeyup" =>
+ ev = E->SEonkeyup;
+ "onerror" =>
+ ev = E->SEonerror;
+ "onload" =>
+ ev = E->SEonload;
+ "onmousedown" =>
+ ev = E->SEonmousedown;
+ "onmouseout" =>
+ ev = E->SEonmouseout;
+ "onmouseover" =>
+ ev = E->SEonmouseover;
+ "onmouseup" =>
+ ev = E->SEonmouseup;
+ }
+ if(ev != E->SEnone) {
+ target = top.findbyobj(o);
+ if(target == nil)
+ break;
+ vimageid := ES->get(ex, o, "@PRIVimageid");
+ if(!ES->isnum(vimageid))
+ break;
+ imageid := ES->toInt32(ex, vimageid);
+ image: ref (Build->Item).Iimage;
+ forloop:
+ for(il := target.frame.doc.images; il != nil; il = tl il) {
+ pick im := hd il {
+ Iimage =>
+ if(im.imageid == imageid) {
+ image = im;
+ break forloop;
+ }
+ }
+ }
+ # BUG: if image has no genattr then the event handler update
+ # will not be done - can never set a handler for an image that
+ # didn't have a handler
+ if(image == nil || image.genattr == nil)
+ break;
+ if(!ES->isobj(val) || val.obj.call == nil)
+ image.genattr.evmask &= ~ev;
+ else
+ image.genattr.evmask |= ev;
+ }
+ "Form" =>
+ action := "";
+ case property {
+ "onreset" =>
+ ev = E->SEonreset;
+ "onsubmit" =>
+ ev = E->SEonsubmit;
+ "action" =>
+ action = str;
+ * =>
+ break;
+ }
+ vframeid := ES->get(ex, o, "@PRIVframeid");
+ vformid := ES->get(ex, o, "@PRIVformid");
+ if(!ES->isnum(vframeid) || !ES->isnum(vformid))
+ break;
+ frameid := ES->toInt32(ex, vframeid);
+ formid := ES->toInt32(ex, vformid);
+ target = top.findbyframeid(frameid);
+ if(target == nil)
+ break;
+ form: ref B->Form;
+ for(fl := target.frame.doc.forms; fl != nil; fl = tl fl) {
+ f := hd fl;
+ if(f.formid == formid) {
+ form = f;
+ break;
+ }
+ }
+ if(form == nil)
+ break;
+ if (ev != E->SEnone) {
+ if(!ES->isobj(val) || val.obj.call == nil)
+ form.evmask &= ~ev;
+ else
+ form.evmask |= ev;
+ break;
+ }
+ if (action != "")
+ form.action = U->mkabs(U->parse(action), target.frame.doc.base);
+ "FormField" =>
+ oform := getobj(ex, o, "form");
+ vframeid := ES->get(ex, oform, "@PRIVframeid");
+ vformid := ES->get(ex, oform, "@PRIVformid");
+ vfieldid := ES->get(ex, o, "@PRIVfieldid");
+ if(!ES->isnum(vframeid) || !ES->isnum(vformid) || !ES->isnum(vfieldid))
+ break;
+ frameid := ES->toInt32(ex, vframeid);
+ formid := ES->toInt32(ex, vformid);
+ fieldid := ES->toInt32(ex, vfieldid);
+ target = top.findbyframeid(frameid);
+ if(target == nil)
+ break;
+ form: ref B->Form;
+ for(fl := target.frame.doc.forms; fl != nil; fl = tl fl) {
+ f := hd fl;
+ if(f.formid == formid) {
+ form = f;
+ break;
+ }
+ }
+ if(form == nil)
+ break;
+ field: ref B->Formfield;
+ for(ffl := form.fields; ffl != nil; ffl = tl ffl) {
+ ff := hd ffl;
+ if(ff.fieldid == fieldid) {
+ field = ff;
+ break;
+ }
+ }
+ if(field == nil)
+ break;
+ case property {
+ "onblur" =>
+ ev = E->SEonblur;
+ "onchange" =>
+ ev = E->SEonchange;
+ "onclick" =>
+ ev = E->SEonclick;
+ "ondblclick" =>
+ ev = E->SEondblclick;
+ "onfocus" =>
+ ev = E->SEonfocus;
+ "onkeydown" =>
+ ev = E->SEonkeydown;
+ "onkeypress" =>
+ ev = E->SEonkeypress;
+ "onkeyup" =>
+ ev = E->SEonkeyup;
+ "onmousedown" =>
+ ev = E->SEonmousedown;
+ "onmouseup" =>
+ ev = E->SEonmouseup;
+ "onselect" =>
+ ev = E->SEonselect;
+ "value" =>
+ field.value = str;
+ if(target.frame.controls == nil ||
+ field.ctlid < 0 ||
+ field.ctlid > len target.frame.controls){
+ break;
+ }
+ c := target.frame.controls[field.ctlid];
+ pick ctl := c {
+ Centry =>
+ ctl.s = str;
+ ctl.sel = (0, 0);
+ E->evchan <-= ref Event.Eformfield(frameid, formid, fieldid, E->EFFredraw);
+ }
+ }
+
+ if(ev != E->SEnone) {
+ if(!ES->isobj(val) || val.obj.call == nil)
+ field.evmask &= ~ev;
+ else
+ field.evmask |= ev;
+ }
+ "document" =>
+ case property {
+ "location" =>
+ target = top.findbyobj(o);
+ if (target == nil)
+ break;
+ # TODO: be more defensive
+ url = U->mkabs(U->parse(str), target.frame.doc.base);
+ "cookie" =>
+ target = top.findbyobj(o);
+ if(target != nil && (CU->config).docookies > 0) {
+ url = target.frame.doc.src;
+ CU->setcookie(url.host, url.path, str);
+ }
+ return;
+ "ondblclick" =>
+ ev = E->SEondblclick;
+ "onkeydown" =>
+ ev = E->SEonkeydown;
+ "onkeypress" =>
+ ev = E->SEonkeypress;
+ "onkeyup" =>
+ ev = E->SEonkeyup;
+ "onmousedown" =>
+ ev = E->SEonmousedown;
+ "onmouseup" =>
+ ev = E->SEonmouseup;
+ }
+ if(ev != E->SEnone){
+ target = top.findbyobj(o);
+ if(target == nil)
+ break;
+ di := target.frame.doc;
+ if(!ES->isobj(val) || val.obj.call == nil)
+ di.evmask &= ~ev;
+ else
+ di.evmask |= ev;
+ }
+ "Option" =>
+ vformfield := ES->get(ex, o, "@PRIVformfield");
+ vindex := ES->get(ex, o, "index");
+ if (!ES->isobj(vformfield) || !ES->isnum(vindex))
+ # not one of our 'options' objects
+ break;
+ oformfield := vformfield.obj;
+ oform := getobj(ex, oformfield, "form");
+ if (oform == nil)
+ break;
+ ES->put(ex, o, property, val);
+ index := ES->toInt32(ex, vindex);
+ updateffopts(ex, oform, oformfield, index);
+ }
+ ES->put(ex, o, property, val);
+
+ if (url != nil && target != nil) {
+ if (!CU->urlequal(url, target.frame.doc.src)) {
+ target.newloc = url.tostring();
+ target.newloctarg = "_top";
+ if(target.frame != nil)
+ target.newloctarg = target.frame.name;
+ }
+ }
+}
+
+canput(ex: ref Exec, o: ref Obj, property: string): ref Val
+{
+ return ES->canput(ex, o, property);
+}
+
+hasproperty(ex: ref Exec, o: ref Obj, property: string): ref Val
+{
+ return ES->hasproperty(ex, o, property);
+}
+
+delete(ex: ref Exec, o: ref Obj, property: string)
+{
+ ES->delete(ex, o, property);
+}
+
+defaultval(ex: ref Exec, o: ref Obj, tyhint: int): ref Val
+{
+ return ES->defaultval(ex, o, tyhint);
+}
+
+call(ex: ref Exec, func, this: ref Obj, args: array of ref Val, nil: int): ref Ref
+{
+ if(dbgdom)
+ sys->print("call %x (class %s), val %s\n", func, func.class, func.val.str);
+ ans := ES->valref(ES->true);
+ case func.val.str{
+ "document.prototype.open" =>
+ sw := top.findbyobj(this);
+ if (sw != nil)
+ sw.docwriteout = "";
+ "document.prototype.close" =>
+ # ignore for now
+ ;
+ "document.prototype.write" =>
+ sw := top.findbyobj(this);
+ if (sw != nil) {
+ for (ai := 0; ai < len args; ai++)
+ sw.docwriteout += ES->toString(ex, ES->biarg(args, ai));
+ }
+ "document.prototype.writeln" =>
+ sw := top.findbyobj(this);
+ if (sw != nil) {
+ for (ai := 0; ai < len args; ai++)
+ sw.docwriteout += ES->toString(ex, ES->biarg(args, ai));
+ sw.docwriteout += "\n";
+ }
+ "navigator.prototype.javaEnabled" or
+ "navigator.prototype.taintEnabled" =>
+ ans = ES->valref(ES->false);
+ "Form.prototype.reset" or "Form.prototype.submit"=>
+ vframeid := ES->get(ex, this, "@PRIVframeid");
+ vformid := ES->get(ex, this, "@PRIVformid");
+ if(ES->isnum(vframeid) && ES->isnum(vformid)) {
+ frameid := ES->toInt32(ex, vframeid);
+ formid := ES->toInt32(ex, vformid);
+ ftype : int;
+ if(func.val.str == "Form.prototype.reset")
+ ftype = E->EFreset;
+ else
+ ftype = E->EFsubmit;
+ E->evchan <-= ref Event.Eform(frameid, formid, ftype);
+ }
+ "FormField.prototype.blur" or
+ "FormField.prototype.click" or
+ "FormField.prototype.focus" or
+ "FormField.prototype.select" =>
+ oform := getobj(ex, this, "form");
+ vformid := ES->get(ex, oform, "@PRIVformid");
+ vframeid := ES->get(ex, oform, "@PRIVframeid");
+ vfieldid := ES->get(ex, this, "@PRIVfieldid");
+ if(ES->isnum(vframeid) && ES->isnum(vformid) && ES->isnum(vfieldid)) {
+ frameid := ES->toInt32(ex, vframeid);
+ formid := ES->toInt32(ex, vformid);
+ fieldid := ES->toInt32(ex, vfieldid);
+ fftype : int;
+ case func.val.str{
+ "FormField.prototype.blur" =>
+ fftype = E->EFFblur;
+ "FormField.prototype.click" =>
+ fftype = E->EFFclick;
+ "FormField.prototype.focus" =>
+ fftype = E->EFFfocus;
+ "FormField.prototype.select" =>
+ fftype = E->EFFselect;
+ * =>
+ fftype = E->EFFnone;
+ }
+ E->evchan <-= ref Event.Eformfield(frameid, formid, fieldid, fftype);
+ }
+ "History.prototype.back" =>
+ E->evchan <-= ref Event.Ego("", "", 0, E->EGback);
+ "History.prototype.forward" =>
+ E->evchan <-= ref Event.Ego("", "", 0, E->EGforward);
+ "History.prototype.go" =>
+ ego : ref Event.Ego;
+ v := ES->biarg(args, 0);
+ if(ES->isstr(v))
+ ego = ref Event.Ego(v.str, "", 0, E->EGlocation);
+ else if(ES->isnum(v)) {
+ delta := ES->toInt32(ex, v);
+ case delta {
+ -1 =>
+ ego = ref Event.Ego("", "", 0, E->EGback);
+ 0 =>
+ ego = ref Event.Ego("", "", 0, E->EGreload);
+ 1 =>
+ ego = ref Event.Ego("", "", 0, E->EGforward);
+ * =>
+ ego = ref Event.Ego("", "", delta, E->EGdelta);
+ }
+ }
+ if(ego != nil)
+ E->evchan <-= ego;
+ "Location.prototype.reload" =>
+ # ignore 'force' argument for now
+ E->evchan <-= ref Event.Ego("", "", 0, E->EGreload);
+ "Location.prototype.replace" =>
+ v := ES->biarg(args, 0);
+ if(ES->isstr(v)) {
+ sw := top.findbyobj(this);
+ if(sw == nil)
+ fname := "_top";
+ else
+ fname = sw.frame.name;
+ if (v.str != nil) {
+ url := U->mkabs(U->parse(v.str), sw.frame.doc.base);
+ E->evchan <-= ref Event.Ego(url.tostring(), fname, 0, E->EGreplace);
+ }
+ }
+ "Window.prototype.alert" =>
+ G->alert(ES->toString(ex, ES->biarg(args, 0)));
+ "Window.prototype.blur" =>
+ ;
+# sw := top.findbyobj(this);
+# if (sw != nil)
+# E->evchan <-= ref Event.Eframefocus(sw.frame.id, 0);
+
+ "Window.prototype.clearTimeout" or
+ "Window.prototype.clearInterval" =>
+ v := ES->biarg(args, 0);
+ id := ES->toInt32(ex, v);
+ clrtimeout(ex, this, id);
+ "Window.prototype.close" =>
+ if(this == top.ex.global)
+ winclose = 1;
+ # no-op
+ ;
+ "Window.prototype.confirm" =>
+ code := G->confirm(ES->toString(ex, ES->biarg(args, 0)));
+ if(code != 1)
+ ans = ES->valref(ES->false);
+ "Window.prototype.focus" =>
+ ;
+# sw := top.findbyobj(this);
+# if (sw != nil)
+# E->evchan <-= ref Event.Eframefocus(sw.frame.id, 1);
+ "Window.prototype.moveBy" or
+ "Window.prototype.moveTo" =>
+ # no-op
+ ;
+ "Window.prototype.open" =>
+ if (dbgdom)
+ sys->print("window.open called\n");
+ u := ES->toString(ex, ES->biarg(args, 0));
+ n := ES->toString(ex, ES->biarg(args, 1));
+ sw : ref ScriptWin;
+ if (n != "")
+ sw = top.findbyname(n);
+ newch := 0;
+ if (sw == nil){
+ sw = top;
+ newch = 1;
+ }
+ if(u != "") {
+ # Want to replace window by navigation to u
+ sw.newloc = u;
+ if (sw.frame.name != "")
+ sw.newloctarg = sw.frame.name;
+ else
+ sw.newloctarg = "_top";
+ url : ref U->Parsedurl;
+ if (sw.frame.doc != nil && sw.frame.doc.base != nil)
+ url = U->mkabs(U->parse(u), sw.frame.doc.base);
+ else
+ url = CU->makeabsurl(u);
+ sw.newloc = url.tostring();
+ }
+ if(newch){
+ # create dummy window
+ dw := ScriptWin.dummy();
+ spawn newcharon(sw.newloc, sw.newloctarg, sw);
+ sw.newloc = "";
+ sw.newloctarg = "";
+ ans = ES->valref(dw.val);
+ }
+ else
+ ans = ES->valref(sw.val);
+ "Window.prototype.prompt" =>
+ msg := ES->toString(ex, ES->biarg(args, 0));
+ dflt := ES->toString(ex, ES->biarg(args, 1));
+ (code, input) := G->prompt(msg, dflt);
+ v := ES->null;
+ if(code == 1)
+ v = ES->strval(input);
+ ans = ES->valref(v);
+ "Window.prototype.resizeBy" or
+ "Window.prototype.resizeTo" =>
+ # no-op
+ ;
+ "Window.prototype.scroll" or
+ "Window.prototype.scrollTo" =>
+ # scroll is done via an event to avoid race in calls to
+ # Layout->fixframegeom() [made by scroll code]
+ sw := top.findbyobj(this);
+ if (sw != nil) {
+ (xv, yv) := (ES->biarg(args, 0), ES->biarg(args, 1));
+ pt := Draw->Point(ES->toInt32(ex, xv), ES->toInt32(ex, yv));
+ E->evchan <-= ref Event.Escroll(sw.frame.id, pt);
+ }
+ "Window.prototype.scrollBy" =>
+ sw := top.findbyobj(this);
+ if (sw != nil) {
+ (dxv, dyv) := (ES->biarg(args, 0), ES->biarg(args, 1));
+ pt := Draw->Point(ES->toInt32(ex, dxv), ES->toInt32(ex, dyv));
+ E->evchan <-= ref Event.Escrollr(sw.frame.id, pt);
+ }
+ "Window.prototype.setTimeout" =>
+ (v1, v2) := (ES->biarg(args, 0), ES->biarg(args, 1));
+ cmd := ES->toString(ex, v1);
+ ms := ES->toInt32(ex, v2);
+ id := addtimeout(ex, this, cmd, ms, E->SEtimeout);
+ ans = ES->valref(ES->numval(real id));
+ "Window.prototype.setInterval" =>
+ (v1, v2) := (ES->biarg(args, 0), ES->biarg(args, 1));
+ cmd := ES->toString(ex, v1);
+ ms := ES->toInt32(ex, v2);
+ id := addtimeout(ex, this, cmd, ms, E->SEinterval);
+ ans = ES->valref(ES->numval(real id));
+ * =>
+ ES->runtime(ex, nil, "unknown or unimplemented func "+func.val.str+" in host call");
+ return nil;
+ }
+ return ans;
+}
+
+construct(ex: ref Exec, func: ref Obj, args: array of ref Val): ref Obj
+{
+ if(dbgdom)
+ sys->print("construct %x (class %s), val %s\n", func, func.class, func.val.str);
+ params: array of string;
+ o: ref Obj;
+#sys->print("Construct(%s)\n", func.val.str);
+ case func.val.str {
+ "Image" =>
+ o = mkhostobj(ex, "Image");
+ params = array [] of {"width", "height"};
+ createdimages = o :: createdimages;
+ "Option" =>
+ o = mkhostobj(ex, "Option");
+ params = array [] of {"text", "value", "defaultSelected", "selected"};
+ * =>
+ return nil;
+ }
+ for (i := 0; i < len args && i < len params; i++)
+ reinitprop(o, params[i], args[i]);
+ return o;
+}
+
+updateffopts(ex: ref Exec, oform, oformfield: ref Obj, ix: int)
+{
+ vframeid := ES->get(ex, oform, "@PRIVframeid");
+ vformid := ES->get(ex, oform, "@PRIVformid");
+ vfieldid := ES->get(ex, oformfield, "@PRIVfieldid");
+ if(!ES->isnum(vframeid) || !ES->isnum(vformid) || !ES->isnum(vfieldid))
+ return;
+ frameid := ES->toInt32(ex, vframeid);
+ formid := ES->toInt32(ex, vformid);
+ fieldid := ES->toInt32(ex, vfieldid);
+
+ target := top.findbyframeid(frameid);
+ if(target == nil)
+ return;
+ form: ref B->Form;
+ for(fl := target.frame.doc.forms; fl != nil; fl = tl fl) {
+ f := hd fl;
+ if(f.formid == formid) {
+ form = f;
+ break;
+ }
+ }
+ if(form == nil)
+ return;
+ field: ref B->Formfield;
+ for(ffl := form.fields; ffl != nil; ffl = tl ffl) {
+ ff := hd ffl;
+ if(ff.fieldid == fieldid) {
+ field = ff;
+ break;
+ }
+ }
+ if(field == nil)
+ return;
+
+ selctl : ref Control.Cselect;
+ pick ctl := target.frame.controls[field.ctlid] {
+ Cselect =>
+ selctl = ctl;
+ * =>
+ return;
+ }
+
+ (opts, nopts) := getarraywithlen(ex, oformfield, "options");
+ if (opts == nil)
+ return;
+ optl: list of ref B->Option;
+ selobj, firstobj: ref Obj;
+ selopt, firstopt: ref B->Option;
+ noptl := 0;
+ for (i := 0; i < nopts; i++) {
+ vopt := ES->get(ex, opts, string i);
+ if (!ES->isobj(vopt) || vopt.obj.class != "Option")
+ continue;
+ oopt := vopt.obj;
+ sel := ES->get(ex, oopt, "selected") == ES->true;
+ val := ES->toString(ex, ES->get(ex, oopt, "value"));
+ text := ES->toString(ex, ES->get(ex, oopt, "text"));
+ option := ref B->Option(sel, val, text);
+ optl = option :: optl;
+ if (noptl++ == 0) {
+ firstobj = oopt;
+ firstopt = option;
+ }
+ if (sel && (selobj == nil || ix == i)) {
+ selobj = oopt;
+ selopt = option;
+ }
+ if (! int(field.flags & B->FFmultiple)) {
+ ES->put(ex, oopt, "selected", ES->false);
+ option.selected = 0;
+ }
+ }
+ if (selobj != nil)
+ ES->put(ex, selobj, "selected", ES->true);
+ else if (firstobj != nil)
+ ES->put(ex, firstobj, "selected", ES->true);
+ if (selopt != nil)
+ selopt.selected = 1;
+ else if (firstopt != nil)
+ firstopt.selected = 1;
+ opta := array [noptl] of B->Option;
+ for (i = noptl - 1; i >= 0; i--)
+ (opta[i], optl) = (*hd optl, tl optl);
+ # race here with charon.b:form_submit() and layout code
+ selctl.options = opta;
+ E->evchan <-= ref Event.Eformfield(frameid, formid, fieldid, E->EFFredraw);
+}
+
+timeout(e : ref ScriptEvent, ms : int)
+{
+ sys->sleep(ms);
+ jevchan <- = e;
+}
+
+# BUGS
+# cannot set a timeout for a window just created by window.open()
+# because it will not have an entry in the ScriptWin tree
+# (This is really a problem with the ScriptEvent adt only taking a frame id)
+#
+addtimeout(ex : ref Exec, win : ref Obj, cmd : string, ms : int, evk: int) : int
+{
+ sw := top.findbyobj(win);
+ if (sw == nil || cmd == nil || ms <= 0)
+ return -1;
+
+ # check for timeout handler array, create if doesn't exist
+ (toa, n) := getarraywithlen(ex, win, "@PRIVtoa");
+ if (toa == nil) {
+ toa = ES->mkobj(ex.arrayproto, "Array");
+ ES->varinstant(toa, ES->DontEnum|ES->DontDelete, "length", ref RefVal(ES->numval(0.)));
+ ES->varinstant(win, ES->DontEnum|ES->DontDelete, "@PRIVtoa", ref RefVal(ES->objval(toa)));
+ }
+ # find first free handler
+ for (ix := 0; ix < n; ix++) {
+ hv := ES->get(ex, toa, string ix);
+
+ if (hv == nil)
+ break;
+ # val == null Timeout has been cancelled, but timer still running
+ # val == undefined Timeout has expired
+ if (hv == ES->undefined)
+ break;
+ }
+
+ # construct a private handler for the timeout
+ # The code is always executed in the scope of the window object
+ # for which the timeout is being set.
+ oldsc := ex.scopechain;
+ ex.scopechain = win :: nil;
+ ES->eval(ex, "function PRIVhandler() {" + cmd + "}");
+ hobj := getobj(ex, win, "PRIVhandler");
+ ex.scopechain = oldsc;
+ if(hobj == nil)
+ return -1;
+ ES->put(ex, toa, string ix, ES->objval(hobj));
+ ev := ref ScriptEvent(evk, sw.frame.id, -1, -1, -1, -1, 0, 0, ix, nil, nil, ms);
+ spawn timeout(ev, ms);
+ return ix;
+}
+
+dotimeout(ex : ref Exec, win : ref Obj, e: ref ScriptEvent) : ref Ecmascript->Obj
+{
+ id := e.which;
+ if (id < 0)
+ return nil;
+
+ (toa, n) := getarraywithlen(ex, win, "@PRIVtoa");
+ if (toa == nil || id >= n)
+ return nil;
+
+ handler := getobj(ex, toa, string id);
+ if (handler == nil)
+ return nil;
+ if(e.kind == E->SEinterval){
+ ev := ref ScriptEvent;
+ *ev = *e;
+ spawn timeout(ev, e.ms);
+ return handler;
+ }
+ if (id == n-1)
+ ES->put(ex, toa, "length", ES->numval(real (n-1)));
+ else
+ ES->put(ex, toa, string id, ES->undefined);
+ return handler;
+}
+
+clrtimeout(ex : ref Exec, win : ref Obj, id : int)
+{
+ if (id < 0)
+ return;
+ (toa, n) := getarraywithlen(ex, win, "@PRIVtoa");
+ if (toa == nil || id >= n)
+ return;
+
+ ES->put(ex, toa, string id, ES->null);
+}
+
+# Make a host object with given class.
+# Get the prototype from the objspecs array
+# (if none yet, make one up and install the methods).
+# Put in required properties, with undefined values initially.
+# If mainex is nil (it will be for bootstrapping the initial object),
+# the prototype has to be filled in later.
+mkhostobj(ex : ref Exec, class: string) : ref Obj
+{
+ ci := specindex(class);
+ proto : ref Obj;
+ if(ex != nil)
+ proto = mkprototype(ex, ci);
+ ans := ES->mkobj(proto, class);
+ initprops(ex, ans, objspecs[ci].props);
+ ans.host = me;
+ return ans;
+}
+
+initprops(ex : ref Exec, o: ref Obj, props: array of PropSpec)
+{
+ if(props == nil)
+ return;
+ for(i := 0; i < len props; i++) {
+ v := ES->undefined;
+ case props[i].initval {
+ IVundef =>
+ v = ES->undefined;
+ IVnull =>
+ v = ES->null;
+ IVtrue =>
+ v = ES->true;
+ IVfalse =>
+ v = ES->false;
+ IVnullstr =>
+ v = nullstrval;
+ IVzero =>
+ v = zeroval;
+ IVzerostr =>
+ v = zerostrval;
+ IVarray =>
+ # need a separate one for each array,
+ # since we'll update these rather than replacing
+ ao := ES->mkobj(ex.arrayproto, "Array");
+ ES->varinstant(ao, ES->DontEnum|ES->DontDelete, "length", ref RefVal(ES->numval(0.)));
+ v = ES->objval(ao);
+ * =>
+ CU->assert(0);
+ }
+ ES->varinstant(o, props[i].attr | ES->DontDelete, props[i].name, ref RefVal(v));
+ }
+}
+
+# Return index into objspecs where class is specified
+specindex(class: string) : int
+{
+ for(i := 0; i < len objspecs; i++)
+ if(objspecs[i].name == class)
+ break;
+ if(i == len objspecs)
+ CU->raisex("EXInternal: couldn't find host object class " + class);
+ return i;
+}
+
+# Make a prototype for host object specified by objspecs[ci]
+mkprototype(ex : ref Exec, ci : int) : ref Obj
+{
+ CU->assert(ex != nil);
+ class := objspecs[ci].name;
+ prototype := ES->mkobj(ex.objproto, class);
+ meths := objspecs[ci].methods;
+ for(k := 0; k < len meths; k++) {
+ name := meths[k].name;
+ fullname := class + ".prototype." + name;
+ args := meths[k].args;
+ ES->biinst(prototype, Builtin(name, fullname, args, len args),
+ ex.funcproto, me);
+ }
+ return prototype;
+}
+
+
+getframeobj(frameid: int) : ref Obj
+{
+ sw := top.findbyframeid(frameid);
+ if(sw != nil)
+ return sw.ex.global;
+ return nil;
+}
+
+getdocobj(ex : ref Exec, frameid: int) : ref Obj
+{
+ return getobj(ex, getframeobj(frameid), "document");
+}
+
+getformobj(ex : ref Exec, frameid, formid: int) : ref Obj
+{
+ # frameids are 1-origin, document.forms is 0-origin
+ return getarrayelem(ex, getdocobj(ex, frameid), "forms", formid-1);
+}
+
+getformfieldobj(frameid, formid, fieldid: int) : ref Obj
+{
+ sw := top.findbyframeid(frameid);
+ if (sw == nil)
+ return nil;
+ flds : list of (ref Build->Formfield, ref Obj);
+ for (fl := sw.forms; fl != nil; fl = tl fl) {
+ sf := hd fl;
+ if (sf.form.formid == formid) {
+ flds = sf.fields;
+ break;
+ }
+ }
+ for (; flds != nil; flds = tl flds) {
+ (fld, obj) := hd flds;
+ if (fld.fieldid == fieldid)
+ return obj;
+ }
+ return nil;
+}
+
+getanchorobj(ex: ref Exec, frameid, anchorid: int) : ref Obj
+{
+ od := getdocobj(ex, frameid);
+ if(od != nil) {
+ (olinks, olinkslen) := getarraywithlen(ex, od, "links");
+ if(olinks != nil) {
+ for(i := 0; i < olinkslen; i++) {
+ ol := getobj(ex, olinks, string i);
+ if(ol != nil) {
+ v := ES->get(ex, ol, "@PRIVanchorid");
+ if(ES->isnum(v) && ES->toInt32(ex, v) == anchorid)
+ return ol;
+ }
+ }
+ }
+ }
+ return nil;
+}
+
+getimageobj(ex: ref Exec, frameid, imageid: int) : ref Obj
+{
+ od := getdocobj(ex, frameid);
+ if(od != nil) {
+ (oimages, oimageslen) := getarraywithlen(ex, od, "images");
+ if(oimages != nil) {
+ for(i := 0; i < oimageslen; i++) {
+ oi := getobj(ex, oimages, string i);
+ if(oi != nil) {
+ v := ES->get(ex, oi, "@PRIVimageid");
+ if(ES->isnum(v) && ES->toInt32(ex, v) == imageid)
+ return oi;
+ }
+ }
+ }
+ }
+ return nil;
+}
+
+# return nil if none such, or not an object
+getobj(ex : ref Exec, o: ref Obj, prop: string) : ref Obj
+{
+ if(o != nil) {
+ v := ES->get(ex, o, prop);
+ if(ES->isobj(v))
+ return ES->toObject(ex, v);
+ }
+ return nil;
+}
+
+# return nil if none such, or not an object
+getarrayelem(ex : ref Exec, o: ref Obj, arrayname: string, index: int) : ref Obj
+{
+ oarr := getobj(ex, o, arrayname);
+ if(oarr != nil) {
+ v := ES->get(ex, oarr, string index);
+ if(ES->isobj(v))
+ return ES->toObject(ex, v);
+ }
+ return nil;
+}
+
+# return "" if none such, or not a string
+getstr(ex : ref Exec, o: ref Obj, prop: string) : string
+{
+
+ if(o != nil) {
+ v := ES->get(ex, o, prop);
+ if(ES->isstr(v))
+ return ES->toString(ex, v);
+ }
+ return "";
+}
+
+# Property index, -1 if doesn't exist
+pind(o: ref Obj, prop: string) : int
+{
+ props := o.props;
+ for(i := 0; i < len props; i++){
+ if(props[i] != nil && props[i].name == prop)
+ return i;
+ }
+ return -1;
+}
+
+# Reinitialize property prop of object o to value v
+# (pay no attention to ReadOnly status, so can't use ES->put).
+# Assume the property exists already.
+reinitprop(o: ref Obj, prop: string, v: ref Val)
+{
+ i := pind(o, prop);
+ if(i < 0) {
+ # set up dummy ex for now - needs sorting out
+ ex := ref Exec;
+ ES->runtime(ex, nil, "missing property " + prop); # shouldn't happen
+ }
+ CU->assert(i >= 0);
+ o.props[i].val.val = v;
+}
+
+# Get the array object named aname from o, and also find its current
+# length value. If there is any problem, return (nil, 0).
+getarraywithlen(ex : ref Exec, o: ref Obj, aname: string) : (ref Obj, int)
+{
+ varray := ES->get(ex, o, aname);
+ if(ES->isobj(varray)) {
+ oarray := ES->toObject(ex, varray);
+ vlen := ES->get(ex, oarray, "length");
+ if(vlen != ES->undefined)
+ return (oarray, ES->toInt32(ex, vlen));
+ }
+ return (nil, 0);
+}
+
+# Put val v as property "index" of object oarray.
+# Also, if the name doesn't conflict with array properties, add the val as
+# a "name" property too
+arrayput(ex : ref Exec, oarray: ref Obj, index: int, name: string, v: ref Val)
+{
+ ES->put(ex, oarray, string index, v);
+ if (name != "length" && prop2index(name) == -1)
+ ES->put(ex, oarray, name, v);
+}
+
+prop2index(p: string): int
+{
+ if (p == nil)
+ return -1;
+ v := 0;
+ for (i := 0; i < len p; i++) {
+ c := p[i];
+ if (c < '0' || c > '9')
+ return -1;
+ v = 10 * v + c - '0';
+ }
+ return v;
+}
+
+# Instantiate window object.
+# mkhostobj has already put the property names and default initial values in;
+# we have to fill in the proper values.
+wininstant(sw: ref ScriptWin)
+{
+ ex := sw.ex;
+ w := ex.global;
+ f := sw.frame;
+
+ sw.error = 0;
+ prevkids := sw.kids;
+ sw.kids = nil;
+ sw.forms = nil;
+ sw.imgs = nil;
+ sw.active = 0;
+
+ # document to be init'd by xfertoscriptobjs - WRONG,
+ # has to be init'd up-front as one frame may refer
+ # to another's document object (esp. for document.write calls)
+ od := getobj(ex, w, "document");
+ if(od == nil) {
+ docv := ES->objval(mkhostobj(ex, "document"));
+ reinitprop(w, "document", docv);
+ od = getobj(ex, w, "document");
+ CU->assert(od != nil);
+ }
+
+ # frames[ ]
+ ao := ES->mkobj(ex.arrayproto, "Array");
+ ES->varinstant(ao, ES->DontEnum|ES->DontDelete, "length", ref RefVal(ES->numval(0.)));
+ reinitprop(w, "frames", ES->objval(ao));
+ for (kl := f.kids; kl != nil; kl = tl kl) {
+ klf := hd kl;
+ # look for original ScriptWin
+ for (oldkl := prevkids; oldkl != nil; oldkl = tl oldkl) {
+ oldksw := hd oldkl;
+ if (oldksw.frame == klf) {
+ wininstant(oldksw);
+ sw.kids = oldksw :: sw.kids;
+ break;
+ }
+ }
+ if (oldkl == nil)
+ sw.addkid(klf);
+ }
+ kn := 0;
+ for (swkl := sw.kids; swkl != nil; swkl = tl swkl) {
+ k := hd swkl;
+ # Yes, frame name should be defined as property of parent
+ arrayput(ex, ao, kn++, "", k.val);
+ if (k.frame != nil && k.frame.name != nil) {
+ ES->put(ex, ao, k.frame.name, k.val);
+ ES->varinstant(w, 0, k.frame.name, ref RefVal(k.val));
+ }
+ }
+
+ reinitprop(w, "length", ES->numval(real len f.kids));
+
+ v := ref Val;
+ if (sw.parent == nil)
+ v = ES->objval(w);
+ else
+ v = ES->objval(sw.parent.ex.global);
+ reinitprop(w, "parent", v);
+
+ if (f.name != nil)
+ reinitprop(w, "name", ES->strval(f.name));
+ reinitprop(w, "self", ES->objval(w));
+ reinitprop(w, "window", ES->objval(w));
+ reinitprop(w, "top", ES->objval(top.ex.global));
+ reinitprop(w, "Math", ES->get(ex, top.ex.global, "Math"));
+ reinitprop(w, "navigator", ES->get(ex, top.ex.global, "navigator"));
+}
+
+# Return initial document object value, based on d
+docinstant(ex: ref Exec, f: ref Layout->Frame) : ref Val
+{
+ od := mkhostobj(ex, "document");
+ docfill(ex, od, f);
+ return ES->objval(od);
+}
+
+# Fill in properties of doc object, based on d.
+# Can be called at various points during build.
+docfill(ex: ref Exec, od: ref Obj, f: ref Layout->Frame)
+{
+ sw := top.findbyframeid(f.id);
+ if(sw == nil)
+ return;
+ di := f.doc;
+ if(di.src != nil) {
+ reinitprop(od, "URL", ES->strval(di.src.tostring()));
+ reinitprop(od, "domain", ES->strval(di.src.host));
+ }
+ if(di.referrer != nil)
+ reinitprop(od, "referrer", ES->strval(di.referrer.tostring()));
+ if(di.doctitle != "")
+ reinitprop(od, "title", ES->strval(di.doctitle));
+ reinitprop(od, "lastModified", ES->strval(di.lastModified));
+ reinitprop(od, "bgColor", colorval(di.background.color));
+ reinitprop(od, "fgColor", colorval(di.text));
+ reinitprop(od, "alinkColor", colorval(di.alink));
+ reinitprop(od, "linkColor", colorval(di.link));
+ reinitprop(od, "vlinkColor", colorval(di.vlink));
+
+ # Forms in d.forms are in reverse order of appearance.
+ # Add any that aren't already in the document.forms object,
+ # assuming that the relative lengths will tell us what needs
+ # to be done.
+ if(di.forms != nil) {
+ newformslen := len di.forms;
+ oldformslen := len sw.forms;
+ oforms := getobj(ex, od, "forms");
+
+ # oforms should be non-nil, because the object is initialized
+ # to an empty array and is readonly. The following test
+ # is just defensive.
+ if(oforms != nil) {
+ # run through our existing list of forms, looking
+ # for any not marked as Transferred (happens as a result
+ # of a script being called in the body of a form, while it is
+ # still being parsed)
+ for (sfl := sw.forms; sfl != nil; sfl = tl sfl) {
+ sf := hd sfl;
+ form := sf.form;
+ if (form.state != B->FormTransferred) {
+# (sf.obj, sf.fields) = forminstant(ex, form, di.frameid);
+ (newobj, newfields) := forminstant(ex, form, di.frameid);
+ *sf.obj = *newobj;
+ sf.fields = newfields;
+ }
+ if (form.state == B->FormDone)
+ form.state = B->FormTransferred;
+ }
+
+ # process additional forms
+ fl := di.forms;
+ for(i := newformslen-1; i >= oldformslen; i--) {
+ form := hd fl;
+ fl = tl fl;
+ if (form.state != B->FormTransferred) {
+ sf := ref ScriptForm (form, nil, i, nil);
+ (sf.obj, sf.fields) = forminstant(ex, form, di.frameid);
+ arrayput(ex, oforms, i, form.name, ES->objval(sf.obj));
+ if(form.name != "")
+ ES->put(ex, od, form.name, ES->objval(sf.obj));
+ sw.forms = sf :: sw.forms;
+ }
+ if (form.state == B->FormDone)
+ form.state = B->FormTransferred;
+ }
+ }
+ }
+
+ # Charon calls "DestAnchor" what Netscape calls "Anchor".
+ # Use same method as for forms to discover new ones.
+ if(di.dests != nil) {
+ newdestslen := len di.dests;
+ (oanchors, oldanchorslen) := getarraywithlen(ex, od, "anchors");
+ if(oanchors != nil) {
+ dl := di.dests;
+ for(i := newdestslen-1; i >= oldanchorslen; i--) {
+ dest := hd dl;
+ dl = tl dl;
+ arrayput(ex, oanchors, i, dest.name, anchorinstant(ex, dest.name));
+ }
+ }
+ }
+
+ # Charon calls "Anchor" what Netscape calls "Link" (how confusing for us!).
+ # Use same method as for forms to discover new ones.
+ # BUG: Areas are supposed to be in this list too.
+ if(di.anchors != nil) {
+ newanchorslen := len di.anchors;
+ (olinks, oldlinkslen) := getarraywithlen(ex, od, "links");
+ if(olinks != nil) {
+ al := di.anchors;
+ for(i := newanchorslen-1; i >= oldlinkslen; i--) {
+ a := hd al;
+ al = tl al;
+ arrayput(ex, olinks, i, a.name, linkinstant(ex, a, f.id));
+ }
+ }
+ }
+
+ if(di.images != nil) {
+ newimageslen := len di.images;
+ (oimages, oldimageslen) := getarraywithlen(ex, od, "images");
+ if(oimages != nil) {
+ il := di.images;
+ for(i := newimageslen-1; i >= oldimageslen; i--) {
+ imit := hd il;
+ il = tl il;
+ pick ii := imit {
+ Iimage =>
+ vim := imageinstant(ex, ii);
+ arrayput(ex, oimages, i, ii.name, vim);
+ ES->put(ex, od, ii.name, vim);
+ if(ES->isobj(vim)) {
+ sw.imgs = ref ScriptImg(ii, vim.obj) :: sw.imgs;
+ }
+ }
+ }
+ }
+ }
+
+ # private variables
+ ES->varinstant(od, ES->DontEnum|ES->DontDelete, "@PRIVframeid",
+ ref RefVal(ES->numval(real di.frameid)));
+}
+
+forminstant(ex : ref Exec, form: ref Build->Form, frameid: int) : (ref Obj, list of (ref Build->Formfield, ref Obj))
+{
+ fields : list of (ref Build->Formfield, ref ES->Obj);
+ oform := mkhostobj(ex, "Form");
+ reinitprop(oform, "action", ES->strval(form.action.tostring()));
+ reinitprop(oform, "encoding", ES->strval("application/x-www-form-urlencoded"));
+ reinitprop(oform, "length", ES->numval(real form.nfields));
+ reinitprop(oform, "method", ES->strval(CU->hmeth[form.method]));
+ reinitprop(oform, "name", ES->strval(form.name));
+ reinitprop(oform, "target", ES->strval(form.target));
+ ffl := form.fields;
+ if(ffl != nil) {
+ velements := ES->get(ex, oform, "elements");
+ if(ES->isobj(velements)) {
+ oelements := ES->toObject(ex, velements);
+ for(i := 0; i < form.nfields; i++) {
+ field := hd ffl;
+ ffl = tl ffl;
+ vfield := fieldinstant(ex, field, oform);
+
+ # convert multiple fields of same name to an array
+ prev := ES->get(ex, oform, field.name);
+ if (prev != nil && ES->isobj(prev)) {
+ newix := 0;
+ ar : ref Obj;
+ if (ES->isarray(prev.obj)) {
+ ar = prev.obj;
+ vlen := ES->get(ex, ar, "length");
+ newix = ES->toInt32(ex, vlen);
+ } else {
+ # create a new array
+ ar = ES->mkobj(ex.arrayproto, "Array");
+ ES->varinstant(ar, ES->DontEnum|ES->DontDelete, "length", ref RefVal(ES->numval(real 2)));
+ ES->put(ex, oform, field.name, ES->objval(ar));
+ arrayput(ex, ar, 0, "", prev);
+ newix = 1;
+ }
+ arrayput(ex, ar, newix, "", vfield);
+ } else {
+ # first time we have seen a field of this name
+ ES->put(ex, oform, field.name, vfield);
+ }
+ # although it is incorrect to add field name to
+ # elements array (as well as being indexed)
+ # - gives rise to name clashes, e.g radio buttons
+ # do it because other browsers do and some fools use it!
+ arrayput(ex, oelements, i, field.name, vfield);
+ fields = (field, ES->toObject(ex, vfield)) :: fields;
+ }
+ }
+ }
+ for(el := form.events; el != nil; el = tl el) {
+ e := hd el;
+ hname := "";
+ case e.attid {
+ Lex->Aonreset =>
+ hname = "onreset";
+ form.evmask |= E->SEonreset;
+ Lex->Aonsubmit =>
+ hname = "onsubmit";
+ form.evmask |= E->SEonsubmit;
+ }
+ if(hname != "")
+ puthandler(ex, oform, hname, e.value);
+ }
+# form.events = nil;
+ # private variables
+ ES->varinstant(oform, ES->DontEnum|ES->DontDelete, "@PRIVformid",
+ ref RefVal(ES->numval(real form.formid)));
+ ES->varinstant(oform, ES->DontEnum|ES->DontDelete, "@PRIVframeid",
+ ref RefVal(ES->numval(real frameid)));
+ return (oform, fields);
+}
+
+fieldinstant(ex : ref Exec, field: ref Build->Formfield, oform: ref Obj) : ref Val
+{
+ ofield := mkhostobj(ex, "FormField");
+ reinitprop(ofield, "form", ES->objval(oform));
+ reinitprop(ofield, "name", ES->strval(field.name));
+ reinitprop(ofield, "value", ES->strval(field.value));
+ reinitprop(ofield, "defaultValue", ES->strval(field.value));
+ chkd := ES->false;
+ if((field.flags & Build->FFchecked) != byte 0)
+ chkd = ES->true;
+ reinitprop(ofield, "checked", chkd);
+ reinitprop(ofield, "defaultChecked", chkd);
+ nopts := len field.options;
+ reinitprop(ofield, "length", ES->numval(real nopts));
+ reinitprop(ofield, "selectedIndex", ES->numval(-1.0)); # BUG: search for selected option
+ ty : string;
+ case field.ftype {
+ Build->Ftext =>
+ ty = "text";
+ reinitprop(ofield, "value", ES->strval(field.value));
+ Build->Fpassword =>
+ ty = "password";
+ Build->Fcheckbox =>
+ ty = "checkbox";
+ Build->Fradio =>
+ ty = "radio";
+ Build->Fsubmit =>
+ ty = "submit";
+ Build->Fhidden =>
+ ty = "hidden";
+ Build->Fimage =>
+ ty = "image";
+ Build->Freset =>
+ ty = "reset";
+ Build->Ffile =>
+ ty = "fileupload";
+ Build->Fbutton =>
+ ty = "button";
+ Build->Fselect =>
+ ty = "select";
+ si := -1;
+ options := ES->mkobj(ex.arrayproto, "Array");
+ ES->varinstant(options, ES->DontEnum|ES->DontDelete, "length",
+ ref RefVal(ES->numval(real nopts)));
+ reinitprop(ofield, "options", ES->objval(options));
+ optl := field.options;
+ vfield := ES->objval(ofield);
+ for(i := 0; i < nopts; i++) {
+ opt := hd optl;
+ optl = tl optl;
+ oopt := mkhostobj(ex, "Option");
+ reinitprop(oopt, "index", ES->numval(real i));
+ reinitprop(oopt, "value", ES->strval(opt.value));
+ reinitprop(oopt, "text", ES->strval(opt.display));
+ # private variables
+ ES->put(ex, oopt, "@PRIVformfield", vfield);
+ if(opt.selected) {
+ si = i;
+ reinitprop(oopt, "selected", ES->true);
+ reinitprop(oopt, "defaultSelected", ES->true);
+ reinitprop(ofield, "selectedIndex", ES->numval(real i));
+ }
+ ES->put(ex, options, string i, ES->objval(oopt));
+ }
+ ES->put(ex, options, "selectedIndex", ES->numval(real si));
+ ES->put(ex, options, "@PRIVformfield", vfield);
+ options.host = me;
+ Build->Ftextarea =>
+ ty = "textarea";
+ }
+ reinitprop(ofield, "type", ES->strval(ty));
+ for(el := field.events; el != nil; el = tl el) {
+ e := hd el;
+ hname := "";
+ case e.attid {
+ Lex->Aonblur =>
+ hname = "onblur";
+ field.evmask |= E->SEonblur;
+ Lex->Aonchange =>
+ hname = "onchange";
+ field.evmask |= E->SEonchange;
+ Lex->Aonclick =>
+ hname = "onclick";
+ field.evmask |= E->SEonclick;
+ Lex->Aondblclick =>
+ hname = "ondblclick";
+ field.evmask |= E->SEondblclick;
+ Lex->Aonfocus =>
+ hname = "onfocus";
+ field.evmask |= E->SEonfocus;
+ Lex->Aonkeydown =>
+ hname = "onkeydown";
+ field.evmask |= E->SEonkeydown;
+ Lex->Aonkeypress =>
+ hname = "onkeypress";
+ field.evmask |= E->SEonkeypress;
+ Lex->Aonkeyup =>
+ hname = "onkeyup";
+ field.evmask |= E->SEonkeyup;
+ Lex->Aonmousedown =>
+ hname = "onmousedown";
+ field.evmask |= E->SEonmousedown;
+ Lex->Aonmouseup =>
+ hname = "onmouseup";
+ field.evmask |= E->SEonmouseup;
+ Lex->Aonselect =>
+ hname = "onselect";
+ field.evmask |= E->SEonselect;
+ }
+ if(hname != "")
+ puthandler(ex, ofield, hname, e.value);
+ }
+# field.events = nil;
+ # private variables
+ ES->varinstant(ofield, ES->DontEnum|ES->DontDelete, "@PRIVfieldid",
+ ref RefVal(ES->numval(real field.fieldid)));
+ return ES->objval(ofield);
+}
+
+# Make an event handler named hname in o, with given body.
+puthandler(ex: ref Exec, o: ref Obj, hname: string, hbody: string)
+{
+ c := ES->eval(ex, "function PRIVhandler() {" + hbody + "}");
+ hobj := getobj(ex, ex.global, "PRIVhandler");
+ if(hobj != nil) {
+ ES->put(ex, o, hname, ES->objval(hobj));
+ }
+}
+
+anchorinstant(ex : ref Exec, nm: string) : ref Val
+{
+ oanchor := mkhostobj(ex, "Anchor");
+ reinitprop(oanchor, "name", ES->strval(nm));
+ return ES->objval(oanchor);
+}
+
+# Build ensures that the anchor href has been made absolute
+linkinstant(ex: ref Exec, anchor: ref Build->Anchor, frameid: int) : ref Val
+{
+ olink := mkhostobj(ex, "Link");
+ u := anchor.href;
+ if(u != nil) {
+ if(u.frag != "")
+ reinitprop(olink, "hash", ES->strval("#" + u.frag));
+ host := u.host;
+ if(u.user != "" || u.passwd != "") {
+ host = u.user;
+ if(u.passwd != "")
+ host += ":" + u.passwd;
+ host += "@" + u.host;
+ }
+ reinitprop(olink, "host", ES->strval(host));
+ hostname := host;
+ if(u.port != "")
+ hostname += ":" + u.port;
+ reinitprop(olink, "hostname", ES->strval(hostname));
+ reinitprop(olink, "href", ES->strval(u.tostring()));
+ reinitprop(olink, "pathname", ES->strval(u.path));
+ if(u.port != "")
+ reinitprop(olink, "port", ES->strval(u.port));
+ reinitprop(olink, "protocol", ES->strval(u.scheme + ":"));
+ if(u.query != "")
+ reinitprop(olink, "search", ES->strval("?" + u.query));
+ }
+ if(anchor.target != "")
+ reinitprop(olink, "target", ES->strval(anchor.target));
+
+ for(el := anchor.events; el != nil; el = tl el) {
+ e := hd el;
+ hname := "";
+ case e.attid {
+ Lex->Aonclick =>
+ hname = "onclick";
+ anchor.evmask |= E->SEonclick;
+ Lex->Aondblclick =>
+ hname = "ondblclick";
+ anchor.evmask |= E->SEondblclick;
+ Lex->Aonkeydown =>
+ hname = "onkeydown";
+ anchor.evmask |= E->SEonkeydown;
+ Lex->Aonkeypress =>
+ hname = "onkeypress";
+ anchor.evmask |= E->SEonkeypress;
+ Lex->Aonkeyup =>
+ hname = "onkeyup";
+ anchor.evmask |= E->SEonkeyup;
+ Lex->Aonmousedown =>
+ hname = "onmousedown";
+ anchor.evmask |= E->SEonmousedown;
+ Lex->Aonmouseout =>
+ hname = "onmouseout";
+ anchor.evmask |= E->SEonmouseout;
+ Lex->Aonmouseover =>
+ hname = "onmouseover";
+ anchor.evmask |= E->SEonmouseover;
+ Lex->Aonmouseup =>
+ hname = "onmouseup";
+ anchor.evmask |= E->SEonmouseup;
+ }
+ if(hname != "")
+ puthandler(ex, olink, hname, e.value);
+ }
+ anchor.events = nil;
+ # private variable
+ ES->varinstant(olink, ES->DontEnum|ES->DontDelete, "@PRIVanchorid",
+ ref RefVal(ES->numval(real anchor.index)));
+ ES->varinstant(olink, ES->DontEnum|ES->DontDelete, "@PRIVframeid",
+ ref RefVal(ES->numval(real frameid)));
+
+ return ES->objval(olink);
+}
+
+imageinstant(ex: ref Exec, im: ref Build->Item.Iimage) : ref Val
+{
+ oim := mkhostobj(ex, "Image");
+ src := im.ci.src.tostring();
+ reinitprop(oim, "border", ES->numval(real im.border));
+ reinitprop(oim, "height", ES->numval(real im.imheight));
+ reinitprop(oim, "hspace", ES->numval(real im.hspace));
+ reinitprop(oim, "name", ES->strval(im.name));
+ reinitprop(oim, "src", ES->strval(src));
+ if(im.ci.lowsrc != nil)
+ reinitprop(oim, "lowsrc", ES->strval(im.ci.lowsrc.tostring()));
+ reinitprop(oim, "vspace", ES->numval(real im.vspace));
+ reinitprop(oim, "width", ES->numval(real im.imwidth));
+ if(im.ci.complete == 0)
+ done := ES->false;
+ else
+ done = ES->true;
+ reinitprop(oim, "complete", done);
+
+ el : list of Lex->Attr = nil;
+ if(im.genattr != nil)
+ el = im.genattr.events;
+ for(; el != nil; el = tl el) {
+ e := hd el;
+ hname := "";
+ case e.attid {
+ Lex->Aonabort =>
+ hname = "onabort";
+ im.genattr.evmask |= E->SEonabort;
+ Lex->Aondblclick =>
+ hname = "ondblclick";
+ im.genattr.evmask |= E->SEondblclick;
+ Lex->Aonerror =>
+ hname = "onerror";
+ im.genattr.evmask |= E->SEonerror;
+ Lex->Aonkeydown =>
+ hname = "onkeydown";
+ im.genattr.evmask |= E->SEonkeydown;
+ Lex->Aonkeypress =>
+ hname = "onkeypress";
+ im.genattr.evmask |= E->SEonkeypress;
+ Lex->Aonkeyup =>
+ hname = "onkeyup";
+ im.genattr.evmask |= E->SEonkeyup;
+ Lex->Aonload =>
+ hname = "onload";
+ im.genattr.evmask |= E->SEonload;
+ Lex->Aonmousedown =>
+ hname = "onmousedown";
+ im.genattr.evmask |= E->SEonmousedown;
+ Lex->Aonmouseout =>
+ hname = "onmouseout";
+ im.genattr.evmask |= E->SEonmouseout;
+ Lex->Aonmouseover =>
+ hname = "onmouseover";
+ im.genattr.evmask |= E->SEonmouseover;
+ Lex->Aonmouseup =>
+ hname = "onmouseup";
+ im.genattr.evmask |= E->SEonmouseup;
+ }
+ if(hname != "")
+ puthandler(ex, oim, hname, e.value);
+ }
+ if(im.genattr != nil)
+ im.genattr.events = nil;
+
+ # private variables
+ ES->varinstant(oim, ES->DontEnum|ES->DontDelete, "@PRIVimageid",
+ ref RefVal(ES->numval(real im.imageid)));
+ # to keep track of src as currently known in item
+# ES->varinstant(oim, ES->DontEnum|ES->DontDelete, "@PRIVsrc",
+# ref RefVal(ES->strval(src)));
+ return ES->objval(oim);
+}
+
+colorval(v: int) : ref Val
+{
+ return ES->strval(sys->sprint("%.6x", v));
+}
+
+# If the o.name is a recognizable color, return it, else dflt
+colorxfer(ex: ref Exec, o: ref Obj, name: string, dflt: int) : int
+{
+ v := ES->get(ex, o, name);
+ if(v == ES->undefined)
+ return dflt;
+ return CU->color(ES->toString(ex, v), dflt);
+}
+
+strxfer(ex : ref Exec, o: ref Obj, name: string, dflt: string) : string
+{
+ v := ES->get(ex, o, name);
+ if(v == ES->undefined)
+ return dflt;
+ return ES->toString(ex, v);
+}
+
+ScriptWin.new(f: ref Layout->Frame, ex: ref Exec, loc: ref Obj, par: ref ScriptWin) : ref ScriptWin
+{
+ return ref ScriptWin(f, ex, loc, ES->objval(ex.global), par, nil, nil, nil, "", "", "", 1, 0, 0, nil);
+}
+
+# Make a new ScriptWin with f as frame and new, empty
+# Window object as obj, to be a child window of sw's window.
+ScriptWin.addkid(sw: self ref ScriptWin, f: ref Layout->Frame)
+{
+ (cex, clocobj) := makeframeex(f);
+ csw := ScriptWin.new(f, cex, clocobj, sw);
+ wininstant(csw);
+ sw.kids = csw :: sw.kids;
+}
+
+ScriptWin.dummy(): ref ScriptWin
+{
+ f := ref Layout->Frame;
+ f.doc = ref Build->Docinfo;
+ f.doc.base = U->parse("");
+ f.doc.src = U->parse("");
+ (cex, clocobj) := makeframeex(f);
+ csw := ScriptWin.new(f, cex, clocobj, nil);
+ wininstant(csw);
+ return csw;
+}
+
+# Find the ScriptWin in the tree with sw as root that has
+# f as frame, returning nil if none.
+#ScriptWin.findbyframe(sw: self ref ScriptWin, f: ref Layout->Frame) : ref ScriptWin
+#{
+# if(sw.frame.id == f.id)
+# return sw;
+# for(l := sw.kids; l != nil; l = tl l) {
+# x := (hd l).findbyframe(f);
+# if(x != nil)
+# return x;
+# }
+# return nil;
+#}
+
+# Find the ScriptWin in the tree with sw as root that has
+# fid as frame id, returning nil if none.
+ScriptWin.findbyframeid(sw: self ref ScriptWin, fid: int) : ref ScriptWin
+{
+ if(sw.frame.id == fid)
+ return sw;
+ for(l := sw.kids; l != nil; l = tl l) {
+ x := (hd l).findbyframeid(fid);
+ if(x != nil)
+ return x;
+ }
+ return nil;
+}
+
+# Find the ScriptWin in the tree with sw as root that has
+# d as doc for the frame, returning nil if none.
+ScriptWin.findbydoc(sw: self ref ScriptWin, d: ref Build->Docinfo) : ref ScriptWin
+{
+ if(sw.frame.doc == d)
+ return sw;
+ for(l := sw.kids; l != nil; l = tl l) {
+ x := (hd l).findbydoc(d);
+ if(x != nil)
+ return x;
+ }
+ return nil;
+}
+
+# obj can either be the frame's Window obj, Location obj, or document obj,
+# or an Image object within the frame
+ScriptWin.findbyobj(sw : self ref ScriptWin, obj : ref Obj) : ref ScriptWin
+{
+ if (sw.locobj == obj || sw.ex.global == obj || obj == getdocobj(sw.ex, sw.frame.id))
+ return sw;
+ if(opener != nil && (opener.locobj == obj || opener.ex.global == obj))
+ return opener;
+ for(sil := sw.imgs; sil != nil; sil = tl sil) {
+ if((hd sil).obj == obj)
+ return sw;
+ }
+ for (l := sw.kids; l != nil; l = tl l) {
+ x := (hd l).findbyobj(obj);
+ if (x != nil)
+ return x;
+ }
+ return nil;
+}
+
+ScriptWin.findbyname(sw : self ref ScriptWin, name : string) : ref ScriptWin
+{
+ if (sw.frame != nil && sw.frame.name == name)
+ return sw;
+ for (l := sw.kids; l != nil; l = tl l) {
+ x := (hd l).findbyname(name);
+ if (x != nil)
+ return x;
+ }
+ return nil;
+}
+
+newcharon(url: string, nm: string, sw: ref ScriptWin)
+{
+ cs := chan of string;
+
+ spawn CH->startcharon(url, cs);
+ for(;;){
+ alt{
+ s := <- cs =>
+ if(s == "B")
+ continue;
+ if(s == "E")
+ exit;
+ (n, l) := sys->tokenize(s, " ");
+ case hd l{
+ "L" =>
+ sw.newloc = hd tl l;
+ sw.newloctarg = nm;
+ checknewlocs(sw);
+ }
+ }
+ }
+}
diff --git a/appl/charon/layout.b b/appl/charon/layout.b
new file mode 100644
index 00000000..6db8f619
--- /dev/null
+++ b/appl/charon/layout.b
@@ -0,0 +1,4828 @@
+implement Layout;
+
+include "common.m";
+include "keyboard.m";
+
+sys: Sys;
+CU: CharonUtils;
+ ByteSource, MaskedImage, CImage, ImageCache, max, min,
+ White, Black, Grey, DarkGrey, LightGrey, Blue, Navy, Red, Green, DarkRed: import CU;
+
+D: Draw;
+ Point, Rect, Font, Image, Display: import D;
+S: String;
+T: StringIntTab;
+U: Url;
+ Parsedurl: import U;
+I: Img;
+ ImageSource: import I;
+J: Script;
+E: Events;
+ Event: import E;
+G: Gui;
+ Popup: import G;
+B: Build;
+
+# B : Build, declared in layout.m so main program can use it
+ Item, ItemSource,
+ IFbrk, IFbrksp, IFnobrk, IFcleft, IFcright, IFwrap, IFhang,
+ IFrjust, IFcjust, IFsmap, IFindentshift, IFindentmask,
+ IFhangmask,
+ Voffbias,
+ ISPnull, ISPvline, ISPhspace, ISPgeneral,
+ Align, Dimen, Formfield, Option, Form,
+ Table, Tablecol, Tablerow, Tablecell,
+ Anchor, DestAnchor, Map, Area, Kidinfo, Docinfo,
+ Anone, Aleft, Acenter, Aright, Ajustify, Achar, Atop, Amiddle,
+ Abottom, Abaseline,
+ Dnone, Dpixels, Dpercent, Drelative,
+ Ftext, Fpassword, Fcheckbox, Fradio, Fsubmit, Fhidden, Fimage,
+ Freset, Ffile, Fbutton, Fselect, Ftextarea,
+ Background,
+ FntR, FntI, FntB, FntT, NumStyle,
+ Tiny, Small, Normal, Large, Verylarge, NumSize, NumFnt, DefFnt,
+ ULnone, ULunder, ULmid,
+ FRnoresize, FRnoscroll, FRhscroll, FRvscroll,
+ FRhscrollauto, FRvscrollauto
+ : import B;
+
+# font stuff
+Fontinfo : adt {
+ name: string;
+ f: ref Font;
+ spw: int; # width of a space in this font
+};
+
+fonts := array[NumFnt] of {
+ FntR*NumSize+Tiny => Fontinfo("/fonts/charon/plain.tiny.font", nil, 0),
+ FntR*NumSize+Small => ("/fonts/charon/plain.small.font", nil, 0),
+ FntR*NumSize+Normal => ("/fonts/charon/plain.normal.font", nil, 0),
+ FntR*NumSize+Large => ("/fonts/charon/plain.large.font", nil, 0),
+ FntR*NumSize+Verylarge => ("/fonts/charon/plain.vlarge.font", nil, 0),
+
+ FntI*NumSize+Tiny => ("/fonts/charon/italic.tiny.font", nil, 0),
+ FntI*NumSize+Small => ("/fonts/charon/italic.small.font", nil, 0),
+ FntI*NumSize+Normal => ("/fonts/charon/italic.normal.font", nil, 0),
+ FntI*NumSize+Large => ("/fonts/charon/italic.large.font", nil, 0),
+ FntI*NumSize+Verylarge => ("/fonts/charon/italic.vlarge.font", nil, 0),
+
+ FntB*NumSize+Tiny => ("/fonts/charon/bold.tiny.font", nil, 0),
+ FntB*NumSize+Small => ("/fonts/charon/bold.small.font", nil, 0),
+ FntB*NumSize+Normal => ("/fonts/charon/bold.normal.font", nil, 0),
+ FntB*NumSize+Large => ("/fonts/charon/bold.large.font", nil, 0),
+ FntB*NumSize+Verylarge => ("/fonts/charon/bold.vlarge.font", nil, 0),
+
+ FntT*NumSize+Tiny => ("/fonts/charon/cw.tiny.font", nil, 0),
+ FntT*NumSize+Small => ("/fonts/charon/cw.small.font", nil, 0),
+ FntT*NumSize+Normal => ("/fonts/charon/cw.normal.font", nil, 0),
+ FntT*NumSize+Large => ("/fonts/charon/cw.large.font", nil, 0),
+ FntT*NumSize+Verylarge => ("/fonts/charon/cw.vlarge.font", nil, 0)
+};
+
+# Seems better to use a slightly smaller font in Controls, to match other browsers
+CtlFnt: con (FntR*NumSize+Small);
+
+# color stuff. have hash table mapping RGB values to D->Image for that color
+Colornode : adt {
+ rgb: int;
+ im: ref Image;
+ next: ref Colornode;
+};
+
+# Source of info for page (html, image, etc.)
+Source: adt {
+ bs: ref ByteSource;
+ redirects: int;
+ pick {
+ Srequired or
+ Shtml =>
+ itsrc: ref ItemSource;
+ Simage =>
+ ci: ref CImage;
+ itl: list of ref Item;
+ imsrc: ref ImageSource;
+ }
+};
+
+Sources: adt {
+ main: ref Source;
+ reqd: ref Source;
+ srcs: list of ref Source;
+
+ new: fn(m : ref Source) : ref Sources;
+ add: fn(srcs: self ref Sources, s: ref Source, required: int);
+ done: fn(srcs: self ref Sources, s: ref Source);
+ waitsrc: fn(srcs : self ref Sources) : ref Source;
+};
+
+NCOLHASH : con 19; # 19 checked for standard colors: only 1 collision
+colorhashtab := array[NCOLHASH] of ref Colornode;
+
+# No line break should happen between adjacent characters if
+# they are 'wordchars' : set in this array, or outside the array range.
+# We include certain punctuation characters that are not traditionally
+# regarded as 'word' characters.
+wordchar := array[16rA0] of {
+ '!' => byte 1,
+ '0'=>byte 1, '1'=>byte 1, '2'=>byte 1, '3'=>byte 1, '4'=>byte 1,
+ '5'=>byte 1, '6'=>byte 1, '7'=>byte 1, '8'=>byte 1, '9'=>byte 1,
+ ':'=>byte 1, ';' => byte 1,
+ '?' => byte 1,
+ 'A'=>byte 1, 'B'=>byte 1, 'C'=>byte 1, 'D'=>byte 1, 'E'=>byte 1, 'F'=>byte 1,
+ 'G'=>byte 1, 'H'=>byte 1, 'I'=>byte 1, 'J'=>byte 1, 'K'=>byte 1, 'L'=>byte 1,
+ 'M'=>byte 1, 'N'=>byte 1, 'O'=>byte 1, 'P'=>byte 1, 'Q'=>byte 1, 'R'=>byte 1,
+ 'S'=>byte 1, 'T'=>byte 1, 'U'=>byte 1, 'V'=>byte 1, 'W'=>byte 1, 'X'=>byte 1,
+ 'Y'=>byte 1, 'Z'=>byte 1,
+ 'a'=>byte 1, 'b'=>byte 1, 'c'=>byte 1, 'd'=>byte 1, 'e'=>byte 1, 'f'=>byte 1,
+ 'g'=>byte 1, 'h'=>byte 1, 'i'=>byte 1, 'j'=>byte 1, 'k'=>byte 1, 'l'=>byte 1,
+ 'm'=>byte 1, 'n'=>byte 1, 'o'=>byte 1, 'p'=>byte 1, 'q'=>byte 1, 'r'=>byte 1,
+ 's'=>byte 1, 't'=>byte 1, 'u'=>byte 1, 'v'=>byte 1, 'w'=>byte 1, 'x'=>byte 1,
+ 'y'=>byte 1, 'z'=>byte 1,
+ '_'=>byte 1,
+ '\''=>byte 1, '"'=>byte 1, '.'=>byte 1, ','=>byte 1, '('=>byte 1, ')'=>byte 1,
+ * => byte 0
+};
+
+TABPIX: con 30; # number of pixels in a tab
+CAPSEP: con 5; # number of pixels separating tab from caption
+SCRBREADTH: con 14; # scrollbar breadth (normal)
+SCRFBREADTH: con 14; # scrollbar breadth (inside child frame or select control)
+FRMARGIN: con 0; # default margin around frames
+RULESP: con 7; # extra space before and after rules
+POPUPLINES: con 12; # number of lines in popup select list
+MINSCR: con 6; # min size in pixels of scrollbar drag widget
+SCRDELTASF: con 10000; # fixed-point scale factor for scrollbar per-pixel step
+
+# all of the following include room for relief
+CBOXWID: con 14; # check box width
+CBOXHT: con 12; # check box height
+ENTVMARGIN : con 4; # vertical margin inside entry box
+ENTHMARGIN : con 6; # horizontal margin inside entry box
+SELMARGIN : con 4; # margin inside select control
+BUTMARGIN: con 4; # margin inside button control
+PBOXWID: con 10; # progress box width
+PBOXHT: con 16; # progress box height
+PBOXBD: con 2; # progress box border width
+
+TABLEMAXTARGET: con 2000; # targetwidth to get max width of table cell
+TABLEFLOATTARGET: con 1; # targetwidth for floating tables
+
+SELBG: con 16r00FFFF; # aqua
+
+ARPAUSE : con 500; # autorepeat initial delay (ms)
+ARTICK : con 100; # autorepeat tick delay (ms)
+
+display: ref D->Display;
+
+dbg := 0;
+dbgtab := 0;
+dbgev := 0;
+linespace := 0;
+lineascent := 0;
+charspace := 0;
+spspace := 0;
+ctllinespace := 0;
+ctllineascent := 0;
+ctlcharspace := 0;
+ctlspspace := 0;
+frameid := 0;
+zp := Point(0,0);
+
+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();
+ E = cu->E;
+ G = cu->G;
+ I = cu->I;
+ J = cu->J;
+ B = cu->B;
+ display = G->display;
+
+ # make sure default and control fonts are loaded
+ getfont(DefFnt);
+ fnt := fonts[DefFnt].f;
+ linespace = fnt.height;
+ lineascent = fnt.ascent;
+ charspace = fnt.width("a"); # a kind of average char width
+ spspace = fonts[DefFnt].spw;
+ getfont(CtlFnt);
+ fnt = fonts[CtlFnt].f;
+ ctllinespace = fnt.height;
+ ctllineascent = fnt.ascent;
+ ctlcharspace = fnt.width("a");
+ ctlspspace = fonts[CtlFnt].spw;
+}
+
+stringwidth(s: string): int
+{
+ return fonts[DefFnt].f.width(s)/charspace;
+}
+
+# Use bsmain to fill frame f.
+# Return buffer containing source when done.
+layout(f: ref Frame, bsmain: ref ByteSource, linkclick: int) : array of byte
+{
+ dbg = int (CU->config).dbg['l'];
+ dbgtab = int (CU->config).dbg['t'];
+ dbgev = int (CU->config).dbg['e'];
+ if(dbgev)
+ CU->event("LAYOUT", 0);
+ sources : ref Sources;
+ hdr := bsmain.hdr;
+ auth := "";
+ url : ref Parsedurl;
+ if (bsmain.req != nil) {
+ auth = bsmain.req.auth;
+ url = bsmain.req.url;
+ }
+# auth := bsmain.req.auth;
+ ans : array of byte = nil;
+ di := Docinfo.new();
+ if(linkclick && f.doc != nil)
+ di.referrer = f.doc.src;
+ f.reset();
+ f.doc = di;
+ di.frameid = f.id;
+ di.src = hdr.actual;
+ di.base = hdr.base;
+ di.refresh = hdr.refresh;
+ if (hdr.chset != nil)
+ di.chset = hdr.chset;
+ di.lastModified = hdr.lastModified;
+ if(J != nil)
+ J->havenewdoc(f);
+ oclipr := f.cim.clipr;
+ f.cim.clipr = f.cr;
+ if(f.framebd != 0) {
+ f.cr = f.r.inset(2);
+ drawborder(f.cim, f.cr, 2, DarkGrey);
+ }
+ fillbg(f, f.cr);
+ G->flush(f.cr);
+ f.cim.clipr = oclipr;
+ if(f.flags&FRvscroll)
+ createvscroll(f);
+ if(f.flags&FRhscroll)
+ createhscroll(f);
+ l := Lay.new(f.cr.dx(), Aleft, f.marginw, di.background);
+ f.layout = l;
+ anyanim := 0;
+ if(hdr.mtype == CU->TextHtml || hdr.mtype == CU->TextPlain) {
+ itsrc := ItemSource.new(bsmain, f, hdr.mtype);
+ sources = Sources.new(ref Source.Shtml(bsmain, 0, itsrc));
+ }
+ else {
+ # for now, must be supported image type
+ if(!I->supported(hdr.mtype)) {
+ sys->print("Need to implement something: source isn't supported image type\n");
+ return nil;
+ }
+ imsrc := I->ImageSource.new(bsmain, 0, 0);
+ ci := CImage.new(url, nil, 0, 0);
+ simage := ref Source.Simage(bsmain, 0, ci, nil, imsrc);
+ sources = Sources.new(simage);
+ it := ref Item.Iimage(nil, 0, 0, 0, 0, 0, nil, len di.images, ci, 0, 0, "", nil, nil, -1, Abottom, byte 0, byte 0, byte 0);
+ di.images = it :: nil;
+ appenditems(f, l, it);
+ simage.itl = it :: nil;
+ }
+ while ((src := sources.waitsrc()) != nil) {
+ if(dbgev)
+ CU->event("LAYOUT GETSOMETHING", 0);
+ bs := src.bs;
+ freeit := 0;
+ if(bs.err != "") {
+ if(dbg)
+ sys->print("error getting %s: %s\n", bs.req.url.tostring(), bs.err);
+ pick s := src {
+ Srequired =>
+ s.itsrc.reqddata = array [0] of byte;
+ sources.done(src);
+ CU->freebs(bs);
+ src.bs = nil;
+ continue;
+ }
+ freeit = 1;
+ }
+ else {
+ if(bs.hdr != nil && !bs.seenhdr) {
+ (use, error, challenge, newurl) := CU->hdraction(bs, 0, src.redirects);
+ if(challenge != nil) {
+ sys->print("Need to implement authorization credential dialog\n");
+ error = "Need authorization";
+ use = 0;
+ }
+ if(error != "" && dbg)
+ sys->print("subordinate error: %s\n", error);
+ if(newurl != nil) {
+ s := ref *src;
+ freeit = 1;
+ pick ps := src {
+ Shtml or Srequired =>
+ sys->print("unexpected redirect of subord\n");
+ Simage =>
+ newci := CImage.new(newurl, nil, ps.ci.width, ps.ci.height);
+ for(itl := ps.itl; itl != nil ; itl = tl itl) {
+ pick imi := hd itl {
+ Iimage =>
+ imi.ci = newci;
+ }
+ }
+ news := ref Source.Simage(nil, 0, newci, ps.itl, nil);
+ sources.add(news, 0);
+ startimreq(news, auth);
+ }
+ }
+ if(!use)
+ freeit = 1;
+ }
+ if(!freeit) {
+ pick s := src {
+ Srequired or
+ Shtml =>
+ if (tagof src == tagof Source.Srequired) {
+ s.itsrc.reqddata = bs.data;
+ sources.done(src);
+ CU->freebs(bs);
+ src.bs = nil;
+ continue;
+# src = sources.main;
+# CU->assert(src != nil);
+ }
+ itl := s.itsrc.getitems();
+ if(di.kidinfo != nil) {
+ if(s.itsrc.kidstk == nil) {
+ layframeset(f, di.kidinfo);
+ G->flush(f.r);
+ freeit = 1;
+ }
+ }
+ else {
+ l.background = di.background;
+ anyanim |= addsubords(sources, di, auth);
+ if(itl != nil) {
+ appenditems(f, l, itl);
+ fixframegeom(f);
+ if(dbgev)
+ CU->event("LAYOUT_DRAWALL", 0);
+ f.dirty(f.totalr);
+ drawall(f);
+ }
+ }
+ if (s.itsrc.reqdurl != nil) {
+ news := ref Source.Srequired(nil, 0, s.itsrc);
+ sources.add(news, 1);
+ rbs := CU->startreq(ref CU->ReqInfo(s.itsrc.reqdurl, CU->HGet, nil, "", ""));
+ news.bs = rbs;
+ } else {
+ if (bs.eof && bs.lim == bs.edata && s.itsrc.toks == nil)
+ freeit = 1;
+ }
+ Simage =>
+ (ret, mim) := s.imsrc.getmim();
+ # mark it done even if error
+ s.ci.complete = ret;
+ if(ret == I->Mimerror) {
+ bs.err = s.imsrc.err;
+ freeit = 1;
+ }
+ else if(ret != I->Mimnone) {
+ if(s.ci.mims == nil) {
+ s.ci.mims = array[1] of { mim };
+ s.ci.width = s.imsrc.width;
+ s.ci.height = s.imsrc.height;
+ if(ret == I->Mimdone && (CU->config).imagelvl <= CU->ImgNoAnim)
+ freeit = 1;
+ }
+ else {
+ n := len s.ci.mims;
+ if(mim != s.ci.mims[n-1]) {
+ newmims := array[n + 1] of ref MaskedImage;
+ newmims[0:] = s.ci.mims;
+ newmims[n] = mim;
+ s.ci.mims = newmims;
+ anyanim = 1;
+ }
+ }
+ if(s.ci.mims[0] == mim)
+ haveimage(f, s.ci, s.itl);
+ if(bs.eof && bs.lim == bs.edata)
+ (CU->imcache).add(s.ci);
+ }
+ if(!freeit && bs.eof && bs.lim == bs.edata)
+ freeit = 1;
+ }
+ }
+ }
+ if(freeit) {
+ if(bs == bsmain)
+ ans = bs.data[0:bs.edata];
+ CU->freebs(bs);
+ src.bs = nil;
+ sources.done(src);
+ }
+ }
+ if(anyanim && (CU->config).imagelvl > CU->ImgNoAnim)
+ spawn animproc(f);
+ if(dbgev)
+ CU->event("LAYOUT_END", 0);
+ return ans;
+}
+
+# return value is 1 if found any existing images needed animation
+addsubords(sources: ref Sources, di: ref Docinfo, auth: string) : int
+{
+ anyanim := 0;
+ if((CU->config).imagelvl == CU->ImgNone)
+ return anyanim;
+ newsims: list of ref Source.Simage = nil;
+ for(il := di.images; il != nil; il = tl il) {
+ it := hd il;
+ pick i := it {
+ Iimage =>
+ if(i.ci.mims == nil) {
+ cachedci := (CU->imcache).look(i.ci);
+ if(cachedci != nil) {
+ i.ci = cachedci;
+ if(i.imwidth == 0)
+ i.imwidth = i.ci.width;
+ if(i.imheight == 0)
+ i.imheight = i.ci.height;
+ anyanim |= (len cachedci.mims > 1);
+ }
+ else {
+ sloop:
+ for(sl := sources.srcs; sl != nil; sl = tl sl) {
+ pick s := hd sl {
+ Simage =>
+ if(s.ci.match(i.ci)) {
+ s.itl = it :: s.itl;
+ # want all items on list to share same ci;
+ # want most-specific dimension specs
+ iciw := i.ci.width;
+ icih := i.ci.height;
+ i.ci = s.ci;
+ if(s.ci.width == 0 && s.ci.height == 0) {
+ s.ci.width = iciw;
+ s.ci.height = icih;
+ }
+ break sloop;
+ }
+ }
+ }
+ if(sl == nil) {
+ # didn't find existing Source for this image
+ s := ref Source.Simage(nil, 0, i.ci, it:: nil, nil);
+ newsims = s :: newsims;
+ sources.add(s, 0);
+ }
+ }
+ }
+ }
+ }
+ # Start requests for new newsources.
+ # di.images are in last-in-document-first order,
+ # so newsources is in first-in-document-first order (good order to load in).
+ for(sl := newsims; sl != nil; sl = tl sl)
+ startimreq(hd sl, auth);
+ return anyanim;
+}
+
+startimreq(s: ref Source.Simage, auth: string)
+{
+ if(dbgev)
+ CU->event(sys->sprint("LAYOUT STARTREQ %s", s.ci.src.tostring()), 0);
+ bs := CU->startreq(ref CU->ReqInfo(s.ci.src, CU->HGet, nil, auth, ""));
+ s.bs = bs;
+ s.imsrc = I->ImageSource.new(bs, s.ci.width, s.ci.height);
+}
+
+createvscroll(f: ref Frame)
+{
+ breadth := SCRBREADTH;
+ if(f.parent != nil)
+ breadth = SCRFBREADTH;
+ length := f.cr.dy();
+ if(f.flags&FRhscroll)
+ length -= breadth;
+ f.vscr = Control.newscroll(f, 1, length, breadth);
+ f.vscr.r = f.vscr.r.addpt(Point(f.cr.max.x-breadth, f.cr.min.y));
+ f.cr.max.x -= breadth;
+ if(f.cr.dx() <= 2*f.marginw)
+ CU->raisex("EXInternal: frame too small for layout");
+ f.vscr.draw(1);
+}
+
+createhscroll(f: ref Frame)
+{
+ breadth := SCRBREADTH;
+ if(f.parent != nil)
+ breadth = SCRFBREADTH;
+ length := f.cr.dx();
+ x := f.cr.min.x;
+ f.hscr = Control.newscroll(f, 0, length, breadth);
+ f.hscr.r = f.hscr.r.addpt(Point(x,f.cr.max.y-breadth));
+ f.cr.max.y -= breadth;
+ if(f.cr.dy() <= 2*f.marginh)
+ CU->raisex("EXInternal: frame too small for layout");
+ f.hscr.draw(1);
+}
+
+# Call after a change to f.layout or f.viewr.min to fix totalr and viewr
+# (We are to leave viewr.min unchanged, if possible, as
+# user might be scrolling).
+fixframegeom(f: ref Frame)
+{
+ l := f.layout;
+ if(dbg)
+ sys->print("fixframegeom, layout width=%d, height=%d\n", l.width, l.height);
+ crwidth := f.cr.dx();
+ crheight := f.cr.dy();
+ layw := max(l.width, crwidth);
+ layh := max(l.height, crheight);
+ f.totalr.max = Point(layw, layh);
+ crchanged := 0;
+ n := l.height+l.margin-crheight;
+ if(n > 0 && f.vscr == nil && (f.flags&FRvscrollauto)) {
+ createvscroll(f);
+ crchanged = 1;
+ crwidth = f.cr.dx();
+ }
+ if(f.viewr.min.y > n)
+ f.viewr.min.y = max(0, n);
+ n = l.width+l.margin-crwidth;
+ if(!crchanged && n > 0 && f.hscr == nil && (f.flags&FRhscrollauto)) {
+ createhscroll(f);
+ crchanged = 1;
+ crheight = f.cr.dy();
+ }
+ if(crchanged) {
+ relayout(f, l, crwidth, l.just);
+ fixframegeom(f);
+ return;
+ }
+ if(f.viewr.min.x > n)
+ f.viewr.min.x = max(0, n);
+ f.viewr.max.x = min(f.viewr.min.x+crwidth, layw);
+ f.viewr.max.y = min(f.viewr.min.y+crheight, layh);
+ if(f.vscr != nil)
+ f.vscr.scrollset(f.viewr.min.y, f.viewr.max.y, f.totalr.max.y, 0, 1);
+ if(f.hscr != nil)
+ f.hscr.scrollset(f.viewr.min.x, f.viewr.max.x, f.totalr.max.x, f.viewr.dx()/5, 1);
+}
+
+# The items its within f are Iimage items,
+# and its image, ci, now has at least a ci.mims[0], which may be partially
+# or fully filled.
+haveimage(f: ref Frame, ci: ref CImage, itl: list of ref Item)
+{
+ if(dbgev)
+ CU->event("HAVEIMAGE", 0);
+ if(dbg)
+ sys->print("\nHAVEIMAGE src=%s w=%d h=%d\n", ci.src.tostring(), ci.width, ci.height);
+ # make all base images repl'd - makes handling backgrounds much easier
+ im := ci.mims[0].im;
+ im.repl = 1;
+ im.clipr = Rect((-16rFFFFFFF, -16r3FFFFFFF), (16r3FFFFFFF, 16r3FFFFFFF));
+ dorelayout := 0;
+ for( ; itl != nil; itl = tl itl) {
+ it := hd itl;
+ pick i := it {
+ Iimage =>
+ if (!(it.state & B->IFbkg)) {
+ # If i.imwidth and i.imheight are not both 0, the HTML specified the dimens.
+ # If one of them is 0, the other is to be scaled by the same factor;
+ # we have to relay the line in that case too.
+ if(i.imwidth == 0 || i.imheight == 0) {
+ i.imwidth = ci.width;
+ i.imheight = ci.height;
+ setimagedims(i);
+ loc := f.find(zp, it);
+ # sometimes the image was added to doc image list, but
+ # never made it to layout (e.g., because html bug prevented
+ # a table from being added).
+ # also, script-created images won't have items
+ if(loc != nil) {
+ f.layout.flags |= Lchanged;
+ markchanges(loc);
+ dorelayout = 1;
+ # Floats are assumed to be premeasured, so if there
+ # are any floats in the loc list, remeasure them
+ for(k := loc.n-1; k > 0; k--) {
+ if(loc.le[k].kind == LEitem) {
+ locit := loc.le[k].item;
+ pick fit := locit {
+ Ifloat =>
+ pick xi := fit.item {
+ Iimage =>
+ fit.height = fit.item.height;
+ Itable =>
+ checktabsize(f, xi, TABLEFLOATTARGET);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if(dbg > 1) {
+ sys->print("\nhaveimage item: ");
+ it.print();
+ }
+ }
+ }
+ if(dorelayout) {
+ relayout(f, f.layout, f.layout.targetwidth, f.layout.just);
+ fixframegeom(f);
+ }
+ f.dirty(f.totalr);
+ drawall(f);
+ if(dbgev)
+ CU->event("HAVEIMAGE_END", 0);
+}
+# For first layout of subelements, such as table cells.
+# After this, content items will be dispersed throughout resulting lay.
+# Return index into f.sublays.
+# (This roundabout way of storing sublayouts avoids pointers to Lay
+# in Build, so that all of the layout-related stuff can be in Layout
+# where it belongs.)
+sublayout(f: ref Frame, targetwidth: int, just: byte, bg: Background, content: ref Item) : int
+{
+ if(dbg)
+ sys->print("sublayout, targetwidth=%d\n", targetwidth);
+ l := Lay.new(targetwidth, just, 0, bg);
+ if(f.sublayid >= len f.sublays) {
+ newsublays := array[len f.sublays + 30] of ref Lay;
+ newsublays[0:] = f.sublays;
+ f.sublays = newsublays;
+ }
+ id := f.sublayid;
+ f.sublays[id] = l;
+ f.sublayid++;
+ appenditems(f, l, content);
+ l.flags &= ~Lchanged;
+ if(dbg)
+ sys->print("after sublayout, width=%d\n", l.width);
+ return id;
+}
+
+# Relayout of lay, given a new target width or if something changed inside
+# or if the global justification for the layout changed.
+# Floats are hard: for now, just relay everything with floats temporarily
+# moved way down, if there are any floats.
+relayout(f: ref Frame, lay: ref Lay, targetwidth: int, just: byte)
+{
+ if(dbg)
+ sys->print("relayout, targetwidth=%d, old target=%d, changed=%d\n",
+ targetwidth, lay.targetwidth, (lay.flags&Lchanged) != byte 0);
+ changeall := (lay.targetwidth != targetwidth || lay.just != just);
+ if(!changeall && !int(lay.flags&Lchanged))
+ return;
+ if(lay.floats != nil) {
+ # move the current y positions of floats to a big value,
+ # so they don't contribute to floatw until after they've
+ # been encountered in current fixgeom
+ for(flist := lay.floats; flist != nil; flist = tl flist) {
+ ff := hd flist;
+ ff.y = 16r6fffffff;
+ }
+ changeall = 1;
+ }
+ lay.targetwidth = targetwidth;
+ lay.just = just;
+ lay.height = 0;
+ lay.width = 0;
+ if(changeall)
+ changelines(lay.start.next, lay.end);
+ fixgeom(f, lay, lay.start.next);
+ lay.flags &= ~Lchanged;
+ if(dbg)
+ sys->print("after relayout, width=%d\n", lay.width);
+}
+
+# Measure and append the items to the end of layout lay,
+# and fix the geometry.
+appenditems(f: ref Frame, lay: ref Lay, items: ref Item)
+{
+ measure(f, items);
+ if(dbg)
+ items.printlist("appenditems, after measure");
+ it := items;
+ if(it == nil)
+ return;
+ lprev := lay.end.prev;
+ l : ref Line;
+ lit := lastitem(lprev.items);
+ if(lit == nil || (it.state&IFbrk)) {
+ # start a new line after existing last line
+ l = Line.new();
+ appendline(lprev, l);
+ l.items = it;
+ }
+ else {
+ # start appending items to existing last line
+ l = lprev;
+ lit.next = it;
+ }
+ l.flags |= Lchanged;
+ while(it != nil) {
+ nexti := it.next;
+ if(nexti == nil || (nexti.state&IFbrk)) {
+ it.next = nil;
+ fixgeom(f, lay, l);
+ if(nexti == nil)
+ break;
+ # now there may be multiple lines containing the
+ # items from l, but the one after the last is lay.end
+ l = Line.new();
+ appendline(lay.end.prev, l);
+ l.flags |= Lchanged;
+ it = nexti;
+ l.items = it;
+ }
+ else
+ it = nexti;
+ }
+}
+
+# Fix up the geometry of line l and successors.
+# Assume geometry of previous line is correct.
+fixgeom(f: ref Frame, lay: ref Lay, l: ref Line)
+{
+ while(l != nil) {
+ fixlinegeom(f, lay, l);
+ mergetext(l);
+ l = l.next;
+ }
+ lay.height = max(lay.height, lay.end.pos.y);
+}
+
+mergetext(l: ref Line)
+{
+ lastit : ref Item;
+ for (it := l.items; it != nil; it = it.next) {
+ pick i := it {
+ Itext =>
+ if (lastit == nil)
+ break; #pick
+ pick pi := lastit {
+ Itext =>
+ # ignore item state flags as fixlinegeom()
+ # will have taken account of them.
+ if (pi.anchorid == i.anchorid &&
+ pi.fnt == i.fnt && pi.fg == i.fg && pi.voff == i.voff && pi.ul == i.ul) {
+ # compatible - merge
+ pi.s += i.s;
+ pi.width += i.width;
+ pi.next = i.next;
+ continue;
+ }
+ }
+ }
+ lastit = it;
+ }
+}
+
+# Fix geom for one line.
+# This may change the overall lay.width, if there is no way
+# to fit the line into the target width.
+fixlinegeom(f: ref Frame, lay: ref Lay, l: ref Line)
+{
+ lprev := l.prev;
+ y := lprev.pos.y + lprev.height;
+ it := l.items;
+ state := it.state;
+ if(dbg > 1) {
+ sys->print("\nfixlinegeom start, y=prev.y+prev.height=%d+%d=%d, changed=%d\n",
+ l.prev.pos.y, lprev.height, y, int (l.flags&Lchanged));
+ if(dbg > 2)
+ it.printlist("items");
+ else {
+ sys->print("first item: ");
+ it.print();
+ }
+ }
+ if(state&IFbrk) {
+ y = pastbrk(lay, y, state);
+ if(dbg > 1 && y != lprev.pos.y + lprev.height)
+ sys->print("after pastbrk, line y is now %d\n", y);
+ }
+ l.pos.y = y;
+ lineh := max(l.height, linespace);
+ lfloatw := floatw(y, y+lineh, lay.floats, Aleft);
+ rfloatw := floatw(y, y+lineh, lay.floats, Aright);
+ if((l.flags&Lchanged) == byte 0) {
+ # possibly adjust lay.width
+ n := (lay.width-rfloatw)-(l.pos.x-lay.margin+l.width);
+ if(n < 0)
+ lay.width += -n;
+ return;
+ }
+ hang := (state&IFhangmask)*TABPIX/10;
+ linehang := hang;
+ hangtogo := hang;
+ indent := ((state&IFindentmask)>>IFindentshift)*TABPIX;
+ just := (state&(IFcjust|IFrjust));
+ if(just == 0 && lay.just != Aleft) {
+ if(lay.just == byte Acenter)
+ just = IFcjust;
+ else if(lay.just == Aright)
+ just = IFrjust;
+ }
+ right := lay.targetwidth - lay.margin;
+ lwid := right - (lfloatw+rfloatw+indent+lay.margin);
+ if(lwid < 0) {
+ if (right - lwid > lay.width)
+ lay.width = right - lwid;
+ right += -lwid;
+ lwid = 0;
+ }
+ lwid += hang;
+ if(dbg > 1) {
+ sys->print("fixlinegeom, now y=%d, lfloatw=%d, rfloatw=%d, indent=%d, hang=%d, lwid=%d\n",
+ y, lfloatw, rfloatw, indent, hang, lwid);
+ }
+ w := 0;
+ lineh = 0;
+ linea := 0;
+ lastit: ref Item = nil;
+ nextfloats: list of ref Item.Ifloat = nil;
+ anystuff := 0;
+ eol := 0;
+ while(it != nil && !eol) {
+ if(dbg > 2) {
+ sys->print("fixlinegeom loop head, w=%d, loop item:\n", w);
+ it.print();
+ }
+ state = it.state;
+ wrapping := int (state&IFwrap);
+ if(anystuff && (state&IFbrk))
+ break;
+ checkw := 1;
+ if(hang && !(state&IFhangmask)) {
+ lwid -= hang;
+ hang = 0;
+ if(hangtogo > 0) {
+ # insert a null spacer item
+ spaceit := Item.newspacer(ISPgeneral, 0);
+ spaceit.width = hangtogo;
+ if(lastit != nil) {
+ spaceit.state = lastit.state & ~(IFbrk|IFbrksp|IFnobrk|IFcleft|IFcright);
+ lastit.next = spaceit;
+ }
+ else
+ lastit = spaceit;
+ spaceit.next = it;
+ }
+ }
+ pick i := it {
+ Ifloat =>
+ if(anystuff) {
+ # float will go after this line
+ nextfloats = i :: nextfloats;
+ }
+ else {
+ # add float beside current line, adjust widths
+ fixfloatxy(lay, y, i);
+ # TODO: only do following if y and/or height changed
+ changelines(l.next, lay.end);
+ newlfloatw := floatw(y, y+lineh, lay.floats, Aleft);
+ newrfloatw := floatw(y, y+lineh, lay.floats, Aright);
+ lwid -= (newlfloatw-lfloatw) + (newrfloatw-rfloatw);
+ if (lwid < 0) {
+ right += -lwid;
+ lwid = 0;
+ }
+ lfloatw = newlfloatw;
+ rfloatw = newrfloatw;
+ }
+ checkw = 0;
+ Itable =>
+ # When just doing layout for cell dimensions, don't
+ # want a "100%" spec to make the table really wide
+ kindspec := 0;
+ if(lay.targetwidth == TABLEMAXTARGET && i.table.width.kind() == Dpercent) {
+ kindspec = i.table.width.kindspec;
+ i.table.width = Dimen.make(Dnone, 0);
+ }
+ checktabsize(f, i, lwid-w);
+ if(kindspec != 0)
+ i.table.width.kindspec = kindspec;
+ Irule =>
+ avail := lwid-w;
+ # When just doing layout for cell dimensions, don't
+ # want a "100%" spec to make the rule really wide
+ if(lay.targetwidth == TABLEMAXTARGET)
+ avail = min(10, avail);
+ i.width = widthfromspec(i.wspec, avail);
+ Iformfield =>
+ checkffsize(f, i, i.formfield);
+ }
+ if(checkw) {
+ iw := it.width;
+ if(wrapping && w + iw > lwid) {
+ # it doesn't fit; see if it can be broken
+ takeit: int;
+ noneok := (anystuff || lfloatw != 0 || rfloatw != 0) && !(state&IFnobrk);
+ (takeit, iw) = trybreak(it, lwid-w, iw, noneok);
+ eol = 1;
+ if(!takeit) {
+ if(lastit == nil) {
+ # Nothing added because one of the float widths
+ # is nonzero, and not enough room for anything else.
+ # Move y down until there's more room and try again.
+ CU->assert(lfloatw != 0 || rfloatw != 0);
+ oldy := y;
+ y = pastbrk(lay, y, IFcleft|IFcright);
+ if(dbg > 1)
+ sys->print("moved y past %d, now y=%d\n", oldy, y);
+ CU->assert(y > oldy); # else infinite recurse
+ # Do the move down by artificially increasing the
+ # height of the previous line
+ lprev.height += y-oldy;
+ fixlinegeom(f, lay, l);
+ return;
+ } else
+ break;
+ }
+ }
+ w += iw;
+ if(hang)
+ hangtogo -= w;
+ (lineh, linea) = lgeom(lineh, linea, it);
+ if(!anystuff) {
+ anystuff = 1;
+ # don't count an ordinary space as 'stuff' if wrapping
+ pick t := it {
+ Itext =>
+ if(wrapping && t.s == " ")
+ anystuff = 0;
+ }
+ }
+ }
+ lastit = it;
+ it = it.next;
+ if(it == nil && !eol) {
+ # perhaps next lines items can now fit on this line
+ nextl := l.next;
+ nit := nextl.items;
+ if(nextl != lay.end && !(nit.state&IFbrk)) {
+ lastit.next = nit;
+ # remove nextl
+ l.next = nextl.next;
+ l.next.prev = l;
+ it = nit;
+ }
+ }
+ }
+ # line is complete, next line will start with it (or it is nil)
+ rest := it;
+ if(lastit == nil)
+ CU->raisex("EXInternal: no items on line");
+ lastit.next = nil;
+
+ l.width = w;
+ x := lfloatw + lay.margin + indent - linehang;
+ # shift line if it begins with a space or a rule
+ pick pi := l.items {
+ Itext =>
+ if(pi.s != nil && pi.s[0] == ' ')
+ x -= fonts[pi.fnt].spw;
+ Irule =>
+ # note: build ensures that rules appear on lines
+ # by themselves
+ if(pi.align == Acenter)
+ just = IFcjust;
+ else if(pi.align == Aright)
+ just = IFrjust;
+ Ifloat =>
+ if(pi.next != nil) {
+ pick qi := pi.next {
+ Itext =>
+ if(qi.s != nil && qi.s[0] == ' ')
+ x -= fonts[qi.fnt].spw;
+ }
+ }
+ }
+ xright := x+w;
+ if (xright + rfloatw > lay.width)
+ lay.width = xright+rfloatw;
+ n := lay.targetwidth-(lay.margin+rfloatw+xright);
+ if(n > 0 && just) {
+ if(just&IFcjust)
+ x += n/2;
+ else
+ x += n;
+ }
+ if(dbg > 1) {
+ sys->print("line geometry fixed, (x,y)=(%d,%d), w=%d, h=%d, a=%d, lfloatw=%d, rfloatw=%d, lay.width=%d\n",
+ x, l.pos.y, w, lineh, linea, lfloatw, rfloatw, lay.width);
+ if(dbg > 2)
+ l.items.printlist("final line items");
+ }
+ l.pos.x = x;
+ l.height = lineh;
+ l.ascent = linea;
+ l.flags &= ~Lchanged;
+
+ if(nextfloats != nil)
+ fixfloatsafter(lay, l, nextfloats);
+
+ if(rest != nil) {
+ nextl := l.next;
+ if(nextl == lay.end || (nextl.items.state&IFbrk)) {
+ nextl = Line.new();
+ appendline(l, nextl);
+ }
+ li := lastitem(rest);
+ li.next = nextl.items;
+ nextl.items = rest;
+ nextl.flags |= Lchanged;
+ }
+}
+
+# Return y coord after y due to a break.
+pastbrk(lay: ref Lay, y, state: int) : int
+{
+ nextralines := 0;
+ if(state&IFbrksp)
+ nextralines = 1;
+ ynext := y;
+ if(state&IFcleft)
+ ynext = floatclry(lay.floats, Aleft, ynext);
+ if(state&IFcright)
+ ynext = max(ynext, floatclry(lay.floats, Aright, ynext));
+ ynext += nextralines*linespace;
+ return ynext;
+}
+
+# Add line l after lprev (and before lprev's current successor)
+appendline(lprev, l: ref Line)
+{
+ l.next = lprev.next;
+ l.prev = lprev;
+ l.next.prev = l;
+ lprev.next = l;
+}
+
+# Mark lines l up to but not including lend as changed
+changelines(l, lend: ref Line)
+{
+ for( ; l != lend; l = l.next)
+ l.flags |= Lchanged;
+}
+
+# Return a ref Font for font number num = (style*NumSize + size)
+getfont(num: int) : ref Font
+{
+ f := fonts[num].f;
+ if(f == nil) {
+ f = Font.open(display, fonts[num].name);
+ if(f == nil) {
+ if(num == DefFnt)
+ CU->raisex(sys->sprint("exLayout: can't open default font %s: %r", fonts[num].name));
+ else {
+ if(int (CU->config).dbg['w'])
+ sys->print("warning: substituting default for font %s\n",
+ fonts[num].name);
+ f = fonts[DefFnt].f;
+ }
+ }
+ fonts[num].f = f;
+ fonts[num].spw = f.width(" ");
+ }
+ return f;
+}
+
+# Set the width, height and ascent fields of all items, getting any necessary fonts.
+# Some widths and heights depend on the available width on the line, and may be
+# wrong until checked during fixlinegeom.
+# Don't do tables here at all (except floating tables).
+# Configure Controls for form fields.
+measure(fr: ref Frame, items: ref Item)
+{
+ for(it := items; it != nil; it = it.next) {
+ pick t := it {
+ Itext =>
+ f := getfont(t.fnt);
+ it.width = f.width(t.s);
+ a := f.ascent;
+ h := f.height;
+ if(t.voff != byte Voffbias) {
+ a -= (int t.voff) - Voffbias;
+ if(a > h)
+ h = a;
+ }
+ it.height = h;
+ it.ascent = a;
+ Irule =>
+ it.height = t.size + 2*RULESP;
+ it.ascent = t.size + RULESP;
+ Iimage =>
+ setimagedims(t);
+ Iformfield =>
+ c := Control.newff(fr, t.formfield);
+ if(c != nil) {
+ t.formfield.ctlid = fr.addcontrol(c);
+ it.width = c.r.dx();
+ it.height = c.r.dy();
+ it.ascent = it.height;
+ pick pc := c {
+ Centry =>
+ it.ascent = lineascent + ENTVMARGIN;
+ Cselect =>
+ it.ascent = lineascent + SELMARGIN;
+ Cbutton =>
+ if(pc.dorelief)
+ it.ascent -= BUTMARGIN;
+ }
+ }
+ Ifloat =>
+ # Leave w at zero, so it doesn't contribute to line width in normal way
+ # (Can find its width in t.item.width).
+ pick i := t.item {
+ Iimage =>
+ setimagedims(i);
+ it.height = t.item.height;
+ Itable =>
+ checktabsize(fr, i, TABLEFLOATTARGET);
+ * =>
+ CU->assert(0);
+ }
+ it.ascent = it.height;
+ Ispacer =>
+ case t.spkind {
+ ISPvline =>
+ f := getfont(t.fnt);
+ it.height = f.height;
+ it.ascent = f.ascent;
+ ISPhspace =>
+ getfont(t.fnt);
+ it.width = fonts[t.fnt].spw;
+ }
+ }
+ }
+}
+
+# Set the dimensions of an image item
+setimagedims(i: ref Item.Iimage)
+{
+ i.width = i.imwidth + 2*(int i.hspace + int i.border);
+ i.height = i.imheight + 2*(int i.vspace + int i.border);
+ i.ascent = i.height - (int i.vspace + int i.border);
+ if((CU->config).imagelvl == CU->ImgNone && i.altrep != "") {
+ f := fonts[DefFnt].f;
+ i.width = max(i.width, f.width(i.altrep));
+ i.height = max(i.height, f.height);
+ i.ascent = f.ascent;
+ }
+}
+
+# Line geometry function:
+# Given current line height (H) and ascent (distance from top to baseline) (A),
+# and an item, see if that item changes height and ascent.
+# Return (H', A'), the updated line height and ascent.
+lgeom(H, A: int, it: ref Item) : (int, int)
+{
+ h := it.height;
+ a := it.ascent;
+ atype := Abaseline;
+ pick i := it {
+ Iimage =>
+ atype = i.align;
+ Itable =>
+ atype = Atop;
+ Ifloat =>
+ return (H, A);
+ }
+ d := h-a;
+ Hnew := H;
+ Anew := A;
+ case int atype {
+ int Abaseline or int Abottom =>
+ if(a > A) {
+ Anew = a;
+ Hnew += (Anew - A);
+ }
+ if(d > Hnew - Anew)
+ Hnew = Anew + d;
+ int Atop =>
+ # OK to ignore what comes after in the line
+ if(h > H)
+ Hnew = h;
+ int Amiddle or int Acenter =>
+ # supposed to align middle with baseline
+ hhalf := h/2;
+ if(hhalf > A)
+ Anew = hhalf;
+ if(hhalf > H-Anew)
+ Hnew = Anew + hhalf;
+ }
+ return (Hnew, Anew);
+}
+
+# Try breaking item bit to make it fit in availw.
+# If that is possible, change bit to be the part that fits
+# and insert the rest between bit and bit.next.
+# iw is the current width of bit.
+# If noneok is 0, break off the minimum size word
+# even if it exceeds availw.
+# Return (1 if supposed to take bit, iw' = new width of bit)
+trybreak(bit: ref Item, availw, iw, noneok: int) : (int, int)
+{
+ if(iw <= 0)
+ return (1, iw);
+ if(availw < 0) {
+ if(noneok)
+ return (0, iw);
+ else
+ availw = 0;
+ }
+ pick t := bit {
+ Itext =>
+ if(len t.s < 2)
+ return (!noneok, iw);
+ (s1, w1, s2, w2) := breakstring(t.s, iw, fonts[t.fnt].f, availw, noneok);
+ if(w1 == 0)
+ return (0, iw);
+ itn := Item.newtext(s2, t.fnt, t.fg, int t.voff, t.ul);
+ itn.width = w2;
+ itn.height = t.height;
+ itn.ascent = t.ascent;
+ itn.anchorid = t.anchorid;
+ itn.state = t.state & ~(IFbrk|IFbrksp|IFnobrk|IFcleft|IFcright);
+ itn.next = t.next;
+ t.next = itn;
+ t.s = s1;
+ t.width = w1;
+ return (1, w1);
+ }
+ return (!noneok, iw);
+}
+
+# s has width sw when drawn in fnt.
+# Break s into s1 and s2 so that s1 fits in availw.
+# If noneok is true, it is ok for s1 to be nil, otherwise might
+# have to return an s1 that overflows availw somewhat.
+# Return (s1, w1, s2, w2) where w1 and w2 are widths of s1 and s2.
+# Assume caller has already checked that sw > availw.
+breakstring(s: string, sw: int, fnt: ref Font, availw, noneok: int) : (string, int, string, int)
+{
+ slen := len s;
+ if(slen < 2) {
+ if(noneok)
+ return (nil, 0, s, sw);
+ else
+ return (s, sw, nil, 0);
+ }
+
+ # Use linear interpolation to guess break point.
+ # We know avail < iw by conditions of trybreak call.
+ i := slen*availw / sw - 1;
+ if(i < 0)
+ i = 0;
+ i = breakpoint(s, i, -1);
+ (ss, ww) := tryw(fnt, s, i);
+ if(ww > availw) {
+ while(ww > availw) {
+ i = breakpoint(s, i-1, -1);
+ if(i <= 0)
+ break;
+ (ss, ww) = tryw(fnt, s, i);
+ }
+ }
+ else {
+ oldi := i;
+ oldss := ss;
+ oldww := ww;
+ while(ww < availw) {
+ oldi = i;
+ oldss = ss;
+ oldww = ww;
+ i = breakpoint(s, i+1, 1);
+ if(i >= slen)
+ break;
+ (ss, ww) = tryw(fnt, s, i);
+ }
+ i = oldi;
+ ss = oldss;
+ ww = oldww;
+ }
+ if(i <= 0 || i >= slen) {
+ if(noneok)
+ return (nil, 0, s, sw);
+ i = breakpoint(s, 1, 1);
+ (ss,ww) = tryw(fnt, s, i);
+ }
+ return (ss, ww, s[i:slen], sw-ww);
+}
+
+# If can break between s[i-1] and s[i], return i.
+# Else move i in direction incr until this is true.
+# (Might end up returning 0 or len s).
+breakpoint(s: string, i, incr: int) : int
+{
+ slen := len s;
+ ans := 0;
+ while(i > 0 && i < slen) {
+ ci := s[i];
+ di := s[i-1];
+
+ # ASCII rules
+ if ((ci < 16rA0 && !int wordchar[ci]) || (di < 16rA0 && !int wordchar[di])) {
+ ans = i;
+ break;
+ }
+
+ # Treat all ideographs as breakable.
+ # The following range includes unassigned unicode code points.
+ # All assigned code points in the range are class ID (ideograph) as defined
+ # by the Unicode consortium's LineBreak data.
+ # There are many other class ID code points outside of this range.
+ # For details on how to do unicode line breaking properly see:
+ # Unicode Standard Annex #14 (http://www.unicode.org/unicode/reports/tr14/)
+
+ if ((ci >= 16r30E && ci <= 16r9FA5) || (di >= 16r30E && di <= 16r9FA5)) {
+ ans = i;
+ break;
+ }
+
+ # consider all other characters as unbreakable
+ i += incr;
+ }
+ if(i == slen)
+ ans = slen;
+ return ans;
+}
+
+# Return (s[0:i], width of that slice in font fnt)
+tryw(fnt: ref Font, s: string, i: int) : (string, int)
+{
+ if(i == 0)
+ return ("", 0);
+ ss := s[0:i];
+ return (ss, fnt.width(ss));
+}
+
+# Return max width of a float that overlaps [ymin, ymax) on given side.
+# Floats are in reverse order of addition, so each float's y is <= that of
+# preceding floats in list. Floats from both sides are intermixed.
+floatw(ymin, ymax: int, flist: list of ref Item.Ifloat, side: byte) : int
+{
+ ans := 0;
+ for( ; flist != nil; flist = tl flist) {
+ fl := hd flist;
+ if(fl.side != side)
+ continue;
+ fymin := fl.y;
+ fymax := fymin + fl.item.height;
+ if((fymin <= ymin && ymin < fymax) ||
+ (ymin <= fymin && fymin < ymax)) {
+ w := fl.x;
+ if(side == Aleft)
+ w += fl.item.width;
+ if(ans < w)
+ ans = w;
+ }
+ }
+ return ans;
+}
+
+# Float f is to be at vertical position >= y.
+# Fix its (x,y) pos and add it to lay.floats, if not already there.
+fixfloatxy(lay: ref Lay, y: int, f: ref Item.Ifloat)
+{
+ height := f.item.height;
+ width := f.item.width;
+ f.y = y;
+ flist := lay.floats;
+ if(f.infloats != byte 0) {
+ # only take previous floats into account for width
+ while(flist != nil) {
+ x := hd flist;
+ flist = tl flist;
+ if(x == f)
+ break;
+ }
+ }
+ f.x = floatw(y, y+height, flist, f.side);
+ endx := f.x + width + lay.margin;
+ if (endx > lay.width)
+ lay.width = endx;
+ if (f.side == Aright)
+ f.x += width;
+ endy := f.y + height + lay.margin;
+ if (endy > lay.height)
+ lay.height = endy;
+ if(f.infloats == byte 0) {
+ lay.floats = f :: lay.floats;
+ f.infloats = byte 1;
+ }
+}
+
+# Floats in flist are to go after line l.
+fixfloatsafter(lay: ref Lay, l: ref Line, flist: list of ref Item.Ifloat)
+{
+ change := 0;
+ y := l.pos.y + l.height;
+ for(itl := Item.revlist(flist); itl != nil; itl = tl itl) {
+ pick fl := hd itl {
+ Ifloat =>
+ oldy := fl.y;
+ fixfloatxy(lay, y, fl);
+ if(fl.y != oldy)
+ change = 1;
+ y += fl.item.height;
+ }
+ }
+# if(change)
+# TODO only change if y and/or height changed
+ changelines(l.next, lay.end);
+}
+
+# If there's a float on given side that starts on or before y and
+# ends after y, return ending y of that float, else return original y.
+# Assume float list is bottom up.
+floatclry(flist: list of ref Item.Ifloat, side: byte, y: int) : int
+{
+ ymax := y;
+ for( ; flist != nil; flist = tl flist) {
+ fl := hd flist;
+ if(fl.side == side) {
+ if(fl.y <= y) {
+ flymax := fl.y + fl.item.height;
+ if (fl.item.height == 0)
+ # assume it will have some height later
+ flymax++;
+ if(flymax > ymax)
+ ymax = flymax;
+ }
+ }
+ }
+ return ymax;
+}
+
+# Do preliminaries to laying out table tab in target width linewidth,
+# setting total height and width.
+sizetable(f: ref Frame, tab: ref Table, availwidth: int)
+{
+ if(dbgtab)
+ sys->print("sizetable %d, availwidth=%d, nrow=%d, ncol=%d, changed=%x, tab.availw=%d\n",
+ tab.tableid, availwidth, tab.nrow, tab.ncol, int (tab.flags&Lchanged), tab.availw);
+ if(tab.ncol == 0 || tab.nrow == 0)
+ return;
+ if(tab.availw == availwidth && (tab.flags&Lchanged) == byte 0)
+ return;
+ (hsp, vsp, pad, bd, cbd, hsep, vsep) := tableparams(tab);
+ totw := widthfromspec(tab.width, availwidth);
+ # reduce totw by spacing, padding, and rule widths
+ # to leave amount left for contents
+ totw -= (tab.ncol-1)*hsep+ 2*(hsp+bd+pad+cbd);
+ if(totw <= 0)
+ totw = 1;
+ if(dbgtab)
+ sys->print("\nsizetable %d, totw=%d, hsp=%d, vsp=%d, pad=%d, bd=%d, cbd=%d, hsep=%d, vsep=%d\n",
+ tab.tableid, totw, hsp, vsp, pad, bd, cbd, hsep, vsep);
+ for(cl := tab.cells; cl != nil; cl = tl cl) {
+ c := hd cl;
+ clay : ref Lay = nil;
+ if(c.layid >= 0)
+ clay = f.sublays[c.layid];
+ if(clay == nil || (clay.flags&Lchanged) != byte 0) {
+ c.minw = -1;
+ tw := TABLEMAXTARGET;
+ if(c.wspec.kind() != Dnone)
+ tw = widthfromspec(c.wspec, totw);
+
+ # When finding max widths, want to lay out using Aleft alignment,
+ # because we don't yet know final width for proper justification.
+ # If the max widths are accepted, we'll redo those needing other justification.
+ if(clay == nil) {
+ if(dbg)
+ sys->print("Initial layout for cell %d.%d\n", tab.tableid, c.cellid);
+ c.layid = sublayout(f, tw, Aleft, c.background, c.content);
+ clay = f.sublays[c.layid];
+ c.content = nil;
+ }
+ else {
+ if(dbg)
+ sys->print("Relayout (for max) for cell %d.%d\n", tab.tableid, c.cellid);
+ relayout(f, clay, tw, Aleft);
+ }
+ clay.flags |= Lchanged; # for min test, below
+ c.maxw = clay.width;
+ if(dbgtab)
+ sys->print("sizetable %d for cell %d max layout done, targw=%d, c.maxw=%d\n",
+ tab.tableid, c.cellid, tw, c.maxw);
+ if(c.wspec.kind() == Dpixels) {
+ # Other browsers don't make the following adjustment for
+ # percentage and relative widths
+ if(c.maxw <= tw)
+ c.maxw = tw;
+ if(dbgtab)
+ sys->print("after spec adjustment, c.maxw=%d\n", c.maxw);
+ }
+ }
+ }
+
+ # calc max column widths
+ colmaxw := array[tab.ncol] of { * => 0};
+ maxw := widthcalc(tab, colmaxw, hsep, 1);
+
+ if(dbgtab)
+ sys->print("sizetable %d maxw=%d, totw=%d\n", tab.tableid, maxw, totw);
+ ci: int;
+ if(maxw <= totw) {
+ # trial layouts are fine,
+ # but if table width was specified, add more space
+ d := 0;
+ adjust := (totw > maxw && tab.width.kind() != Dnone);
+ for(ci = 0; ci < tab.ncol; ci++) {
+ if (adjust) {
+ delta := (totw-maxw);
+ d = delta / (tab.ncol - ci);
+ if (d <= 0) {
+ d = delta;
+ adjust = 0;
+ }
+ maxw += d;
+ }
+ tab.cols[ci].width = colmaxw[ci] + d;
+ }
+ }
+ else {
+ # calc min column widths and apportion out
+ # differences
+ if(dbgtab)
+ sys->print("sizetable %d, availwidth %d, need min widths too\n", tab.tableid, availwidth);
+ for(cl = tab.cells; cl != nil; cl = tl cl) {
+ c := hd cl;
+ clay := f.sublays[c.layid];
+ if(c.minw == -1 || (clay.flags&Lchanged) != byte 0) {
+ if(dbg)
+ sys->print("Relayout (for min) for cell %d.%d\n", tab.tableid, c.cellid);
+ relayout(f, clay, 1, Aleft);
+ c.minw = clay.width;
+ if(dbgtab)
+ sys->print("sizetable %d for cell %d min layout done, c.min=%d\n",
+ tab.tableid, c.cellid, clay.width);
+ }
+ }
+ colminw := array[tab.ncol] of { * => 0};
+ minw := widthcalc(tab, colminw, hsep, 0);
+ w := totw - minw;
+ d := maxw - minw;
+ if(dbgtab)
+ sys->print("sizetable %d minw=%d, w=%d, d=%d\n", tab.tableid, minw, w, d);
+ for(ci = 0; ci < tab.ncol; ci++) {
+ wd : int;
+ if(w < 0 || d < 0)
+ wd = colminw[ci];
+ else
+ wd = colminw[ci] + (colmaxw[ci] - colminw[ci])*w/d;
+ if(dbgtab)
+ sys->print("sizetable %d col[%d].width = %d\n", tab.tableid, ci, wd);
+ tab.cols[ci].width = wd;
+ }
+
+ if(dbgtab)
+ sys->print("sizetable %d, availwidth %d, doing final layouts\n", tab.tableid, availwidth);
+ }
+
+ # now have col widths; set actual cell dimensions
+ # and relayout (note: relayout will do no work if the target width
+ # and just haven't changed from last layout)
+ for(cl = tab.cells; cl != nil; cl = tl cl) {
+ c := hd cl;
+ clay := f.sublays[c.layid];
+ wd := cellwidth(tab, c, hsep);
+ if(dbgtab)
+ sys->print("sizetable %d for cell %d, clay.width=%d, cellwidth=%d\n",
+ tab.tableid, c.cellid, clay.width, wd);
+ if(dbg)
+ sys->print("Relayout (final) for cell %d.%d\n", tab.tableid, c.cellid);
+ relayout(f, clay, wd, c.align.halign);
+ if(dbgtab)
+ sys->print("sizetable %d for cell %d, final width %d, got width %d, height %d\n",
+ tab.tableid, c.cellid, wd, clay.width, clay.height);
+ }
+
+ # set row heights and ascents
+ # first pass: ignore cells with rowspan > 1
+ for(ri := 0; ri < tab.nrow; ri++) {
+ row := tab.rows[ri];
+ h := 0;
+ a := 0;
+ n : int;
+ for(rcl := row.cells; rcl != nil; rcl = tl rcl) {
+ c := hd rcl;
+ if(c.rowspan > 1 || c.layid < 0)
+ continue;
+ al := c.align.valign;
+ if(al == Anone)
+ al = tab.rows[c.row].align.valign;
+ clay := f.sublays[c.layid];
+ if(al == Abaseline) {
+ n = c.ascent;
+ if(n > a) {
+ h += (n - a);
+ a = n;
+ }
+ n = clay.height - c.ascent;
+ if(n > h-a)
+ h = a + n;
+ }
+ else {
+ n = clay.height;
+ if(n > h)
+ h = n;
+ }
+ }
+ row.height = h;
+ row.ascent = a;
+ }
+ # second pass: take care of rowspan > 1
+ # (this algorithm isn't quite right -- it might add more space
+ # than is needed in the presence of multiple overlapping rowspans)
+ for(cl = tab.cells; cl != nil; cl = tl cl) {
+ c := hd cl;
+ if(c.rowspan > 1) {
+ spanht := 0;
+ for(i := 0; i < c.rowspan && c.row+i < tab.nrow; i++)
+ spanht += tab.rows[c.row+i].height;
+ if(c.layid < 0)
+ continue;
+ clay := f.sublays[c.layid];
+ ht := clay.height - (c.rowspan-1)*vsep;
+ if(ht > spanht) {
+ # add extra space to last spanned row
+ i = c.row+c.rowspan-1;
+ if(i >= tab.nrow)
+ i = tab.nrow - 1;
+ tab.rows[i].height += ht - spanht;
+ if(dbgtab)
+ sys->print("sizetable %d, row %d height %d\n", tab.tableid, i, tab.rows[i].height);
+ }
+ }
+ }
+ # get total width, heights, and col x / row y positions
+ totw = bd + hsp + cbd + pad;
+ for(ci = 0; ci < tab.ncol; ci++) {
+ tab.cols[ci].pos.x = totw;
+ if(dbgtab)
+ sys->print("sizetable %d, col %d at x=%d\n", tab.tableid, ci, totw);
+ totw += tab.cols[ci].width + hsep;
+ }
+ totw = totw - (cbd+pad) + bd;
+ toth := bd + vsp + cbd + pad;
+ # first time: move tab.caption items into layout
+ if(tab.caption != nil) {
+ # lay caption with Aleft; drawing will center it over the table width
+ tab.caption_lay = sublayout(f, availwidth, Aleft, f.layout.background, tab.caption);
+ caplay := f.sublays[tab.caption_lay];
+ tab.caph = caplay.height + CAPSEP;
+ tab.caption = nil;
+ }
+ else if(tab.caption_lay >= 0) {
+ caplay := f.sublays[tab.caption_lay];
+ if(tab.availw != availwidth || (caplay.flags&Lchanged) != byte 0) {
+ relayout(f, caplay, availwidth, Aleft);
+ tab.caph = caplay.height + CAPSEP;
+ }
+ }
+ if(tab.caption_place == Atop)
+ toth += tab.caph;
+ for(ri = 0; ri < tab.nrow; ri++) {
+ tab.rows[ri].pos.y = toth;
+ if(dbgtab)
+ sys->print("sizetable %d, row %d at y=%d\n", tab.tableid, ri, toth);
+ toth += tab.rows[ri].height + vsep;
+ }
+ toth = toth - (cbd+pad) + bd;
+ if(tab.caption_place == Abottom)
+ toth += tab.caph;
+ tab.totw = totw;
+ tab.toth = toth;
+ tab.availw = availwidth;
+ tab.flags &= ~Lchanged;
+ if(dbgtab)
+ sys->print("\ndone sizetable %d, availwidth %d, totw=%d, toth=%d\n\n",
+ tab.tableid, availwidth, totw, toth);
+}
+
+# Calculate various table spacing parameters
+tableparams(tab: ref Table) : (int, int, int, int, int, int, int)
+{
+ bd := tab.border;
+ hsp := tab.cellspacing;
+ vsp := hsp;
+ pad := tab.cellpadding;
+ if(bd != 0)
+ cbd := 1;
+ else
+ cbd = 0;
+ hsep := 2*(cbd+pad)+hsp;
+ vsep := 2*(cbd+pad)+vsp;
+ return (hsp, vsp, pad, bd, cbd, hsep, vsep);
+}
+
+# return cell width, taking multicol spanning into account
+cellwidth(tab: ref Table, c: ref Tablecell, hsep: int) : int
+{
+ if(c.colspan == 1)
+ return tab.cols[c.col].width;
+ wd := (c.colspan-1)*hsep;
+ for(i := 0; i < c.colspan && c.col + i < tab.ncol; i++)
+ wd += tab.cols[c.col + i].width;
+ return wd;
+}
+
+# return cell height, taking multirow spanning into account
+cellheight(tab: ref Table, c: ref Tablecell, vsep: int) : int
+{
+ if(c.rowspan == 1)
+ return tab.rows[c.row].height;
+ ht := (c.rowspan-1)*vsep;
+ for(i := 0; i < c.rowspan && c.row + i < tab.nrow; i++)
+ ht += tab.rows[c.row + i].height;
+ return ht;
+}
+
+# Calculate the column widths w as the max of the cells
+# maxw or minw (as domax is 1 or 0).
+# Return the total of all w.
+# (hseps were accounted for by the adjustment that got
+# totw from availwidth).
+# hsep is amount of free space available between columns
+# where there is multicolumn spanning.
+# This is a two-pass algorithm. The first pass ignores
+# cells that span multiple columns. The second pass
+# sees if those multispanners need still more space, and
+# if so, apportions the space out.
+widthcalc(tab: ref Table, w: array of int, hsep, domax: int) : int
+{
+ anyspan := 0;
+ totw := 0;
+ for(pass := 1; pass <= 2; pass++) {
+ if(pass==2 && !anyspan)
+ break;
+ totw = 0;
+ for(ci := 0; ci < tab.ncol; ci++) {
+ for(ri := 0; ri < tab.nrow; ri++) {
+ c := tab.grid[ri][ci];
+ if(c == nil)
+ continue;
+ if(domax)
+ cwd := c.maxw;
+ else
+ cwd = c.minw;
+ if(pass == 1) {
+ if(c.colspan > 1) {
+ anyspan = 1;
+ continue;
+ }
+ if(cwd > w[ci])
+ w[ci] = cwd;
+ }
+ else {
+ if(c.colspan == 1 || !(ci==c.col && ri==c.row))
+ continue;
+ curw := 0;
+ iend := ci+c.colspan;
+ if(iend > tab.ncol)
+ iend = tab.ncol;
+ for(i:=ci; i < iend; i++)
+ curw += w[i];
+
+ # padding between spanned cols is free
+ cwd -= hsep*(c.colspan-1);
+ diff := cwd-curw;
+ if(diff <= 0)
+ continue;
+ # doesn't fit: apportion diff among cols
+ # in proportion to their current w
+ for(i = ci; i < iend; i++) {
+ if(curw == 0)
+ w[i] = diff/c.colspan;
+ else
+ w[i] += diff*w[i]/curw;
+ }
+ }
+ }
+ totw += w[ci];
+ }
+ }
+ return totw;
+}
+
+layframeset(f: ref Frame, ki: ref Kidinfo)
+{
+ fwid := f.cr.dx();
+ fht := f.cr.dy();
+ if(dbg)
+ sys->print("layframeset, configuring frame %d wide by %d high\n", fwid, fht);
+ (nrow, rowh) := frdimens(ki.rows, fht);
+ (ncol, colw) := frdimens(ki.cols, fwid);
+ l := ki.kidinfos;
+ y := f.cr.min.y;
+ for(i := 0; i < nrow; i++) {
+ x := f.cr.min.x;
+ for(j := 0; j < ncol; j++) {
+ if(l == nil)
+ return;
+ r := Rect(Point(x,y), Point(x+colw[j],y+rowh[i]));
+ if(dbg)
+ sys->print("kid gets rect (%d,%d)(%d,%d)\n", r.min.x, r.min.y, r.max.x, r.max.y);
+ kidki := hd l;
+ l = tl l;
+ kidf := Frame.newkid(f, kidki, r);
+ if(!kidki.isframeset)
+ f.kids = kidf :: f.kids;
+ if(kidf.framebd != 0) {
+ kidf.cr = kidf.r.inset(2);
+ drawborder(kidf.cim, kidf.cr, 2, DarkGrey);
+ }
+ if(kidki.isframeset) {
+ layframeset(kidf, kidki);
+ for(al := kidf.kids; al != nil; al = tl al)
+ f.kids = (hd al) :: f.kids;
+ }
+ x += colw[j];
+ }
+ y += rowh[i];
+ }
+}
+
+# Use the dimension specs in dims to allocate total space t.
+# Return (number of dimens, array of allocated space)
+frdimens(dims: array of B->Dimen, t: int): (int, array of int)
+{
+ n := len dims;
+ if(n == 1)
+ return (1, array[] of {t});
+ totpix := 0;
+ totpcnt := 0;
+ totrel := 0;
+ for(i := 0; i < n; i++) {
+ v := dims[i].spec();
+ kind := dims[i].kind();
+ if(v < 0) {
+ v = 0;
+ dims[i] = Dimen.make(kind, v);
+ }
+ case kind {
+ B->Dpixels => totpix += v;
+ B->Dpercent => totpcnt += v;
+ B->Drelative => totrel += v;
+ B->Dnone => totrel++;
+ }
+ }
+ spix := 1.0;
+ spcnt := 1.0;
+ min_relu := 0;
+ if(totrel > 0)
+ min_relu = 30; # allow for scrollbar (14) and a bit
+ relu := real min_relu;
+ tt := totpix + (t*totpcnt/100) + totrel*min_relu;
+ # want
+ # t == totpix*spix + (totpcnt/100)*spcnt*t + totrel*relu
+ if(tt < t) {
+ # need to expand one of spix, spcnt, relu
+ if(totrel == 0) {
+ if(totpcnt != 0)
+ # spix==1.0, relu==0, solve for spcnt
+ spcnt = real ((t-totpix) * 100)/ real (t*totpcnt);
+ else
+ # relu==0, totpcnt==0, solve for spix
+ spix = real t/ real totpix;
+ }
+ else
+ # spix=1.0, spcnt=1.0, solve for relu
+ relu += real (t-tt)/ real totrel;
+ }
+ else {
+ # need to contract one or more of spix, spcnt, and have relu==min_relu
+ totpixrel := totpix+totrel*min_relu;
+ if(totpixrel < t) {
+ # spix==1.0, solve for spcnt
+ spcnt = real ((t-totpixrel) * 100)/ real (t*totpcnt);
+ }
+ else {
+ # let spix==spcnt, solve
+ trest := t - totrel*min_relu;
+ if(trest > 0) {
+ spcnt = real trest/real (totpix+(t*totpcnt/100));
+ }
+ else {
+ spcnt = real t / real tt;
+ relu = 0.0;
+ }
+ spix = spcnt;
+ }
+ }
+ x := array[n] of int;
+ tt = 0;
+ for(i = 0; i < n-1; i++) {
+ vr := real dims[i].spec();
+ case dims[i].kind() {
+ B->Dpixels => vr = vr * spix;
+ B->Dpercent => vr = vr * real t * spcnt / 100.0;
+ B->Drelative => vr = vr * relu;
+ B->Dnone => vr = relu;
+ }
+ x[i] = int vr;
+ tt += x[i];
+ }
+ x[n-1] = t - tt;
+ return (n, x);
+}
+
+# Return last item of list of items, or nil if no items
+lastitem(it: ref Item) : ref Item
+{
+ ans : ref Item = it;
+ for( ; it != nil; it = it.next)
+ ans = it;
+ return ans;
+}
+
+# Lay out table if availw changed or tab changed
+checktabsize(f: ref Frame, t: ref Item.Itable, availw: int)
+{
+ tab := t.table;
+ if (dbgtab)
+ sys->print("checktabsize %d, availw %d, tab.availw %d, changed %d\n", tab.tableid, availw, tab.availw, (tab.flags&Lchanged)>byte 0);
+ if(availw != tab.availw || int (tab.flags&Lchanged)) {
+ sizetable(f, tab, availw);
+ t.width = tab.totw + 2*tab.border;
+ t.height = tab.toth + 2*tab.border;
+ t.ascent = t.height;
+ }
+}
+
+widthfromspec(wspec: Dimen, availw: int) : int
+{
+ w := availw;
+ spec := wspec.spec();
+ case wspec.kind() {
+ Dpixels => w = spec;
+ Dpercent => w = spec*w/100;
+ }
+ return w;
+}
+
+# An image may have arrived for an image input field
+checkffsize(f: ref Frame, i: ref Item, ff: ref Formfield)
+{
+ if(ff.ftype == Fimage && ff.image != nil) {
+ pick imi := ff.image {
+ Iimage =>
+ if(imi.ci.mims != nil && ff.ctlid >= 0) {
+ pick b := f.controls[ff.ctlid] {
+ Cbutton =>
+ if(b.pic == nil) {
+ b.pic = imi.ci.mims[0].im;
+ b.picmask = imi.ci.mims[0].mask;
+ w := b.pic.r.dx();
+ h := b.pic.r.dy();
+ b.r.max.x = b.r.min.x + w;
+ b.r.max.y = b.r.min.y + h;
+ i.width = w;
+ i.height = h;
+ i.ascent = h;
+ }
+ }
+ }
+ }
+ }
+ else if(ff.ftype == Fselect) {
+ opts := ff.options;
+ if(ff.ctlid >=0) {
+ pick c := f.controls[ff.ctlid] {
+ Cselect =>
+ if(len opts != len c.options) {
+ nc := Control.newff(f, ff);
+ f.controls[ff.ctlid] = nc;
+ i.width = nc.r.dx();
+ i.height = nc.r.dy();
+ i.ascent = lineascent + SELMARGIN;
+ }
+ }
+ }
+ }
+}
+
+drawall(f: ref Frame)
+{
+ oclipr := f.cim.clipr;
+ origin := f.lptosp(zp);
+ clipr := f.dirtyr.addpt(origin);
+ f.cim.clipr = clipr;
+ fillbg(f, clipr);
+ if(dbg > 1)
+ sys->print("drawall, cr=(%d,%d,%d,%d), viewr=(%d,%d,%d,%d), origin=(%d,%d)\n",
+ f.cr.min.x, f.cr.min.y, f.cr.max.x, f.cr.max.y,
+ f.viewr.min.x, f.viewr.min.y, f.viewr.max.x, f.viewr.max.y,
+ origin.x, origin.y);
+ if(f.layout != nil)
+ drawlay(f, f.layout, origin);
+ f.cim.clipr = oclipr;
+ G->flush(f.cr);
+ f.isdirty = 0;
+}
+
+drawlay(f: ref Frame, lay: ref Lay, origin: Point)
+{
+ for(l := lay.start.next; l != lay.end; l = l.next)
+ drawline(f, origin, l, lay);
+}
+
+# Draw line l in frame f, assuming that content's (0,0)
+# aligns with layorigin in f.cim.
+drawline(f : ref Frame, layorigin : Point, l: ref Line, lay: ref Lay)
+{
+ im := f.cim;
+ o := layorigin.add(l.pos);
+ x := o.x;
+ y := o.y;
+ lr := Rect(zp, Point(l.width, l.height)).addpt(o);
+ isdirty := f.isdirty && lr.Xrect(f.dirtyr.addpt(f.lptosp(zp)));
+ inview := lr.Xrect(f.cr) && isdirty;
+
+ # note: drawimg must always be called to update
+ # draw point of animated images
+ for(it := l.items; it != nil; it = it.next) {
+ pick i := it {
+ Itext =>
+ if (!inview || i.s == nil)
+ break;
+ fnt := fonts[i.fnt];
+ width := i.width;
+ yy := y+l.ascent - fnt.f.ascent + (int i.voff) - Voffbias;
+ if (f.prctxt != nil) {
+ if (yy < f.cr.min.y)
+ continue;
+ endy := yy + fnt.f.height;
+ if (endy > f.cr.max.y) {
+ # do not draw
+ if (yy < f.prctxt.endy)
+ f.prctxt.endy = yy;
+ continue;
+ }
+ }
+ fgi := colorimage(i.fg);
+ im.text(Point(x, yy), fgi, zp, fnt.f, i.s);
+ if(i.ul != ULnone) {
+ if(i.ul == ULmid)
+ yy += 2*i.ascent/3;
+ else
+ yy += i.height - 1;
+ # don't underline leading space
+ # have already adjusted x pos in fixlinegeom()
+ ulx := x;
+ ulw := width;
+ if (i.s[0] == ' ') {
+ ulx += fnt.spw;
+ ulw -= fnt.spw;
+ }
+ if (i.s[len i.s - 1] == ' ')
+ ulw -= fnt.spw;
+ if (ulw < 1)
+ continue;
+ im.drawop(Rect(Point(ulx,yy),Point(ulx+ulw,yy+1)), fgi, nil, zp, Draw->S);
+ }
+ Irule =>
+ if (!inview)
+ break;
+ yy := y + RULESP;
+ im.draw(Rect(Point(x,yy),Point(x+i.width,yy+i.size)),
+ display.black, nil, zp);
+ Iimage =>
+ yy := y;
+ if(i.align == Abottom)
+ # bottom aligns with baseline
+ yy += l.ascent - i.imheight;
+ else if(i.align == Amiddle)
+ yy += l.ascent - (i.imheight/2);
+ drawimg(f, Point(x,yy), i);
+ Iformfield =>
+ ff := i.formfield;
+ if(ff.ctlid >= 0 && ff.ctlid < len f.controls) {
+ ctl := f.controls[ff.ctlid];
+ dims := ctl.r.max.sub(ctl.r.min);
+ # align as text
+ yy := y + l.ascent - i.ascent;
+ p := Point(x,yy);
+ ctl.r = Rect(p, p.add(dims));
+ if (!inview)
+ break;
+ if (f.prctxt != nil) {
+ if (yy < f.cr.min.y)
+ continue;
+ if (ctl.r.max.y > f.cr.max.y) {
+ # do not draw
+ if (yy < f.prctxt.endy)
+ f.prctxt.endy = yy;
+ continue;
+ }
+ }
+ ctl.draw(0);
+ }
+ Itable =>
+ # don't check inview - table can contain images
+ drawtable(f, lay, Point(x,y), i.table);
+ t := i.table;
+ Ifloat =>
+ xx := layorigin.x + lay.margin;
+ if(i.side == Aright) {
+ xx -= i.x;
+# # for main layout of frame, floats hug
+# # right edge of frame, not layout
+# # (other browsers do that)
+# if(f.layout == lay)
+ xx += lay.targetwidth;
+# else
+# xx += lay.width;
+ }
+ else
+ xx += i.x;
+ pick fi := i.item {
+ Iimage =>
+ drawimg(f, Point(xx, layorigin.y + i.y + (int fi.border + int fi.vspace)), fi);
+ Itable =>
+ drawtable(f, lay, Point(xx, layorigin.y + i.y), fi.table);
+ }
+ }
+ x += it.width;
+ }
+}
+
+drawimg(f: ref Frame, iorigin: Point, i: ref Item.Iimage)
+{
+ ci := i.ci;
+ im := f.cim;
+ iorigin.x += int i.hspace + int i.border;
+ # y coord is already adjusted for border and vspace
+ if(ci.mims != nil) {
+ r := Rect(iorigin, iorigin.add(Point(i.imwidth,i.imheight)));
+ inview := r.Xrect(f.cr);
+ if(i.ctlid >= 0) {
+ # animated
+ c := f.controls[i.ctlid];
+ dims := c.r.max.sub(c.r.min);
+ c.r = Rect(iorigin, iorigin.add(dims));
+ if (inview) {
+ pick ac := c {
+ Canimimage =>
+ ac.redraw = 1;
+ ac.bg = f.layout.background;
+ }
+ c.draw(0);
+ }
+ }
+ else if (inview) {
+ mim := ci.mims[0];
+ iorigin = iorigin.add(mim.origin);
+ im.draw(r, mim.im, mim.mask, zp);
+ }
+ if(inview && i.border != byte 0) {
+ if(i.anchorid != 0)
+ bdcol := f.doc.link;
+ else
+ bdcol = Black;
+ drawborder(im, r, int i.border, bdcol);
+ }
+ }
+ else if((CU->config).imagelvl == CU->ImgNone && i.altrep != "") {
+ fnt := fonts[DefFnt].f;
+ yy := iorigin.y+(i.imheight-fnt.height)/2;
+ xx := iorigin.x + (i.width-fnt.width(i.altrep))/2;
+ if(i.anchorid != 0)
+ col := f.doc.link;
+ else
+ col = DarkGrey;
+ fgi := colorimage(col);
+ im.text(Point(xx, yy), fgi, zp, fnt, i.altrep);
+ }
+}
+
+drawtable(f : ref Frame, parentlay: ref Lay, torigin: Point, tab: ref Table)
+{
+ if (dbgtab)
+ sys->print("drawtable %d\n", tab.tableid);
+ if(tab.ncol == 0 || tab.nrow == 0)
+ return;
+ im := f.cim;
+ (hsp, vsp, pad, bd, cbd, hsep, vsep) := tableparams(tab);
+ x := torigin.x;
+ y := torigin.y;
+ capy := y;
+ boxy := y;
+ if(tab.caption_place == Abottom)
+ capy = y+tab.toth-tab.caph+vsp;
+ else
+ boxy = y+tab.caph;
+ if (tab.background.color != -1 && tab.background.color != parentlay.background.color) {
+# if(tab.background.image != parentlay.background.image ||
+# tab.background.color != parentlay.background.color) {
+ bgi := colorimage(tab.background.color);
+ im.draw(((x,boxy),(x+tab.totw,boxy+tab.toth-tab.caph)),
+ bgi, nil, zp);
+ }
+ if(bd != 0)
+ drawborder(im, ((x+bd,boxy+bd),(x+tab.totw-bd,boxy+tab.toth-tab.caph-bd)),
+ 1, Black);
+ for(cl := tab.cells; cl != nil; cl = tl cl) {
+ c := hd cl;
+ if (c.layid == -1 || c.layid >= len f.sublays) {
+ # for some reason (usually scrolling)
+ # we are drawing this cell before it has been layed out
+ continue;
+ }
+ clay := f.sublays[c.layid];
+ if(clay == nil)
+ continue;
+ cx := x + tab.cols[c.col].pos.x;
+ cy := y + tab.rows[c.row].pos.y;
+ wd := cellwidth(tab, c, hsep);
+ ht := cellheight(tab, c, vsep);
+ if(c.background.image != nil && c.background.image.ci != nil && c.background.image.ci.mims != nil) {
+ cellr := Rect((cx-pad,cy-pad),(cx+wd+pad,cy+ht+pad));
+ ci := c.background.image.ci;
+ bgi := ci.mims[0].im;
+ bgmask := ci.mims[0].mask;
+ im.draw(cellr, bgi, bgmask, bgi.r.min);
+ } else if(c.background.color != -1 && c.background.color != tab.background.color) {
+ bgi := colorimage(c.background.color);
+ im.draw(((cx-pad,cy-pad),(cx+wd+pad,cy+ht+pad)),
+ bgi, nil, zp);
+ }
+ if(bd != 0)
+ drawborder(im, ((cx-pad+1,cy-pad+1),(cx+wd+pad-1,cy+ht+pad-1)),
+ 1, Black);
+ if(c.align.valign != Atop && c.align.valign != Abaseline) {
+ n := ht - clay.height;
+ if(c.align.valign == Amiddle)
+ cy += n/2;
+ else if(c.align.valign == Abottom)
+ cy += n;
+ }
+ if(dbgtab)
+ sys->print("drawtable %d cell %d at (%d,%d)\n",
+ tab.tableid, c.cellid, cx, cy);
+ drawlay(f, clay, Point(cx,cy));
+ }
+ if(tab.caption_lay >= 0) {
+ caplay := f.sublays[tab.caption_lay];
+ capx := x;
+ if(caplay.width < tab.totw)
+ capx += (tab.totw-caplay.width) / 2;
+ drawlay(f, caplay, Point(capx,capy));
+ }
+}
+
+# Draw border of width n just outside r, using src color
+drawborder(im: ref Image, r: Rect, n, color: int)
+{
+ x := r.min.x-n;
+ y := r.min.y - n;
+ xr := r.max.x+n;
+ ybi := r.max.y;
+ src := colorimage(color);
+ im.draw((Point(x,y),Point(xr,y+n)), src, nil, zp); # top
+ im.draw((Point(x,ybi),Point(xr,ybi+n)), src, nil, zp); # bottom
+ im.draw((Point(x,y+n),Point(x+n,ybi)), src, nil, zp); # left
+ im.draw((Point(xr-n,y+n),Point(xr,ybi)), src, nil, zp); # right
+}
+
+# Draw relief border just outside r, width 2 border,
+# colors white/lightgrey/darkgrey/black
+# to give raised relief (if raised != 0) or sunken.
+drawrelief(im: ref Image, r: Rect, raised: int)
+{
+ # ((x,y),(xr,yb)) == r
+ x := r.min.x;
+ x1 := x-1;
+ x2 := x-2;
+ xr := r.max.x;
+ xr1 := xr+1;
+ xr2 := xr+2;
+ y := r.min.y;
+ y1 := y-1;
+ y2 := y-2;
+ yb := r.max.y;
+ yb1 := yb+1;
+ yb2 := yb+2;
+
+ # colors for top/left outside, top/left inside, bottom/right outside, bottom/right inside
+ tlo, tli, bro, bri: ref Image;
+ if(raised) {
+ tlo = colorimage(Grey);
+ tli = colorimage(White);
+ bro = colorimage(Black);
+ bri = colorimage(DarkGrey);
+ }
+ else {
+ tlo = colorimage(DarkGrey);
+ tli = colorimage(Black);
+ bro = colorimage(White);
+ bri = colorimage(Grey);
+ }
+
+ im.draw((Point(x2,y2), Point(xr1,y1)), tlo, nil, zp); # top outside
+ im.draw((Point(x1,y1), Point(xr,y)), tli, nil, zp); # top inside
+ im.draw((Point(x2,y1), Point(x1,yb1)), tlo, nil, zp); # left outside
+ im.draw((Point(x1,y), Point(x,yb)), tli, nil, zp); # left inside
+ im.draw((Point(xr,y1),Point(xr1,yb)), bri, nil, zp); # right inside
+ im.draw((Point(xr1,y),Point(xr2,yb1)), bro, nil, zp); # right outside
+ im.draw((Point(x1,yb),Point(xr1,yb1)), bri, nil, zp); # bottom inside
+ im.draw((Point(x,yb1),Point(xr2,yb2)), bro, nil, zp); # bottom outside
+}
+
+# Fill r with color
+drawfill(im: ref Image, r: Rect, color: int)
+{
+ im.draw(r, colorimage(color), nil, zp);
+}
+
+# Draw string in default font at p
+drawstring(im: ref Image, p: Point, s: string)
+{
+ im.text(p, colorimage(Black), zp, fonts[DefFnt].f, s);
+}
+
+# Return (width, height) of string in default font
+measurestring(s: string) : Point
+{
+ f := fonts[DefFnt].f;
+ return (f.width(s), f.height);
+}
+
+# Mark as "changed" everything with change flags on the loc path
+markchanges(loc: ref Loc)
+{
+ lastf : ref Frame = nil;
+ for(i := 0; i < loc.n; i++) {
+ case loc.le[i].kind {
+ LEframe =>
+ lastf = loc.le[i].frame;
+ lastf.layout.flags |= Lchanged;
+ LEline =>
+ loc.le[i].line.flags |= Lchanged;
+ LEitem =>
+ pick it := loc.le[i].item {
+ Itable =>
+ it.table.flags |= Lchanged;
+ Ifloat =>
+ # whole layout will be redone if layout changes
+ # and there are any floats
+ ;
+ }
+ LEtablecell =>
+ if(lastf == nil)
+ CU->raisex("EXInternal: markchanges no lastf");
+ c := loc.le[i].tcell;
+ clay := lastf.sublays[c.layid];
+ if(clay != nil)
+ clay.flags |= Lchanged;
+ }
+ }
+}
+
+# one-item cache for colorimage
+prevrgb := -1;
+prevrgbimage : ref Image = nil;
+
+colorimage(rgb: int) : ref Image
+{
+ if(rgb == prevrgb)
+ return prevrgbimage;
+ prevrgb = rgb;
+ if(rgb == Black)
+ prevrgbimage = display.black;
+ else if(rgb == White)
+ prevrgbimage = display.white;
+ else {
+ hv := rgb % NCOLHASH;
+ if (hv < 0)
+ hv = -hv;
+ xhd := colorhashtab[hv];
+ x := xhd;
+ while(x != nil && x.rgb != rgb)
+ x = x.next;
+ if(x == nil) {
+# pix := I->closest_rgbpix((rgb>>16)&255, (rgb>>8)&255, rgb&255);
+# im := display.color(pix);
+ im := display.rgb((rgb>>16)&255, (rgb>>8)&255, rgb&255);
+ if(im == nil)
+ CU->raisex(sys->sprint("exLayout: can't allocate color #%8.8ux: %r", rgb));
+ x = ref Colornode(rgb, im, xhd);
+ colorhashtab[hv] = x;
+ }
+ prevrgbimage = x.im;
+ }
+ return prevrgbimage;
+}
+
+# Use f.background.image (if not nil) or f.background.color to fill r (in cim coord system)
+# with background color.
+fillbg(f: ref Frame, r: Rect)
+{
+ bgi: ref Image;
+ ii := f.doc.background.image;
+ if (ii != nil && ii.ci != nil && ii.ci.mims != nil)
+ bgi = ii.ci.mims[0].im;
+ if(bgi == nil)
+ bgi = colorimage(f.doc.background.color);
+ f.cim.drawop(r, bgi, nil, f.viewr.min, Draw->S);
+}
+
+TRIup, TRIdown, TRIleft, TRIright: con iota;
+# Assume r is a square
+drawtriangle(im: ref Image, r: Rect, kind, style: int)
+{
+ drawfill(im, r, Grey);
+ b := r.max.x - r.min.x;
+ if(b < 4)
+ return;
+ b2 := b/2;
+ bm2 := b-ReliefBd;
+ p := array[3] of Point;
+ col012, col20 : ref Image;
+ d := colorimage(DarkGrey);
+ l := colorimage(White);
+ case kind {
+ TRIup =>
+ p[0] = Point(b2, ReliefBd);
+ p[1] = Point(bm2,bm2);
+ p[2] = Point(ReliefBd,bm2);
+ col012 = d;
+ col20 = l;
+ TRIdown =>
+ p[0] = Point(b2,bm2);
+ p[1] = Point(ReliefBd,ReliefBd);
+ p[2] = Point(bm2,ReliefBd);
+ col012 = l;
+ col20 = d;
+ TRIleft =>
+ p[0] = Point(bm2, ReliefBd);
+ p[1] = Point(bm2, bm2);
+ p[2] = Point(ReliefBd,b2);
+ col012 = d;
+ col20 = l;
+ TRIright =>
+ p[0] = Point(ReliefBd,bm2);
+ p[1] = Point(ReliefBd,ReliefBd);
+ p[2] = Point(bm2,b2);
+ col012 = l;
+ col20 = d;
+ }
+ if(style == ReliefSunk) {
+ t := col012;
+ col012 = col20;
+ col20 = t;
+ }
+ for(i := 0; i < 3; i++)
+ p[i] = p[i].add(r.min);
+ im.fillpoly(p, ~0, colorimage(Grey), zp);
+ im.line(p[0], p[1], 0, 0, ReliefBd/2, col012, zp);
+ im.line(p[1], p[2], 0, 0, ReliefBd/2, col012, zp);
+ im.line(p[2], p[0], 0, 0, ReliefBd/2, col20, zp);
+}
+
+abs(a: int) : int
+{
+ if(a < 0)
+ return -a;
+ return a;
+}
+
+Frame.new() : ref Frame
+{
+ f := ref Frame;
+ f.parent = nil;
+ f.cim = nil;
+ f.r = Rect(zp, zp);
+ f.animpid = 0;
+ f.reset();
+ return f;
+}
+
+Frame.newkid(parent: ref Frame, ki: ref Kidinfo, r: Rect) : ref Frame
+{
+ f := ref Frame;
+ f.parent = parent;
+ f.cim = parent.cim;
+ f.r = r;
+ f.animpid = 0;
+ f.reset();
+ f.src = ki.src;
+ f.name = ki.name;
+ f.marginw = ki.marginw;
+ f.marginh = ki.marginh;
+ f.framebd = ki.framebd;
+ f.flags = ki.flags;
+ return f;
+}
+
+# Note: f.parent, f.cim and f.r should not be reset
+# And if f.parent is true, don't reset params set in frameset.
+Frame.reset(f: self ref Frame)
+{
+ f.id = ++frameid;
+ f.doc = nil;
+ if(f.parent == nil) {
+ f.src = nil;
+ f.name = "";
+ f.marginw = FRMARGIN;
+ f.marginh = FRMARGIN;
+ f.framebd = 0;
+ f.flags = FRvscrollauto | FRhscrollauto;
+ }
+ f.layout = nil;
+ f.sublays = nil;
+ f.sublayid = 0;
+ f.controls = nil;
+ f.controlid = 0;
+ f.cr = f.r;
+ f.isdirty = 1;
+ f.dirtyr = f.cr;
+ f.viewr = Rect(zp, zp);
+ f.totalr = f.viewr;
+ f.vscr = nil;
+ f.hscr = nil;
+ hadkids := (f.kids != nil);
+ f.kids = nil;
+ if(f.animpid != 0)
+ CU->kill(f.animpid, 0);
+ if(J != nil && hadkids)
+ J->frametreechanged(f);
+ f.animpid = 0;
+}
+
+Frame.dirty(f: self ref Frame, r: Draw->Rect)
+{
+ if (f.isdirty)
+ f.dirtyr= f.dirtyr.combine(r);
+ else {
+ f.dirtyr = r;
+ f.isdirty = 1;
+ }
+}
+
+Frame.addcontrol(f: self ref Frame, c: ref Control) : int
+{
+ if(len f.controls <= f.controlid) {
+ newcontrols := array[len f.controls + 30] of ref Control;
+ newcontrols[0:] = f.controls;
+ f.controls = newcontrols;
+ }
+ f.controls[f.controlid] = c;
+ ans := f.controlid++;
+ return ans;
+}
+
+Frame.xscroll(f: self ref Frame, kind, val: int)
+{
+ newx := f.viewr.min.x;
+ case kind {
+ CAscrollpage =>
+ newx += val*(f.cr.dx()*8/10);
+ CAscrollline =>
+ newx += val*f.cr.dx()/10;
+ CAscrolldelta =>
+ newx += val;
+ CAscrollabs =>
+ newx = val;
+ }
+ f.scrollabs(Point(newx, f.viewr.min.y));
+}
+
+# Don't actually scroll by "page" and "line",
+# But rather, 80% and 10%, which give more
+# context in the first case, and more motion
+# in the second.
+Frame.yscroll(f: self ref Frame, kind, val: int)
+{
+ newy := f.viewr.min.y;
+ case kind {
+ CAscrollpage =>
+ newy += val*(f.cr.dy()*8/10);
+ CAscrollline =>
+ newy += val*f.cr.dy()/20;
+ CAscrolldelta =>
+ newy += val;
+ CAscrollabs =>
+ newy = val;
+ }
+ f.scrollabs(Point(f.viewr.min.x, newy));
+}
+
+Frame.scrollrel(f : self ref Frame, p : Point)
+{
+ (x, y) := p;
+ x += f.viewr.min.x;
+ y += f.viewr.min.y;
+ f.scrollabs(f.viewr.min.add(p));
+}
+
+Frame.scrollabs(f : self ref Frame, p : Point)
+{
+ (x, y) := p;
+ lay := f.layout;
+ margin := 0;
+ if (lay != nil)
+ margin = lay.margin;
+ x = max(0, min(x, f.totalr.max.x));
+ y = max(0, min(y, f.totalr.max.y + margin - f.cr.dy()));
+ (oldx, oldy) := f.viewr.min;
+ if (oldx != x || oldy != y) {
+ f.viewr.min = (x, y);
+ fixframegeom(f);
+ # blit scroll
+ dx := f.viewr.min.x - oldx;
+ dy := f.viewr.min.y - oldy;
+ origin := f.lptosp(zp);
+ destr := f.viewr.addpt(origin);
+ srcpt := destr.min.add((dx, dy));
+ oclipr := f.cim.clipr;
+ f.cim.clipr = f.cr;
+ f.cim.drawop(destr, f.cim, nil, srcpt, Draw->S);
+ if (dx > 0)
+ f.dirty(Rect((f.viewr.max.x - dx, f.viewr.min.y), f.viewr.max));
+ else if (dx < 0)
+ f.dirty(Rect(f.viewr.min, (f.viewr.min.x - dx, f.viewr.max.y)));
+
+ if (dy > 0)
+ f.dirty(Rect((f.viewr.min.x, f.viewr.max.y-dy), f.viewr.max));
+ else if (dy < 0)
+ f.dirty(Rect(f.viewr.min, (f.viewr.max.x, f.viewr.min.y-dy)));
+#f.cim.draw(destr, display.white, nil, zp);
+ drawall(f);
+ f.cim.clipr = oclipr;
+ }
+}
+
+# Convert layout coords (where (0,0) is top left of layout)
+# to screen coords (i.e., coord system of mouse, f.cr, etc.)
+Frame.sptolp(f: self ref Frame, sp: Point) : Point
+{
+ return f.viewr.min.add(sp.sub(f.cr.min));
+}
+
+# Reverse translation of sptolp
+Frame.lptosp(f: self ref Frame, lp: Point) : Point
+{
+ return lp.add(f.cr.min.sub(f.viewr.min));
+}
+
+# Return Loc of Item or Scrollbar containing p (p in screen coords)
+# or item it, if that is not nil.
+Frame.find(f: self ref Frame, p: Point, it: ref Item) : ref Loc
+{
+ return framefind(Loc.new(), f, p, it);
+}
+
+# Find it (if non-nil) or place where p is (known to be inside f's layout).
+framefind(loc: ref Loc, f: ref Frame, p: Point, it: ref Item) : ref Loc
+{
+ loc.add(LEframe, f.r.min);
+ loc.le[loc.n-1].frame = f;
+ if(it == nil) {
+ if(f.vscr != nil && p.in(f.vscr.r)) {
+ loc.add(LEcontrol, f.vscr.r.min);
+ loc.le[loc.n-1].control = f.vscr;
+ loc.pos = p.sub(f.vscr.r.min);
+ return loc;
+ }
+ if(f.hscr != nil && p.in(f.hscr.r)) {
+ loc.add(LEcontrol, f.hscr.r.min);
+ loc.le[loc.n-1].control = f.hscr;
+ loc.pos = p.sub(f.hscr.r.min);
+ return loc;
+ }
+ }
+ if(it != nil || p.in(f.cr)) {
+ lay := f.layout;
+ if(f.kids != nil) {
+ for(fl := f.kids; fl != nil; fl = tl fl) {
+ kf := hd fl;
+ try := framefind(loc, kf, p, it);
+ if(try != nil)
+ return try;
+ }
+ }
+ else if(lay != nil)
+ return layfind(loc, f, lay, f.lptosp(zp), p, it);
+ }
+ return nil;
+}
+
+# Find it (if non-nil) or place where p is (known to be inside f's layout).
+# p (in screen coords), lay offset by origin also in screen coords
+layfind(loc: ref Loc, f: ref Frame, lay: ref Lay, origin, p: Point, it: ref Item) : ref Loc
+{
+ for(flist := lay.floats; flist != nil; flist = tl flist) {
+ fl := hd flist;
+ fymin := fl.y+origin.y;
+ fymax := fymin + fl.item.height;
+ inside := 0;
+ xx : int;
+ if(it != nil || (fymin <= p.y && p.y < fymax)) {
+ xx = origin.x + lay.margin;
+ if(fl.side == Aright) {
+ xx -= fl.x;
+ xx += lay.targetwidth;
+# if(lay == f.layout)
+# xx = origin.x + (f.cr.dx() - lay.margin) - fl.x;
+## xx += f.cr.dx() - fl.x;
+# else
+# xx += lay.width - fl.x;
+ }
+ else
+ xx += fl.x;
+ if(p.x >= xx && p.x < xx+fl.item.width)
+ inside = 1;
+ }
+ fp := Point(xx,fymin);
+ match := 0;
+ if(it != nil) {
+ pick fi := fl.item {
+ Itable =>
+ loc.add(LEitem, fp);
+ loc.le[loc.n-1].item = fl;
+ loc.pos = p.sub(fp);
+ lloc := tablefind(loc, f, fi, fp, p, it);
+ if(lloc != nil)
+ return lloc;
+ Iimage =>
+ match = (it == fl || it == fl.item);
+ }
+ }
+ if(match || inside) {
+ loc.add(LEitem, fp);
+ loc.le[loc.n-1].item = fl;
+ loc.pos = p.sub(fp);
+ if(it == fl.item) {
+ loc.add(LEitem, fp);
+ loc.le[loc.n-1].item = fl.item;
+ }
+ if(inside) {
+ pick fi := fl.item {
+ Itable =>
+ loc = tablefind(loc, f, fi, fp, p, it);
+ }
+ }
+ return loc;
+ }
+ }
+ for(l :=lay.start; l != nil; l = l.next) {
+ o := origin.add(l.pos);
+ if(it != nil || (o.y <= p.y && p.y < o.y+l.height)) {
+ lloc := linefind(loc, f, l, o, p, it);
+ if(lloc != nil)
+ return lloc;
+ if(it == nil && o.y + l.height >= p.y)
+ break;
+ }
+ }
+ return nil;
+}
+
+# p (in screen coords), line at o, also in screen coords
+linefind(loc: ref Loc, f: ref Frame, l: ref Line, o, p: Point, it: ref Item) : ref Loc
+{
+ loc.add(LEline, o);
+ loc.le[loc.n-1].line = l;
+ x := o.x;
+ y := o.y;
+ inside := 0;
+ for(i := l.items; i != nil; i = i.next) {
+ if(it != nil || (x <= p.x && p.x < x+i.width)) {
+ yy := y;
+ h := 0;
+ pick pi := i {
+ Itext =>
+ fnt := fonts[pi.fnt].f;
+ yy += l.ascent - fnt.ascent + (int pi.voff) - Voffbias;
+ h = fnt.height;
+ Irule =>
+ h = pi.size;
+ Iimage =>
+ yy = y;
+ if(pi.align == Abottom)
+ yy += l.ascent - pi.imheight;
+ else if(pi.align == Amiddle)
+ yy += l.ascent - (pi.imheight/2);
+ h = pi.imheight;
+ Iformfield =>
+ h = pi.height;
+ yy += l.ascent - pi.ascent;
+ if(it != nil) {
+ if(it == pi.formfield.image) {
+ loc.add(LEitem, Point(x,yy));
+ loc.le[loc.n-1].item = i;
+ loc.add(LEitem, Point(x,yy));
+ loc.le[loc.n-1].item = it;
+ loc.pos = zp; # doesn't matter, its an 'it' test
+ return loc;
+ }
+ }
+ else if(yy < p.y && p.y < yy+h && pi.formfield.ctlid >= 0) {
+ loc.add(LEcontrol, Point(x,yy));
+ loc.le[loc.n-1].control = f.controls[pi.formfield.ctlid];
+ loc.pos = p.sub(Point(x,yy));
+ return loc;
+ }
+ Itable =>
+ lloc := tablefind(loc, f, pi, Point(x,y), p, it);
+ if(lloc != nil)
+ return lloc;
+ # else leave h==0 so p test will fail
+
+ # floats were handled separately. nulls can be picked by 'it' test
+ # leave h==0, so p test will fail
+ }
+ if(it == i || (it == nil && yy <= p.y && p.y < yy+h)) {
+ loc.add(LEitem, Point(x,yy));
+ loc.le[loc.n-1].item = i;
+ loc.pos = p.sub(Point(x,yy));
+ return loc;
+ }
+ if(it == nil)
+ return nil;
+ }
+ x += i.width;
+ if(it == nil && x >= p.x)
+ break;
+ }
+ loc.n--;
+ return nil;
+}
+
+tablefind(loc: ref Loc, f: ref Frame, ti: ref Item.Itable, torigin: Point, p: Point, it: ref Item) : ref Loc
+{
+ loc.add(LEitem, torigin);
+ loc.le[loc.n-1].item = ti;
+ t := ti.table;
+ (hsp, vsp, pad, bd, cbd, hsep, vsep) := tableparams(t);
+ if(t.caption_lay >= 0) {
+ caplay := f.sublays[t.caption_lay];
+ capy := torigin.y;
+ if(t.caption_place == Abottom)
+ capy += t.toth-t.caph+vsp;
+ lloc := layfind(loc, f, caplay, Point(torigin.x,capy), p, it);
+ if(lloc != nil)
+ return lloc;
+ }
+ for(cl := t.cells; cl != nil; cl = tl cl) {
+ c := hd cl;
+ if(c.layid == -1 || c.layid >= len f.sublays)
+ continue;
+ clay := f.sublays[c.layid];
+ if(clay == nil)
+ continue;
+ cx := torigin.x + t.cols[c.col].pos.x;
+ cy := torigin.y + t.rows[c.row].pos.y;
+ wd := cellwidth(t, c, hsep);
+ ht := cellheight(t, c, vsep);
+ if(it == nil && !p.in(Rect(Point(cx,cy),Point(cx+wd,cy+ht))))
+ continue;
+ if(c.align.valign != Atop && c.align.valign != Abaseline) {
+ n := ht - clay.height;
+ if(c.align.valign == Amiddle)
+ cy += n/2;
+ else if(c.align.valign == Abottom)
+ cy += n;
+ }
+ loc.add(LEtablecell, Point(cx,cy));
+ loc.le[loc.n-1].tcell = c;
+ lloc := layfind(loc, f, clay, Point(cx,cy), p, it);
+ if(lloc != nil)
+ return lloc;
+ loc.n--;
+ if(it == nil)
+ return nil;
+ }
+ loc.n--;
+ return nil;
+}
+
+# (called from jscript)
+# 'it' is an Iimage item in frame f whose image is to be switched
+# to come from the src URL.
+#
+# For now, assume this is called only after the entire build process
+# has finished. Also, only handle the case where the image has
+# been preloaded and is in the cache now. This isn't right (BUG), but will
+# cover most of the cases of extant image swapping, and besides,
+# image swapping is mostly cosmetic anyway.
+#
+# For now, pay no attention to scaling issues or animation issues.
+Frame.swapimage(f: self ref Frame, im: ref Item.Iimage, src: string)
+{
+ u := U->parse(src);
+ if(u.scheme == "")
+ return;
+ u = U->mkabs(u, f.doc.base);
+ # width=height=0 finds u if in cache
+ newci := CImage.new(u, nil, 0, 0);
+ cachedci := (CU->imcache).look(newci);
+ if(cachedci == nil || cachedci.mims == nil)
+ return;
+ im.ci = cachedci;
+
+ # we're assuming image will have same dimensions
+ # as one that is replaced, so no relayout is needed;
+ # otherwise need to call haveimage() instead of drawall()
+ # Netscape scales replacement image to size of replaced image
+
+ f.dirty(f.totalr);
+ drawall(f);
+}
+
+Frame.focus(f : self ref Frame, focus, raisex : int)
+{
+ di := f.doc;
+ if (di == nil || (CU->config).doscripts == 0)
+ return;
+ if (di.evmask && raisex) {
+ kind := E->SEonfocus;
+ if (!focus)
+ kind = E->SEonblur;
+ if(di.evmask & kind)
+ se := ref E->ScriptEvent(kind, f.id, -1, -1, -1, -1, -1, -1, 0, nil, nil, 0);
+ }
+}
+
+Control.newff(f: ref Frame, ff: ref B->Formfield) : ref Control
+{
+ ans : ref Control = nil;
+ case ff.ftype {
+ Ftext or Fpassword or Ftextarea =>
+ nh := ff.size;
+ nv := 1;
+ linewrap := 0;
+ if(ff.ftype == Ftextarea) {
+ nh = ff.cols;
+ nv = ff.rows;
+ linewrap = 1;
+ }
+ ans = Control.newentry(f, nh, nv, linewrap);
+ if(ff.ftype == Fpassword)
+ ans.flags |= CFsecure;
+ ans.entryset(ff.value);
+ Fcheckbox or Fradio =>
+ ans = Control.newcheckbox(f, ff.ftype==Fradio);
+ if((ff.flags&B->FFchecked) != byte 0)
+ ans.flags |= CFactive;
+ Fsubmit or Fimage or Freset or Fbutton =>
+ if(ff.image == nil)
+ ans = Control.newbutton(f, nil, nil, ff.value, nil, 0, 1);
+ else {
+ pick i := ff.image {
+ Iimage =>
+ pic, picmask : ref Image;
+ if(i.ci.mims != nil) {
+ pic = i.ci.mims[0].im;
+ picmask = i.ci.mims[0].mask;
+ }
+ lab := "";
+ if((CU->config).imagelvl == CU->ImgNone) {
+ lab = i.altrep;
+ i = nil;
+ }
+ ans = Control.newbutton(f, pic, picmask, lab, i, 0, 0);
+ }
+ }
+ Fselect =>
+ n := len ff.options;
+ if(n > 0) {
+ ao := array[n] of Option;
+ l := ff.options;
+ for(i := 0; i < n; i++) {
+ o := hd l;
+ # these are copied, so selected can be used for current state
+ ao[i] = *o;
+ l = tl l;
+ }
+ nvis := ff.size;
+ ans = Control.newselect(f, nvis, ao);
+ }
+ Ffile =>
+ if(dbg)
+ sys->print("warning: unimplemented file form field\n");
+ }
+ if(ans != nil)
+ ans.ff = ff;
+ return ans;
+}
+
+Control.newscroll(f: ref Frame, isvert, length, breadth: int) : ref Control
+{
+ # need room for at least two squares and 2 borders of size 2
+ if(length < 12) {
+ breadth = 0;
+ length = 0;
+ }
+ else if(breadth*2 + 4 > length)
+ breadth = (length - 4) / 2;
+ maxpt : Point;
+ flags := CFenabled;
+ if(isvert) {
+ maxpt = Point(breadth, length);
+ flags |= CFscrvert;
+ }
+ else
+ maxpt = Point(length, breadth);
+ return ref Control.Cscrollbar(f, nil, Rect(zp,maxpt), flags, nil, 0, 0, 1, 0, nil, (0, 0));
+}
+
+Control.newentry(f: ref Frame, nh, nv, linewrap: int) : ref Control
+{
+ w := ctlcharspace*nh + 2*ENTHMARGIN;
+ h := ctllinespace*nv + 2*ENTVMARGIN;
+ scr : ref Control;
+ if (linewrap) {
+ scr = Control.newscroll(f, 1, h-4, SCRFBREADTH);
+ scr.r.addpt(Point(w,0));
+ w += SCRFBREADTH;
+ }
+ ans := ref Control.Centry(f, nil, Rect(zp,Point(w,h)), CFenabled, nil, scr, "", (0, 0), 0, linewrap, 0);
+ if (scr != nil) {
+ pick pscr := scr {
+ Cscrollbar =>
+ pscr.ctl = ans;
+ }
+ scr.scrollset(0, 1, 1, 0, 0);
+ }
+ return ans;
+}
+
+Control.newbutton(f: ref Frame, pic, picmask: ref Image, lab: string, it: ref Item.Iimage, candisable, dorelief: int) : ref Control
+{
+ dpic, dpicmask: ref Image;
+ w := 0;
+ h := 0;
+ if(pic != nil) {
+ w = pic.r.dx();
+ h = pic.r.dy();
+ }
+ else if(it != nil) {
+ w = it.imwidth;
+ h = it.imheight;
+ }
+ else {
+ w = fonts[CtlFnt].f.width(lab);
+ h = ctllinespace;
+ }
+ if(dorelief) {
+ # form image buttons are shown without margins in other browsers
+ w += 2*BUTMARGIN;
+ h += 2*BUTMARGIN;
+ }
+ r := Rect(zp, Point(w,h));
+ if(candisable && pic != nil) {
+ # make "greyed out" image:
+ # - convert pic to monochrome (ones where pic is non-white)
+ # - draw pic in White, then DarkGrey shifted (-1,-1) and use
+ # union of those two areas as mask
+ dpicmask = display.newimage(pic.r, Draw->GREY1, 0, D->White);
+ dpic = display.newimage(pic.r, pic.chans, 0, D->White);
+ dpic.draw(dpic.r, colorimage(White), pic, zp);
+ dpicmask.draw(dpicmask.r, display.black, pic, zp);
+ dpic.draw(dpic.r.addpt(Point(-1,-1)), colorimage(DarkGrey), pic, zp);
+ dpicmask.draw(dpicmask.r.addpt(Point(-1,-1)), display.black, pic, zp);
+ }
+ b := ref Control.Cbutton(f, nil, r, CFenabled, nil, pic, picmask, dpic, dpicmask, lab, dorelief);
+ return b;
+}
+
+Control.newcheckbox(f: ref Frame, isradio: int) : ref Control
+{
+ return ref Control.Ccheckbox(f, nil, Rect((0,0),(CBOXWID,CBOXHT)), CFenabled, nil, isradio);
+}
+
+Control.newselect(f: ref Frame, nvis: int, options: array of B->Option) : ref Control
+{
+ nvis = min(5, len options);
+ if (nvis < 1)
+ nvis = 1;
+ fnt := fonts[CtlFnt].f;
+ w := 0;
+ first := -1;
+ for(i := 0; i < len options; i++) {
+ if (first == -1 && options[i].selected)
+ first = i;
+ w = max(w, fnt.width(options[i].display));
+ }
+ if (first == -1)
+ first = 0;
+ if (len options -nvis > 0 && len options - nvis < first)
+ first = len options - nvis;
+ w += 2*SELMARGIN;
+ h := ctllinespace*nvis + 2*SELMARGIN;
+ scr: ref Control;
+ if (nvis > 1 && nvis < len options) {
+ scr = Control.newscroll(f, 1, h, SCRFBREADTH);
+ scr.r.addpt(Point(w,0));
+ }
+ if (nvis < len options)
+ w += SCRFBREADTH;
+ ans := ref Control.Cselect(f, nil, Rect(zp, Point(w,h)), CFenabled, nil, nil, scr, nvis, first, options);
+ if(scr != nil) {
+ pick pscr := scr {
+ Cscrollbar =>
+ pscr.ctl = ans;
+ }
+ scr.scrollset(first, first+nvis, len options, len options, 0);
+ }
+ return ans;
+}
+
+Control.newlistbox(f: ref Frame, nrow, ncol: int, options: array of B->Option) : ref Control
+{
+ fnt := fonts[CtlFnt].f;
+ w := charspace*ncol + 2*SELMARGIN;
+ h := fnt.height*nrow + 2*SELMARGIN;
+
+ vscr: ref Control = nil;
+ #if(nrow < len options) {
+ vscr = Control.newscroll(f, 1, (h-4)+SCRFBREADTH, SCRFBREADTH);
+ vscr.r.addpt(Point(w-SCRFBREADTH,0));
+ w += SCRFBREADTH;
+ #}
+
+ maxw := 0;
+ for(i := 0; i < len options; i++)
+ maxw = max(maxw, fnt.width(options[i].display));
+
+ hscr: ref Control = nil;
+ #if(w < maxw) {
+ # allow for border (inset(2))
+ hscr = Control.newscroll(f, 0, (w-4)-SCRFBREADTH, SCRFBREADTH);
+ hscr.r.addpt(Point(0, h-SCRBREADTH));
+ h += SCRFBREADTH;
+ #}
+
+ ans := ref Control.Clistbox(f, nil, Rect(zp, Point(w,h)), CFenabled, nil, hscr, vscr, nrow, 0, 0, maxw/charspace, options, nil);
+ if(vscr != nil) {
+ pick pscr := vscr {
+ Cscrollbar =>
+ pscr.ctl = ans;
+ }
+ vscr.scrollset(0, nrow, len options, len options, 0);
+ }
+ if(hscr != nil) {
+ pick pscr := hscr {
+ Cscrollbar =>
+ pscr.ctl = ans;
+ }
+ hscr.scrollset(0, w-SCRFBREADTH, maxw, 0, 0);
+ }
+ return ans;
+}
+
+Control.newanimimage(f: ref Frame, cim: ref CU->CImage, bg: Background) : ref Control
+{
+ return ref Control.Canimimage(f, nil, Rect((0,0),(cim.width,cim.height)), 0, nil, cim, 0, 0, big 0, bg);
+}
+
+Control.newlabel(f: ref Frame, s: string) : ref Control
+{
+ w := fonts[DefFnt].f.width(s);
+ h := ctllinespace + 2*ENTVMARGIN; # give it same height as an entry box
+ return ref Control.Clabel(f, nil, Rect(zp,Point(w,h)), 0, nil, s);
+}
+
+Control.disable(c: self ref Control)
+{
+ if(c.flags & CFenabled) {
+ win := c.f.cim;
+ c.flags &= ~CFenabled;
+ if(c.f.cim != nil)
+ c.draw(1);
+ }
+}
+
+Control.enable(c: self ref Control)
+{
+ if(!(c.flags & CFenabled)) {
+ c.flags |= CFenabled;
+ if(c.f.cim != nil)
+ c.draw(1);
+ }
+}
+
+changeevent(c: ref Control)
+{
+ onchange := 0;
+ pick pc := c {
+ Centry =>
+ onchange = pc.onchange;
+ pc.onchange = 0;
+# this code reproduced Navigator 2 bug
+# changes to Select Formfield selection only resulted in onchange event upon
+# loss of focus. Now handled by domouse() code so event can be raised
+# immediately
+# Cselect =>
+# onchange = pc.onchange;
+# pc.onchange = 0;
+ }
+ if(onchange && (c.ff.evmask & E->SEonchange)) {
+ se := ref E->ScriptEvent(E->SEonchange, c.f.id, c.ff.form.formid, c.ff.fieldid, -1, -1, -1, -1, 1, nil, nil, 0);
+ J->jevchan <-= se;
+ }
+}
+
+blurfocusevent(c: ref Control, kind, raisex: int)
+{
+ if((CU->config).doscripts && c.ff != nil && c.ff.evmask) {
+ if(kind == E->SEonblur)
+ changeevent(c);
+ if (!raisex || !(c.ff.evmask & kind))
+ return;
+ se := ref E->ScriptEvent(kind, c.f.id, c.ff.form.formid, c.ff.fieldid, -1, -1, -1, -1, 0, nil, nil, 0);
+ J->jevchan <-= se;
+ }
+}
+
+Control.losefocus(c: self ref Control, raisex: int)
+{
+ if(c.flags & CFhasfocus) {
+ c.flags &= ~CFhasfocus;
+ if(c.f.cim != nil) {
+ blurfocusevent(c, E->SEonblur, raisex);
+ c.draw(1);
+ }
+ }
+}
+
+Control.gainfocus(c: self ref Control, raisex: int)
+{
+ if(!(c.flags & CFhasfocus)) {
+ c.flags |= CFhasfocus;
+ if(c.f.cim != nil) {
+ blurfocusevent(c, E->SEonfocus, raisex);
+ c.draw(1);
+ }
+ G->clientfocus();
+ }
+}
+
+Control.scrollset(c: self ref Control, v1, v2, vmax, nsteps, draw: int)
+{
+ pick sc := c {
+ Cscrollbar =>
+ if(v1 < 0)
+ v1 = 0;
+ if(v2 > vmax)
+ v2 = vmax;
+ if(v1 > v2)
+ v1 = v2;
+ if(v1 == 0 && v2 == vmax) {
+ sc.mindelta = 1;
+ sc.deltaval = 0;
+ sc.top = 0;
+ sc.bot = 0;
+ }
+ else {
+ length, breadth: int;
+ if(sc.flags&CFscrvert) {
+ length = sc.r.max.y - sc.r.min.y;
+ breadth = sc.r.max.x - sc.r.min.x;
+ }
+ else {
+ length = sc.r.max.x - sc.r.min.x;
+ breadth = sc.r.max.y - sc.r.min.y;
+ }
+ l := length - (2*breadth + MINSCR);
+ if(l < 0)
+ CU->raisex("EXInternal: negative scrollbar trough");
+ sc.top = l*v1/vmax;
+ sc.bot = l*(vmax-v2)/vmax;
+ if (nsteps == 0)
+ sc.mindelta = 1;
+ else
+ sc.mindelta = max(1, length/nsteps);
+ sc.deltaval = max(1, vmax/(l/sc.mindelta))*SCRDELTASF;
+ }
+ if(sc.f.cim != nil && draw)
+ sc.draw(1);
+ }
+}
+
+SPECMASK : con 16rf000;
+CTRLMASK : con 16r1f;
+DEL : con 16r7f;
+TAB : con '\t';
+CR: con '\n';
+
+Control.dokey(ctl: self ref Control, keychar: int) : int
+{
+ if(!(ctl.flags&CFenabled))
+ return CAnone;
+ ans := CAnone;
+ pick c := ctl {
+ Centry =>
+ olds := c.s;
+ slen := len c.s;
+ (sels, sele) := normalsel(c.sel);
+ modified := 0;
+ (osels, osele) := (sels, sele);
+ case keychar {
+ ('a' & CTRLMASK) or Keyboard->Home =>
+ (sels, sele) = (0, 0);
+ ('e' & CTRLMASK) or Keyboard->End =>
+ (sels, sele) = (slen, slen);
+ 'f' & CTRLMASK or Keyboard->Right =>
+ if(sele < slen)
+ (sels, sele) = (sele+1, sele+1);
+ 'b' & CTRLMASK or Keyboard->Left =>
+ if(sels > 0)
+ (sels, sele) = (sels-1, sels-1);
+ Keyboard->Up =>
+ if (c.linewrap)
+ sels = sele = entryupdown(c, sels, -1);
+ Keyboard->Down =>
+ if (c.linewrap)
+ sels = sele = entryupdown(c, sele, 1);
+ 'u' & CTRLMASK =>
+ entrydelrange(c, 0, slen);
+ modified = 1;
+ (sels, sele) = c.sel;
+ 'c' & CTRLMASK =>
+ entrysetsnarf(c);
+ 'v' & CTRLMASK =>
+ entryinsertsnarf(c);
+ modified = 1;
+ (sels, sele) = c.sel;
+ 'h' & CTRLMASK or DEL=>
+ if (sels != sele) {
+ entrydelrange(c, sels, sele);
+ modified = 1;
+ } else if(sels > 0) {
+ entrydelrange(c, sels-1, sels);
+ modified = 1;
+ }
+ (sels, sele) = c.sel;
+ Keyboard->Del =>
+ if (sels != sele) {
+ entrydelrange(c, sels, sele);
+ modified = 1;
+ } else if(sels < len c.s) {
+ entrydelrange(c, sels, sels+1);
+ modified = 1;
+ }
+ (sels, sele) = c.sel;
+ TAB =>
+ ans = CAtabkey;
+ * =>
+ if ((keychar & SPECMASK) == Keyboard->Spec)
+ # ignore all other special keys
+ break;
+ if(keychar == CR) {
+ if(c.linewrap)
+ keychar = '\n';
+ else
+ ans = CAreturnkey;
+ }
+ if(keychar > CTRLMASK || (keychar == '\n' && c.linewrap)) {
+ if (sels != sele) {
+ entrydelrange(c, sels, sele);
+ (sels, sele) = c.sel;
+ }
+ slen = len c.s;
+ c.s[slen] = 0; # expand string by 1 char
+ for(k := slen; k > sels; k--)
+ c.s[k] = c.s[k-1];
+ c.s[sels] = keychar;
+ (sels, sele) = (sels+1, sels+1);
+ modified = 1;
+ }
+ }
+ c.sel = (sels, sele);
+ if(osels != sels || osele != sele || modified) {
+ entryscroll(c);
+ c.draw(1);
+ }
+ if (c.s != olds)
+ c.onchange = 1;
+ }
+ return ans;
+}
+
+Control.domouse(ctl: self ref Control, p: Point, mtype: int, oldgrab : ref Control) : (int, ref Control)
+{
+ up := (mtype == E->Mlbuttonup || mtype == E->Mldrop);
+ down := (mtype == E->Mlbuttondown);
+ drag := (mtype == E->Mldrag);
+ hold := (mtype == E->Mhold);
+ move := (mtype == E->Mmove);
+
+ # any button actions stop auto-repeat
+ # it's up to the individual controls to re-instate it
+ if (!move)
+ E->autorepeat(nil, 0, 0);
+
+ if(!(ctl.flags&CFenabled))
+ return (CAnone, nil);
+ ans := CAnone;
+ changed := 0;
+ newgrab : ref Control;
+ grabbed := oldgrab != nil;
+ pick c := ctl {
+ Cbutton =>
+ if(down) {
+ c.flags |= CFactive;
+ newgrab = c;
+ changed = 1;
+ }
+ else if(move && c.ff == nil) {
+ ans = CAflyover;
+ }
+ else if (drag && grabbed) {
+ newgrab = c;
+ active := 0;
+ if (p.in(c.r))
+ active = CFactive;
+ if ((c.flags & CFactive) != active)
+ changed = 1;
+ c.flags = (c.flags & ~CFactive) | active;
+ }
+ else if(up) {
+ if (c.flags & CFactive)
+ ans = CAbuttonpush;
+ c.flags &= ~CFactive;
+ changed = 1;
+ }
+ Centry =>
+ if(c.scr != nil && !grabbed && p.x >= c.r.max.x-SCRFBREADTH) {
+ pick scr := c.scr {
+ Cscrollbar =>
+ return scr.domouse(p, mtype, oldgrab);
+ }
+ }
+ (sels, sele) := c.sel;
+ if(mtype == E->Mlbuttonup && grabbed) {
+ if (sels != sele)
+ ans = CAselected;
+ }
+ if(down || (drag && grabbed)) {
+ newgrab = c;
+ x := c.r.min.x+ENTHMARGIN;
+ fnt := fonts[CtlFnt].f;
+ s := c.s;
+ if(c.flags&CFsecure) {
+ for(i := 0; i < len s; i++)
+ s[i] = '*';
+ }
+ (osels, osele) := c.sel;
+ s1 := " ";
+ i := 0;
+ iend := len s - 1;
+ if(c.linewrap) {
+ (lines, linestarts, topline, cursline) := entrywrapcalc(c);
+ if(len lines > 1) {
+ lineno := topline + (p.y - (c.r.min.y+ENTVMARGIN)) / ctllinespace;
+ lineno = min(lineno, len lines -1);
+ lineno = max(lineno, 0);
+
+ i = linestarts[lineno];
+ iend = i + len lines[lineno] -1;
+ }
+ } else
+ x -= fnt.width(s[:c.left]);
+ for(; i <= iend; i++) {
+ s1[0] = s[i];
+ cx := fnt.width(s1);
+ if(p.x < x + cx)
+ break;
+ x += cx;
+ }
+ sele = i;
+
+ if (down)
+ sels = sele;
+ c.sel = (sels, sele);
+
+ if (sels != osels || sele != osele) {
+ changed = 1;
+ entryscroll(c);
+ if (p.x < c.r.min.x + ENTHMARGIN || p.x > c.r.max.x - ENTHMARGIN
+ || p.y < c.r.min.y + ENTVMARGIN || p.y > c.r.max.y - ENTVMARGIN) {
+ E->autorepeat(ref (Event.Emouse)(p, mtype), ARTICK, ARTICK);
+ }
+ }
+
+ if(!(c.flags&CFhasfocus))
+ ans = CAkeyfocus;
+ }
+ Ccheckbox=>
+ if(up) {
+ if(c.isradio) {
+ if(!(c.flags&CFactive)) {
+ c.flags |= CFactive;
+ changed = 1;
+ ans = CAbuttonpush;
+ # turn off other radio button
+ frm := c.ff.form;
+ for(lf := frm.fields; lf != nil; lf = tl lf) {
+ ff := hd lf;
+ if(ff == c.ff)
+ continue;
+ if(ff.ftype == Fradio && ff.name==c.ff.name && ff.ctlid >= 0) {
+ d := c.f.controls[ff.ctlid];
+ if(d.flags&CFactive) {
+ d.flags &= ~CFactive;
+ d.draw(1);
+ break; # at most one other should be on
+ }
+ }
+ }
+ }
+ }
+ else {
+ c.flags ^= CFactive;
+ changed = 1;
+ }
+ }
+ Cselect =>
+ if (c.nvis == 1 && up && c.popup == nil && c.r.contains(p))
+ return (CAdopopup, nil);
+ if(c.scr != nil && (grabbed || p.x >= c.r.max.x-SCRFBREADTH)) {
+ pick scr := c.scr {
+ Cscrollbar =>
+ (a, grab) := scr.domouse(p, mtype, oldgrab);
+ if (grab != nil)
+ grab = c;
+ return (a, grab);
+ }
+ return (ans, nil);
+ }
+ n := (p.y - (c.r.min.y+SELMARGIN))/ctllinespace + c.first;
+ if (n >= c.first && n < c.first+c.nvis) {
+ if ((c.ff.flags&B->FFmultiple) != byte 0) {
+ if (down) {
+ c.options[n].selected ^= 1;
+ changed = 1;
+ }
+ } else if (up || drag) {
+ changed = c.options[n].selected == 0;
+ c.options[n].selected = 1;
+ for(i := 0; i < len c.options; i++) {
+ if(i != n)
+ c.options[i].selected = 0;
+ }
+ }
+ }
+ if (up) {
+ if (c.popup != nil)
+ ans = CAdonepopup;
+ else
+ ans = CAchanged;
+ }
+ Clistbox =>
+ if(c.vscr != nil && (c.grab == c.vscr || (!grabbed && p.x >= c.r.max.x-SCRFBREADTH))) {
+ c.grab = nil;
+ pick vscr := c.vscr {
+ Cscrollbar =>
+ (a, grab) := vscr.domouse(p, mtype, oldgrab);
+ if (grab != nil) {
+ c.grab = c.vscr;
+ grab = c;
+ }
+ return (a, grab);
+ }
+ }
+ else if(c.hscr != nil && (c.grab == c.hscr || (!grabbed && p.y >= c.r.max.y-SCRFBREADTH))) {
+ c.grab = nil;
+ pick hscr := c.hscr {
+ Cscrollbar =>
+ (a, grab) := hscr.domouse(p, mtype, oldgrab);
+ if (grab != nil) {
+ c.grab = c.hscr;
+ grab = c;
+ }
+ return (a, grab);
+ }
+ }
+ else if(up) {
+ fnt := fonts[CtlFnt].f;
+ n := (p.y - (c.r.min.y+SELMARGIN))/fnt.height + c.first;
+ if(n >= 0 && n < len c.options) {
+ c.options[n].selected ^= 1;
+ # turn off other selections
+ for(i := 0; i < len c.options; i++) {
+ if(i != n)
+ c.options[i].selected = 0;
+ }
+ ans = CAchanged;
+ changed = 1;
+ }
+ }
+ Cscrollbar =>
+ val := 0;
+ v, vmin, vmax, b: int;
+ if(c.flags&CFscrvert) {
+ v = p.y;
+ vmin = c.r.min.y;
+ vmax = c.r.max.y;
+ b = c.r.dx();
+ }
+ else {
+ v = p.x;
+ vmin = c.r.min.x;
+ vmax = c.r.max.x;
+ b = c.r.dy();
+ }
+ vsltop := vmin+b+c.top;
+ vslbot := vmax-b-c.bot;
+ actflags := 0;
+ oldactflags := c.flags&CFscrallact;
+
+ if ((down || drag) && !up && !hold)
+ newgrab = c;
+
+ if (down) {
+ newgrab = c;
+ holdval := 0;
+ repeat := 1;
+ if (v >= vsltop && v < vslbot) {
+ holdval = v - vsltop;
+ actflags = CFactive;
+ repeat = 0;
+ }
+ if(v < vmin+b) {
+ holdval = -1;
+ actflags = CFscracta1;
+ }
+ else if(v < vsltop) {
+ holdval = -1;
+ actflags = CFscracttr1;
+ }
+ else if(v >= vmax-b) {
+ holdval = 1;
+ actflags = CFscracta2;
+ }
+ else if(v >= vslbot) {
+ holdval = 1;
+ actflags = CFscracttr2;
+ }
+ c.holdstate = (actflags, holdval);
+ if (repeat) {
+ E->autorepeat(ref (Event.Emouse)(p, E->Mhold), ARPAUSE, ARTICK);
+ }
+ }
+ if (drag) {
+ (actflags, val) = c.holdstate;
+ if (actflags == CFactive) {
+ # dragging main scroll widget (relative to top of drag block)
+ val = (v - vsltop) - val;
+ if(abs(val) >= c.mindelta) {
+ ans = CAscrolldelta;
+ val = (c.deltaval * (val / c.mindelta))/SCRDELTASF;
+ }
+ } else {
+ E->autorepeat(ref (Event.Emouse)(p, E->Mhold), ARTICK, ARTICK);
+ }
+ }
+ if (up || hold) {
+ # set the action according to the hold state
+ # Note: main widget (case CFactive) handled by drag
+ act := 0;
+ (act, val) = c.holdstate;
+ case act {
+ CFscracta1 or
+ CFscracta2 =>
+ ans = CAscrollline;
+ CFscracttr1 or
+ CFscracttr2 =>
+ ans = CAscrollpage;
+ }
+ if (up) {
+ c.holdstate = (0, 0);
+ } else { # hold
+ (actflags, nil) = c.holdstate;
+ if (ans != CAnone) {
+ E->autorepeat(ref (Event.Emouse)(p, E->Mhold), ARTICK, ARTICK);
+ newgrab = c;
+ }
+ }
+ }
+ c.flags = (c.flags & ~CFscrallact) | actflags;
+ if(ans != CAnone) {
+ ftoscroll := c.f;
+ if(c.ctl != nil) {
+ pick cff := c.ctl {
+ Centry =>
+ ny := (cff.r.dy() - 2 * ENTVMARGIN) / ctllinespace;
+ (nil, linestarts, topline, nil) := entrywrapcalc(cff);
+ nlines := len linestarts;
+ case ans {
+ CAscrollpage =>
+ topline += val*ny;
+ CAscrollline =>
+ topline += val;
+ CAscrolldelta =>
+# # insufficient for large number of lines
+ topline += val;
+# if(val > 0)
+# topline++;
+# else
+# topline--;
+ }
+ if (topline+ny >= nlines)
+ topline = (nlines-1) - ny;
+ if (topline < 0)
+ topline = 0;
+ cff.left = linestarts[topline];
+ c.scrollset(topline, topline+ny, nlines - 1, nlines, 1);
+ cff.draw(1);
+ return (ans, newgrab);
+ Cselect =>
+ newfirst := cff.first;
+ case ans {
+ CAscrollpage =>
+ newfirst += val*cff.nvis;
+ CAscrollline =>
+ newfirst += val;
+ CAscrolldelta =>
+# # insufficient for very long select lists
+ newfirst += val;
+# if(val > 0)
+# newfirst++;
+# else
+# newfirst--;
+ }
+ newfirst = max(0, min(newfirst, len cff.options - cff.nvis));
+ cff.first = newfirst;
+ nopt := len cff.options;
+ c.scrollset(newfirst, newfirst+cff.nvis, nopt, nopt, 0);
+ cff.draw(1);
+ return (ans, newgrab);
+ Clistbox =>
+ if(c.flags&CFscrvert) {
+ newfirst := cff.first;
+ case ans {
+ CAscrollpage =>
+ newfirst += val*cff.nvis;
+ CAscrollline =>
+ newfirst += val;
+ CAscrolldelta =>
+ newfirst += val;
+# if(val > 0)
+# newfirst++;
+# else
+# newfirst--;
+ }
+ newfirst = max(0, min(newfirst, len cff.options - cff.nvis));
+ cff.first = newfirst;
+ c.scrollset(newfirst, newfirst+cff.nvis, len cff.options, 0, 1);
+ # TODO: need redraw only vscr and content
+ }
+ else {
+ hw := cff.maxcol;
+ w := (c.r.max.x - c.r.min.x - SCRFBREADTH)/charspace;
+ newstart := cff.start;
+ case ans {
+ CAscrollpage =>
+ newstart += val*hw;
+ CAscrollline =>
+ newstart += val;
+ CAscrolldelta =>
+ if(val > 0)
+ newstart++;
+ else
+ newstart--;
+ }
+ if(hw < w)
+ newstart = 0;
+ else
+ newstart = max(0, min(newstart, hw - w));
+ cff.start = newstart;
+ c.scrollset(newstart, w+newstart, hw, 0, 1);
+ # TODO: need redraw only hscr and content
+ }
+ cff.draw(1);
+ return (ans, newgrab);
+ }
+ }
+ else {
+ if(c.flags&CFscrvert)
+ c.f.yscroll(ans, val);
+ else
+ c.f.xscroll(ans, val);
+ }
+ changed = 1;
+ }
+ else if(actflags != oldactflags) {
+ changed = 1;
+ }
+ }
+ if(changed)
+ ctl.draw(1);
+ return (ans, newgrab);
+}
+
+# returns a new popup control
+Control.dopopup(ctl: self ref Control): ref Control
+{
+ sel : ref Control.Cselect;
+ pick c := ctl {
+ Cselect =>
+ if (c.nvis > 1)
+ return nil;
+ sel = c;
+ * =>
+ return nil;
+ }
+
+ w := sel.r.dx();
+ nopt := len sel.options;
+ nvis := min(nopt, POPUPLINES);
+ first := sel.first;
+ if (first + nvis > nopt)
+ first = nopt - nvis;
+ h := ctllinespace*nvis + 2*SELMARGIN;
+ r := Rect(sel.r.min, sel.r.min.add(Point(w, h)));
+ popup := G->getpopup(r);
+ if (popup == nil)
+ return nil;
+ scr : ref Control;
+ if (nvis < nopt) {
+ scr = Control.newscroll(sel.f, 1, h, SCRFBREADTH);
+ scr.r.addpt(Point(w,0));
+ }
+ newsel := ref Control.Cselect(sel.f, sel.ff, r, sel.flags, popup, sel, scr, nvis, first, sel.options);
+ if(scr != nil) {
+ pick pscr := scr {
+ Cscrollbar =>
+ pscr.ctl = newsel;
+ }
+ scr.popup = popup;
+ scr.scrollset(first, first+nvis, nopt, nopt, 0);
+ }
+ newsel.draw(1);
+ return newsel;
+}
+
+# returns original control for which this was a popup
+Control.donepopup(ctl: self ref Control): ref Control
+{
+ owner: ref Control;
+ pick c := ctl {
+ Cselect =>
+ if (c.owner == nil)
+ return nil;
+ owner = c.owner;
+ * =>
+ return nil;
+ }
+ G->cancelpopup();
+ pick c := owner {
+ Cselect =>
+ for (first := 0; first < len c.options; first++)
+ if (c.options[first].selected)
+ break;
+ if (first == len c.options)
+ first = 0;
+ c.first = first;
+ }
+ owner.draw(1);
+ return owner;
+}
+
+
+Control.reset(ctl: self ref Control)
+{
+ pick c := ctl {
+ Cbutton =>
+ c.flags &= ~CFactive;
+ Centry =>
+ c.s = "";
+ c.sel = (0, 0);
+ c.left = 0;
+ if(c.ff != nil && c.ff.value != "")
+ c.s = c.ff.value;
+ if (c.scr != nil)
+ c.scr.scrollset(0, 1, 1, 0, 0);
+ Ccheckbox=>
+ c.flags &= ~CFactive;
+ if(c.ff != nil && (c.ff.flags&B->FFchecked) != byte 0)
+ c.flags |= CFactive;
+ Cselect =>
+ nopt := len c.options;
+ if(c.ff != nil) {
+ l := c.ff.options;
+ for(i := 0; i < nopt; i++) {
+ o := hd l;
+ c.options[i].selected = o.selected;
+ l = tl l;
+ }
+ }
+ c.first = 0;
+ if(c.scr != nil) {
+ c.scr.scrollset(0, c.nvis, nopt, nopt, 0);
+ }
+ Clistbox =>
+ c.first = 0;
+ nopt := len c.options;
+ if(c.vscr != nil) {
+ c.vscr.scrollset(0, c.nvis, nopt, nopt, 0);
+ }
+ hw := 0;
+ for(i := 0; i < len c.options; i++)
+ hw = max(hw, fonts[DefFnt].f.width(c.options[i].display));
+ if(c.hscr != nil) {
+ c.hscr.scrollset(0, c.r.max.x, hw, 0, 0);
+ }
+ Canimimage =>
+ c.cur = 0;
+ }
+ ctl.draw(0);
+}
+
+Control.draw(ctl: self ref Control, flush: int)
+{
+ win := ctl.f.cim;
+ if (ctl.popup != nil)
+ win = ctl.popup.image;
+ if (win == nil)
+ return;
+ oclipr := win.clipr;
+ clipr := oclipr;
+ any: int;
+ if (ctl.popup == nil) {
+ (clipr, any) = ctl.r.clip(ctl.f.cr);
+ if(!any && ctl != ctl.f.vscr && ctl != ctl.f.hscr)
+ return;
+ win.clipr = clipr;
+ }
+ pick c := ctl {
+ Cbutton =>
+ if(c.ff != nil && c.ff.image != nil && c.pic == nil) {
+ # check to see if image arrived
+ # (dimensions will have been set by checkffsize, if needed;
+ # this code is only for when the HTML specified the dimensions)
+ pick imi := c.ff.image {
+ Iimage =>
+ if(imi.ci.mims != nil) {
+ c.pic = imi.ci.mims[0].im;
+ c.picmask = imi.ci.mims[0].mask;
+ }
+ }
+ }
+ if(c.dorelief || c.pic == nil)
+ win.draw(c.r, colorimage(Grey), nil, zp);
+ if(c.pic != nil) {
+ p, m: ref Image;
+ if(c.flags & CFenabled) {
+ p = c.pic;
+ m = c.picmask;
+ }
+ else {
+ p = c.dpic;
+ m = c.dpicmask;
+ }
+ w := p.r.dx();
+ h := p.r.dy();
+ x := c.r.min.x + (c.r.dx() - w) / 2;
+ y := c.r.min.y + (c.r.dy() - h) / 2;
+ if((c.flags & CFactive) && c.dorelief) {
+ x++;
+ y++;
+ }
+ win.draw(Rect((x,y),(x+w,y+h)), p, m, zp);
+ }
+ else if(c.label != "") {
+ p := c.r.min.add(Point(BUTMARGIN, BUTMARGIN));
+ if(c.flags & CFactive)
+ p = p.add(Point(1,1));
+ win.text(p, colorimage(Black), zp, fonts[CtlFnt].f, c.label);
+ }
+ if(c.dorelief) {
+ relief := ReliefRaised;
+ if(c.flags & CFactive)
+ relief = ReliefSunk;
+ drawrelief(win, c.r.inset(2), relief);
+ }
+ Centry =>
+ win.draw(c.r, colorimage(White), nil, zp);
+ insetr := c.r.inset(2);
+ drawrelief(win,insetr, ReliefSunk);
+ eclipr := c.r;
+ eclipr.min.x += ENTHMARGIN;
+ eclipr.max.x -= ENTHMARGIN;
+ eclipr.min.y += ENTVMARGIN;
+ eclipr.max.y -= ENTVMARGIN;
+# if (c.scr != nil)
+# eclipr.max.x -= SCRFBREADTH;
+ (eclipr, any) = clipr.clip(eclipr);
+ win.clipr = eclipr;
+ p := c.r.min.add(Point(ENTHMARGIN,ENTVMARGIN));
+ s := c.s;
+ fnt := fonts[CtlFnt].f;
+ if(c.left > 0)
+ s = s[c.left:];
+ if(c.flags&CFsecure) {
+ for(i := 0; i < len s; i++)
+ s[i] = '*';
+ }
+
+ (sels, sele) := normalsel(c.sel);
+ (sels, sele) = (sels-c.left, sele-c.left);
+
+ lines : array of string;
+ linestarts : array of int;
+ textw := c.r.dx()-2*ENTHMARGIN;
+ if (c.scr != nil) {
+ textw -= SCRFBREADTH;
+ c.scr.r = c.scr.r.subpt(c.scr.r.min);
+ c.scr.r = c.scr.r.addpt(Point(insetr.max.x-SCRFBREADTH,insetr.min.y));
+ c.scr.draw(0);
+ }
+ if (c.linewrap)
+ (lines, linestarts) = wrapstring(fnt, s, textw);
+ else
+ (lines, linestarts) = (array [] of {s}, array [] of {0});
+
+ q := p;
+ black := colorimage(Black);
+ white := colorimage(White);
+ navy := colorimage(Navy);
+ nlines := len lines;
+ for (n := 0; n < nlines; n++) {
+ segs : list of (int, int, int);
+ # only show cursor or selection if we have focus
+ if (c.flags & CFhasfocus)
+ segs = selsegs(len lines[n], sels-linestarts[n], sele-linestarts[n]);
+ else
+ segs = (0, len lines[n], 0) :: nil;
+ for (; segs != nil; segs = tl segs) {
+ (ss, se, sel) := hd segs;
+ txt := lines[n][ss:se];
+ w := fnt.width(txt);
+ txtcol : ref Image;
+ if (!sel)
+ txtcol = black;
+ else {
+ txtcol = white;
+ bgcol := navy;
+ if (n < nlines-1 && sele >= linestarts[n+1])
+ w = (p.x-q.x) + textw;
+ selr := Rect((q.x, q.y-1), (q.x+w, q.y+ctllinespace+1));
+ if (selr.dx() == 0) {
+ # empty selection - assume cursor
+ bgcol = black;
+ selr.max.x = selr.min.x + 2;
+ }
+ win.draw(selr, bgcol, nil, zp);
+ }
+ if (se > ss)
+ win.text(q, txtcol, zp, fnt, txt);
+ q.x += w;
+ }
+ q = (p.x, q.y + ctllinespace);
+ }
+ Ccheckbox=>
+ win.draw(c.r, colorimage(White), nil, zp);
+ if(c.isradio) {
+ a := CBOXHT/2;
+ a1 := a-1;
+ cen := Point(c.r.min.x+a,c.r.min.y+a);
+ win.ellipse(cen, a1, a1, 1, colorimage(DarkGrey), zp);
+ win.arc(cen, a, a, 0, colorimage(Black), zp, 45, 180);
+ win.arc(cen, a, a, 0, colorimage(Grey), zp, 225, 180);
+ if(c.flags&CFactive)
+ win.fillellipse(cen, 2, 2, colorimage(Black), zp);
+ }
+ else {
+ ir := c.r.inset(2);
+ ir.min.x += CBOXWID-CBOXHT;
+ ir.max.x -= CBOXWID-CBOXHT;
+ drawrelief(win, ir, ReliefSunk);
+ if(c.flags&CFactive) {
+ p1 := Point(ir.min.x, ir.min.y);
+ p2 := Point(ir.max.x, ir.max.y);
+ p3 := Point(ir.max.x, ir.min.y);
+ p4 := Point(ir.min.x, ir.max.y);
+ win.line(p1, p2, D->Endsquare, D->Endsquare, 0, colorimage(Black), zp);
+ win.line(p3, p4, D->Endsquare, D->Endsquare, 0, colorimage(Black), zp);
+ }
+ }
+ Cselect =>
+ black := colorimage(Black);
+ white := colorimage(White);
+ navy := colorimage(Navy);
+ win.draw(c.r, white, nil, zp);
+ drawrelief(win, c.r.inset(2), ReliefSunk);
+ ir := c.r.inset(SELMARGIN);
+ p := ir.min;
+ fnt := fonts[CtlFnt].f;
+ drawsel := c.nvis > 1;
+ for(i := c.first; i < len c.options && i < c.first+c.nvis; i++) {
+ if(drawsel && c.options[i].selected) {
+ maxx := ir.max.x;
+ if (c.scr != nil)
+ maxx -= SCRFBREADTH;
+ r := Rect((p.x-SELMARGIN,p.y),(maxx,p.y+ctllinespace));
+ win.draw(r, navy, nil, zp);
+ win.text(p, white, zp, fnt, c.options[i].display);
+ }
+ else {
+ win.text(p, black, zp, fnt, c.options[i].display);
+ }
+ p.y += ctllinespace;
+ }
+ if (c.nvis == 1 && len c.options > 1) {
+ # drop down select list - draw marker (must be same width as scroll bar)
+ r := Rect((ir.max.x - SCRFBREADTH, ir.min.y), ir.max);
+ drawtriangle(win, r, TRIdown, ReliefRaised);
+ }
+ if(c.scr != nil) {
+ c.scr.r = Rect((ir.max.x - SCRFBREADTH, ir.min.y), ir.max);
+ c.scr.draw(0);
+ }
+ Clistbox =>
+ black := colorimage(Black);
+ white := colorimage(White);
+ navy := colorimage(Navy);
+ win.draw(c.r, white, nil, zp);
+ insetr := c.r.inset(2);
+ #drawrelief(win, c.r.inset(2), ReliefSunk);
+ ir := c.r.inset(SELMARGIN);
+ p := ir.min;
+ fnt := fonts[CtlFnt].f;
+ for(i := c.first; i < len c.options && i < c.first+c.nvis; i++) {
+ txt := "";
+ if (c.start < len c.options[i].display)
+ txt = c.options[i].display[c.start:];
+ if(c.options[i].selected) {
+ r := Rect((p.x-SELMARGIN,p.y),(c.r.max.x-SCRFBREADTH,p.y+fnt.height));
+ win.draw(r, navy, nil, zp);
+ win.text(p, white, zp, fnt, txt);
+ }
+ else {
+ win.text(p, black, zp, fnt, txt);
+ }
+ p.y +=fnt.height;
+ }
+ if(c.vscr != nil) {
+ c.vscr.r = c.vscr.r.subpt(c.vscr.r.min);
+ c.vscr.r = c.vscr.r.addpt(Point(insetr.max.x-SCRFBREADTH,insetr.min.y));
+ c.vscr.draw(0);
+ }
+ if(c.hscr != nil) {
+ c.hscr.r = c.hscr.r.subpt(c.hscr.r.min);
+ c.hscr.r = c.hscr.r.addpt(Point(insetr.min.x, insetr.max.y-SCRFBREADTH));
+ c.hscr.draw(0);
+ }
+ drawrelief(win, insetr, ReliefSunk);
+
+ Cscrollbar =>
+ # Scrollbar components: arrow 1 (a1), trough 1 (t1), slider (s), trough 2 (t2), arrow 2 (a2)
+ x := c.r.min.x;
+ y := c.r.min.y;
+ ra1, rt1, rs, rt2, ra2: Rect;
+ b, l, a1kind, a2kind: int;
+ if(c.flags&CFscrvert) {
+ l = c.r.max.y - c.r.min.y;
+ b = c.r.max.x - c.r.min.x;
+ xr := x+b;
+ yt1 := y+b;
+ ys := yt1+c.top;
+ yb := y+l;
+ ya2 := yb-b;
+ yt2 := ya2-c.bot;
+ ra1 = Rect(Point(x,y),Point(xr,yt1));
+ rt1 = Rect(Point(x,yt1),Point(xr,ys));
+ rs = Rect(Point(x,ys),Point(xr,yt2));
+ rt2 = Rect(Point(x,yt2),Point(xr,ya2));
+ ra2 = Rect(Point(x,ya2),Point(xr,yb));
+ a1kind = TRIup;
+ a2kind = TRIdown;
+ }
+ else {
+ l = c.r.max.x - c.r.min.x;
+ b = c.r.max.y - c.r.min.y;
+ yb := y+b;
+ xt1 := x+b;
+ xs := xt1+c.top;
+ xr := x+l;
+ xa2 := xr-b;
+ xt2 := xa2-c.bot;
+ ra1 = Rect(Point(x,y),Point(xt1,yb));
+ rt1 = Rect(Point(xt1,y),Point(xs,yb));
+ rs = Rect(Point(xs,y),Point(xt2,yb));
+ rt2 = Rect(Point(xt2,y),Point(xa2,yb));
+ ra2 = Rect(Point(xa2,y),Point(xr,yb));
+ a1kind = TRIleft;
+ a2kind = TRIright;
+ }
+ a1relief := ReliefRaised;
+ if(c.flags&CFscracta1)
+ a1relief = ReliefSunk;
+ a2relief := ReliefRaised;
+ if(c.flags&CFscracta2)
+ a2relief = ReliefSunk;
+ drawtriangle(win, ra1, a1kind, a1relief);
+ drawtriangle(win, ra2, a2kind, a2relief);
+ drawfill(win, rt1, Grey);
+ rs = rs.inset(2);
+ drawfill(win, rs, Grey);
+ rsrelief := ReliefRaised;
+ if(c.flags&CFactive)
+ rsrelief = ReliefSunk;
+ drawrelief(win, rs, rsrelief);
+ drawfill(win, rt2, Grey);
+ Canimimage =>
+ i := c.cur;
+ if(c.redraw)
+ i = 0;
+ else if(i > 0) {
+ iprev := i-1;
+ if(c.cim.mims[iprev].bgcolor != -1) {
+ i = iprev;
+ # get i back to before all "reset to previous"
+ # images (which will be skipped in following
+ # image drawing loop)
+ while(i > 0 && c.cim.mims[i].bgcolor == -2)
+ i--;
+ }
+ }
+ bgi := colorimage(c.bg.color);
+ if(c.bg.image != nil && c.bg.image.ci != nil && len c.bg.image.ci.mims > 0)
+ bgi = c.bg.image.ci.mims[0].im;
+ for( ; i <= c.cur; i++) {
+ mim := c.cim.mims[i];
+ if(i > 0 && i < c.cur && mim.bgcolor == -2)
+ continue;
+ p := c.r.min.add(mim.origin);
+ r := mim.im.r;
+ r = Rect(p, p.add(Point(r.dx(), r.dy())));
+
+ # IE takes "clear-to-background" disposal method to mean
+ # clear to background of HTML page, ignoring any background
+ # color specified in the GIF.
+ # IE clears to background before frame 0
+ if(i == 0)
+ win.draw(c.r, bgi, nil, zp);
+
+ if(i != c.cur && mim.bgcolor >= 0)
+ win.draw(r, bgi, nil, zp);
+ else
+ win.draw(r, mim.im, mim.mask, zp);
+ }
+ Clabel =>
+ p := c.r.min.add(Point(0,ENTVMARGIN));
+ win.text(p, colorimage(Black), zp, fonts[DefFnt].f, c.s);
+ }
+ if(flush) {
+ if (ctl.popup != nil)
+ ctl.popup.flush(ctl.r);
+ else
+ G->flush(ctl.r);
+ }
+ win.clipr = oclipr;
+}
+
+# Break s up into substrings that fit in width availw
+# when printing with font fnt.
+# The second returned array contains the indexes into the original
+# string where the corresponding line starts (which might not be simply
+# the sum of the preceding lines because of cr/lf's in the original string
+# which are omitted from the lines array.
+# Empty lines (ending in cr) get put into the array as empty strings.
+# The start indices array has an entry for the phantom next line, to avoid
+# the need for special cases in the rest of the code.
+wrapstring(fnt: ref Font, s: string, availw: int) : (array of string, array of int)
+{
+ sl : list of (string, int) = nil;
+ sw := fnt.width(s);
+ n := 0;
+ k := 0; # index into original s where current s starts
+ origlen := len s;
+ done := 0;
+ while(!done) {
+ kincr := 0;
+ s1, s2: string;
+ if(s == "") {
+ s1 = s;
+ done = 1;
+ }
+ else {
+ # if any newlines in s1, it's a forced break
+ # (and newlines aren't to appear in result)
+ (s1, s2) = S->splitl(s, "\n");
+ if(s2 != nil && fnt.width(s1) <= availw) {
+ s = s2[1:];
+ sw = fnt.width(s);
+ kincr = (len s1) + 1;
+ }
+ else if(sw <= availw) {
+ s1 = s;
+ done = 1;
+ }
+ else {
+ (s1, nil, s, sw) = breakstring(s, sw, fnt, availw, 0);
+ kincr = len s1;
+ if(s == "")
+ done = 1;
+ }
+ }
+ sl = (s1, k) :: sl;
+ k += kincr;
+ n++;
+ }
+ # reverse sl back to original order
+ lines := array[n] of string;
+ linestarts := array[n+1] of int;
+ linestarts[n] = origlen;
+ while(sl != nil) {
+ (ss, nn) := hd sl;
+ lines[--n] = ss;
+ linestarts[n] = nn;
+ sl = tl sl;
+ }
+ return (lines, linestarts);
+}
+
+normalsel(sel : (int, int)) : (int, int)
+{
+ (s, e) := sel;
+ if (s > e)
+ (e, s) = sel;
+ return (s, e);
+}
+
+selsegs(n, s, e : int) : list of (int, int, int)
+{
+ if (e < 0 || s > n)
+ # selection is not in 0..n
+ return (0, n, 0) :: nil;
+
+ if (e > n) {
+ # second half of string is selected
+ if (s <= 0)
+ return (0, n, 1) :: nil;
+ return (0, s, 0) :: (s, n, 1) :: nil;
+ }
+
+ if (s < 0) {
+ # first half of string is selected
+ if (e >= n)
+ return (0, n, 1) :: nil;
+ return (0, e, 1) :: (e, n, 0) :: nil;
+ }
+ # middle section of string is selected
+ return (0, s, 0) :: (s, e, 1) :: (e, n, 0) :: nil;
+}
+
+# Figure out in which area of scrollbar, if any, p lies.
+# Then use p and mtype from mouse event to return desired action.
+Control.entryset(c: self ref Control, s: string)
+{
+ pick e := c {
+ Centry =>
+ e.s = s;
+ e.sel = (0, 0);
+ e.left = 0;
+ # calculate scroll bar settings
+ if (e.linewrap && e.scr != nil) {
+ (lines, nil, nil, nil) := entrywrapcalc(e);
+ nlines := len lines;
+ ny := (e.r.dy() - 2 * ENTVMARGIN)/ctllinespace;
+ e.scr.scrollset(0, ny, (nlines - 1), nlines, 0);
+ }
+ }
+}
+
+entryupdown(e: ref Control.Centry, cur : int, delta : int) : int
+{
+ e.sel = (cur, cur);
+ (lines, linestarts, topline, cursline) := entrywrapcalc(e);
+ newl := cursline + delta;
+ if (newl < 0 || newl >= len lines)
+ return cur;
+
+ fnt := fonts[CtlFnt].f;
+ x := cur - linestarts[cursline];
+ w := fnt.width(lines[cursline][0:x]);
+ l := lines[newl];
+ if (len l == 0)
+ return linestarts[newl];
+ prevw := fnt.width(l);
+ curw := prevw;
+ for (ix := len l - 1; ix > 0 ; ix--) {
+ prevw = curw;
+ curw = fnt.width(l[:ix]);
+ if (curw < w)
+ break;
+ }
+ # decide on closest (curw <= w <= prevw)
+ if (prevw-w <= w - curw)
+ # closer to rhs
+ ix++;
+ return linestarts[newl]+ix;
+}
+
+# delete given range of characters, and redraw
+entrydelrange(e: ref Control.Centry, istart, iend: int)
+{
+ n := iend - istart;
+ (sels, sele) := normalsel(e.sel);
+ if(n > 0) {
+ e.s = e.s[0:istart] + e.s[iend:];
+
+ if(sels > istart) {
+ if(sels < iend)
+ sels = istart;
+ else
+ sels -= n;
+ }
+ if (sele > istart) {
+ if (sele < iend)
+ sele = istart;
+ else
+ sele -= n;
+ }
+
+ if(e.left > istart)
+ e.left = max(istart-1, 0);
+ e.sel = (sels, sele);
+ entryscroll(e);
+ }
+}
+
+snarf : string;
+entrysetsnarf(e: ref Control.Centry)
+{
+ if (e.s == nil)
+ return;
+ s := e.s;
+ (sels, sele) := normalsel(e.sel);
+ if (sels != sele)
+ s = e.s[sels:sele];
+
+ f := sys->open("/chan/snarf", sys->OWRITE);
+ if (f == nil)
+ snarf = s;
+ else {
+ data := array of byte s;
+ sys->write(f, data, len data);
+ }
+}
+
+entryinsertsnarf(e: ref Control.Centry)
+{
+ f := sys->open("/chan/snarf", sys->OREAD);
+ if(f != nil) {
+ buf := array[sys->ATOMICIO] of byte;
+ n := sys->read(f, buf, len buf);
+ if(n > 0) {
+ # trim a trailing newline, as a service...
+ if(buf[n-1] == byte '\n')
+ n--;
+ }
+ snarf = "";
+ if (n > 0)
+ snarf = string buf[:n];
+ }
+
+ if (snarf != nil) {
+ (sels, sele) := normalsel(e.sel);
+ if (sels != sele) {
+ entrydelrange(e, sels, sele);
+ (sels, sele) = e.sel;
+ }
+ lhs, rhs : string;
+ if (sels > 0)
+ lhs = e.s[:sels];
+ if (sels < len e.s)
+ rhs = e.s[sels:];
+ e.entryset(lhs + snarf + rhs);
+ e.sel = (len lhs, len lhs + len snarf);
+ }
+}
+
+# make sure can see cursor and following char or two
+entryscroll(e: ref Control.Centry)
+{
+ s := e.s;
+ slen := len s;
+ if(e.flags&CFsecure) {
+ for(i := 0; i < slen; i++)
+ s[i] = '*';
+ }
+ if(e.linewrap) {
+ # For multiple line entries, c.left is the char
+ # at the beginning of the topmost visible line,
+ # and we just want to scroll to make sure that
+ # the line with the cursor is visible
+ (lines, linestarts, topline, cursline) := entrywrapcalc(e);
+ vislines := (e.r.dy()-2*ENTVMARGIN) / ctllinespace;
+ nlines := len linestarts;
+ if(cursline < topline)
+ topline = cursline;
+ else {
+ if(cursline >= topline+vislines)
+ topline = cursline-vislines+1;
+ if (topline + vislines >= nlines)
+ topline = max(0, (nlines-1) - vislines);
+ }
+ e.left = linestarts[topline];
+ if (e.scr != nil)
+ e.scr.scrollset(topline, topline+vislines, nlines-1, nlines, 1);
+ }
+ else {
+ (nil, sele) := e.sel;
+ # sele is always the drag point
+ if(sele < e.left)
+ e.left = sele;
+ else if(sele > e.left) {
+ fnt := fonts[CtlFnt].f;
+ wantw := e.r.dx() -2*ENTHMARGIN; # - 2*ctlspspace;
+ while(e.left < sele-1) {
+ w := fnt.width(e.s[e.left:sele]);
+ if(w < wantw)
+ break;
+ e.left++;
+ }
+ }
+ }
+}
+
+# Given e, a Centry with line wrapping,
+# return (wrapped lines, line start indices, line# of top displayed line, line# containing cursor).
+entrywrapcalc(e: ref Control.Centry) : (array of string, array of int, int, int)
+{
+ s := e.s;
+ if(e.flags&CFsecure) {
+ for(i := 0; i < len s; i++)
+ s[i] = '*';
+ }
+ (nil, sele) := e.sel;
+ textw := e.r.dx()-2*ENTHMARGIN;
+ if (e.scr != nil)
+ textw -= SCRFBREADTH;
+ (lines, linestarts) := wrapstring(fonts[CtlFnt].f, s, textw);
+ topline := 0;
+ cursline := 0;
+ for(i := 0; i < len lines; i++) {
+ s = lines[i];
+ i1 := linestarts[i];
+ i2 := linestarts[i+1];
+ if(e.left >= i1 && e.left < i2)
+ topline = i;
+ if(sele >= i1 && sele < i2)
+ cursline = i;
+ }
+ if(sele == linestarts[len lines])
+ cursline = len lines - 1;
+ return (lines, linestarts, topline, cursline);
+}
+
+Lay.new(targwidth: int, just: byte, margin: int, bg: Background) : ref Lay
+{
+ ans := ref Lay(Line.new(), Line.new(),
+ targwidth, 0, 0, margin, nil, bg, just, byte 0);
+ if(ans.targetwidth < 0)
+ ans.targetwidth = 0;
+ ans.start.pos = Point(margin, margin);
+ ans.start.next = ans.end;
+ ans.end.prev = ans.start;
+ # dummy item at end so ans.end will have correct y coord
+ it := Item.newspacer(ISPnull, 0);
+ it.state = IFbrk|IFcleft|IFcright;
+ ans.end.items = it;
+ return ans;
+}
+
+Line.new() : ref Line
+{
+ return ref Line(
+ nil, nil, nil, # items, next, prev
+ zp, # pos
+ 0, 0, 0, # width, height, ascent
+ byte 0); # flags
+}
+
+Loc.new() : ref Loc
+{
+ return ref Loc(array[10] of Locelem, 0, zp); # le, n, pos
+}
+
+Loc.add(loc: self ref Loc, kind: int, pos: Point)
+{
+ if(loc.n == len loc.le) {
+ newa := array[len loc.le + 10] of Locelem;
+ newa[0:] = loc.le;
+ loc.le = newa;
+ }
+ loc.le[loc.n].kind = kind;
+ loc.le[loc.n].pos = pos;
+ loc.n++;
+}
+
+# return last frame in loc's path
+Loc.lastframe(loc: self ref Loc) : ref Frame
+{
+ if (loc == nil)
+ return nil;
+ for(i := loc.n-1; i >=0; i--)
+ if(loc.le[i].kind == LEframe)
+ return loc.le[i].frame;
+ return nil;
+}
+
+Loc.print(loc: self ref Loc, msg: string)
+{
+ sys->print("%s: Loc with %d components, pos=(%d,%d)\n", msg, loc.n, loc.pos.x, loc.pos.y);
+ for(i := 0; i < loc.n; i++) {
+ case loc.le[i].kind {
+ LEframe =>
+ sys->print("frame %x\n", loc.le[i].frame);
+ LEline =>
+ sys->print("line %x\n", loc.le[i].line);
+ LEitem =>
+ sys->print("item: %x", loc.le[i].item);
+ loc.le[i].item.print();
+ LEtablecell =>
+ sys->print("tablecell: %x, cellid=%d\n", loc.le[i].tcell, loc.le[i].tcell.cellid);
+ LEcontrol =>
+ sys->print("control %x\n", loc.le[i].control);
+ }
+ }
+}
+
+Sources.new(m : ref Source) : ref Sources
+{
+ srcs := ref Sources;
+ srcs.main = m;
+ return srcs;
+}
+
+Sources.add(srcs: self ref Sources, s: ref Source, required: int)
+{
+ if (required) {
+ CU->assert(srcs.reqd == nil);
+ srcs.reqd = s;
+ } else
+ srcs.srcs = s :: srcs.srcs;
+}
+
+Sources.done(srcs: self ref Sources, s: ref Source)
+{
+ if (s == srcs.main) {
+ if (srcs.reqd != nil) {
+ sys->print("FREEING MAIN WHEN REQD != nil\n");
+ if (s.bs == nil)
+ sys->print("s.bs == nil\n");
+ else
+ sys->print("main.eof = %d main.lim = %d, main.edata = %d\n", s.bs.eof, s.bs.lim, s.bs.edata);
+ }
+ srcs.main = nil;
+ }
+ else if (s == srcs.reqd)
+ srcs.reqd = nil;
+ else {
+ new : list of ref Source;
+ for (old := srcs.srcs; old != nil; old = tl old) {
+ src := hd old;
+ if (src == s)
+ continue;
+ new = src :: new;
+ }
+ srcs.srcs = new;
+ }
+}
+
+Sources.waitsrc(srcs: self ref Sources) : ref Source
+{
+ if (srcs == nil)
+ return nil;
+
+ bsl : list of ref ByteSource;
+
+ if (srcs.reqd == nil && srcs.main != nil) {
+ pick s := srcs.main {
+ Shtml =>
+ if (s.itsrc.toks != nil || s.itsrc.reqddata != nil)
+ return s;
+ }
+ }
+
+ # always check for subordinates
+ for (sl := srcs.srcs; sl != nil; sl = tl sl)
+ bsl = (hd sl).bs :: bsl;
+ # reqd is taken in preference to main source as main
+ # cannot be processed until we have the whole of reqd
+ if (srcs.reqd != nil)
+ bsl = srcs.reqd.bs :: bsl;
+ else if (srcs.main != nil)
+ bsl = srcs.main.bs :: bsl;
+ if (bsl == nil)
+ return nil;
+ bs : ref ByteSource;
+ for (;;) {
+ bs = CU->waitreq(bsl);
+ if (srcs.reqd == nil || srcs.reqd.bs != bs)
+ break;
+ # only interested in reqd if we have got it all
+ if (bs.err != "" || bs.eof)
+ return srcs.reqd;
+ }
+ if (srcs.main != nil && srcs.main.bs == bs)
+ return srcs.main;
+ found : ref Source;
+ for(sl = srcs.srcs; sl != nil; sl = tl sl) {
+ s := hd sl;
+ if(s.bs == bs) {
+ found = s;
+ break;
+ }
+ }
+ CU->assert(found != nil);
+ return found;
+}
+
+# spawned to animate images in frame f
+animproc(f: ref Frame)
+{
+ f.animpid = sys->pctl(0, nil);
+ aits : list of ref Item = nil;
+ # let del be millisecs to sleep before next frame change
+ del := 10000000;
+ d : int;
+ for(il := f.doc.images; il != nil; il = tl il) {
+ it := hd il;
+ pick i := it {
+ Iimage =>
+ ms := i.ci.mims;
+ if(len ms > 1) {
+ loc := f.find(zp, it);
+ if(loc == nil) {
+ # could be background, I suppose -- don't animate it
+ if(dbg)
+ sys->print("couldn't find item for animated image\n");
+ continue;
+ }
+ p := loc.le[loc.n-1].pos;
+ p.x += int i.hspace + int i.border;
+ # BUG: should get background from least enclosing layout
+ ctl := Control.newanimimage(f, i.ci, f.layout.background);
+ ctl.r = ctl.r.addpt(p);
+ i.ctlid = f.addcontrol(ctl);
+ d = ms[0].delay;
+ if(dbg)
+ sys->print("added anim ctl %d for image %s, initial delay %d\n",
+ i.ctlid, i.ci.src.tostring(), d);
+ aits = it :: aits;
+ if(d < del)
+ del = d;
+ }
+ }
+ }
+ if(aits == nil)
+ return;
+ tot := big 0;
+ for(;;) {
+ sys->sleep(del);
+ tot = tot + big del;
+ newdel := 10000000;
+ for(al := aits; al != nil; al = tl al) {
+ it := hd al;
+ pick i := hd al {
+ Iimage =>
+ ms := i.ci.mims;
+ pick c := f.controls[i.ctlid] {
+ Canimimage =>
+ m := ms[c.cur];
+ d = m.delay;
+ if(d > 0)
+ d -= int (tot - c.ts);
+ if(d == 0) {
+ # advance to next frame and show it
+ c.cur++;
+ if(c.cur == len ms)
+ c.cur = 0;
+ d = ms[c.cur].delay;
+ c.ts = tot;
+ c.draw(1);
+ }
+ if(d < newdel)
+ newdel = d;
+ }
+ }
+ }
+ del = newdel;
+ }
+}
diff --git a/appl/charon/layout.m b/appl/charon/layout.m
new file mode 100644
index 00000000..a91b7e8d
--- /dev/null
+++ b/appl/charon/layout.m
@@ -0,0 +1,235 @@
+Layout: module
+{
+PATH: con "/dis/charon/layout.dis";
+
+ReliefBd: con 2;
+ReliefSunk, ReliefRaised : con iota;
+
+# Frames
+
+Frame: adt
+{
+ id: int; # unique id
+ doc: ref Build->Docinfo; # various global attributes from HTML and headers
+ src: ref Url->Parsedurl;
+ name: string; # current name (assigned by parent frame, or by default)
+ marginw: int; # margin on sides
+ marginh: int; # margin on top and bottom
+ framebd: int; # frame border desired
+ flags: int; # Build->FRnoresize, etc.
+ layout: ref Lay; # representation of layout
+ sublays: array of ref Lay; # table cells, captions
+ sublayid: int; # next sublayid to use
+ controls: cyclic array of ref Control; # controls
+ controlid: int; # next control id to use
+ cim: ref Draw->Image; # image where we draw contents
+ r: Draw->Rect; # part of cimage.r for this frame (including scrollbars)
+ cr: Draw->Rect; # part of r for contents (excluding scrollbars, including margins)
+ totalr: Draw->Rect; # total rectangle for page -- (0,0) is top left
+ viewr: Draw->Rect; # view: subrect of totalr currently on screen
+ vscr: cyclic ref Control; # vertical scrollbar
+ hscr: cyclic ref Control; # horizontal scrollbar
+ parent: cyclic ref Frame; # if this frame is in a frameset
+ kids: cyclic list of ref Frame; # if this frame is a frameset
+ animpid: int; # image animating thread
+ prctxt: ref Printcontext; # nil if not printing
+
+# TEMP
+dirtyr: Draw->Rect;
+dirty: fn (f: self ref Frame, r: Draw->Rect);
+isdirty: int;
+
+ # reset() clears everything but parent, cim and r
+ # new() and newkid() call reset
+ # newkid() fills in name, etc., from ki, and copies cim from parent
+ new: fn() : ref Frame;
+ newkid: fn(parent: ref Frame, ki: ref Build->Kidinfo, r: Draw->Rect) : ref Frame;
+ reset: fn(f: self ref Frame);
+ addcontrol: fn(f: self ref Frame, c: ref Control) : int;
+ lptosp: fn(f: self ref Frame, lp: Draw->Point) : Draw->Point;
+ sptolp: fn(f: self ref Frame, sp: Draw->Point) : Draw->Point;
+ xscroll: fn(f: self ref Frame, kind, val: int); # kind is CAscrollpage, etc
+ yscroll: fn(f: self ref Frame, kind, val: int);
+ scrollabs: fn(f : self ref Frame, p : Draw->Point);
+ scrollrel: fn(f : self ref Frame, p : Draw->Point);
+ find: fn(f: self ref Frame, p: Draw->Point, it: ref Build->Item) : ref Loc;
+ swapimage: fn(f: self ref Frame, it: ref Build->Item.Iimage, src: string);
+ focus: fn(f : self ref Frame, focus, raisex : int);
+};
+
+Printcontext: adt {
+ mask: ref Draw->Image;
+ endy: int;
+};
+
+# Line flags
+Ldrawn, Lmoved, Lchanged: con byte (1<<iota);
+
+# Layout engine organizes Items into Lines
+Line: adt
+{
+ items: ref Build->Item;
+ next: cyclic ref Line;
+ prev: cyclic ref Line;
+ pos: Draw->Point;
+ width: int;
+ height: int;
+ ascent: int;
+ flags: byte;
+
+ new: fn() : ref Line;
+};
+
+# A place where an item, or a where mouse or keyboard focus could be.
+Loc: adt
+{
+ le: array of Locelem;
+ n: int; # locs[0:n] form access path
+ pos: Draw->Point; # offset in final item
+
+ new: fn() : ref Loc;
+ add: fn(loc: self ref Loc, kind: int, pos: Draw->Point);
+ lastframe: fn(loc: self ref Loc) : ref Frame;
+ print: fn(loc: self ref Loc, msg: string);
+};
+
+# Don't use pick so that can make array of Locelems (rather than ref Locelems),
+# which saves a lot of alloc/frees in search functions.
+# (Also, saves memory overall, in Limbo).
+Locelem: adt
+{
+ kind: int; # LEframe, etc.
+ pos: Draw->Point; # position in screen coords of this element
+ frame: ref Frame; # root, or kid of previous (a frame)
+ line: ref Line; # a line in lay of previous
+ item: ref Build->Item; # an item in previous (a line or item)
+ tcell: ref Build->Tablecell; # a cell in previous (a table item)
+ control: ref Control; # a control in previous item, or scrollbar in previous frame
+};
+
+# Locelem kinds
+LEframe, LEline, LEitem, LEtablecell, LEcontrol : con iota;
+
+# One of the possible controls, and possible associated form field
+Control: adt {
+ f: cyclic ref Frame;
+ ff: ref Build->Formfield;
+ r: Draw->Rect; # coords in f.cim coord system
+ flags: int;
+ popup: ref Gui->Popup;
+ pick {
+ Cbutton =>
+ pic: ref Draw->Image; # picture on button (if no label)
+ picmask: ref Draw->Image; # mask for pic
+ dpic: ref Draw->Image; # disabled ("greyed out") pic
+ dpicmask: ref Draw->Image; # mask for dpic
+ label: string; # label on button (if no pic), or else flyover hint
+ dorelief: int; # draw background & relief?
+ Centry =>
+ scr: ref Control;
+ s: string; # current contents
+ sel: (int,int); # range of characters in s that are selected
+ left: int; # index of character in s that is at left of window
+ linewrap: int; # true if supposed to line-wrap
+ onchange: int; # true if want onchange event
+ Ccheckbox=>
+ isradio: int; # true if for radio button
+ Cselect =>
+ #
+ owner: ref Control; # if this is a popup
+ scr: ref Control; # if needed
+ nvis: int; # number of visible options
+ first: int; # index of current top visible option
+ options: array of Build->Option;
+# onchange: int; # true if want onchange event
+ Clistbox =>
+ hscr: ref Control;
+ vscr: ref Control;
+ nvis: int;
+ first: int; # index of current top visible option
+ start: int; # index of current start column
+ maxcol: int; # max column
+ options: array of Build->Option;
+ grab: cyclic ref Control;
+ Cscrollbar =>
+ top: int; # pixels in trough above/left of slider
+ bot: int; # pixels in trough below/right of slider
+ mindelta: int; # need delta of at least this (pixels)
+ deltaval: int;
+ ctl: cyclic ref Control; # if non-nil, scrolls this control
+ holdstate: (int, int);
+ Canimimage =>
+ cim: ref CharonUtils->CImage;
+ cur: int; # current frame
+ redraw: int; # need to redraw all?
+ ts: big; # timestamp of current frame
+ bg: Build->Background; # if need restore-to-background
+ Clabel =>
+ s: string;
+ }
+
+ newff: fn(f: ref Frame, ff: ref Build->Formfield) : ref Control;
+ newscroll: fn(f: ref Frame, isvert, length, breadth: int) : ref Control;
+ newentry: fn(f: ref Frame, nh, nv, linewrap: int) : ref Control;
+ newbutton: fn(f: ref Frame, pic, picmask: ref Draw->Image, lab: string, it: ref Build->Item.Iimage, candisable, dorelief: int) : ref Control;
+ newcheckbox: fn(f: ref Frame, isradio: int) : ref Control;
+ newselect: fn(f: ref Frame, nvis: int, options: array of Build->Option) : ref Control;
+ newlistbox: fn(f: ref Frame, nvis, w: int, options: array of Build->Option) : ref Control;
+ newanimimage: fn(f: ref Frame, cim: ref CharonUtils->CImage, bg: Build->Background) : ref Control;
+ newlabel: fn(f: ref Frame, s: string) : ref Control;
+ disable: fn(b: self ref Control);
+ enable: fn(b: self ref Control);
+ losefocus: fn(b: self ref Control, raisex: int);
+ gainfocus: fn(b: self ref Control, raisex: int);
+ scrollset: fn(sc: self ref Control, v1, v2, vmax, nsteps, draw: int);
+ entryset: fn(e: self ref Control, s: string);
+ # returns CAnone, etc.
+ dokey: fn(c: self ref Control, keychar: int) : int;
+ # domouse returns (action, grab) action = CAnone etc, grab = control that has grabbed mouse
+ domouse: fn(c: self ref Control, p: Draw->Point, mtype: int, oldgrab : ref Control) : (int, ref Control);
+ dopopup: fn(c: self ref Control): ref Control;
+ donepopup: fn(c: self ref Control): ref Control;
+ reset: fn(c: self ref Control);
+ draw: fn(c: self ref Control, flush: int);
+};
+
+# Control flags
+CFactive, CFenabled, CFsecure, CFhasfocus, CFscrvert, CFscracta1, CFscracta2, CFscracttr1, CFscracttr2: con (1<<iota);
+CFscrallact : con (CFactive|CFscracta1|CFscracta2|CFscracttr1|CFscracttr2);
+
+# Control Actions
+CAnone, CAscrollpage, CAscrollline, CAscrolldelta, CAscrollabs,
+CAbuttonpush, CAflyover, CAreturnkey, CAtabkey, CAkeyfocus, CAselected, CAchanged, CAdopopup, CAdonepopup: con iota;
+
+# Result of layout
+Lay: adt
+{
+ start: ref Line; # fake before-the-first-line
+ end: ref Line; # fake after-the-last-line
+ targetwidth: int; # target width
+ width: int; # actual width
+ height: int; # actual height
+ margin: int; # extra space on all four sides
+ floats:list of ref Build->Item.Ifloat; # floats, from bottom up
+ background: Build->Background; # background for layout
+ just: byte; # default line justification
+ flags: byte; # Lchanged
+
+ new: fn(targetwidth: int, just: byte, margin: int, bg: Build->Background) : ref Lay;
+};
+
+#B: Build;
+
+init: fn(cu: CharonUtils);
+layout: fn(f: ref Frame, bs: ref CharonUtils->ByteSource, linkclick: int) : array of byte;
+
+drawrelief: fn(im: ref Draw->Image, r: Draw->Rect, style: int);
+drawborder: fn(im: ref Draw->Image, r: Draw->Rect, n, color: int);
+drawfill: fn(im: ref Draw->Image, r: Draw->Rect, color: int);
+drawstring: fn(im: ref Draw->Image, p: Draw->Point, s: string);
+measurestring: fn(s: string) : Draw->Point;
+drawall: fn(f: ref Frame);
+relayout: fn(f: ref Frame, l: ref Lay, targetw: int, just: byte);
+
+stringwidth: fn(s: string): int;
+};
diff --git a/appl/charon/lex.b b/appl/charon/lex.b
new file mode 100644
index 00000000..4b7274c0
--- /dev/null
+++ b/appl/charon/lex.b
@@ -0,0 +1,1340 @@
+implement Lex;
+
+include "common.m";
+
+# local copies from CU
+sys: Sys;
+CU: CharonUtils;
+S: String;
+T: StringIntTab;
+C: Ctype;
+J: Script;
+ctype: array of byte;
+
+EOF : con -2;
+EOB : con -1;
+
+tagnames = array[] of {
+ " ",
+ "!",
+ "a",
+ "abbr",
+ "acronym",
+ "address",
+ "applet",
+ "area",
+ "b",
+ "base",
+ "basefont",
+ "bdo",
+ "big",
+ "blink",
+ "blockquote",
+ "body",
+ "bq",
+ "br",
+ "button",
+ "caption",
+ "center",
+ "cite",
+ "code",
+ "col",
+ "colgroup",
+ "dd",
+ "del",
+ "dfn",
+ "dir",
+ "div",
+ "dl",
+ "dt",
+ "em",
+ "fieldset",
+ "font",
+ "form",
+ "frame",
+ "frameset",
+ "h1",
+ "h2",
+ "h3",
+ "h4",
+ "h5",
+ "h6",
+ "head",
+ "hr",
+ "html",
+ "i",
+ "iframe",
+ "image",
+ "img",
+ "input",
+ "ins",
+ "isindex",
+ "kbd",
+ "label",
+ "legend",
+ "li",
+ "link",
+ "map",
+ "menu",
+ "meta",
+ "nobr",
+ "noframes",
+ "noscript",
+ "object",
+ "ol",
+ "optgroup",
+ "option",
+ "p",
+ "param",
+ "pre",
+ "q",
+ "s",
+ "samp",
+ "script",
+ "select",
+ "small",
+ "span",
+ "strike",
+ "strong",
+ "style",
+ "sub",
+ "sup",
+ "table",
+ "tbody",
+ "td",
+ "textarea",
+ "tfoot",
+ "th",
+ "thead",
+ "title",
+ "tr",
+ "tt",
+ "u",
+ "ul",
+ "var",
+ "xmp"
+};
+
+tagtable : array of T->StringInt; # initialized from tagnames
+
+attrnames = array[] of {
+ "abbr",
+ "accept",
+ "accept-charset",
+ "accesskey",
+ "action",
+ "align",
+ "alink",
+ "alt",
+ "archive",
+ "axis",
+ "background",
+ "bgcolor",
+ "border",
+ "cellpadding",
+ "cellspacing",
+ "char",
+ "charoff",
+ "charset",
+ "checked",
+ "cite",
+ "class",
+ "classid",
+ "clear",
+ "code",
+ "codebase",
+ "codetype",
+ "color",
+ "cols",
+ "colspan",
+ "compact",
+ "content",
+ "coords",
+ "data",
+ "datafld",
+ "dataformatas",
+ "datapagesize",
+ "datasrc",
+ "datetime",
+ "declare",
+ "defer",
+ "dir",
+ "disabled",
+ "enctype",
+ "event",
+ "face",
+ "for",
+ "frame",
+ "frameborder",
+ "headers",
+ "height",
+ "href",
+ "hreflang",
+ "hspace",
+ "http-equiv",
+ "id",
+ "ismap",
+ "label",
+ "lang",
+ "language",
+ "link",
+ "longdesc",
+ "lowsrc",
+ "marginheight",
+ "marginwidth",
+ "maxlength",
+ "media",
+ "method",
+ "multiple",
+ "name",
+ "nohref",
+ "noresize",
+ "noshade",
+ "nowrap",
+ "object",
+ "onabort",
+ "onblur",
+ "onchange",
+ "onclick",
+ "ondblclick",
+ "onerror",
+ "onfocus",
+ "onkeydown",
+ "onkeypress",
+ "onkeyup",
+ "onload",
+ "onmousedown",
+ "onmousemove",
+ "onmouseout",
+ "onmouseover",
+ "onmouseup",
+ "onreset",
+ "onresize",
+ "onselect",
+ "onsubmit",
+ "onunload",
+ "profile",
+ "prompt",
+ "readonly",
+ "rel",
+ "rev",
+ "rows",
+ "rowspan",
+ "rules",
+ "scheme",
+ "scope",
+ "scrolling",
+ "selected",
+ "shape",
+ "size",
+ "span",
+ "src",
+ "standby",
+ "start",
+ "style",
+ "summary",
+ "tabindex",
+ "target",
+ "text",
+ "title",
+ "type",
+ "usemap",
+ "valign",
+ "value",
+ "valuetype",
+ "version",
+ "vlink",
+ "vspace",
+ "width"
+};
+
+attrtable : array of T->StringInt; # initialized from attrnames
+
+chartab:= array[] of { T->StringInt
+ ("AElig", 'Æ'),
+ ("Aacute", 'Á'),
+ ("Acirc", 'Â'),
+ ("Agrave", 'À'),
+ ("Alpha", 'Α'),
+ ("Aring", 'Å'),
+ ("Atilde", 'Ã'),
+ ("Auml", 'Ä'),
+ ("Beta", 'Β'),
+ ("Ccedil", 'Ç'),
+ ("Chi", 'Χ'),
+ ("Dagger", '‡'),
+ ("Delta", 'Δ'),
+ ("ETH", 'Ð'),
+ ("Eacute", 'É'),
+ ("Ecirc", 'Ê'),
+ ("Egrave", 'È'),
+ ("Epsilon", 'Ε'),
+ ("Eta", 'Η'),
+ ("Euml", 'Ë'),
+ ("Gamma", 'Γ'),
+ ("Iacute", 'Í'),
+ ("Icirc", 'Î'),
+ ("Igrave", 'Ì'),
+ ("Iota", 'Ι'),
+ ("Iuml", 'Ï'),
+ ("Kappa", 'Κ'),
+ ("Lambda", 'Λ'),
+ ("Mu", 'Μ'),
+ ("Ntilde", 'Ñ'),
+ ("Nu", 'Ν'),
+ ("OElig", 'Œ'),
+ ("Oacute", 'Ó'),
+ ("Ocirc", 'Ô'),
+ ("Ograve", 'Ò'),
+ ("Omega", 'Ω'),
+ ("Omicron", 'Ο'),
+ ("Oslash", 'Ø'),
+ ("Otilde", 'Õ'),
+ ("Ouml", 'Ö'),
+ ("Phi", 'Φ'),
+ ("Pi", 'Π'),
+ ("Prime", '″'),
+ ("Psi", 'Ψ'),
+ ("Rho", 'Ρ'),
+ ("Scaron", 'Š'),
+ ("Sigma", 'Σ'),
+ ("THORN", 'Þ'),
+ ("Tau", 'Τ'),
+ ("Theta", 'Θ'),
+ ("Uacute", 'Ú'),
+ ("Ucirc", 'Û'),
+ ("Ugrave", 'Ù'),
+ ("Upsilon", 'Υ'),
+ ("Uuml", 'Ü'),
+ ("Xi", 'Ξ'),
+ ("Yacute", 'Ý'),
+ ("Yuml", 'Ÿ'),
+ ("Zeta", 'Ζ'),
+ ("aacute", 'á'),
+ ("acirc", 'â'),
+ ("acute", '´'),
+ ("aelig", 'æ'),
+ ("agrave", 'à'),
+ ("alefsym", 'ℵ'),
+ ("alpha", 'α'),
+ ("amp", '&'),
+ ("and", '∧'),
+ ("ang", '∠'),
+ ("aring", 'å'),
+ ("asymp", '≈'),
+ ("atilde", 'ã'),
+ ("auml", 'ä'),
+ ("bdquo", '„'),
+ ("beta", 'β'),
+ ("brvbar", '¦'),
+ ("bull", '•'),
+ ("cap", '∩'),
+ ("ccedil", 'ç'),
+ ("cdots", '⋯'),
+ ("cedil", '¸'),
+ ("cent", '¢'),
+ ("chi", 'χ'),
+ ("circ", 'ˆ'),
+ ("clubs", '♣'),
+ ("cong", '≅'),
+ ("copy", '©'),
+ ("crarr", '↵'),
+ ("cup", '∪'),
+ ("curren", '¤'),
+ ("dArr", '⇓'),
+ ("dagger", '†'),
+ ("darr", '↓'),
+ ("ddots", '⋱'),
+ ("deg", '°'),
+ ("delta", 'δ'),
+ ("diams", '♦'),
+ ("divide", '÷'),
+ ("eacute", 'é'),
+ ("ecirc", 'ê'),
+ ("egrave", 'è'),
+ ("emdash", '—'),
+ ("empty", '∅'),
+ ("emsp", ' '),
+ ("endash", '–'),
+ ("ensp", ' '),
+ ("epsilon", 'ε'),
+ ("equiv", '≡'),
+ ("eta", 'η'),
+ ("eth", 'ð'),
+ ("euml", 'ë'),
+ ("euro", '€'),
+ ("exist", '∃'),
+ ("fnof", 'ƒ'),
+ ("forall", '∀'),
+ ("frac12", '½'),
+ ("frac14", '¼'),
+ ("frac34", '¾'),
+ ("frasl", '⁄'),
+ ("gamma", 'γ'),
+ ("ge", '≥'),
+ ("gt", '>'),
+ ("hArr", '⇔'),
+ ("harr", '↔'),
+ ("hearts", '♥'),
+ ("hellip", '…'),
+ ("iacute", 'í'),
+ ("icirc", 'î'),
+ ("iexcl", '¡'),
+ ("igrave", 'ì'),
+ ("image", 'ℑ'),
+ ("infin", '∞'),
+ ("int", '∫'),
+ ("iota", 'ι'),
+ ("iquest", '¿'),
+ ("isin", '∈'),
+ ("iuml", 'ï'),
+ ("kappa", 'κ'),
+ ("lArr", '⇐'),
+ ("lambda", 'λ'),
+ ("lang", '〈'),
+ ("laquo", '«'),
+ ("larr", '←'),
+ ("lceil", '⌈'),
+ ("ldots", '…'),
+ ("ldquo", '“'),
+ ("le", '≤'),
+ ("lfloor", '⌊'),
+ ("lowast", '∗'),
+ ("loz", '◊'),
+ ("lrm", '‎'),
+ ("lsaquo", '‹'),
+ ("lsquo", '‘'),
+ ("lt", '<'),
+ ("macr", '¯'),
+ ("mdash", '—'),
+ ("micro", 'µ'),
+ ("middot", '·'),
+ ("minus", '−'),
+ ("mu", 'μ'),
+ ("nabla", '∇'),
+ ("nbsp", ' '),
+ ("ndash", '–'),
+ ("ne", '≠'),
+ ("ni", '∋'),
+ ("not", '¬'),
+ ("notin", '∉'),
+ ("nsub", '⊄'),
+ ("ntilde", 'ñ'),
+ ("nu", 'ν'),
+ ("oacute", 'ó'),
+ ("ocirc", 'ô'),
+ ("oelig", 'œ'),
+ ("ograve", 'ò'),
+ ("oline", '‾'),
+ ("omega", 'ω'),
+ ("omicron", 'ο'),
+ ("oplus", '⊕'),
+ ("or", '∨'),
+ ("ordf", 'ª'),
+ ("ordm", 'º'),
+ ("oslash", 'ø'),
+ ("otilde", 'õ'),
+ ("otimes", '⊗'),
+ ("ouml", 'ö'),
+ ("para", '¶'),
+ ("part", '∂'),
+ ("permil", '‰'),
+ ("perp", '⊥'),
+ ("phi", 'φ'),
+ ("pi", 'π'),
+ ("piv", 'ϖ'),
+ ("plusmn", '±'),
+ ("pound", '£'),
+ ("prime", '′'),
+ ("prod", '∏'),
+ ("prop", '∝'),
+ ("psi", 'ψ'),
+ ("quad", ' '),
+ ("quot", '"'),
+ ("quot", '"'),
+ ("rArr", '⇒'),
+ ("radic", '√'),
+ ("rang", '〉'),
+ ("raquo", '»'),
+ ("rarr", '→'),
+ ("rceil", '⌉'),
+ ("rdquo", '”'),
+ ("real", 'ℜ'),
+ ("reg", '®'),
+ ("rfloor", '⌋'),
+ ("rho", 'ρ'),
+ ("rlm", '‏'),
+ ("rsaquo", '›'),
+ ("rsquo", '’'),
+ ("sbquo", '‚'),
+ ("scaron", 'š'),
+ ("sdot", '⋅'),
+ ("sect", '§'),
+ ("shy", '­'),
+ ("sigma", 'σ'),
+ ("sigmaf", 'ς'),
+ ("sim", '∼'),
+ ("sp", ' '),
+ ("spades", '♠'),
+ ("sub", '⊂'),
+ ("sube", '⊆'),
+ ("sum", '∑'),
+ ("sup", '⊃'),
+ ("sup1", '¹'),
+ ("sup2", '²'),
+ ("sup3", '³'),
+ ("supe", '⊇'),
+ ("szlig", 'ß'),
+ ("tau", 'τ'),
+ ("there4", '∴'),
+ ("theta", 'θ'),
+ ("thetasym", 'ϑ'),
+ ("thinsp", ' '),
+ ("thorn", 'þ'),
+ ("tilde", '˜'),
+ ("times", '×'),
+ ("trade", '™'),
+ ("uArr", '⇑'),
+ ("uacute", 'ú'),
+ ("uarr", '↑'),
+ ("ucirc", 'û'),
+ ("ugrave", 'ù'),
+ ("uml", '¨'),
+ ("upsih", 'ϒ'),
+ ("upsilon", 'υ'),
+ ("uuml", 'ü'),
+ ("varepsilon", '∈'),
+ ("varphi", 'ϕ'),
+ ("varpi", 'ϖ'),
+ ("varrho", 'ϱ'),
+ ("vdots", '⋮'),
+ ("vsigma", 'ς'),
+ ("vtheta", 'ϑ'),
+ ("weierp", '℘'),
+ ("xi", 'ξ'),
+ ("yacute", 'ý'),
+ ("yen", '¥'),
+ ("yuml", 'ÿ'),
+ ("zeta", 'ζ'),
+ ("zwj", '‍'),
+ ("zwnj", '‌'),
+};
+
+# Characters Winstart..Winend are those that Windows
+# uses interpolated into the Latin1 set.
+# They aren't supposed to appear in HTML, but they do....
+Winstart : con 16r7f;
+Winend: con 16r9f;
+winchars := array[] of { '•',
+ '•', '•', '‚', 'ƒ', '„', '…', '†', '‡',
+ 'ˆ', '‰', 'Š', '‹', 'Œ', '•', '•', '•',
+ '•', '‘', '’', '“', '”', '•', '–', '—',
+ '˜', '™', 'š', '›', 'œ', '•', '•', 'Ÿ'
+};
+
+NAMCHAR : con (C->L|C->U|C->D|C->N);
+LETTER : con (C->L|C->U);
+
+dbg := 0;
+warn := 0;
+
+init(cu: CharonUtils)
+{
+ CU = cu;
+ sys = load Sys Sys->PATH;
+ S = load String String->PATH;
+ C = cu->C;
+ J = cu->J;
+ T = load StringIntTab StringIntTab->PATH;
+ tagtable = CU->makestrinttab(tagnames);
+ attrtable = CU->makestrinttab(attrnames);
+ ctype = C->ctype;
+}
+
+TokenSource.new(b: ref CU->ByteSource, chset : Btos, mtype: int) : ref TokenSource
+{
+ ts := ref TSstate (
+ 0, # bi
+ 0, # prevbi
+ "", # s
+ 0, # si
+ Convcs->Startstate, # state
+ Convcs->Startstate # prevstate
+ );
+ ans := ref TokenSource(
+ b, # b
+ chset, # chset
+ ts, # state
+ mtype, # mtype
+ 0 # inxmp
+ );
+ dbg = int (CU->config).dbg['x'];
+ warn = (int (CU->config).dbg['w']) || dbg;
+ return ans;
+}
+
+TokenSource.gettoks(ts: self ref TokenSource): array of ref Token
+{
+ ToksMax : con 500; # max chunk of tokens returned
+ a := array[ToksMax] of ref Token;
+ ai := 0;
+ pcdai := 0;
+ lim := 0;
+ # put some dbg output in here
+ if(ts.mtype == CU->TextHtml) {
+ pcdstate : ref TSstate;
+gather:
+ while(ai < ToksMax-1) { # always allow space for a Data token
+ state := getstate(ts);
+ c := getchar(ts);
+ if (c < ' ') {
+ c = eatctls(c, ts);
+ if (c < 0)
+ break;
+ }
+ tok : ref Token;
+ if(c == '<') {
+ tok = gettag(ts);
+ if (tok != nil && ts.inxmp && tok.tag != Txmp+RBRA) {
+ rewind(ts, state);
+ getchar(ts); # consume the '<'
+ tok = ref Token(Data, "<", nil);
+ }
+ if(tok != nil && tok.tag != Comment) {
+ a[ai++] = tok;
+ case (tok.tag) {
+ Tselect or Ttitle or Toption=>
+ # Several tags expect PCDATA after them.
+ # Capture state so we can rewind if necessary
+ pcdstate = state;
+ pcdai = ai-1;
+ Ttextarea =>
+ pcdstate = state;
+ pcdai = ai-1;
+ # not sure if we should parse entity references
+ tok = gettagdata(ts, tok.tag, 1);
+ if(tok != nil) {
+ pcdstate = nil;
+ a[ai++] = tok;
+ }
+ Tscript =>
+ pcdstate = state;
+ pcdai = ai-1;
+ # special rules for getting Data
+ tok = getscriptdata(ts);
+ if(tok != nil) {
+ pcdstate = nil;
+ a[ai++] = tok;
+ }
+ Txmp =>
+ pcdstate = nil;
+ ts.inxmp = 1;
+ Txmp+RBRA =>
+ pcdstate = nil;
+ ts.inxmp = 0;
+ Data =>
+ ;
+ Tmeta =>
+ pcdstate = nil;
+ break gather;
+ * =>
+ pcdstate = nil;
+ }
+ }
+ } else {
+ tok = getdata(ts, c);
+ if(tok != nil)
+ a[ai++] = tok;
+ }
+ if(tok == nil && !eof(ts)) {
+ # we need more input to complete the token
+ lim = ts.state.bi;
+ rewind(ts, state);
+ break gather;
+ } else
+ if(dbg > 1)
+ sys->print("lex: got token %s\n", tok.tostring());
+ }
+ # Several tags expect PCDATA after them.
+ # which means that build needs to see another tag or eof
+ # after any data in order to know that PCDATA is ended.
+ # Rewind if we haven't got to the following tag yet.
+ if (pcdstate != nil && !eof(ts)) {
+ rewind(ts, pcdstate);
+ ai = pcdai;
+ }
+ }
+ else {
+ # plain text (non-html) tokens
+ while(ai < ToksMax) {
+ tok := getplaindata(ts);
+ if(tok == nil)
+ break;
+ else
+ a[ai++] = tok;
+ if(dbg > 1)
+ sys->print("lex: got token %s\n", tok.tostring());
+ }
+ }
+ if(dbg)
+ sys->print("lex: returning %d tokens\n", ai);
+ if (lim > ts.b.lim)
+ ts.b.lim = lim;
+ else
+ ts.b.lim = ts.state.prevbi;
+ if(ai == 0)
+ return nil;
+ return a[0:ai];
+}
+
+# must not be called from within TokenSource.gettoks()
+# as it will not work with rewind() and ungetchar()
+#
+TokenSource.setchset(ts: self ref TokenSource, chset: Btos)
+{
+ st := ts.state;
+ nchars := st.si;
+ if (nchars > 0 && nchars < len st.s) {
+ # align bi to the current input char
+ bs := ts.b;
+ (state, nil, n) := ts.chset->btos(st.prevcsstate, bs.data[st.prevbi:st.bi], nchars);
+ st.bi = st.prevbi + n;
+ st.prevbi = st.bi;
+ }
+ ts.chset = chset;
+ st.csstate = st.prevcsstate = Convcs->Startstate;
+ st.s = nil;
+ st.si = 0;
+}
+
+
+eof(ts : ref TokenSource) : int
+{
+ st := ts.state;
+ bs := ts.b;
+ return (st.s == nil && bs.eof && st.prevbi == bs.edata);
+}
+
+# For case where source isn't HTML.
+# Just make data tokens, one per line (or partial line,
+# at end of buffer), ignoring non-whitespace control
+# characters and dumping \r's
+getplaindata(ts: ref TokenSource): ref Token
+{
+ s := "";
+ j := 0;
+
+ for(c := getchar(ts); c >= 0; c = getchar(ts)) {
+ if(c < ' ') {
+ if(ctype[c] == C->W) {
+ if(c == '\r') {
+ # ignore it unless no following '\n',
+ # in which case treat it like '\n'
+ c = getchar(ts);
+ if(c != '\n') {
+ if(c >= 0)
+ ungetchar(ts);
+ c = '\n';
+ }
+ }
+ }
+ else
+ c = 0; # ignore
+ }
+ if(c != 0)
+ s[j++] = c;
+ if(c == '\n')
+ break;
+ }
+ if(s == "")
+ return nil;
+ return ref Token(Data, s, nil);
+}
+
+eatctls(c: int, ts: ref TokenSource): int
+{
+ while (c >= 0) {
+ if (c >= ' ')
+ return c;
+ if(ctype[c] == C->W) {
+ if(c == '\r') {
+ c = getchar(ts);
+ if (c != '\n' && c >= 0) {
+ ungetchar(ts);
+ c = '\n';
+ }
+ }
+ return c;
+ }
+ c = getchar(ts);
+ }
+ return -1;
+}
+
+# Gather data up to next start-of-tag or end-of-buffer.
+# Translate entity references (&amp;) if not in <XMP> section.
+# Ignore non-whitespace control characters and get rid of \r's.
+getdata(ts: ref TokenSource, firstc : int): ref Token
+{
+ s := "";
+ j := 0;
+ c := firstc;
+
+ while(c >= 0) {
+ if (c < ' ')
+ c = eatctls(c, ts);
+ if (c < 0)
+ break;
+ if(c == '&' && !ts.inxmp) {
+ ok : int;
+ (c, ok) = ampersand(ts);
+ if(!ok) {
+ ungetchar(ts);
+ break; # incomplete entity reference (ts backed up by ampersand)
+ }
+ }
+ else if(c == '<') {
+ ungetchar(ts);
+ break;
+ }
+ if(c != 0)
+ s[j++] = c;
+ c = getchar(ts);
+ }
+ if(s == "")
+ return nil;
+ return ref Token(Data, s, nil);
+}
+
+# The rules for lexing scripts are different (ugh).
+# Gather up everything until see a </SCRIPT>.
+getscriptdata(ts: ref TokenSource): ref Token
+{
+ tok := gettagdata(ts, Tscript, 0);
+ if (tok != nil)
+ tok.text = CU->stripscript(tok.text);
+ return tok;
+}
+
+gettagdata(ts: ref TokenSource, tag, doentities: int): ref Token
+{
+ s := "";
+ j := 0;
+ c := getchar(ts);
+
+ while(c >= 0) {
+ if (c == '<') {
+ tstate := getstate(ts);
+ tok := gettag(ts);
+ rewind(ts, tstate);
+ if (tok != nil && tok.tag == tag+RBRA) {
+ ungetchar(ts);
+ return ref Token(Data, s, nil);
+ }
+ # tag was not </tag>, take as regular data
+ }
+ if (doentities && c == '&')
+ (c, nil) = ampersand(ts);
+
+ if(c < 0)
+ break;
+ if(c != 0)
+ s[j++] = c;
+ c = getchar(ts);
+ }
+ if(eof(ts))
+ return ref Token(Data, s, nil);
+
+ return nil;
+}
+
+# We've just seen a '<'. Gather up stuff to closing '>' (if buffer
+# ends before then, return nil).
+# If it's a tag, look up the name, gather the attributes, and return
+# the appropriate token.
+# Else it's either just plain data or some kind of ignorable stuff:
+# return a Data or Comment token as appropriate.
+gettag(ts: ref TokenSource): ref Token
+{
+ rbra := 0;
+ ans : ref Token = nil;
+ al: list of Attr;
+ start := getstate(ts);
+ c := getchar(ts);
+
+ # dummy loop: break out of this when hit end of buffer
+ eob:
+ for(;;) {
+ if(c == '/') {
+ rbra = RBRA;
+ c = getchar(ts);
+ }
+ if(c < 0)
+ break eob;
+ if(c>=C->NCTYPE || !int (ctype[c]&LETTER)) {
+ # not a tag
+ if(c == '!') {
+ ans = comment(ts);
+ if(ans != nil)
+ return ans;
+ break eob;
+ }
+ else {
+ rewind(ts, start);
+ return ref Token(Data, "<", nil);
+ }
+ }
+ # c starts a tagname
+ ans = ref Token(Notfound, nil, nil);
+ name := "";
+ name[0] = lowerc(c);
+ i := 1;
+ for(;;) {
+ c = getchar(ts);
+ if(c < 0)
+ break eob;
+ if(c>=C->NCTYPE || !int (ctype[c]&NAMCHAR))
+ break;
+ name[i++] = lowerc(c);
+ }
+ (fnd, tag) := T->lookup(tagtable, name);
+ if(fnd)
+ ans.tag = tag+rbra;
+ else
+ ans.text = name; # for warning print, in build
+attrloop:
+ for(;;) {
+ # look for "ws name" or "ws name ws = ws val" (ws=whitespace)
+ # skip whitespace
+ while(c < C->NCTYPE && ctype[c] == C->W) {
+ c = getchar(ts);
+ if(c < 0)
+ break eob;
+ }
+ if(c == '>')
+ break attrloop;
+ if(c == '<') {
+ if(warn)
+ sys->print("warning: unclosed tag; last name=%s\n", name);
+ ungetchar(ts);
+ break attrloop;
+ }
+ if(c >= C->NCTYPE || !int (ctype[c]&LETTER)) {
+ if(warn)
+ sys->print("warning: expected attribute name; last name=%s\n", name);
+ # skip to next attribute name
+ for(;;) {
+ c = getchar(ts);
+ if(c < 0)
+ break eob;
+ if(c < C->NCTYPE && int (ctype[c]&LETTER))
+ continue attrloop;
+ if(c == '<') {
+ if(warn)
+ sys->print("warning: unclosed tag; last name=%s\n", name);
+ ungetchar(ts);
+ break attrloop;
+ }
+ if(c == '>')
+ break attrloop;
+ }
+ }
+ # gather attribute name
+ name = "";
+ name[0] = lowerc(c);
+ i = 1;
+ for(;;) {
+ c = getchar(ts);
+ if(c < 0)
+ break eob;
+ if(c >= C->NCTYPE || !int (ctype[c]&NAMCHAR))
+ break;
+ name[i++] = lowerc(c);
+ }
+ (afnd, attid) := T->lookup(attrtable, name);
+ if(warn && !afnd)
+ sys->print("warning: unknown attribute name %s\n", name);
+ # skip whitespace
+ while(c < C->NCTYPE && ctype[c] == C->W) {
+ c = getchar(ts);
+ if(c < 0)
+ break eob;
+ }
+ if(c != '=') {
+ # no value for this attr
+ if(afnd)
+ al = (attid, "") :: al;
+ continue attrloop;
+ }
+ # c is '=' here; skip whitespace
+ for(;;) {
+ c = getchar(ts);
+ if(c < 0)
+ break eob;
+ if(c >= C->NCTYPE || ctype[c] != C->W)
+ break;
+ }
+ # gather value
+ quote := 0;
+ if(c == '\'' || c == '"') {
+ quote = c;
+ c = getchar(ts);
+ if(c < 0)
+ break eob;
+ }
+ val := "";
+ nv := 0;
+ valloop:
+ for(;;) {
+ if(c < 0)
+ break eob;
+# other browsers allow value strings to be broken across lines
+# especially the case for Javascript event handlers / URLs
+ if (c == '>' && !quote)
+ break valloop;
+# old code otherwise ok - keep for now for reference
+# if(c == '>') {
+# if(quote) {
+# # c might be part of string (though not good style)
+# # but if line ends before close quote, assume
+# # there was an unmatched quote
+# ti := ts.i;
+# for(;;) {
+# c = getchar(ts);
+# if(c < 0)
+# break eob;
+# if(c == quote) {
+# backup(ts, ti);
+# val[nv++] = '>';
+# c = getchar(ts);
+# continue valloop;
+# }
+# if(c == '\n') {
+# if(warn)
+# sys->print("warning: apparent unmatched quote\n");
+# backup(ts, ti);
+# quote = 0;
+# c = '>';
+# break valloop;
+# }
+# }
+# }
+# else
+# break valloop;
+# }
+ if(quote) {
+ if(c == quote) {
+ c = getchar(ts);
+ if(c < 0)
+ break eob;
+ break valloop;
+ }
+ if(c == '\r') {
+ c = getchar(ts);
+ continue valloop;
+ }
+ if(c == '\t' || c == '\n')
+ c = ' ';
+ }
+ else {
+ if(c < C->NCTYPE && ctype[c]==C->W)
+ break valloop;
+ }
+ if(c == '&') {
+ ok : int;
+ (c, ok) = ampersand(ts);
+ if(!ok)
+ break eob;
+ }
+ val[nv++] = c;
+ c = getchar(ts);
+ }
+ if(afnd)
+ al = (attid, val) :: al;
+ }
+ ans.attr = al;
+ return ans;
+ }
+ if(eof(ts)) {
+ if(warn)
+ sys->print("warning: incomplete tag at end of page\n");
+ rewind(ts, start);
+ return ref Token(Data, "<", nil);
+ }
+ return nil;
+}
+
+
+# We've just read a '<!',
+# so this may be a comment or other ignored section, or it may
+# be just a literal string if there is no close before end of file
+# (other browsers do that).
+# The accepted practice seems to be (note: contrary to SGML spec!):
+# If see <!--, look for --> to close, or if none, > to close.
+# If see <!(not --), look for > to close.
+# If no close before end of file, leave original characters in as literal data.
+#
+# If we see ignorable stuff, return Comment token.
+# Else return nil (caller should back up and try again when more data arrives,
+# unless at end of file, in which case caller should just make '<' a data token).
+comment(ts: ref TokenSource) : ref Token
+{
+ havecomment := 0;
+ commentstart := 0;
+ c := getchar(ts);
+ if(c == '-') {
+ state := getstate(ts);
+ c = getchar(ts);
+ if(c == '-') {
+ commentstart = 1;
+ if(findstr(ts, "-->"))
+ havecomment = 1;
+ else
+ rewind(ts, state);
+ }
+ }
+ if(!havecomment) {
+ if(c == '>')
+ havecomment = 1;
+ else if(c >= 0) {
+ if(findstr(ts, ">"))
+ havecomment = 1;
+ }
+ }
+ if(havecomment)
+ return ref Token(Comment, nil, nil);
+ return nil;
+}
+
+# Look for string s in token source.
+# If found, return 1, with buffer at next char after s,
+# else return 0 (caller should back up).
+findstr(ts: ref TokenSource, s: string) : int
+{
+ n := len s;
+ eix := n-1;
+ buf := "";
+ c : int;
+
+ if (n == 1) {
+ while ((c = getchar(ts)) >= 0)
+ if (c == s[0])
+ return 1;
+ return 0;
+ }
+
+ for (i := 0; i < n; i++) {
+ c = getchar(ts);
+ if (c < 0)
+ return 0;
+ buf[i] = c;
+ }
+
+ for (;;) {
+ # this could be much more efficient by tracking
+ # the start char through buf
+ if (buf == s)
+ return 1;
+ c = getchar(ts);
+ if (c < 0)
+ return 0;
+ buf = buf[1:];
+ buf[eix] = c;
+ }
+ return 0; # keep the compiler quiet
+}
+
+# We've just read an '&'; look for an entity reference
+# name, and if found, return (translated char, 1).
+# Otherwise the input stream is rewound to just after
+# the '&'
+# if there is a complete entity name but it isn't known,
+# ('&', 1) is returned, if an incomplete name is encountered
+# (0, 0) is returned
+ampersand(ts: ref TokenSource): (int, int)
+{
+ state := getstate(ts);
+ c := getchar(ts);
+ fnd := 0;
+ ans := 0;
+ if(c == '#') {
+ v := 0;
+ c = getchar(ts);
+ if (c == 'x' || c == 'X') {
+ for (c = getchar(ts); c >= 0; c = getchar(ts)) {
+ if (int (ctype[c] & C->D)) {
+ v = v*16 + c-'0';
+ continue;
+ }
+ c = lowerc(c);
+ if (c >= 'a' && c <= 'f') {
+ v = v*16 + 10 + c-'a';
+ continue;
+ }
+ break;
+ }
+ } else {
+ while(c >= 0) {
+ if(ctype[c] != C->D)
+ break;
+ v = v*10 + c-'0';
+ c = getchar(ts);
+ }
+ }
+ if(c >= 0) {
+ if(!(c == ';' || c == '\n' || c == '\r' || c == '<'))
+ ungetchar(ts);
+ c = v;
+ if(c==160)
+ c = ' '; # non-breaking space
+ if(c >= Winstart && c <= Winend)
+ c = winchars[c-Winstart];
+ ans = c;
+ fnd = (v != 0);
+ }
+ }
+ # only US-ASCII chars can make up &charnames;
+ else if(c >= 0 && c < 16r80 && int (ctype[c] & LETTER)) {
+ s := "";
+ s[0] = c;
+ k := 1;
+ for(;;) {
+ c = getchar(ts);
+ if(c < 0)
+ break;
+ if(c < 16r80 && int (ctype[c]&NAMCHAR))
+ s[k++] = c;
+ else {
+ if(!(c == ';' || c == '\n' || c == '\r'))
+ ungetchar(ts);
+ break;
+ }
+ }
+ if (c < 0 || c == ' ' || c == ';' || c == '\n' || c == '\r' || c == '<')
+ (fnd, ans) = T->lookup(chartab, s);
+ }
+ if(!fnd) {
+ if(c < 0 && !eof(ts)) {
+ # was incomplete
+ rewind(ts, state);
+ return (0, 0);
+ }
+ else {
+ rewind(ts, state);
+ return ('&', 1);
+ }
+ }
+ # elide soft hyphens (&shy; / &xAD;)
+# not suficient - need to do it for all input in getdata() which is too heavy handed
+# if (ans == '­')
+# ans = 0;
+ return (ans, 1);
+}
+
+# If c is an uppercase letter, return its lowercase version,
+# otherwise return c.
+# Assume c is a NAMCHAR, so don't need range check on ctype[]
+lowerc(c: int) : int
+{
+ if(ctype[c] == C->U) {
+ # this works for accented characters in Latin1, too
+ return c + 16r20;
+ }
+ return c;
+}
+
+Token.aval(t: self ref Token, attid: int): (int, string)
+{
+ attr := t.attr;
+ while(attr != nil) {
+ a := hd attr;
+ if(a.attid == attid)
+ return (1, a.value);
+ attr = tl attr;
+ }
+ return (0, "");
+}
+
+
+# for debugging
+Token.tostring(t: self ref Token) : string
+{
+ ans := "";
+ tag := t.tag;
+ if(tag == Data)
+ ans = ans + "'" + t.text + "'";
+ else {
+ ans = ans + "<";
+ if(tag >= RBRA) {
+ tag -= RBRA;
+ ans = ans + "/";
+ }
+ tname := tagnames[tag];
+ if(tag == Notfound)
+ tname = "?";
+ ans = ans + S->toupper(tname);
+ for(al := t.attr; al != nil; al = tl al) {
+ a := hd al;
+ aname := attrnames[a.attid];
+ ans = ans + " " + aname;
+ if(a.value != "")
+ ans = ans + "='" + a.value + "'";
+ }
+ ans = ans + ">";
+ }
+ return ans;
+}
+
+
+CONVBLK : con 1024; # number of characters to convert at a time
+
+# Returns -1 if no complete character left before current end of data.
+getchar(ts: ref TokenSource): int
+{
+ st := ts.state;
+ if (st.s == nil || st.si >= len st.s) {
+ bs := ts.b;
+ st.si = 0;
+ st.s = "";
+ st.prevcsstate = st.csstate;
+ st.prevbi = st.bi;
+ edata := bs.edata;
+ if (st.bi >= edata)
+ return -1;
+ (state, s, n ) := ts.chset->btos(st.csstate, bs.data[st.bi:edata], CONVBLK);
+ if (s == nil) {
+ if (bs.eof && edata == bs.edata) {
+ # must have been an encoding error at eof
+ st.prevbi = st.bi = edata;
+ }
+ return -1;
+ }
+ st.csstate = state;
+ st.s = s;
+ st.bi += n;
+ }
+ return st.s[st.si++];
+}
+
+# back up by one input character
+# NOTE: can only call this function post a successful getchar() call
+ungetchar(ts : ref TokenSource)
+{
+ st := ts.state;
+ # assert(len st.s >= 1 && st.si > 0)
+ if (st.si <= 0)
+ raise "EXInternal:too many backups";
+ st.si--;
+}
+
+rewind(ts : ref TokenSource, state : ref TSstate)
+{
+ ts.state = state;
+}
+
+# return a copy of the TokenSource state
+getstate(ts : ref TokenSource) : ref TSstate
+{
+ return ref *ts.state;
+}
+
diff --git a/appl/charon/lex.m b/appl/charon/lex.m
new file mode 100644
index 00000000..e6dfcb57
--- /dev/null
+++ b/appl/charon/lex.m
@@ -0,0 +1,105 @@
+Lex: module
+{
+ PATH: con "/dis/charon/lex.dis";
+
+ # HTML 4.0 tags (blink, nobr)
+ # sorted in lexical order; used as array indices
+ Notfound, Comment,
+ Ta, Tabbr, Tacronym, Taddress, Tapplet, Tarea, Tb,
+ Tbase, Tbasefont, Tbdo, Tbig, Tblink, Tblockquote, Tbody,
+ Tbq, Tbr, Tbutton, Tcaption, Tcenter, Tcite, Tcode, Tcol, Tcolgroup,
+ Tdd, Tdel, Tdfn, Tdir, Tdiv, Tdl, Tdt, Tem,
+ Tfieldset, Tfont, Tform, Tframe, Tframeset,
+ Th1, Th2, Th3, Th4, Th5, Th6, Thead, Thr, Thtml, Ti, Tiframe, Timage,
+ Timg, Tinput, Tins, Tisindex, Tkbd, Tlabel, Tlegend, Tli, Tlink, Tmap,
+ Tmenu, Tmeta, Tnobr, Tnoframes, Tnoscript,
+ Tobject, Tol, Toptgroup, Toption, Tp, Tparam, Tpre,
+ Tq, Ts, Tsamp, Tscript, Tselect, Tsmall, Tspan, Tstrike, Tstrong,
+ Tstyle, Tsub, Tsup, Ttable, Ttbody, Ttd, Ttextarea, Ttfoot, Tth,
+ Tthead, Ttitle, Ttr, Ttt, Tu, Tul, Tvar, Txmp,
+ Numtags
+ : con iota;
+ RBRA : con Numtags;
+ Data: con Numtags+RBRA;
+
+ tagnames: array of string;
+
+ # HTML 4.0 tag attributes
+ # Keep sorted in lexical order
+ Aabbr, Aaccept, Aaccept_charset, Aaccesskey, Aaction,
+ Aalign, Aalink, Aalt, Aarchive, Aaxis,
+ Abackground, Abgcolor, Aborder,
+ Acellpadding, Acellspacing, Achar, Acharoff,
+ Acharset, Achecked, Acite, Aclass, Aclassid, Aclear,
+ Acode, Acodebase, Acodetype,
+ Acolor, Acols, Acolspan, Acompact, Acontent, Acoords,
+ Adata, Adatafld, Adataformatas, Adatapagesize, Adatasrc,
+ Adatetime, Adeclare, Adefer, Adir, Adisabled,
+ Aenctype, Aevent,
+ Aface, Afor, Aframe, Aframeborder,
+ Aheaders, Aheight, Ahref, Ahreflang, Ahspace, Ahttp_equiv,
+ Aid, Aismap, Alabel, Alang, Alanguage, Alink, Alongdesc, Alowsrc,
+ Amarginheight, Amarginwidth, Amaxlength, Amedia, Amethod, Amultiple,
+ Aname, Anohref, Anoresize, Anoshade, Anowrap, Aobject,
+ Aonabort, Aonblur, Aonchange, Aonclick, Aondblclick,
+ Aonerror, Aonfocus, Aonkeydown, Aonkeypress, Aonkeyup, Aonload,
+ Aonmousedown, Aonmousemove, Aonmouseout, Aonmouseover,
+ Aonmouseup, Aonreset, Aonresize, Aonselect, Aonsubmit, Aonunload,
+ Aprofile, Aprompt, Areadonly, Arel, Arev, Arows, Arowspan, Arules,
+ Ascheme, Ascope, Ascrolling, Aselected, Ashape, Asize,
+ Aspan, Asrc, Astandby, Astart, Astyle, Asummary,
+ Atabindex, Atarget, Atext, Atitle, Atype, Ausemap,
+ Avalign, Avalue, Avaluetype, Aversion, Avlink, Avspace, Awidth,
+ Numattrs
+ : con iota;
+
+ attrnames: array of string;
+
+ Token: adt
+ {
+ tag: int;
+ text: string; # text in Data, attribute text in tag
+ attr: list of Attr;
+
+ aval: fn(t: self ref Token, attid: int) : (int, string);
+ tostring: fn(t: self ref Token) : string;
+ };
+
+ Attr: adt
+ {
+ attid: int;
+ value: string;
+ };
+
+ # A source of HTML tokens.
+ # After calling new with a ByteSource (which is past 'gethdr' stage),
+ # call gettoks repeatedly until get nil. Errors are signalled by exceptions.
+ # Possible exceptions raised:
+ # EXInternal (start, gettoks)
+ # exGeterror (gettoks)
+ # exAbort (gettoks)
+ TokenSource: adt
+ {
+ b: ref CharonUtils->ByteSource;
+ chset: Btos; # charset converter
+ state : ref TSstate;
+ mtype: int; # CU->TextHtml or CU->TextPlain
+ inxmp: int;
+
+ new: fn(b: ref CharonUtils->ByteSource, chset : Btos, mtype: int) : ref TokenSource;
+ gettoks: fn(ts: self ref TokenSource) : array of ref Token;
+ setchset: fn(ts: self ref TokenSource, conv : Btos);
+ };
+
+ TSstate : adt {
+ bi : int;
+ prevbi : int;
+ s : string;
+ si : int;
+ csstate : Convcs->State;
+ prevcsstate : Convcs->State;
+ };
+
+
+ init: fn(cu: CharonUtils);
+};
diff --git a/appl/charon/mkfile b/appl/charon/mkfile
new file mode 100644
index 00000000..6345f815
--- /dev/null
+++ b/appl/charon/mkfile
@@ -0,0 +1,92 @@
+<../../mkconfig
+
+TARG=\
+ build.dis\
+ cookiesrv.dis\
+ chutils.dis\
+ ctype.dis\
+ date.dis\
+ event.dis\
+ file.dis\
+ ftp.dis\
+ gui.dis\
+ http.dis\
+ img.dis\
+ jscript.dis\
+ layout.dis\
+ lex.dis\
+ url.dis\
+
+ICONS=\
+ bookmark.bit\
+ charon.bit\
+ circarrow.bit\
+ conf.bit\
+ down.bit\
+ edit.bit\
+ exit.bit\
+ help.bit\
+ history.bit\
+ home.bit\
+ maxf.bit\
+ minus.bit\
+ plus.bit\
+ redleft.bit\
+ redright.bit\
+ ssloff.bit\
+ sslon.bit\
+ stop.bit\
+ task.bit\
+ up.bit\
+
+MODULES=\
+ build.m\
+ charon.m\
+ chutils.m\
+ common.m\
+ cookiesrv.m\
+ ctype.m\
+ date.m\
+ event.m\
+ gui.m\
+ img.m\
+ layout.m\
+ lex.m\
+ script.m\
+ transport.m\
+ rgb.inc\
+ ycbcr.inc\
+ url.m\
+
+SYSMODULES=\
+ bufio.m\
+ daytime.m\
+ debug.m\
+ draw.m\
+ ecmascript.m\
+ sh.m\
+ ssl3.m\
+ string.m\
+ strinttab.m\
+ sys.m\
+ tk.m\
+ tkclient.m\
+
+DISBIN=$ROOT/dis/charon
+
+all:V: charon.dis
+
+<$ROOT/mkfiles/mkdis
+
+install:V: $ROOT/dis/charon.dis
+
+$ROOT/dis/charon.dis: charon.dis
+ rm -f $target && cp charon.dis $target
+
+charon.dis: $MODULES $SYS_MODULES
+
+img.dis: img.b $MODULE $SYS_MODULE
+ limbo $LIMBOFLAGS -c -gw img.b
+
+nuke:V:
+ rm -f $ROOT/dis/charon.dis
diff --git a/appl/charon/paginate.b b/appl/charon/paginate.b
new file mode 100644
index 00000000..31411d30
--- /dev/null
+++ b/appl/charon/paginate.b
@@ -0,0 +1,511 @@
+implement Paginate;
+
+include "common.m";
+include "print.m";
+include "paginate.m";
+
+sys: Sys;
+print: Print;
+L: Layout;
+D: Draw;
+
+Frame, Lay, Line, Control: import Layout;
+Docinfo: import Build;
+Image, Display, Rect, Point: import D;
+Item: import Build;
+MaskedImage: import CharonUtils;
+
+disp: ref Display;
+p0: Point;
+nulimg: ref Image;
+
+DPI: con 110;
+
+init(layout: Layout, draw: Draw, display: ref Draw->Display): string
+{
+ sys = load Sys Sys->PATH;
+ L = layout;
+ D = draw;
+ disp = display;
+ if (L == nil || D == nil || disp == nil)
+ return "bad args";
+ print = load Print Print->PATH;
+ if (print == nil)
+ return sys->sprint("cannot load module %s: %r", Print->PATH);
+ print->init();
+#nullfd := sys->open("/dev/null", Sys->OWRITE);
+#print->set_printfd(nullfd);
+ p0 = Point(0, 0);
+ nulimg = disp.newimage(((0, 0), (1, 1)), 3, 0, 0);
+ return nil;
+}
+
+paginate(frame: ref Layout->Frame, orient: int, pnums, cancel: chan of int, result: chan of (string, ref Pageset))
+{
+ pidc := chan of int;
+ spawn watchdog(pidc, cancel, nil, sys->pctl(0, nil));
+ watchpid := <- pidc;
+
+ if (frame.kids != nil) {
+ result <-= ("cannot print frameset", nil);
+ kill(watchpid);
+ return;
+ }
+
+ defp := print->get_defprinter();
+ if (defp == nil) {
+ result <-= ("no default printer", nil);
+ kill(watchpid);
+ return;
+ }
+
+ # assuming printer's X & Y resolution are the same
+ if (orient == PORTRAIT)
+ defp.popt.orientation = Print->PORTRAIT;
+ else
+ defp.popt.orientation = Print->LANDSCAPE;
+ (dpi, pagew, pageh) := print->get_size(defp);
+ pagew = (DPI * pagew)/dpi;
+ pageh = (DPI * pageh)/dpi;
+
+ pfr := copyframe(frame);
+ pr := Rect(p0, (pagew, pageh));
+ pfr.r = pr;
+ pfr.cr = pr;
+ pfr.viewr = pr;
+ l := pfr.layout;
+ L->relayout(pfr, l, pagew, l.just);
+ maxy := l.height + l.margin; # don't include bottom margin
+ prctxt := ref Layout->Printcontext;
+ pfr.prctxt = prctxt;
+ pfr.cim = nulimg;
+ pnum := 1;
+ startys : list of int;
+
+ for (y := 0; y < maxy;) {
+ startys = y :: startys;
+ pnums <-= pnum++;
+ endy := y + pageh;
+ prctxt.endy = pageh;
+ pfr.viewr.min.y = y;
+ pfr.viewr.max.y = endy;
+ L->drawall(pfr);
+ y += prctxt.endy;
+ }
+
+ # startys are in reverse order
+ ys : list of int;
+ for (; startys != nil; startys = tl startys)
+ ys = hd startys :: ys;
+
+ pageset := ref Pageset(defp, pfr, ys);
+ result <-= (nil, pageset);
+ kill(watchpid);
+}
+
+printpageset(pset: ref Pageset, pnums, cancel: chan of int)
+{
+ pidc := chan of int;
+ stopdog := chan of int;
+ spawn watchdog(pidc, cancel, stopdog, sys->pctl(0, nil));
+ watchpid := <- pidc;
+
+ frame := pset.frame;
+ pageh := frame.cr.dy();
+ white := disp.rgb2cmap(255, 255, 255);
+ prctxt := frame.prctxt;
+ l := frame.layout;
+ maxy := l.height + l.margin; # don't include bottom margin
+ maxy = max(maxy, pageh);
+ pnum := 1;
+
+ for (pages := pset.pages; pages != nil; pages = tl pages) {
+ y := hd pages;
+ if (y + pageh > maxy)
+ pageh = maxy - y;
+ frame.cr.max.y = pageh;
+ frame.cim = disp.newimage(frame.cr, 3, 0, white);
+ if (frame.cim == nil) {
+ pnums <-= -1;
+ kill(watchpid);
+ return;
+ }
+ pnums <-= pnum++;
+ endy := y + pageh;
+ prctxt.endy = pageh;
+ frame.viewr.min.y = y;
+ frame.viewr.max.y = endy;
+ L->drawall(frame);
+ stopdog <-= 1;
+#start := sys->millisec();
+ if (print->print_image(pset.printer, disp, frame.cim, 100, cancel) == -1) {
+ # cancelled
+ kill(watchpid);
+ return;
+ }
+ stopdog <-= 1;
+#sys->print("PAGE %d: %dms\n", pnum -1, sys->millisec()-start);
+ }
+ pnums <-= -1;
+ kill(watchpid);
+}
+
+watchdog(pidc, cancel, pause: chan of int, pid: int)
+{
+ pidc <-= sys->pctl(0, nil);
+ if (pause == nil)
+ pause = chan of int;
+ for (;;) alt {
+ <- cancel =>
+ kill(pid);
+ return;
+ <- pause =>
+ <- pause;
+ }
+}
+
+kill(pid: int)
+{
+ sys->fprint(sys->open("/prog/" + string pid +"/ctl", Sys->OWRITE), "kill");
+}
+
+killgrp(pid: int)
+{
+ sys->fprint(sys->open("/prog/" + string pid +"/ctl", Sys->OWRITE), "killgrp");
+}
+
+max(a, b: int): int
+{
+ if (a > b)
+ return a;
+ return b;
+}
+
+copyframe(f: ref Frame): ref Frame
+{
+
+ zr := Draw->Rect(p0, p0);
+ newf := ref Frame(
+ -1, # id
+ nil, # doc
+ nil, # src
+ " PRINT FRAME ", # name
+ f.marginw, # marginw
+ f.marginh, # marginh
+ 0, # framebd
+ Build->FRnoscroll, # flags
+ nil, # layout - filled in below, needs this frame ref
+ nil, # sublays - filled in by geometry code
+ 0, # sublayid
+ nil, # controls - filled in below, needs this frame ref
+ 0, # controlid - filled in below
+ nil, # cim
+ zr, # r
+ zr, # cr
+ zr, # totalr
+ zr, # viewr
+ nil, # vscr
+ nil, # hscr
+ nil, # parent
+ nil, # kids
+ 0, # animpid
+ nil # prctxt
+ );
+
+ newf.doc = copydoc(f, newf, f.doc);
+ controls := array [len f.controls] of ref Control;
+ for (i := 0; i < len controls; i++)
+ controls[i] = copycontrol(f, newf, f.controls[i]);
+ newf.layout = copylay(f, newf, f.layout);
+ newf.controls = controls;
+ newf.controlid = len controls;
+
+ return newf;
+}
+
+copysublay(oldf, f: ref Frame, oldid:int): int
+{
+ if (oldid < 0)
+ return -1;
+ if (f.sublayid >= len f.sublays)
+ f.sublays = (array [len f.sublays + 30] of ref Lay)[:] = f.sublays;
+ id := f.sublayid++;
+ lay := copylay(oldf, f, oldf.sublays[oldid]);
+ f.sublays[id] = lay;
+ return id;
+}
+
+copydoc(oldf, f : ref Frame, doc: ref Build->Docinfo): ref Docinfo
+{
+ background := copybackground(oldf, f, doc.background);
+ newdoc := ref Build->Docinfo(
+ nil, #src
+ nil, #base
+ nil, #referrer
+ nil, #doctitle
+ background,
+ nil, #backgrounditem
+ doc.text, doc.link, doc.vlink, doc.alink,
+ nil, #target
+ nil, #refresh
+ nil, #chset
+ nil, #lastModified
+ 0, #scripttype
+ 0, #hasscripts
+ nil, #events
+ 0, #evmask
+ nil, #kidinfo
+ 0, #frameid
+ nil, #anchors
+ nil, #dests
+ nil, #forms
+ nil, #tables
+ nil, #maps
+ nil #images
+ );
+ return newdoc;
+}
+
+copylay(oldf, f: ref Frame, l: ref Lay): ref Lay
+{
+ start := copyline(oldf, f, nil, l.start);
+ end := start;
+ for (line := l.start.next; line != nil; line = line.next)
+ end = copyline(oldf, f, end, line);
+
+ newl := ref Lay(
+ start,
+ end,
+ l.targetwidth, # targetwidth
+ l.width, # width
+ l.height, # height
+ l.margin, # margin
+ nil, # floats - filled in by geometry code
+ copybackground(oldf, f, l.background),
+ l.just,
+ Layout->Lchanged
+ );
+ start.flags = end.flags = byte 0;
+ return newl;
+}
+
+copycontrol(oldf, f: ref Frame, ctl: ref Control): ref Control
+{
+ if (ctl == nil)
+ return nil;
+
+ pick c := ctl {
+ Cbutton =>
+ return ref Control.Cbutton(f, nil, c.r, c.flags, nil, c.pic, c.picmask, c.dpic, c.dpicmask, c.label, c.dorelief);
+ Centry =>
+ scr := copycontrol(oldf, f, c.scr);
+ return ref Control.Centry(f, nil, c.r, c.flags, nil, scr, c.s, c.sel, c.left, c.linewrap, 0);
+ Ccheckbox=>
+ return ref Control.Ccheckbox(f, nil, c.r, c.flags, nil, c.isradio);
+ Cselect =>
+ scr := copycontrol(oldf, f, c.scr);
+ options := (array [len c.options] of Build->Option)[:] = c.options;
+ return ref Control.Cselect(f, nil, c.r, c.flags, nil, nil, scr, c.nvis, c.first, options);
+ Clistbox =>
+ hscr := copycontrol(oldf, f, c.hscr);
+ vscr := copycontrol(oldf, f, c.vscr);
+ options := (array [len c.options] of Build->Option)[:] = c.options;
+ return ref Control.Clistbox(f, nil, c.r, c.flags, nil, hscr, vscr, c.nvis, c.first, c.start, c.maxcol, options, nil);
+ Cscrollbar =>
+ # do not copy ctl as this is set by those associated controls
+ return ref Control.Cscrollbar(f, nil, c.r, c.flags, nil, c.top, c.bot, c.mindelta, c.deltaval, nil, c.holdstate);
+ Canimimage =>
+ bg := copybackground(oldf, f, c.bg);
+ return ref Control.Canimimage(f, nil, c.r, c.flags, nil, c.cim, 0, 0, big 0, bg);
+ Clabel =>
+ return ref Control.Clabel(f, nil, c.r, c.flags, nil, c.s);
+ * =>
+ return nil;
+ }
+}
+
+copyline(oldf, f: ref Frame, prev, l: ref Line): ref Line
+{
+ if (l == nil)
+ return nil;
+ cp := ref *l;
+ items := copyitems(oldf, f, l.items);
+ newl := ref Line (items, nil, prev, l.pos, l.width, l.height, l.ascent, Layout->Lchanged);
+ if (prev != nil)
+ prev.next = newl;
+ return newl;
+}
+
+copyitems(oldf, f: ref Frame, items: ref Item): ref Item
+{
+ if (items == nil)
+ return nil;
+ item := copyitem(oldf, f, items);
+ end := item;
+ for (items = items.next; items != nil; items = items.next) {
+ end.next = copyitem(oldf, f, items);
+ end = end.next;
+ }
+ return item;
+}
+
+copyitem(oldf, f : ref Frame, item: ref Item): ref Item
+{
+ if (item == nil)
+ return nil;
+ pick it := item {
+ Itext =>
+ return ref Item.Itext(
+ nil, it.width, it.height, it.ascent, 0, it.state, nil,
+ it.s, it.fnt, it.fg, it.voff, it.ul);
+ Irule =>
+ return ref Item.Irule(
+ nil, it.width, it.height, it.ascent, 0, it.state, nil,
+ it.align, it.noshade, it.size, it.wspec);
+ Iimage =>
+ # need to copy the image to prevent
+ # ongoing image fetches from messing up our layout
+ ci := copycimage(it.ci);
+ return ref Item.Iimage(
+ nil, it.width, it.height, it.ascent, 0, it.state, nil,
+ it.imageid, ci, it.imwidth, it.imheight, it.altrep,
+ nil, it.name, -1, it.align, it.hspace, it.vspace, it.border);
+ Iformfield =>
+ return ref Item.Iformfield(
+ nil, it.width, it.height, it.ascent, 0, it.state, nil,
+ copyformfield(oldf, f, it.formfield)
+ );
+ Itable =>
+ return ref Item.Itable(
+ nil, it.width, it.height, it.ascent, 0, it.state, nil,
+ copytable(oldf, f, it.table));
+ Ifloat =>
+ items := copyitem(oldf, f, it.item);
+ return ref Item.Ifloat(
+ nil, it.width, it.height, it.ascent, 0, it.state, nil,
+ items, it.x, it.y, it.side, byte 0);
+ Ispacer =>
+ return ref Item.Ispacer(
+ nil, it.width, it.height, it.ascent, 0, it.state, nil,
+ it.spkind, it.fnt);
+ * =>
+ return nil;
+ }
+}
+
+copycimage(ci: ref CharonUtils->CImage): ref CharonUtils->CImage
+{
+ if (ci == nil)
+ return nil;
+ mims : array of ref MaskedImage;
+ if (len ci.mims > 0)
+ # if len> 1 then animated, but we only want first frame
+ mims = array [1] of {0 => ci.mims[0]};
+ return ref CharonUtils->CImage(nil, nil, nil, 0, ci.width, ci.height, nil, mims, 0);
+}
+
+copyformfield(oldf, f: ref Frame, ff: ref Build->Formfield): ref Build->Formfield
+{
+ image := copyitem(oldf, f, ff.image);
+ # should be safe to reference Option list
+ newff := ref Build->Formfield(
+ ff.ftype, 0, nil, ff.name, ff.value, ff.size, ff.maxlength, ff.rows,
+ ff.cols, ff.flags, ff.options, image, ff.ctlid, nil, 0
+ );
+ return newff;
+}
+
+copytable(oldf, f: ref Frame, tbl: ref Build->Table): ref Build->Table
+{
+ nrow := tbl.nrow;
+ ncol := tbl.ncol;
+ caption_lay := copysublay(oldf, f, tbl.caption_lay);
+ cols := (array [ncol] of Build->Tablecol)[:] = tbl.cols;
+ rows := array [nrow] of ref Build->Tablerow;
+ for (i := 0; i < nrow; i++) {
+ r := tbl.rows[i];
+ rows[i] = ref Build->Tablerow(nil, r.height, r.ascent, r.align, r.background, r.pos, r.flags);
+ }
+
+ cells : list of ref Build->Tablecell;
+ grid := array [nrow] of {* => array [ncol] of ref Build->Tablecell};
+ for (rix := 0; rix < nrow; rix++) {
+ rowcells: list of ref Build->Tablecell = nil;
+ for (colix := 0; colix < ncol; colix++) {
+ cell := copytablecell(oldf, f, tbl.grid[rix][colix]);
+ if (cell == nil)
+ continue;
+ grid[rix][colix] = cell;
+ cells = cell :: cells;
+ rowcells = cell :: rowcells;
+ }
+ # reverse the row cells;
+ rcells : list of ref Build->Tablecell = nil;
+ for (; rowcells != nil; rowcells = tl rowcells)
+ rcells = hd rowcells :: rcells;
+ rows[rix].cells = rcells;
+ }
+
+ # reverse the cells
+ sllec: list of ref Build->Tablecell;
+ for (; cells != nil; cells = tl cells)
+ sllec = hd cells :: sllec;
+ cells = sllec;
+
+ return ref Build->Table(
+ tbl.tableid, # tableid
+ nrow, # nrow
+ ncol, # ncol
+ len cells, # ncell
+ tbl.align, # align
+ tbl.width, # width
+ tbl.border, # border
+ tbl.cellspacing, # cellspacing
+ tbl.cellpadding, # cellpadding
+ tbl.background, # background
+ nil, # caption
+ tbl.caption_place, # caption_place
+ caption_lay, # caption_lay
+ nil, # currows
+ cols, # cols
+ rows, # rows
+ cells, # cells
+ tbl.totw, # totw
+ tbl.toth, # toth
+ tbl.caph, # caph
+ tbl.availw, # availw
+ grid, # grid
+ nil, # tabletok
+ Layout->Lchanged # flags
+ );
+}
+
+copytablecell(oldf, f: ref Frame, cell: ref Build->Tablecell): ref Build->Tablecell
+{
+ if (cell == nil)
+ return nil;
+
+ layid := copysublay(oldf, f, cell.layid);
+ background := copybackground(oldf, f, cell.background);
+ newcell := ref Build->Tablecell(
+ cell.cellid,
+ nil, # content
+ layid,
+ cell.rowspan, cell.colspan, cell.align,
+ cell.flags, cell.wspec, cell.hspec,
+ background, cell.minw, cell.maxw,
+ cell.ascent, cell.row, cell.col, cell.pos);
+ return newcell;
+}
+
+copybackground(oldf, f: ref Frame, bg: Build->Background): Build->Background
+{
+ img := copyitem(oldf, f, bg.image);
+ if (img != nil) {
+ pick i := img {
+ Iimage =>
+ bg.image = i;
+ }
+ }
+ return bg;
+}
diff --git a/appl/charon/paginate.m b/appl/charon/paginate.m
new file mode 100644
index 00000000..ff49429f
--- /dev/null
+++ b/appl/charon/paginate.m
@@ -0,0 +1,16 @@
+Paginate: module {
+ PATH: con "/dis/charon/paginate.dis";
+
+ init: fn(layout: Layout, draw: Draw, display: ref Draw->Display): string;
+
+ Pageset: adt {
+ printer: ref Print->Printer;
+ frame: ref Layout->Frame;
+ pages: list of int;
+ };
+
+ PORTRAIT, LANDSCAPE: con iota;
+
+ paginate: fn(frame: ref Layout->Frame, orient: int, pagenums, cancel: chan of int, result: chan of (string, ref Pageset));
+ printpageset: fn(pages: ref Pageset, pagenums, cancel: chan of int);
+};
diff --git a/appl/charon/rgb.inc b/appl/charon/rgb.inc
new file mode 100644
index 00000000..3cdc31ba
--- /dev/null
+++ b/appl/charon/rgb.inc
@@ -0,0 +1,620 @@
+# closest color in the rgbvmap, indexed by B+16*(G+16*B)
+closestrgb:= array[16*16*16] of {
+ byte 255,byte 255,byte 255,byte 254,byte 254,byte 237,byte 220,byte 203,
+ byte 253,byte 236,byte 219,byte 202,byte 252,byte 235,byte 218,byte 201,
+ byte 255,byte 255,byte 255,byte 254,byte 254,byte 237,byte 220,byte 203,
+ byte 253,byte 236,byte 219,byte 202,byte 252,byte 235,byte 218,byte 201,
+ byte 255,byte 255,byte 255,byte 250,byte 250,byte 250,byte 220,byte 249,
+ byte 249,byte 249,byte 232,byte 248,byte 248,byte 248,byte 231,byte 201,
+ byte 251,byte 251,byte 250,byte 250,byte 250,byte 250,byte 249,byte 249,
+ byte 249,byte 249,byte 232,byte 248,byte 248,byte 248,byte 231,byte 201,
+ byte 251,byte 251,byte 250,byte 250,byte 250,byte 233,byte 233,byte 249,
+ byte 249,byte 232,byte 215,byte 215,byte 248,byte 231,byte 214,byte 197,
+ byte 234,byte 234,byte 250,byte 250,byte 233,byte 233,byte 216,byte 216,
+ byte 249,byte 232,byte 215,byte 198,byte 198,byte 231,byte 214,byte 197,
+ byte 217,byte 217,byte 217,byte 246,byte 233,byte 216,byte 216,byte 199,
+ byte 199,byte 215,byte 215,byte 198,byte 198,byte 198,byte 214,byte 197,
+ byte 200,byte 200,byte 246,byte 246,byte 246,byte 216,byte 199,byte 199,
+ byte 245,byte 245,byte 198,byte 244,byte 244,byte 244,byte 227,byte 197,
+ byte 247,byte 247,byte 246,byte 246,byte 246,byte 246,byte 199,byte 245,
+ byte 245,byte 245,byte 228,byte 244,byte 244,byte 244,byte 227,byte 193,
+ byte 230,byte 230,byte 246,byte 246,byte 229,byte 229,byte 212,byte 245,
+ byte 245,byte 228,byte 228,byte 211,byte 244,byte 227,byte 210,byte 193,
+ byte 213,byte 213,byte 229,byte 229,byte 212,byte 212,byte 212,byte 195,
+ byte 228,byte 228,byte 211,byte 211,byte 194,byte 227,byte 210,byte 193,
+ byte 196,byte 196,byte 242,byte 242,byte 212,byte 195,byte 195,byte 241,
+ byte 241,byte 211,byte 211,byte 194,byte 194,byte 240,byte 210,byte 193,
+ byte 243,byte 243,byte 242,byte 242,byte 242,byte 195,byte 195,byte 241,
+ byte 241,byte 241,byte 194,byte 194,byte 240,byte 240,byte 239,byte 205,
+ byte 226,byte 226,byte 242,byte 242,byte 225,byte 225,byte 195,byte 241,
+ byte 241,byte 224,byte 224,byte 240,byte 240,byte 239,byte 239,byte 205,
+ byte 209,byte 209,byte 225,byte 225,byte 208,byte 208,byte 208,byte 224,
+ byte 224,byte 223,byte 223,byte 223,byte 239,byte 239,byte 222,byte 205,
+ byte 192,byte 192,byte 192,byte 192,byte 207,byte 207,byte 207,byte 207,
+ byte 206,byte 206,byte 206,byte 206,byte 205,byte 205,byte 205,byte 205,
+ byte 255,byte 255,byte 255,byte 254,byte 254,byte 237,byte 220,byte 203,
+ byte 253,byte 236,byte 219,byte 202,byte 252,byte 235,byte 218,byte 201,
+ byte 255,byte 238,byte 221,byte 221,byte 254,byte 237,byte 220,byte 203,
+ byte 253,byte 236,byte 219,byte 202,byte 252,byte 235,byte 218,byte 201,
+ byte 255,byte 221,byte 221,byte 221,byte 204,byte 250,byte 220,byte 249,
+ byte 249,byte 249,byte 232,byte 248,byte 248,byte 248,byte 231,byte 201,
+ byte 251,byte 221,byte 221,byte 204,byte 250,byte 250,byte 249,byte 249,
+ byte 249,byte 249,byte 232,byte 248,byte 248,byte 248,byte 231,byte 201,
+ byte 251,byte 251,byte 204,byte 250,byte 250,byte 233,byte 233,byte 249,
+ byte 249,byte 232,byte 215,byte 215,byte 248,byte 231,byte 214,byte 197,
+ byte 234,byte 234,byte 250,byte 250,byte 233,byte 233,byte 216,byte 216,
+ byte 249,byte 232,byte 215,byte 198,byte 198,byte 231,byte 214,byte 197,
+ byte 217,byte 217,byte 217,byte 246,byte 233,byte 216,byte 216,byte 199,
+ byte 199,byte 215,byte 215,byte 198,byte 198,byte 198,byte 214,byte 197,
+ byte 200,byte 200,byte 246,byte 246,byte 246,byte 216,byte 199,byte 199,
+ byte 245,byte 245,byte 198,byte 244,byte 244,byte 244,byte 227,byte 197,
+ byte 247,byte 247,byte 246,byte 246,byte 246,byte 246,byte 199,byte 245,
+ byte 245,byte 245,byte 228,byte 244,byte 244,byte 244,byte 227,byte 193,
+ byte 230,byte 230,byte 246,byte 246,byte 229,byte 229,byte 212,byte 245,
+ byte 245,byte 228,byte 228,byte 211,byte 244,byte 227,byte 210,byte 193,
+ byte 213,byte 213,byte 229,byte 229,byte 212,byte 212,byte 212,byte 195,
+ byte 228,byte 228,byte 211,byte 211,byte 194,byte 227,byte 210,byte 193,
+ byte 196,byte 196,byte 242,byte 242,byte 212,byte 195,byte 195,byte 241,
+ byte 241,byte 211,byte 211,byte 194,byte 194,byte 240,byte 210,byte 193,
+ byte 243,byte 243,byte 242,byte 242,byte 242,byte 195,byte 195,byte 241,
+ byte 241,byte 241,byte 194,byte 194,byte 240,byte 240,byte 239,byte 205,
+ byte 226,byte 226,byte 242,byte 242,byte 225,byte 225,byte 195,byte 241,
+ byte 241,byte 224,byte 224,byte 240,byte 240,byte 239,byte 239,byte 205,
+ byte 209,byte 209,byte 225,byte 225,byte 208,byte 208,byte 208,byte 224,
+ byte 224,byte 223,byte 223,byte 223,byte 239,byte 239,byte 222,byte 205,
+ byte 192,byte 192,byte 192,byte 192,byte 207,byte 207,byte 207,byte 207,
+ byte 206,byte 206,byte 206,byte 206,byte 205,byte 205,byte 205,byte 205,
+ byte 255,byte 255,byte 255,byte 191,byte 191,byte 191,byte 220,byte 190,
+ byte 190,byte 190,byte 173,byte 189,byte 189,byte 189,byte 172,byte 201,
+ byte 255,byte 221,byte 221,byte 221,byte 204,byte 191,byte 220,byte 190,
+ byte 190,byte 190,byte 173,byte 189,byte 189,byte 189,byte 172,byte 201,
+ byte 255,byte 221,byte 221,byte 204,byte 204,byte 204,byte 186,byte 186,
+ byte 186,byte 186,byte 186,byte 185,byte 185,byte 185,byte 168,byte 201,
+ byte 188,byte 221,byte 204,byte 204,byte 204,byte 187,byte 186,byte 186,
+ byte 186,byte 186,byte 232,byte 185,byte 185,byte 185,byte 168,byte 201,
+ byte 188,byte 204,byte 204,byte 204,byte 187,byte 187,byte 186,byte 186,
+ byte 186,byte 186,byte 169,byte 185,byte 185,byte 185,byte 168,byte 197,
+ byte 188,byte 188,byte 204,byte 187,byte 187,byte 233,byte 216,byte 186,
+ byte 186,byte 186,byte 215,byte 185,byte 185,byte 185,byte 168,byte 197,
+ byte 217,byte 217,byte 183,byte 183,byte 183,byte 216,byte 216,byte 199,
+ byte 182,byte 182,byte 215,byte 198,byte 198,byte 181,byte 214,byte 197,
+ byte 184,byte 184,byte 183,byte 183,byte 183,byte 183,byte 199,byte 182,
+ byte 182,byte 182,byte 182,byte 181,byte 181,byte 181,byte 181,byte 197,
+ byte 184,byte 184,byte 183,byte 183,byte 183,byte 183,byte 182,byte 182,
+ byte 182,byte 182,byte 182,byte 181,byte 181,byte 181,byte 164,byte 193,
+ byte 184,byte 184,byte 183,byte 183,byte 183,byte 183,byte 182,byte 182,
+ byte 182,byte 228,byte 165,byte 181,byte 181,byte 164,byte 164,byte 193,
+ byte 167,byte 167,byte 183,byte 229,byte 166,byte 212,byte 212,byte 182,
+ byte 182,byte 165,byte 211,byte 211,byte 181,byte 164,byte 210,byte 193,
+ byte 180,byte 180,byte 179,byte 179,byte 179,byte 179,byte 195,byte 178,
+ byte 178,byte 178,byte 211,byte 194,byte 177,byte 177,byte 177,byte 193,
+ byte 180,byte 180,byte 179,byte 179,byte 179,byte 179,byte 195,byte 178,
+ byte 178,byte 178,byte 178,byte 177,byte 177,byte 177,byte 177,byte 205,
+ byte 180,byte 180,byte 179,byte 179,byte 179,byte 179,byte 178,byte 178,
+ byte 178,byte 161,byte 161,byte 177,byte 177,byte 177,byte 160,byte 205,
+ byte 163,byte 163,byte 162,byte 162,byte 162,byte 162,byte 208,byte 178,
+ byte 161,byte 161,byte 223,byte 177,byte 177,byte 160,byte 160,byte 205,
+ byte 192,byte 192,byte 192,byte 192,byte 207,byte 207,byte 207,byte 207,
+ byte 206,byte 206,byte 206,byte 206,byte 205,byte 205,byte 205,byte 205,
+ byte 176,byte 176,byte 191,byte 191,byte 191,byte 191,byte 190,byte 190,
+ byte 190,byte 190,byte 173,byte 189,byte 189,byte 189,byte 172,byte 201,
+ byte 176,byte 221,byte 221,byte 204,byte 191,byte 191,byte 190,byte 190,
+ byte 190,byte 190,byte 173,byte 189,byte 189,byte 189,byte 172,byte 201,
+ byte 188,byte 221,byte 204,byte 204,byte 204,byte 187,byte 186,byte 186,
+ byte 186,byte 186,byte 173,byte 185,byte 185,byte 185,byte 168,byte 201,
+ byte 188,byte 204,byte 204,byte 204,byte 187,byte 187,byte 186,byte 186,
+ byte 186,byte 186,byte 169,byte 185,byte 185,byte 185,byte 168,byte 201,
+ byte 188,byte 188,byte 204,byte 187,byte 187,byte 187,byte 186,byte 186,
+ byte 186,byte 186,byte 169,byte 185,byte 185,byte 185,byte 168,byte 197,
+ byte 188,byte 188,byte 187,byte 187,byte 187,byte 170,byte 170,byte 186,
+ byte 186,byte 169,byte 169,byte 185,byte 185,byte 168,byte 168,byte 197,
+ byte 184,byte 184,byte 183,byte 183,byte 183,byte 170,byte 170,byte 182,
+ byte 182,byte 169,byte 152,byte 152,byte 181,byte 168,byte 151,byte 197,
+ byte 184,byte 184,byte 183,byte 183,byte 183,byte 183,byte 182,byte 182,
+ byte 182,byte 182,byte 182,byte 181,byte 181,byte 181,byte 164,byte 197,
+ byte 184,byte 184,byte 183,byte 183,byte 183,byte 183,byte 182,byte 182,
+ byte 182,byte 182,byte 165,byte 181,byte 181,byte 181,byte 164,byte 193,
+ byte 184,byte 184,byte 183,byte 183,byte 183,byte 166,byte 166,byte 182,
+ byte 182,byte 165,byte 165,byte 181,byte 181,byte 164,byte 164,byte 193,
+ byte 167,byte 167,byte 167,byte 166,byte 166,byte 166,byte 149,byte 182,
+ byte 165,byte 165,byte 165,byte 148,byte 181,byte 164,byte 147,byte 193,
+ byte 180,byte 180,byte 179,byte 179,byte 179,byte 179,byte 149,byte 178,
+ byte 178,byte 178,byte 148,byte 177,byte 177,byte 177,byte 147,byte 193,
+ byte 180,byte 180,byte 179,byte 179,byte 179,byte 179,byte 178,byte 178,
+ byte 178,byte 178,byte 178,byte 177,byte 177,byte 177,byte 160,byte 205,
+ byte 180,byte 180,byte 179,byte 179,byte 179,byte 162,byte 162,byte 178,
+ byte 178,byte 161,byte 161,byte 177,byte 177,byte 160,byte 160,byte 205,
+ byte 163,byte 163,byte 162,byte 162,byte 162,byte 162,byte 145,byte 161,
+ byte 161,byte 161,byte 144,byte 144,byte 160,byte 160,byte 160,byte 205,
+ byte 192,byte 192,byte 192,byte 192,byte 207,byte 207,byte 207,byte 207,
+ byte 206,byte 206,byte 206,byte 206,byte 205,byte 205,byte 205,byte 205,
+ byte 176,byte 176,byte 191,byte 191,byte 191,byte 174,byte 174,byte 190,
+ byte 190,byte 173,byte 156,byte 156,byte 189,byte 172,byte 155,byte 138,
+ byte 176,byte 176,byte 204,byte 191,byte 191,byte 174,byte 174,byte 190,
+ byte 190,byte 173,byte 156,byte 156,byte 189,byte 172,byte 155,byte 138,
+ byte 188,byte 204,byte 204,byte 204,byte 187,byte 187,byte 186,byte 186,
+ byte 186,byte 186,byte 169,byte 185,byte 185,byte 185,byte 168,byte 138,
+ byte 188,byte 188,byte 204,byte 187,byte 187,byte 187,byte 186,byte 186,
+ byte 186,byte 186,byte 169,byte 185,byte 185,byte 185,byte 168,byte 138,
+ byte 188,byte 188,byte 187,byte 187,byte 187,byte 170,byte 170,byte 186,
+ byte 186,byte 169,byte 169,byte 185,byte 185,byte 168,byte 151,byte 134,
+ byte 171,byte 171,byte 187,byte 187,byte 170,byte 170,byte 170,byte 186,
+ byte 186,byte 169,byte 152,byte 152,byte 185,byte 168,byte 151,byte 134,
+ byte 171,byte 171,byte 183,byte 183,byte 170,byte 170,byte 170,byte 153,
+ byte 182,byte 169,byte 152,byte 135,byte 135,byte 168,byte 151,byte 134,
+ byte 184,byte 184,byte 183,byte 183,byte 183,byte 183,byte 153,byte 182,
+ byte 182,byte 182,byte 182,byte 181,byte 181,byte 181,byte 164,byte 134,
+ byte 184,byte 184,byte 183,byte 183,byte 183,byte 183,byte 182,byte 182,
+ byte 182,byte 182,byte 165,byte 181,byte 181,byte 181,byte 164,byte 130,
+ byte 167,byte 167,byte 183,byte 183,byte 166,byte 166,byte 166,byte 182,
+ byte 182,byte 165,byte 165,byte 181,byte 181,byte 164,byte 147,byte 130,
+ byte 150,byte 150,byte 166,byte 166,byte 166,byte 149,byte 149,byte 182,
+ byte 165,byte 165,byte 148,byte 148,byte 164,byte 164,byte 147,byte 130,
+ byte 150,byte 150,byte 179,byte 179,byte 179,byte 149,byte 132,byte 178,
+ byte 178,byte 178,byte 148,byte 131,byte 177,byte 177,byte 147,byte 130,
+ byte 180,byte 180,byte 179,byte 179,byte 179,byte 179,byte 132,byte 178,
+ byte 178,byte 178,byte 161,byte 177,byte 177,byte 177,byte 160,byte 142,
+ byte 163,byte 163,byte 179,byte 179,byte 162,byte 162,byte 162,byte 178,
+ byte 178,byte 161,byte 161,byte 177,byte 177,byte 160,byte 160,byte 142,
+ byte 146,byte 146,byte 162,byte 162,byte 145,byte 145,byte 145,byte 161,
+ byte 161,byte 144,byte 144,byte 144,byte 160,byte 160,byte 159,byte 142,
+ byte 129,byte 129,byte 129,byte 129,byte 128,byte 128,byte 128,byte 128,
+ byte 143,byte 143,byte 143,byte 143,byte 142,byte 142,byte 142,byte 142,
+ byte 175,byte 175,byte 191,byte 191,byte 174,byte 174,byte 157,byte 157,
+ byte 190,byte 173,byte 156,byte 139,byte 139,byte 172,byte 155,byte 138,
+ byte 175,byte 175,byte 191,byte 191,byte 174,byte 174,byte 157,byte 157,
+ byte 190,byte 173,byte 156,byte 139,byte 139,byte 172,byte 155,byte 138,
+ byte 188,byte 188,byte 204,byte 187,byte 187,byte 187,byte 157,byte 186,
+ byte 186,byte 186,byte 156,byte 185,byte 185,byte 185,byte 168,byte 138,
+ byte 188,byte 188,byte 187,byte 187,byte 187,byte 170,byte 170,byte 186,
+ byte 186,byte 169,byte 169,byte 185,byte 185,byte 168,byte 168,byte 138,
+ byte 171,byte 171,byte 187,byte 187,byte 170,byte 170,byte 170,byte 186,
+ byte 186,byte 169,byte 152,byte 152,byte 185,byte 168,byte 151,byte 134,
+ byte 171,byte 171,byte 187,byte 170,byte 170,byte 170,byte 170,byte 153,
+ byte 169,byte 169,byte 152,byte 135,byte 135,byte 168,byte 151,byte 134,
+ byte 154,byte 154,byte 154,byte 170,byte 170,byte 170,byte 153,byte 153,
+ byte 169,byte 152,byte 152,byte 135,byte 135,byte 135,byte 151,byte 134,
+ byte 154,byte 154,byte 183,byte 183,byte 183,byte 153,byte 153,byte 153,
+ byte 182,byte 182,byte 135,byte 135,byte 181,byte 181,byte 164,byte 134,
+ byte 184,byte 184,byte 183,byte 183,byte 183,byte 166,byte 166,byte 182,
+ byte 182,byte 165,byte 165,byte 181,byte 181,byte 164,byte 164,byte 130,
+ byte 167,byte 167,byte 183,byte 166,byte 166,byte 166,byte 149,byte 182,
+ byte 165,byte 165,byte 165,byte 148,byte 181,byte 164,byte 147,byte 130,
+ byte 150,byte 150,byte 150,byte 166,byte 149,byte 149,byte 149,byte 132,
+ byte 165,byte 165,byte 148,byte 148,byte 131,byte 147,byte 147,byte 130,
+ byte 133,byte 133,byte 179,byte 179,byte 149,byte 132,byte 132,byte 132,
+ byte 178,byte 148,byte 148,byte 131,byte 131,byte 131,byte 130,byte 130,
+ byte 133,byte 133,byte 179,byte 179,byte 179,byte 132,byte 132,byte 178,
+ byte 178,byte 178,byte 131,byte 131,byte 131,byte 177,byte 160,byte 142,
+ byte 163,byte 163,byte 179,byte 162,byte 162,byte 162,byte 132,byte 178,
+ byte 161,byte 161,byte 144,byte 131,byte 177,byte 160,byte 160,byte 142,
+ byte 146,byte 146,byte 162,byte 162,byte 145,byte 145,byte 145,byte 161,
+ byte 161,byte 144,byte 144,byte 143,byte 160,byte 160,byte 159,byte 142,
+ byte 129,byte 129,byte 129,byte 129,byte 128,byte 128,byte 128,byte 128,
+ byte 143,byte 143,byte 143,byte 143,byte 142,byte 142,byte 142,byte 142,
+ byte 158,byte 158,byte 158,byte 112,byte 174,byte 157,byte 157,byte 140,
+ byte 140,byte 156,byte 156,byte 139,byte 139,byte 139,byte 155,byte 138,
+ byte 158,byte 158,byte 158,byte 112,byte 174,byte 157,byte 157,byte 140,
+ byte 140,byte 156,byte 156,byte 139,byte 139,byte 139,byte 155,byte 138,
+ byte 158,byte 158,byte 124,byte 124,byte 124,byte 157,byte 157,byte 140,
+ byte 123,byte 123,byte 156,byte 139,byte 139,byte 122,byte 155,byte 138,
+ byte 125,byte 125,byte 124,byte 124,byte 124,byte 170,byte 170,byte 123,
+ byte 123,byte 169,byte 152,byte 152,byte 122,byte 168,byte 151,byte 138,
+ byte 171,byte 171,byte 124,byte 124,byte 170,byte 170,byte 170,byte 153,
+ byte 123,byte 169,byte 152,byte 135,byte 135,byte 168,byte 151,byte 134,
+ byte 154,byte 154,byte 154,byte 170,byte 170,byte 170,byte 153,byte 153,
+ byte 169,byte 152,byte 152,byte 135,byte 135,byte 135,byte 151,byte 134,
+ byte 154,byte 154,byte 154,byte 170,byte 170,byte 153,byte 153,byte 153,
+ byte 136,byte 152,byte 135,byte 135,byte 135,byte 135,byte 134,byte 134,
+ byte 137,byte 137,byte 137,byte 120,byte 153,byte 153,byte 153,byte 136,
+ byte 136,byte 136,byte 135,byte 135,byte 135,byte 118,byte 164,byte 134,
+ byte 137,byte 137,byte 120,byte 120,byte 120,byte 166,byte 136,byte 136,
+ byte 136,byte 165,byte 165,byte 118,byte 118,byte 164,byte 147,byte 130,
+ byte 150,byte 150,byte 120,byte 166,byte 166,byte 149,byte 149,byte 136,
+ byte 165,byte 165,byte 148,byte 148,byte 118,byte 164,byte 147,byte 130,
+ byte 150,byte 150,byte 150,byte 149,byte 149,byte 149,byte 132,byte 132,
+ byte 165,byte 148,byte 148,byte 131,byte 131,byte 147,byte 147,byte 130,
+ byte 133,byte 133,byte 133,byte 149,byte 132,byte 132,byte 132,byte 132,
+ byte 115,byte 148,byte 131,byte 131,byte 131,byte 131,byte 130,byte 130,
+ byte 133,byte 133,byte 133,byte 116,byte 132,byte 132,byte 132,byte 132,
+ byte 115,byte 115,byte 131,byte 131,byte 131,byte 131,byte 160,byte 142,
+ byte 133,byte 133,byte 116,byte 162,byte 162,byte 132,byte 132,byte 115,
+ byte 161,byte 161,byte 144,byte 131,byte 131,byte 160,byte 160,byte 142,
+ byte 146,byte 146,byte 146,byte 145,byte 145,byte 145,byte 128,byte 161,
+ byte 144,byte 144,byte 144,byte 143,byte 160,byte 160,byte 159,byte 142,
+ byte 129,byte 129,byte 129,byte 129,byte 128,byte 128,byte 128,byte 128,
+ byte 143,byte 143,byte 143,byte 143,byte 142,byte 142,byte 142,byte 142,
+ byte 141,byte 141,byte 112,byte 112,byte 112,byte 157,byte 140,byte 140,
+ byte 140,byte 127,byte 139,byte 126,byte 126,byte 126,byte 109,byte 138,
+ byte 141,byte 141,byte 112,byte 112,byte 112,byte 157,byte 140,byte 140,
+ byte 140,byte 127,byte 139,byte 126,byte 126,byte 126,byte 109,byte 138,
+ byte 125,byte 125,byte 124,byte 124,byte 124,byte 124,byte 140,byte 123,
+ byte 123,byte 123,byte 123,byte 122,byte 122,byte 122,byte 122,byte 138,
+ byte 125,byte 125,byte 124,byte 124,byte 124,byte 124,byte 123,byte 123,
+ byte 123,byte 123,byte 123,byte 122,byte 122,byte 122,byte 105,byte 138,
+ byte 125,byte 125,byte 124,byte 124,byte 124,byte 124,byte 153,byte 123,
+ byte 123,byte 123,byte 152,byte 122,byte 122,byte 122,byte 105,byte 134,
+ byte 154,byte 154,byte 124,byte 124,byte 124,byte 153,byte 153,byte 153,
+ byte 123,byte 123,byte 135,byte 135,byte 122,byte 122,byte 105,byte 134,
+ byte 137,byte 137,byte 137,byte 120,byte 153,byte 153,byte 153,byte 136,
+ byte 136,byte 136,byte 135,byte 135,byte 135,byte 118,byte 105,byte 134,
+ byte 137,byte 137,byte 120,byte 120,byte 120,byte 153,byte 136,byte 136,
+ byte 136,byte 119,byte 119,byte 118,byte 118,byte 118,byte 118,byte 134,
+ byte 137,byte 137,byte 120,byte 120,byte 120,byte 120,byte 136,byte 136,
+ byte 119,byte 119,byte 119,byte 118,byte 118,byte 118,byte 101,byte 130,
+ byte 121,byte 121,byte 120,byte 120,byte 120,byte 120,byte 136,byte 119,
+ byte 119,byte 119,byte 102,byte 118,byte 118,byte 118,byte 101,byte 130,
+ byte 133,byte 133,byte 120,byte 120,byte 149,byte 132,byte 132,byte 119,
+ byte 119,byte 102,byte 148,byte 131,byte 131,byte 101,byte 101,byte 130,
+ byte 117,byte 117,byte 116,byte 116,byte 116,byte 132,byte 132,byte 115,
+ byte 115,byte 115,byte 131,byte 131,byte 114,byte 114,byte 114,byte 130,
+ byte 117,byte 117,byte 116,byte 116,byte 116,byte 116,byte 132,byte 115,
+ byte 115,byte 115,byte 131,byte 114,byte 114,byte 114,byte 114,byte 142,
+ byte 117,byte 117,byte 116,byte 116,byte 116,byte 116,byte 115,byte 115,
+ byte 115,byte 115,byte 98,byte 114,byte 114,byte 114,byte 97,byte 142,
+ byte 100,byte 100,byte 116,byte 99,byte 99,byte 99,byte 99,byte 115,
+ byte 98,byte 98,byte 98,byte 114,byte 114,byte 97,byte 97,byte 142,
+ byte 129,byte 129,byte 129,byte 129,byte 128,byte 128,byte 128,byte 128,
+ byte 143,byte 143,byte 143,byte 143,byte 142,byte 142,byte 142,byte 142,
+ byte 113,byte 113,byte 112,byte 112,byte 112,byte 112,byte 140,byte 140,
+ byte 127,byte 127,byte 110,byte 126,byte 126,byte 126,byte 109,byte 75,
+ byte 113,byte 113,byte 112,byte 112,byte 112,byte 112,byte 140,byte 140,
+ byte 127,byte 127,byte 110,byte 126,byte 126,byte 126,byte 109,byte 75,
+ byte 125,byte 125,byte 124,byte 124,byte 124,byte 124,byte 123,byte 123,
+ byte 123,byte 123,byte 123,byte 122,byte 122,byte 122,byte 105,byte 75,
+ byte 125,byte 125,byte 124,byte 124,byte 124,byte 124,byte 123,byte 123,
+ byte 123,byte 123,byte 106,byte 122,byte 122,byte 122,byte 105,byte 75,
+ byte 125,byte 125,byte 124,byte 124,byte 124,byte 124,byte 123,byte 123,
+ byte 123,byte 123,byte 106,byte 122,byte 122,byte 122,byte 105,byte 71,
+ byte 125,byte 125,byte 124,byte 124,byte 124,byte 107,byte 107,byte 123,
+ byte 123,byte 106,byte 106,byte 122,byte 122,byte 105,byte 105,byte 71,
+ byte 137,byte 137,byte 120,byte 120,byte 120,byte 107,byte 136,byte 136,
+ byte 136,byte 106,byte 106,byte 118,byte 118,byte 105,byte 88,byte 71,
+ byte 137,byte 137,byte 120,byte 120,byte 120,byte 120,byte 136,byte 136,
+ byte 119,byte 119,byte 119,byte 118,byte 118,byte 118,byte 101,byte 71,
+ byte 121,byte 121,byte 120,byte 120,byte 120,byte 120,byte 136,byte 119,
+ byte 119,byte 119,byte 102,byte 118,byte 118,byte 118,byte 101,byte 67,
+ byte 121,byte 121,byte 120,byte 120,byte 120,byte 103,byte 103,byte 119,
+ byte 119,byte 102,byte 102,byte 118,byte 118,byte 101,byte 101,byte 67,
+ byte 104,byte 104,byte 120,byte 103,byte 103,byte 103,byte 103,byte 119,
+ byte 102,byte 102,byte 102,byte 118,byte 118,byte 101,byte 84,byte 67,
+ byte 117,byte 117,byte 116,byte 116,byte 116,byte 116,byte 115,byte 115,
+ byte 115,byte 115,byte 115,byte 114,byte 114,byte 114,byte 114,byte 67,
+ byte 117,byte 117,byte 116,byte 116,byte 116,byte 116,byte 115,byte 115,
+ byte 115,byte 115,byte 115,byte 114,byte 114,byte 114,byte 97,byte 79,
+ byte 117,byte 117,byte 116,byte 116,byte 116,byte 99,byte 99,byte 115,
+ byte 115,byte 98,byte 98,byte 114,byte 114,byte 97,byte 97,byte 79,
+ byte 100,byte 100,byte 99,byte 99,byte 99,byte 99,byte 82,byte 98,
+ byte 98,byte 98,byte 81,byte 114,byte 97,byte 97,byte 97,byte 79,
+ byte 66,byte 66,byte 66,byte 66,byte 65,byte 65,byte 65,byte 65,
+ byte 64,byte 64,byte 64,byte 64,byte 79,byte 79,byte 79,byte 79,
+ byte 96,byte 96,byte 112,byte 112,byte 111,byte 111,byte 94,byte 127,
+ byte 127,byte 110,byte 110,byte 93,byte 126,byte 109,byte 92,byte 75,
+ byte 96,byte 96,byte 112,byte 112,byte 111,byte 111,byte 94,byte 127,
+ byte 127,byte 110,byte 110,byte 93,byte 126,byte 109,byte 92,byte 75,
+ byte 125,byte 125,byte 124,byte 124,byte 124,byte 124,byte 123,byte 123,
+ byte 123,byte 123,byte 106,byte 122,byte 122,byte 105,byte 105,byte 75,
+ byte 125,byte 125,byte 124,byte 124,byte 124,byte 107,byte 107,byte 123,
+ byte 123,byte 106,byte 106,byte 122,byte 122,byte 105,byte 105,byte 75,
+ byte 108,byte 108,byte 124,byte 124,byte 107,byte 107,byte 107,byte 123,
+ byte 123,byte 106,byte 106,byte 122,byte 122,byte 105,byte 88,byte 71,
+ byte 108,byte 108,byte 124,byte 107,byte 107,byte 107,byte 90,byte 123,
+ byte 106,byte 106,byte 106,byte 89,byte 122,byte 105,byte 88,byte 71,
+ byte 91,byte 91,byte 120,byte 107,byte 107,byte 90,byte 90,byte 136,
+ byte 106,byte 106,byte 89,byte 89,byte 118,byte 105,byte 88,byte 71,
+ byte 121,byte 121,byte 120,byte 120,byte 120,byte 120,byte 136,byte 119,
+ byte 119,byte 119,byte 102,byte 118,byte 118,byte 118,byte 101,byte 71,
+ byte 121,byte 121,byte 120,byte 120,byte 120,byte 103,byte 103,byte 119,
+ byte 119,byte 102,byte 102,byte 118,byte 118,byte 101,byte 101,byte 67,
+ byte 104,byte 104,byte 120,byte 103,byte 103,byte 103,byte 103,byte 119,
+ byte 102,byte 102,byte 102,byte 118,byte 118,byte 101,byte 84,byte 67,
+ byte 104,byte 104,byte 103,byte 103,byte 103,byte 103,byte 86,byte 102,
+ byte 102,byte 102,byte 85,byte 85,byte 101,byte 101,byte 84,byte 67,
+ byte 87,byte 87,byte 116,byte 116,byte 116,byte 86,byte 86,byte 115,
+ byte 115,byte 115,byte 85,byte 85,byte 114,byte 114,byte 84,byte 67,
+ byte 117,byte 117,byte 116,byte 116,byte 116,byte 116,byte 115,byte 115,
+ byte 115,byte 115,byte 98,byte 114,byte 114,byte 114,byte 97,byte 79,
+ byte 100,byte 100,byte 99,byte 99,byte 99,byte 99,byte 99,byte 115,
+ byte 98,byte 98,byte 98,byte 114,byte 114,byte 97,byte 97,byte 79,
+ byte 83,byte 83,byte 99,byte 99,byte 82,byte 82,byte 82,byte 98,
+ byte 98,byte 81,byte 81,byte 81,byte 97,byte 97,byte 80,byte 79,
+ byte 66,byte 66,byte 66,byte 66,byte 65,byte 65,byte 65,byte 65,
+ byte 64,byte 64,byte 64,byte 64,byte 79,byte 79,byte 79,byte 79,
+ byte 95,byte 95,byte 111,byte 111,byte 94,byte 94,byte 94,byte 77,
+ byte 110,byte 110,byte 93,byte 93,byte 76,byte 109,byte 92,byte 75,
+ byte 95,byte 95,byte 111,byte 111,byte 94,byte 94,byte 94,byte 77,
+ byte 110,byte 110,byte 93,byte 93,byte 76,byte 109,byte 92,byte 75,
+ byte 108,byte 108,byte 124,byte 111,byte 107,byte 94,byte 94,byte 123,
+ byte 123,byte 106,byte 93,byte 93,byte 122,byte 105,byte 92,byte 75,
+ byte 108,byte 108,byte 108,byte 107,byte 107,byte 107,byte 90,byte 123,
+ byte 106,byte 106,byte 106,byte 89,byte 122,byte 105,byte 88,byte 75,
+ byte 91,byte 91,byte 107,byte 107,byte 107,byte 90,byte 90,byte 123,
+ byte 106,byte 106,byte 89,byte 89,byte 105,byte 105,byte 88,byte 71,
+ byte 91,byte 91,byte 91,byte 107,byte 90,byte 90,byte 90,byte 73,
+ byte 106,byte 106,byte 89,byte 89,byte 72,byte 88,byte 88,byte 71,
+ byte 91,byte 91,byte 91,byte 90,byte 90,byte 90,byte 73,byte 73,
+ byte 106,byte 89,byte 89,byte 72,byte 72,byte 88,byte 88,byte 71,
+ byte 74,byte 74,byte 120,byte 120,byte 120,byte 73,byte 73,byte 119,
+ byte 119,byte 102,byte 89,byte 72,byte 72,byte 101,byte 101,byte 71,
+ byte 104,byte 104,byte 120,byte 103,byte 103,byte 103,byte 103,byte 119,
+ byte 102,byte 102,byte 102,byte 118,byte 118,byte 101,byte 84,byte 67,
+ byte 104,byte 104,byte 103,byte 103,byte 103,byte 103,byte 86,byte 102,
+ byte 102,byte 102,byte 85,byte 85,byte 101,byte 101,byte 84,byte 67,
+ byte 87,byte 87,byte 87,byte 103,byte 86,byte 86,byte 86,byte 86,
+ byte 102,byte 85,byte 85,byte 85,byte 85,byte 84,byte 84,byte 67,
+ byte 87,byte 87,byte 87,byte 86,byte 86,byte 86,byte 69,byte 69,
+ byte 115,byte 85,byte 85,byte 85,byte 68,byte 68,byte 67,byte 67,
+ byte 70,byte 70,byte 116,byte 116,byte 99,byte 69,byte 69,byte 69,
+ byte 115,byte 98,byte 85,byte 68,byte 68,byte 97,byte 97,byte 79,
+ byte 100,byte 100,byte 99,byte 99,byte 99,byte 82,byte 82,byte 98,
+ byte 98,byte 98,byte 81,byte 68,byte 97,byte 97,byte 97,byte 79,
+ byte 83,byte 83,byte 83,byte 82,byte 82,byte 82,byte 82,byte 98,
+ byte 81,byte 81,byte 81,byte 64,byte 97,byte 97,byte 80,byte 79,
+ byte 66,byte 66,byte 66,byte 66,byte 65,byte 65,byte 65,byte 65,
+ byte 64,byte 64,byte 64,byte 64,byte 79,byte 79,byte 79,byte 79,
+ byte 78,byte 78,byte 49,byte 49,byte 94,byte 77,byte 77,byte 48,
+ byte 48,byte 93,byte 93,byte 76,byte 76,byte 63,byte 92,byte 75,
+ byte 78,byte 78,byte 49,byte 49,byte 94,byte 77,byte 77,byte 48,
+ byte 48,byte 93,byte 93,byte 76,byte 76,byte 63,byte 92,byte 75,
+ byte 62,byte 62,byte 61,byte 61,byte 61,byte 61,byte 77,byte 60,
+ byte 60,byte 60,byte 93,byte 76,byte 59,byte 59,byte 59,byte 75,
+ byte 62,byte 62,byte 61,byte 61,byte 61,byte 61,byte 90,byte 60,
+ byte 60,byte 60,byte 89,byte 59,byte 59,byte 59,byte 88,byte 75,
+ byte 91,byte 91,byte 61,byte 61,byte 61,byte 90,byte 73,byte 60,
+ byte 60,byte 60,byte 89,byte 72,byte 59,byte 59,byte 88,byte 71,
+ byte 74,byte 74,byte 61,byte 61,byte 90,byte 73,byte 73,byte 73,
+ byte 60,byte 89,byte 89,byte 72,byte 72,byte 72,byte 71,byte 71,
+ byte 74,byte 74,byte 74,byte 90,byte 73,byte 73,byte 73,byte 73,
+ byte 56,byte 89,byte 72,byte 72,byte 72,byte 72,byte 71,byte 71,
+ byte 58,byte 58,byte 57,byte 57,byte 57,byte 73,byte 73,byte 56,
+ byte 56,byte 56,byte 72,byte 72,byte 55,byte 55,byte 55,byte 71,
+ byte 58,byte 58,byte 57,byte 57,byte 57,byte 57,byte 56,byte 56,
+ byte 56,byte 56,byte 56,byte 55,byte 55,byte 55,byte 55,byte 67,
+ byte 87,byte 87,byte 57,byte 57,byte 57,byte 86,byte 86,byte 56,
+ byte 56,byte 56,byte 85,byte 85,byte 55,byte 55,byte 84,byte 67,
+ byte 87,byte 87,byte 87,byte 86,byte 86,byte 86,byte 69,byte 69,
+ byte 56,byte 85,byte 85,byte 85,byte 68,byte 68,byte 67,byte 67,
+ byte 70,byte 70,byte 70,byte 53,byte 69,byte 69,byte 69,byte 69,
+ byte 52,byte 85,byte 85,byte 68,byte 68,byte 68,byte 67,byte 67,
+ byte 70,byte 70,byte 53,byte 53,byte 53,byte 69,byte 69,byte 52,
+ byte 52,byte 52,byte 68,byte 68,byte 68,byte 51,byte 51,byte 79,
+ byte 54,byte 54,byte 53,byte 53,byte 53,byte 69,byte 69,byte 52,
+ byte 52,byte 52,byte 68,byte 68,byte 51,byte 51,byte 80,byte 79,
+ byte 83,byte 83,byte 53,byte 82,byte 82,byte 65,byte 65,byte 52,
+ byte 52,byte 81,byte 64,byte 64,byte 51,byte 80,byte 80,byte 79,
+ byte 66,byte 66,byte 66,byte 66,byte 65,byte 65,byte 65,byte 65,
+ byte 64,byte 64,byte 64,byte 64,byte 79,byte 79,byte 79,byte 79,
+ byte 50,byte 50,byte 49,byte 49,byte 49,byte 77,byte 77,byte 48,
+ byte 48,byte 48,byte 76,byte 76,byte 63,byte 63,byte 46,byte 12,
+ byte 50,byte 50,byte 49,byte 49,byte 49,byte 77,byte 77,byte 48,
+ byte 48,byte 48,byte 76,byte 76,byte 63,byte 63,byte 46,byte 12,
+ byte 62,byte 62,byte 61,byte 61,byte 61,byte 61,byte 77,byte 60,
+ byte 60,byte 60,byte 60,byte 59,byte 59,byte 59,byte 59,byte 12,
+ byte 62,byte 62,byte 61,byte 61,byte 61,byte 61,byte 60,byte 60,
+ byte 60,byte 60,byte 60,byte 59,byte 59,byte 59,byte 42,byte 12,
+ byte 62,byte 62,byte 61,byte 61,byte 61,byte 61,byte 73,byte 60,
+ byte 60,byte 60,byte 43,byte 59,byte 59,byte 59,byte 42,byte 8,
+ byte 74,byte 74,byte 61,byte 61,byte 61,byte 73,byte 73,byte 60,
+ byte 60,byte 60,byte 72,byte 72,byte 72,byte 59,byte 42,byte 8,
+ byte 74,byte 74,byte 74,byte 57,byte 73,byte 73,byte 73,byte 73,
+ byte 56,byte 56,byte 72,byte 72,byte 72,byte 72,byte 42,byte 8,
+ byte 58,byte 58,byte 57,byte 57,byte 57,byte 57,byte 73,byte 56,
+ byte 56,byte 56,byte 72,byte 55,byte 55,byte 55,byte 55,byte 8,
+ byte 58,byte 58,byte 57,byte 57,byte 57,byte 57,byte 56,byte 56,
+ byte 56,byte 56,byte 56,byte 55,byte 55,byte 55,byte 38,byte 4,
+ byte 58,byte 58,byte 57,byte 57,byte 57,byte 57,byte 56,byte 56,
+ byte 56,byte 56,byte 39,byte 55,byte 55,byte 55,byte 38,byte 4,
+ byte 70,byte 70,byte 57,byte 57,byte 40,byte 69,byte 69,byte 69,
+ byte 56,byte 39,byte 85,byte 68,byte 68,byte 38,byte 38,byte 4,
+ byte 70,byte 70,byte 53,byte 53,byte 53,byte 69,byte 69,byte 52,
+ byte 52,byte 52,byte 68,byte 68,byte 68,byte 51,byte 51,byte 4,
+ byte 54,byte 54,byte 53,byte 53,byte 53,byte 69,byte 69,byte 52,
+ byte 52,byte 52,byte 68,byte 68,byte 51,byte 51,byte 51,byte 0,
+ byte 54,byte 54,byte 53,byte 53,byte 53,byte 53,byte 69,byte 52,
+ byte 52,byte 52,byte 35,byte 51,byte 51,byte 51,byte 34,byte 0,
+ byte 37,byte 37,byte 53,byte 36,byte 36,byte 36,byte 36,byte 52,
+ byte 35,byte 35,byte 35,byte 51,byte 51,byte 34,byte 34,byte 0,
+ byte 3,byte 3,byte 3,byte 3,byte 2,byte 2,byte 2,byte 2,
+ byte 1,byte 1,byte 1,byte 1,byte 0,byte 0,byte 0,byte 0,
+ byte 33,byte 33,byte 49,byte 49,byte 32,byte 32,byte 77,byte 48,
+ byte 48,byte 47,byte 47,byte 63,byte 63,byte 46,byte 46,byte 12,
+ byte 33,byte 33,byte 49,byte 49,byte 32,byte 32,byte 77,byte 48,
+ byte 48,byte 47,byte 47,byte 63,byte 63,byte 46,byte 46,byte 12,
+ byte 62,byte 62,byte 61,byte 61,byte 61,byte 61,byte 60,byte 60,
+ byte 60,byte 43,byte 43,byte 59,byte 59,byte 59,byte 42,byte 12,
+ byte 62,byte 62,byte 61,byte 61,byte 61,byte 44,byte 44,byte 60,
+ byte 60,byte 43,byte 43,byte 59,byte 59,byte 42,byte 42,byte 12,
+ byte 45,byte 45,byte 61,byte 61,byte 44,byte 44,byte 44,byte 60,
+ byte 60,byte 43,byte 43,byte 59,byte 59,byte 42,byte 42,byte 8,
+ byte 45,byte 45,byte 61,byte 44,byte 44,byte 44,byte 73,byte 60,
+ byte 43,byte 43,byte 26,byte 72,byte 59,byte 42,byte 42,byte 8,
+ byte 74,byte 74,byte 57,byte 44,byte 44,byte 73,byte 73,byte 56,
+ byte 43,byte 43,byte 26,byte 72,byte 72,byte 42,byte 42,byte 8,
+ byte 58,byte 58,byte 57,byte 57,byte 57,byte 57,byte 56,byte 56,
+ byte 56,byte 56,byte 39,byte 55,byte 55,byte 55,byte 38,byte 8,
+ byte 58,byte 58,byte 57,byte 57,byte 57,byte 40,byte 40,byte 56,
+ byte 56,byte 39,byte 39,byte 55,byte 55,byte 38,byte 38,byte 4,
+ byte 41,byte 41,byte 40,byte 40,byte 40,byte 40,byte 40,byte 56,
+ byte 39,byte 39,byte 39,byte 55,byte 55,byte 38,byte 38,byte 4,
+ byte 41,byte 41,byte 40,byte 40,byte 40,byte 23,byte 23,byte 39,
+ byte 39,byte 39,byte 22,byte 68,byte 38,byte 38,byte 38,byte 4,
+ byte 54,byte 54,byte 53,byte 53,byte 53,byte 69,byte 69,byte 52,
+ byte 52,byte 52,byte 68,byte 68,byte 51,byte 51,byte 21,byte 4,
+ byte 54,byte 54,byte 53,byte 53,byte 53,byte 53,byte 69,byte 52,
+ byte 52,byte 52,byte 35,byte 51,byte 51,byte 51,byte 34,byte 0,
+ byte 37,byte 37,byte 53,byte 36,byte 36,byte 36,byte 36,byte 52,
+ byte 35,byte 35,byte 35,byte 51,byte 51,byte 34,byte 34,byte 0,
+ byte 37,byte 37,byte 36,byte 36,byte 36,byte 36,byte 36,byte 35,
+ byte 35,byte 35,byte 35,byte 18,byte 34,byte 34,byte 34,byte 0,
+ byte 3,byte 3,byte 3,byte 3,byte 2,byte 2,byte 2,byte 2,
+ byte 1,byte 1,byte 1,byte 1,byte 0,byte 0,byte 0,byte 0,
+ byte 16,byte 16,byte 32,byte 32,byte 31,byte 31,byte 31,byte 47,
+ byte 47,byte 30,byte 30,byte 30,byte 46,byte 46,byte 29,byte 12,
+ byte 16,byte 16,byte 32,byte 32,byte 31,byte 31,byte 31,byte 47,
+ byte 47,byte 30,byte 30,byte 30,byte 46,byte 46,byte 29,byte 12,
+ byte 45,byte 45,byte 44,byte 44,byte 44,byte 44,byte 31,byte 60,
+ byte 43,byte 43,byte 30,byte 59,byte 59,byte 42,byte 42,byte 12,
+ byte 45,byte 45,byte 44,byte 44,byte 44,byte 44,byte 27,byte 43,
+ byte 43,byte 43,byte 26,byte 26,byte 42,byte 42,byte 42,byte 12,
+ byte 28,byte 28,byte 44,byte 44,byte 27,byte 27,byte 27,byte 43,
+ byte 43,byte 26,byte 26,byte 26,byte 42,byte 42,byte 25,byte 8,
+ byte 28,byte 28,byte 44,byte 44,byte 27,byte 27,byte 27,byte 43,
+ byte 43,byte 26,byte 26,byte 9,byte 42,byte 42,byte 25,byte 8,
+ byte 28,byte 28,byte 28,byte 27,byte 27,byte 27,byte 10,byte 43,
+ byte 26,byte 26,byte 26,byte 9,byte 42,byte 42,byte 25,byte 8,
+ byte 41,byte 41,byte 57,byte 40,byte 40,byte 40,byte 40,byte 56,
+ byte 39,byte 39,byte 39,byte 55,byte 55,byte 38,byte 38,byte 8,
+ byte 41,byte 41,byte 40,byte 40,byte 40,byte 40,byte 23,byte 39,
+ byte 39,byte 39,byte 22,byte 55,byte 38,byte 38,byte 38,byte 4,
+ byte 24,byte 24,byte 40,byte 40,byte 23,byte 23,byte 23,byte 39,
+ byte 39,byte 22,byte 22,byte 22,byte 38,byte 38,byte 21,byte 4,
+ byte 24,byte 24,byte 24,byte 23,byte 23,byte 23,byte 23,byte 39,
+ byte 22,byte 22,byte 22,byte 5,byte 38,byte 38,byte 21,byte 4,
+ byte 24,byte 24,byte 53,byte 23,byte 23,byte 6,byte 6,byte 52,
+ byte 52,byte 22,byte 5,byte 5,byte 51,byte 21,byte 21,byte 4,
+ byte 37,byte 37,byte 53,byte 36,byte 36,byte 36,byte 36,byte 52,
+ byte 35,byte 35,byte 35,byte 51,byte 51,byte 34,byte 34,byte 0,
+ byte 37,byte 37,byte 36,byte 36,byte 36,byte 36,byte 36,byte 35,
+ byte 35,byte 35,byte 35,byte 18,byte 34,byte 34,byte 34,byte 0,
+ byte 20,byte 20,byte 36,byte 36,byte 19,byte 19,byte 19,byte 35,
+ byte 35,byte 18,byte 18,byte 18,byte 34,byte 34,byte 17,byte 0,
+ byte 3,byte 3,byte 3,byte 3,byte 2,byte 2,byte 2,byte 2,
+ byte 1,byte 1,byte 1,byte 1,byte 0,byte 0,byte 0,byte 0,
+ byte 15,byte 15,byte 15,byte 15,byte 14,byte 14,byte 14,byte 14,
+ byte 13,byte 13,byte 13,byte 13,byte 12,byte 12,byte 12,byte 12,
+ byte 15,byte 15,byte 15,byte 15,byte 14,byte 14,byte 14,byte 14,
+ byte 13,byte 13,byte 13,byte 13,byte 12,byte 12,byte 12,byte 12,
+ byte 15,byte 15,byte 15,byte 15,byte 14,byte 14,byte 14,byte 14,
+ byte 13,byte 13,byte 13,byte 13,byte 12,byte 12,byte 12,byte 12,
+ byte 15,byte 15,byte 15,byte 15,byte 14,byte 14,byte 14,byte 14,
+ byte 13,byte 13,byte 13,byte 13,byte 12,byte 12,byte 12,byte 12,
+ byte 11,byte 11,byte 11,byte 11,byte 10,byte 10,byte 10,byte 10,
+ byte 9,byte 9,byte 9,byte 9,byte 8,byte 8,byte 8,byte 8,
+ byte 11,byte 11,byte 11,byte 11,byte 10,byte 10,byte 10,byte 10,
+ byte 9,byte 9,byte 9,byte 9,byte 8,byte 8,byte 8,byte 8,
+ byte 11,byte 11,byte 11,byte 11,byte 10,byte 10,byte 10,byte 10,
+ byte 9,byte 9,byte 9,byte 9,byte 8,byte 8,byte 8,byte 8,
+ byte 11,byte 11,byte 11,byte 11,byte 10,byte 10,byte 10,byte 10,
+ byte 9,byte 9,byte 9,byte 9,byte 8,byte 8,byte 8,byte 8,
+ byte 7,byte 7,byte 7,byte 7,byte 6,byte 6,byte 6,byte 6,
+ byte 5,byte 5,byte 5,byte 5,byte 4,byte 4,byte 4,byte 4,
+ byte 7,byte 7,byte 7,byte 7,byte 6,byte 6,byte 6,byte 6,
+ byte 5,byte 5,byte 5,byte 5,byte 4,byte 4,byte 4,byte 4,
+ byte 7,byte 7,byte 7,byte 7,byte 6,byte 6,byte 6,byte 6,
+ byte 5,byte 5,byte 5,byte 5,byte 4,byte 4,byte 4,byte 4,
+ byte 7,byte 7,byte 7,byte 7,byte 6,byte 6,byte 6,byte 6,
+ byte 5,byte 5,byte 5,byte 5,byte 4,byte 4,byte 4,byte 4,
+ byte 3,byte 3,byte 3,byte 3,byte 2,byte 2,byte 2,byte 2,
+ byte 1,byte 1,byte 1,byte 1,byte 0,byte 0,byte 0,byte 0,
+ byte 3,byte 3,byte 3,byte 3,byte 2,byte 2,byte 2,byte 2,
+ byte 1,byte 1,byte 1,byte 1,byte 0,byte 0,byte 0,byte 0,
+ byte 3,byte 3,byte 3,byte 3,byte 2,byte 2,byte 2,byte 2,
+ byte 1,byte 1,byte 1,byte 1,byte 0,byte 0,byte 0,byte 0,
+ byte 3,byte 3,byte 3,byte 3,byte 2,byte 2,byte 2,byte 2,
+ byte 1,byte 1,byte 1,byte 1,byte 0,byte 0,byte 0,byte 0
+};
+
+rgbvmap_r := array[256] of {
+ 16rff, 16rff, 16rff, 16rff, 16rff, 16rff, 16rff, 16rff,
+ 16rff, 16rff, 16rff, 16rff, 16rff, 16rff, 16rff, 16rff,
+ 16ree, 16ree, 16ree, 16ree, 16ree, 16ree, 16ree, 16ree,
+ 16ree, 16ree, 16ree, 16ree, 16ree, 16ree, 16ree, 16ree,
+ 16rdd, 16rdd, 16rdd, 16rdd, 16rdd, 16rdd, 16rdd, 16rdd,
+ 16rdd, 16rdd, 16rdd, 16rdd, 16rdd, 16rdd, 16rdd, 16rdd,
+ 16rcc, 16rcc, 16rcc, 16rcc, 16rcc, 16rcc, 16rcc, 16rcc,
+ 16rcc, 16rcc, 16rcc, 16rcc, 16rcc, 16rcc, 16rcc, 16rcc,
+ 16raa, 16raa, 16raa, 16raa, 16rbb, 16rbb, 16rbb, 16raa,
+ 16rbb, 16rbb, 16rbb, 16raa, 16rbb, 16rbb, 16rbb, 16raa,
+ 16r9e, 16r9e, 16r9e, 16r9e, 16r9e, 16raa, 16raa, 16raa,
+ 16r9e, 16raa, 16raa, 16raa, 16r9e, 16raa, 16raa, 16raa,
+ 16r99, 16r93, 16r93, 16r93, 16r93, 16r93, 16r99, 16r99,
+ 16r99, 16r93, 16r99, 16r99, 16r99, 16r93, 16r99, 16r99,
+ 16r88, 16r88, 16r88, 16r88, 16r88, 16r88, 16r88, 16r88,
+ 16r88, 16r88, 16r88, 16r88, 16r88, 16r88, 16r88, 16r88,
+ 16r55, 16r55, 16r55, 16r5d, 16r5d, 16r5d, 16r55, 16r5d,
+ 16r77, 16r77, 16r55, 16r5d, 16r77, 16r77, 16r55, 16r55,
+ 16r4f, 16r4f, 16r4f, 16r4f, 16r55, 16r55, 16r55, 16r4f,
+ 16r55, 16r66, 16r66, 16r4f, 16r55, 16r66, 16r66, 16r4f,
+ 16r49, 16r49, 16r49, 16r49, 16r49, 16r4c, 16r4c, 16r4c,
+ 16r49, 16r4c, 16r55, 16r55, 16r49, 16r4c, 16r55, 16r55,
+ 16r44, 16r44, 16r44, 16r44, 16r44, 16r44, 16r44, 16r44,
+ 16r44, 16r44, 16r44, 16r44, 16r44, 16r44, 16r44, 16r44,
+ 16r00, 16r00, 16r00, 16r00, 16r00, 16r00, 16r00, 16r00,
+ 16r00, 16r00, 16r00, 16r00, 16r33, 16r00, 16r00, 16r00,
+ 16r00, 16r00, 16r00, 16r00, 16r00, 16r00, 16r00, 16r00,
+ 16r00, 16r00, 16r00, 16r00, 16r00, 16r22, 16r00, 16r00,
+ 16r00, 16r00, 16r00, 16r00, 16r00, 16r00, 16r00, 16r00,
+ 16r00, 16r00, 16r00, 16r00, 16r00, 16r00, 16r11, 16r00,
+ 16r00, 16r00, 16r00, 16r00, 16r00, 16r00, 16r00, 16r00,
+ 16r00, 16r00, 16r00, 16r00, 16r00, 16r00, 16r00, 16r00
+};
+
+rgbvmap_g := array[256] of {
+ 16rff, 16rff, 16rff, 16rff, 16raa, 16raa, 16raa, 16raa,
+ 16r55, 16r55, 16r55, 16r55, 16r00, 16r00, 16r00, 16r00,
+ 16r00, 16ree, 16ree, 16ree, 16ree, 16r9e, 16r9e, 16r9e,
+ 16r9e, 16r4f, 16r4f, 16r4f, 16r4f, 16r00, 16r00, 16r00,
+ 16r00, 16r00, 16rdd, 16rdd, 16rdd, 16rdd, 16r93, 16r93,
+ 16r93, 16r93, 16r49, 16r49, 16r49, 16r49, 16r00, 16r00,
+ 16r00, 16r00, 16r00, 16rcc, 16rcc, 16rcc, 16rcc, 16r88,
+ 16r88, 16r88, 16r88, 16r44, 16r44, 16r44, 16r44, 16r00,
+ 16rff, 16rff, 16rff, 16raa, 16rbb, 16rbb, 16rbb, 16r55,
+ 16r5d, 16r5d, 16r5d, 16r00, 16r00, 16r00, 16r00, 16rff,
+ 16ree, 16ree, 16ree, 16ree, 16r9e, 16raa, 16raa, 16raa,
+ 16r4f, 16r55, 16r55, 16r55, 16r00, 16r00, 16r00, 16r00,
+ 16r00, 16rdd, 16rdd, 16rdd, 16rdd, 16r93, 16r99, 16r99,
+ 16r99, 16r49, 16r4c, 16r4c, 16r4c, 16r00, 16r00, 16r00,
+ 16r00, 16r00, 16rcc, 16rcc, 16rcc, 16rcc, 16r88, 16r88,
+ 16r88, 16r88, 16r44, 16r44, 16r44, 16r44, 16r00, 16r00,
+ 16rff, 16rff, 16raa, 16rbb, 16rbb, 16rbb, 16r55, 16r5d,
+ 16r77, 16r77, 16r00, 16r00, 16r00, 16r00, 16rff, 16rff,
+ 16ree, 16ree, 16ree, 16r9e, 16raa, 16raa, 16raa, 16r4f,
+ 16r55, 16r66, 16r66, 16r00, 16r00, 16r00, 16r00, 16ree,
+ 16rdd, 16rdd, 16rdd, 16rdd, 16r93, 16r99, 16r99, 16r99,
+ 16r49, 16r4c, 16r55, 16r55, 16r00, 16r00, 16r00, 16r00,
+ 16r00, 16rcc, 16rcc, 16rcc, 16rcc, 16r88, 16r88, 16r88,
+ 16r88, 16r44, 16r44, 16r44, 16r44, 16r00, 16r00, 16r00,
+ 16rff, 16raa, 16rbb, 16rbb, 16rbb, 16r55, 16r5d, 16r77,
+ 16r77, 16r00, 16r00, 16r00, 16r33, 16rff, 16rff, 16rff,
+ 16ree, 16ree, 16r9e, 16raa, 16raa, 16raa, 16r4f, 16r55,
+ 16r66, 16r66, 16r00, 16r00, 16r00, 16r22, 16ree, 16ree,
+ 16rdd, 16rdd, 16rdd, 16r93, 16r99, 16r99, 16r99, 16r49,
+ 16r4c, 16r55, 16r55, 16r00, 16r00, 16r00, 16r11, 16rdd,
+ 16rcc, 16rcc, 16rcc, 16rcc, 16r88, 16r88, 16r88, 16r88,
+ 16r44, 16r44, 16r44, 16r44, 16r00, 16r00, 16r00, 16r00
+};
+
+rgbvmap_b := array[256] of {
+ 16rff, 16raa, 16r55, 16r00, 16rff, 16raa, 16r55, 16r00,
+ 16rff, 16raa, 16r55, 16r00, 16rff, 16raa, 16r55, 16r00,
+ 16r00, 16ree, 16r9e, 16r4f, 16r00, 16ree, 16r9e, 16r4f,
+ 16r00, 16ree, 16r9e, 16r4f, 16r00, 16ree, 16r9e, 16r4f,
+ 16r49, 16r00, 16rdd, 16r93, 16r49, 16r00, 16rdd, 16r93,
+ 16r49, 16r00, 16rdd, 16r93, 16r49, 16r00, 16rdd, 16r93,
+ 16r88, 16r44, 16r00, 16rcc, 16r88, 16r44, 16r00, 16rcc,
+ 16r88, 16r44, 16r00, 16rcc, 16r88, 16r44, 16r00, 16rcc,
+ 16raa, 16r55, 16r00, 16rff, 16rbb, 16r5d, 16r00, 16rff,
+ 16rbb, 16r5d, 16r00, 16rff, 16rbb, 16r5d, 16r00, 16rff,
+ 16ree, 16r9e, 16r4f, 16r00, 16ree, 16raa, 16r55, 16r00,
+ 16ree, 16raa, 16r55, 16r00, 16ree, 16raa, 16r55, 16r00,
+ 16r00, 16rdd, 16r93, 16r49, 16r00, 16rdd, 16r99, 16r4c,
+ 16r00, 16rdd, 16r99, 16r4c, 16r00, 16rdd, 16r99, 16r4c,
+ 16r44, 16r00, 16rcc, 16r88, 16r44, 16r00, 16rcc, 16r88,
+ 16r44, 16r00, 16rcc, 16r88, 16r44, 16r00, 16rcc, 16r88,
+ 16r55, 16r00, 16rff, 16rbb, 16r5d, 16r00, 16rff, 16rbb,
+ 16r77, 16r00, 16rff, 16rbb, 16r77, 16r00, 16rff, 16raa,
+ 16r9e, 16r4f, 16r00, 16ree, 16raa, 16r55, 16r00, 16ree,
+ 16raa, 16r66, 16r00, 16ree, 16raa, 16r66, 16r00, 16ree,
+ 16rdd, 16r93, 16r49, 16r00, 16rdd, 16r99, 16r4c, 16r00,
+ 16rdd, 16r99, 16r55, 16r00, 16rdd, 16r99, 16r55, 16r00,
+ 16r00, 16rcc, 16r88, 16r44, 16r00, 16rcc, 16r88, 16r44,
+ 16r00, 16rcc, 16r88, 16r44, 16r00, 16rcc, 16r88, 16r44,
+ 16r00, 16rff, 16rbb, 16r5d, 16r00, 16rff, 16rbb, 16r77,
+ 16r00, 16rff, 16rbb, 16r77, 16r33, 16rff, 16raa, 16r55,
+ 16r4f, 16r00, 16ree, 16raa, 16r55, 16r00, 16ree, 16raa,
+ 16r66, 16r00, 16ree, 16raa, 16r66, 16r22, 16ree, 16r9e,
+ 16r93, 16r49, 16r00, 16rdd, 16r99, 16r4c, 16r00, 16rdd,
+ 16r99, 16r55, 16r00, 16rdd, 16r99, 16r55, 16r11, 16rdd,
+ 16rcc, 16r88, 16r44, 16r00, 16rcc, 16r88, 16r44, 16r00,
+ 16rcc, 16r88, 16r44, 16r00, 16rcc, 16r88, 16r44, 16r00
+};
diff --git a/appl/charon/script.m b/appl/charon/script.m
new file mode 100644
index 00000000..b491ace6
--- /dev/null
+++ b/appl/charon/script.m
@@ -0,0 +1,14 @@
+Script: module
+{
+ JSCRIPTPATH: con "/dis/charon/jscript.dis";
+
+ defaultStatus: string;
+ jevchan: chan of ref Events->ScriptEvent;
+ versions : array of string;
+
+ init: fn(cu: CharonUtils): string;
+ frametreechanged: fn(top: ref Layout->Frame);
+ havenewdoc: fn(f: ref Layout->Frame);
+ evalscript: fn(f: ref Layout->Frame, s: string) : (string, string, string);
+ framedone: fn(f : ref Layout->Frame, hasscripts : int);
+};
diff --git a/appl/charon/transport.m b/appl/charon/transport.m
new file mode 100644
index 00000000..5cc8bbe0
--- /dev/null
+++ b/appl/charon/transport.m
@@ -0,0 +1,13 @@
+Transport: module
+{
+ HTTPPATH: con "/dis/charon/http.dis";
+ FTPPATH: con "/dis/charon/ftp.dis";
+ FILEPATH: con "/dis/charon/file.dis";
+
+ init: fn(cu: CharonUtils);
+ connect: fn(nc: ref CharonUtils->Netconn, bs: ref CharonUtils->ByteSource);
+ writereq: fn(nc: ref CharonUtils->Netconn, bs: ref CharonUtils->ByteSource);
+ gethdr: fn(nc: ref CharonUtils->Netconn, bs: ref CharonUtils->ByteSource);
+ getdata: fn(nc: ref CharonUtils->Netconn, bs: ref CharonUtils->ByteSource): int;
+ defaultport: fn(scheme: string) : int;
+};
diff --git a/appl/charon/url.b b/appl/charon/url.b
new file mode 100644
index 00000000..1334ee97
--- /dev/null
+++ b/appl/charon/url.b
@@ -0,0 +1,225 @@
+implement Url;
+
+include "sys.m";
+include "string.m";
+include "url.m";
+
+dbg: con 0;
+
+sys: Sys;
+S: String;
+schemechars : array of byte;
+
+init(): string
+{
+ sys = load Sys Sys->PATH;
+ S = load String String->PATH;
+ if (S == nil)
+ return sys->sprint("cannot load %s: %r", String->PATH);
+
+ schemechars = array [128] of { * => byte 0 };
+ alphabet := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-.";
+ for (i := 0; i < len alphabet; i++)
+ schemechars[alphabet[i]] = byte 1;
+ return nil;
+}
+
+# To allow relative urls, only fill in specified pieces (don't apply defaults)
+# general syntax: <scheme>:<scheme-specific>
+# for IP schemes, <scheme-specific> is
+# //<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
+#
+parse(url: string): ref Parsedurl
+{
+ if (dbg)
+ sys->print("URL parse: [%s]\n", url);
+ scheme, user, passwd, host, port, path, params, query, frag : string;
+ gotscheme := 0;
+ for (i := 0; i < len url; i++) {
+ c := url[i];
+ if (c == ':') {
+ gotscheme = 1;
+ break;
+ }
+ if (c < 0 || c > len schemechars || schemechars[c] == byte 0)
+ break;
+ }
+ if (gotscheme) {
+ if (i > 0)
+ scheme = S->tolower(url[0:i]);
+ if (i+1 < len url)
+ url = url[i+1:];
+ else
+ url = nil;
+ }
+
+ if (scheme != nil && !relscheme(scheme))
+ path = url;
+ else {
+ if(!S->prefix("//", url))
+ path = url;
+ else {
+ netloc: string;
+ (netloc, path) = S->splitl(url[2:], "/");
+ if(scheme == "file")
+ host = netloc;
+ else {
+ (up,hp) := split(netloc, "@");
+ if(hp == "")
+ hp = up;
+ else
+ (user, passwd) = split(up, ":");
+ (host, port) = split(hp, ":");
+ }
+ }
+ if(scheme == "file") {
+ if(host == "")
+ host = "localhost";
+ } else {
+ if (path == nil)
+ path = "/";
+ else {
+ (path, frag) = split(path, "#");
+ (path, query) = split(path, "?");
+ (path, params) = split(path, ";");
+ }
+ }
+ }
+ return ref Parsedurl(scheme, user, passwd, host, port, path, params, query, frag);
+}
+
+relscheme(s: string): int
+{
+ # schemes we know to be suitable as "Relative Uniform Resource Locators"
+ # as defined in RFC1808 (+ others)
+ return (s=="http" || s=="https" || s=="file" || s=="ftp" || s=="nntp");
+}
+
+Parsedurl.tostring(u: self ref Parsedurl): string
+{
+ return tostring(u);
+}
+
+tostring(u: ref Parsedurl) : string
+{
+ if (u == nil)
+ return "";
+
+ ans := "";
+ if (u.scheme != nil)
+ ans = u.scheme + ":";
+ if(u.host != "") {
+ ans = ans + "//";
+ if(u.user != "") {
+ ans = ans + u.user;
+ if(u.passwd != "")
+ ans = ans + ":" + u.passwd;
+ ans = ans + "@";
+ }
+ ans = ans + u.host;
+ if(u.port != "")
+ ans = ans + ":" + u.port;
+ }
+ ans = ans + u.path;
+ if(u.params != "")
+ ans = ans + ";" + u.params;
+ if(u.query != "")
+ ans = ans + "?" + u.query;
+ if(u.frag != "")
+ ans = ans + "#" + u.frag;
+ return ans;
+}
+
+mkabs(u, b: ref Parsedurl): ref Parsedurl
+{
+ if (dbg)
+ sys->print("URL mkabs [%s] [%s]\n", tostring(u), tostring(b));
+ if (tostring(b) == "")
+ return u;
+ if (tostring(u) == "")
+ return b;
+
+ if (u.scheme != nil && !relscheme(u.scheme))
+ return u;
+
+ if (u.scheme == nil) {
+ if (b.scheme == nil)
+ # try http
+ u.scheme = "http";
+ else {
+ if (!relscheme(b.scheme))
+ return nil;
+ u.scheme = b.scheme;
+ }
+ }
+
+ r := ref *u;
+ if (r.host == nil) {
+ r.user = b.user;
+ r.passwd = b.passwd;
+ r.host = b.host;
+ r.port = b.port;
+ if (r.path == nil || r.path[0] != '/') {
+ if (r.path == nil) {
+ r.path = b.path;
+ if (r.params == nil) {
+ r.params = b.params;
+ if (r.query == nil)
+ r.query = b.query;
+ }
+ } else {
+ (p1,nil) := S->splitr(b.path, "/");
+ r.path = canonize(p1 + r.path);
+ }
+ }
+ }
+ r.path = canonize(r.path);
+ if (dbg)
+ sys->print("URL mkabs returns [%s]\n", tostring(r));
+ return r;
+}
+
+# Like splitl, but assume one char match, and omit that from second part.
+# If c doesn't appear in s, the return is (s, "").
+split(s, c: string) : (string, string)
+{
+ (a,b) := S->splitl(s, c);
+ if(b != "")
+ b = b[1:];
+ return (a,b);
+}
+
+# remove ./ and ../ from s
+canonize(s: string): string
+{
+ ans := "";
+ (nil, file) := S->splitr(s, "/");
+ if (file == nil || file == "." | file == "..")
+ ans = "/";
+
+ (nil,path) := sys->tokenize(s, "/");
+ revpath : list of string = nil;
+ for(p := path; p != nil; p = tl p) {
+ seg := hd p;
+ if(seg == "..") {
+ if (revpath != nil)
+ revpath = tl revpath;
+ } else if(seg != ".")
+ revpath = seg :: revpath;
+ }
+ while(revpath != nil && hd revpath == "..")
+ revpath = tl revpath;
+ if(revpath != nil) {
+ ans ="/" + (hd revpath) + ans;
+ revpath = tl revpath;
+ while(revpath != nil) {
+ ans = "/" + (hd revpath) + ans;
+ revpath = tl revpath;
+ }
+ }
+ return ans;
+}
+
+
+
+
diff --git a/appl/charon/url.m b/appl/charon/url.m
new file mode 100644
index 00000000..18879da8
--- /dev/null
+++ b/appl/charon/url.m
@@ -0,0 +1,30 @@
+Url: module
+{
+ PATH : con "/dis/charon/url.dis";
+
+ # "Common Internet Scheme" url syntax (rfc 1808)
+ #
+ # <scheme>://<user>:<passwd>@<host>:<port>/<path>;<params>?<query>#<fragment>
+ #
+ # relative urls might omit some prefix of the above
+ # the path of absolute urls include the leading '/'
+ Parsedurl: adt
+ {
+ scheme: string;
+ user: string;
+ passwd: string;
+ host: string;
+ port: string;
+ path: string;
+ params: string;
+ query: string;
+ frag: string;
+
+ tostring: fn(u: self ref Parsedurl): string;
+ };
+
+ init: fn(): string; # call before anything else
+ parse: fn(url: string): ref Parsedurl;
+ mkabs: fn(u, base: ref Parsedurl): ref Parsedurl;
+};
+
diff --git a/appl/charon/xxx.inc b/appl/charon/xxx.inc
new file mode 100644
index 00000000..0b3d20ad
--- /dev/null
+++ b/appl/charon/xxx.inc
@@ -0,0 +1,75 @@
+Cr2r := array [256] of {
+ -179, -178, -177, -175, -174, -172, -171, -170, -168, -167, -165, -164, -163, -161, -160, -158,
+ -157, -156, -154, -153, -151, -150, -149, -147, -146, -144, -143, -142, -140, -139, -137, -136,
+ -135, -133, -132, -130, -129, -128, -126, -125, -123, -122, -121, -119, -118, -116, -115, -114,
+ -112, -111, -109, -108, -107, -105, -104, -102, -101, -100, -98, -97, -95, -94, -93, -91,
+ -90, -88, -87, -86, -84, -83, -81, -80, -79, -77, -76, -74, -73, -72, -70, -69,
+ -67, -66, -64, -63, -62, -60, -59, -57, -56, -55, -53, -52, -50, -49, -48, -46,
+ -45, -43, -42, -41, -39, -38, -36, -35, -34, -32, -31, -29, -28, -27, -25, -24,
+ -22, -21, -20, -18, -17, -15, -14, -13, -11, -10, -8, -7, -6, -4, -3, -1,
+ 0, 1, 3, 4, 6, 7, 8, 10, 11, 13, 14, 15, 17, 18, 20, 21,
+ 22, 24, 25, 27, 28, 29, 31, 32, 34, 35, 36, 38, 39, 41, 42, 43,
+ 45, 46, 48, 49, 50, 52, 53, 55, 56, 57, 59, 60, 62, 63, 64, 66,
+ 67, 69, 70, 72, 73, 74, 76, 77, 79, 80, 81, 83, 84, 86, 87, 88,
+ 90, 91, 93, 94, 95, 97, 98, 100, 101, 102, 104, 105, 107, 108, 109, 111,
+ 112, 114, 115, 116, 118, 119, 121, 122, 123, 125, 126, 128, 129, 130, 132, 133,
+ 135, 136, 137, 139, 140, 142, 143, 144, 146, 147, 149, 150, 151, 153, 154, 156,
+ 157, 158, 160, 161, 163, 164, 165, 167, 168, 170, 171, 172, 174, 175, 177, 178,
+};
+
+Cr2g := array [256] of {
+ -91, -91, -90, -89, -89, -88, -87, -86, -86, -85, -84, -84, -83, -82, -81, -81,
+ -80, -79, -79, -78, -77, -76, -76, -75, -74, -74, -73, -72, -71, -71, -70, -69,
+ -69, -68, -67, -66, -66, -65, -64, -64, -63, -62, -61, -61, -60, -59, -59, -58,
+ -57, -56, -56, -55, -54, -54, -53, -52, -51, -51, -50, -49, -49, -48, -47, -46,
+ -46, -45, -44, -44, -43, -42, -41, -41, -40, -39, -39, -38, -37, -36, -36, -35,
+ -34, -34, -33, -32, -31, -31, -30, -29, -29, -28, -27, -26, -26, -25, -24, -24,
+ -23, -22, -21, -21, -20, -19, -19, -18, -17, -16, -16, -15, -14, -14, -13, -12,
+ -11, -11, -10, -9, -9, -8, -7, -6, -6, -5, -4, -4, -3, -2, -1, -1,
+ 0, 1, 1, 2, 3, 4, 4, 5, 6, 6, 7, 8, 9, 9, 10, 11,
+ 11, 12, 13, 14, 14, 15, 16, 16, 17, 18, 19, 19, 20, 21, 21, 22,
+ 23, 24, 24, 25, 26, 26, 27, 28, 29, 29, 30, 31, 31, 32, 33, 34,
+ 34, 35, 36, 36, 37, 38, 39, 39, 40, 41, 41, 42, 43, 44, 44, 45,
+ 46, 46, 47, 48, 49, 49, 50, 51, 51, 52, 53, 54, 54, 55, 56, 56,
+ 57, 58, 59, 59, 60, 61, 61, 62, 63, 64, 64, 65, 66, 66, 67, 68,
+ 69, 69, 70, 71, 71, 72, 73, 74, 74, 75, 76, 76, 77, 78, 79, 79,
+ 80, 81, 81, 82, 83, 84, 84, 85, 86, 86, 87, 88, 89, 89, 90, 91,
+};
+
+Cb2g := array [256] of {
+ -44, -44, -43, -43, -43, -42, -42, -42, -41, -41, -41, -40, -40, -40, -39, -39,
+ -39, -38, -38, -38, -37, -37, -36, -36, -36, -35, -35, -35, -34, -34, -34, -33,
+ -33, -33, -32, -32, -32, -31, -31, -31, -30, -30, -30, -29, -29, -29, -28, -28,
+ -28, -27, -27, -26, -26, -26, -25, -25, -25, -24, -24, -24, -23, -23, -23, -22,
+ -22, -22, -21, -21, -21, -20, -20, -20, -19, -19, -19, -18, -18, -18, -17, -17,
+ -17, -16, -16, -15, -15, -15, -14, -14, -14, -13, -13, -13, -12, -12, -12, -11,
+ -11, -11, -10, -10, -10, -9, -9, -9, -8, -8, -8, -7, -7, -7, -6, -6,
+ -6, -5, -5, -4, -4, -4, -3, -3, -3, -2, -2, -2, -1, -1, -1, 0,
+ 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5,
+ 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11,
+ 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 15, 16, 16,
+ 17, 17, 17, 18, 18, 18, 19, 19, 19, 20, 20, 20, 21, 21, 21, 22,
+ 22, 22, 23, 23, 23, 24, 24, 24, 25, 25, 25, 26, 26, 26, 27, 27,
+ 28, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32, 32, 32, 33,
+ 33, 33, 34, 34, 34, 35, 35, 35, 36, 36, 36, 37, 37, 38, 38, 38,
+ 39, 39, 39, 40, 40, 40, 41, 41, 41, 42, 42, 42, 43, 43, 43, 44,
+};
+
+Cb2b := array [256] of {
+ -227, -225, -223, -222, -220, -218, -216, -214, -213, -211, -209, -207, -206, -204, -202, -200,
+ -198, -197, -195, -193, -191, -190, -188, -186, -184, -183, -181, -179, -177, -175, -174, -172,
+ -170, -168, -167, -165, -163, -161, -159, -158, -156, -154, -152, -151, -149, -147, -145, -144,
+ -142, -140, -138, -136, -135, -133, -131, -129, -128, -126, -124, -122, -120, -119, -117, -115,
+ -113, -112, -110, -108, -106, -105, -103, -101, -99, -97, -96, -94, -92, -90, -89, -87,
+ -85, -83, -82, -80, -78, -76, -74, -73, -71, -69, -67, -66, -64, -62, -60, -58,
+ -57, -55, -53, -51, -50, -48, -46, -44, -43, -41, -39, -37, -35, -34, -32, -30,
+ -28, -27, -25, -23, -21, -19, -18, -16, -14, -12, -11, -9, -7, -5, -4, -2,
+ 0, 2, 4, 5, 7, 9, 11, 12, 14, 16, 18, 19, 21, 23, 25, 27,
+ 28, 30, 32, 34, 35, 37, 39, 41, 43, 44, 46, 48, 50, 51, 53, 55,
+ 57, 58, 60, 62, 64, 66, 67, 69, 71, 73, 74, 76, 78, 80, 82, 83,
+ 85, 87, 89, 90, 92, 94, 96, 97, 99, 101, 103, 105, 106, 108, 110, 112,
+ 113, 115, 117, 119, 120, 122, 124, 126, 128, 129, 131, 133, 135, 136, 138, 140,
+ 142, 144, 145, 147, 149, 151, 152, 154, 156, 158, 159, 161, 163, 165, 167, 168,
+ 170, 172, 174, 175, 177, 179, 181, 183, 184, 186, 188, 190, 191, 193, 195, 197,
+ 198, 200, 202, 204, 206, 207, 209, 211, 213, 214, 216, 218, 220, 222, 223, 225,
+};
diff --git a/appl/charon/ycbcr.inc b/appl/charon/ycbcr.inc
new file mode 100644
index 00000000..9234debd
--- /dev/null
+++ b/appl/charon/ycbcr.inc
@@ -0,0 +1,621 @@
+# closest color in the rgbvmap, indexed by Cr+16*(Cb+16*Y)
+closestycbcr:= array[16*16*16] of {
+ byte 247,byte 200,byte 200,byte 217,byte 234,byte 234,byte 251,byte 251,
+ byte 251,byte 255,byte 176,byte 176,byte 175,byte 141,byte 113,byte 96,
+ byte 247,byte 200,byte 217,byte 217,byte 234,byte 251,byte 251,byte 251,
+ byte 251,byte 255,byte 176,byte 176,byte 175,byte 141,byte 113,byte 96,
+ byte 200,byte 200,byte 217,byte 234,byte 234,byte 251,byte 251,byte 251,
+ byte 255,byte 255,byte 176,byte 176,byte 175,byte 141,byte 113,byte 96,
+ byte 200,byte 217,byte 217,byte 234,byte 251,byte 251,byte 251,byte 251,
+ byte 255,byte 255,byte 176,byte 176,byte 175,byte 141,byte 113,byte 96,
+ byte 200,byte 217,byte 234,byte 234,byte 251,byte 251,byte 251,byte 255,
+ byte 255,byte 255,byte 176,byte 176,byte 175,byte 141,byte 113,byte 96,
+ byte 217,byte 217,byte 234,byte 251,byte 251,byte 251,byte 251,byte 255,
+ byte 255,byte 255,byte 176,byte 176,byte 175,byte 141,byte 113,byte 96,
+ byte 217,byte 234,byte 234,byte 251,byte 251,byte 251,byte 255,byte 255,
+ byte 255,byte 255,byte 176,byte 176,byte 175,byte 141,byte 113,byte 96,
+ byte 217,byte 234,byte 251,byte 251,byte 251,byte 251,byte 255,byte 255,
+ byte 255,byte 255,byte 176,byte 176,byte 175,byte 141,byte 113,byte 96,
+ byte 234,byte 234,byte 251,byte 251,byte 251,byte 251,byte 255,byte 255,
+ byte 255,byte 255,byte 176,byte 176,byte 175,byte 141,byte 113,byte 96,
+ byte 234,byte 251,byte 251,byte 251,byte 251,byte 255,byte 255,byte 255,
+ byte 255,byte 238,byte 176,byte 176,byte 175,byte 141,byte 113,byte 96,
+ byte 250,byte 250,byte 250,byte 250,byte 250,byte 254,byte 254,byte 254,
+ byte 254,byte 254,byte 191,byte 191,byte 191,byte 112,byte 112,byte 111,
+ byte 233,byte 250,byte 250,byte 250,byte 237,byte 237,byte 237,byte 237,
+ byte 237,byte 237,byte 191,byte 191,byte 174,byte 157,byte 112,byte 111,
+ byte 249,byte 249,byte 249,byte 203,byte 203,byte 203,byte 203,byte 203,
+ byte 203,byte 203,byte 190,byte 190,byte 157,byte 140,byte 140,byte 127,
+ byte 249,byte 249,byte 249,byte 253,byte 253,byte 253,byte 253,byte 253,
+ byte 253,byte 253,byte 190,byte 190,byte 173,byte 140,byte 127,byte 110,
+ byte 232,byte 232,byte 219,byte 219,byte 219,byte 219,byte 219,byte 219,
+ byte 219,byte 219,byte 173,byte 156,byte 156,byte 139,byte 110,byte 93,
+ byte 248,byte 248,byte 252,byte 252,byte 252,byte 252,byte 252,byte 252,
+ byte 252,byte 252,byte 189,byte 189,byte 139,byte 139,byte 126,byte 126,
+ byte 230,byte 247,byte 247,byte 200,byte 217,byte 217,byte 234,byte 251,
+ byte 251,byte 188,byte 188,byte 175,byte 158,byte 113,byte 96,byte 95,
+ byte 230,byte 247,byte 200,byte 200,byte 217,byte 234,byte 234,byte 251,
+ byte 251,byte 188,byte 176,byte 175,byte 158,byte 113,byte 96,byte 95,
+ byte 247,byte 247,byte 200,byte 217,byte 217,byte 234,byte 251,byte 251,
+ byte 251,byte 188,byte 176,byte 175,byte 158,byte 113,byte 96,byte 95,
+ byte 247,byte 200,byte 200,byte 217,byte 234,byte 234,byte 251,byte 251,
+ byte 251,byte 176,byte 176,byte 175,byte 158,byte 113,byte 96,byte 95,
+ byte 247,byte 200,byte 217,byte 217,byte 234,byte 251,byte 251,byte 251,
+ byte 251,byte 176,byte 176,byte 175,byte 158,byte 113,byte 96,byte 95,
+ byte 200,byte 200,byte 217,byte 234,byte 234,byte 251,byte 251,byte 251,
+ byte 255,byte 176,byte 176,byte 175,byte 158,byte 113,byte 96,byte 95,
+ byte 200,byte 217,byte 217,byte 234,byte 251,byte 251,byte 251,byte 251,
+ byte 255,byte 176,byte 176,byte 175,byte 158,byte 113,byte 96,byte 95,
+ byte 200,byte 217,byte 234,byte 234,byte 251,byte 251,byte 251,byte 255,
+ byte 255,byte 176,byte 176,byte 175,byte 158,byte 113,byte 96,byte 95,
+ byte 217,byte 217,byte 234,byte 251,byte 251,byte 251,byte 251,byte 238,
+ byte 238,byte 238,byte 176,byte 175,byte 158,byte 113,byte 96,byte 95,
+ byte 250,byte 250,byte 250,byte 250,byte 250,byte 250,byte 254,byte 254,
+ byte 238,byte 221,byte 191,byte 191,byte 112,byte 112,byte 112,byte 111,
+ byte 233,byte 233,byte 250,byte 250,byte 250,byte 250,byte 254,byte 254,
+ byte 254,byte 191,byte 191,byte 174,byte 174,byte 112,byte 111,byte 94,
+ byte 216,byte 233,byte 233,byte 233,byte 250,byte 220,byte 220,byte 220,
+ byte 220,byte 220,byte 174,byte 174,byte 157,byte 140,byte 111,byte 77,
+ byte 249,byte 249,byte 249,byte 249,byte 249,byte 253,byte 253,byte 253,
+ byte 253,byte 190,byte 190,byte 190,byte 140,byte 127,byte 127,byte 110,
+ byte 232,byte 232,byte 232,byte 232,byte 236,byte 236,byte 236,byte 236,
+ byte 236,byte 190,byte 173,byte 173,byte 156,byte 127,byte 110,byte 93,
+ byte 198,byte 248,byte 248,byte 248,byte 202,byte 202,byte 202,byte 202,
+ byte 202,byte 189,byte 189,byte 139,byte 139,byte 126,byte 126,byte 76,
+ byte 231,byte 248,byte 248,byte 248,byte 235,byte 235,byte 235,byte 235,
+ byte 235,byte 189,byte 189,byte 172,byte 139,byte 126,byte 109,byte 109,
+ byte 213,byte 230,byte 230,byte 247,byte 200,byte 217,byte 217,byte 234,
+ byte 251,byte 188,byte 188,byte 188,byte 141,byte 113,byte 95,byte 78,
+ byte 213,byte 230,byte 247,byte 247,byte 200,byte 217,byte 234,byte 234,
+ byte 251,byte 188,byte 188,byte 158,byte 141,byte 113,byte 95,byte 78,
+ byte 230,byte 230,byte 247,byte 200,byte 200,byte 217,byte 234,byte 251,
+ byte 251,byte 188,byte 188,byte 158,byte 141,byte 113,byte 95,byte 78,
+ byte 230,byte 247,byte 247,byte 200,byte 217,byte 217,byte 234,byte 251,
+ byte 251,byte 188,byte 188,byte 158,byte 141,byte 113,byte 95,byte 78,
+ byte 230,byte 247,byte 200,byte 200,byte 217,byte 234,byte 234,byte 251,
+ byte 251,byte 188,byte 175,byte 158,byte 141,byte 113,byte 95,byte 78,
+ byte 247,byte 247,byte 200,byte 217,byte 217,byte 234,byte 251,byte 251,
+ byte 251,byte 188,byte 175,byte 158,byte 141,byte 113,byte 95,byte 78,
+ byte 247,byte 200,byte 200,byte 217,byte 234,byte 234,byte 251,byte 251,
+ byte 251,byte 176,byte 175,byte 158,byte 141,byte 113,byte 95,byte 78,
+ byte 247,byte 200,byte 217,byte 217,byte 234,byte 251,byte 251,byte 251,
+ byte 238,byte 176,byte 175,byte 158,byte 141,byte 113,byte 95,byte 78,
+ byte 200,byte 200,byte 217,byte 234,byte 234,byte 251,byte 251,byte 221,
+ byte 221,byte 221,byte 175,byte 158,byte 141,byte 113,byte 95,byte 78,
+ byte 246,byte 246,byte 233,byte 250,byte 250,byte 250,byte 250,byte 250,
+ byte 221,byte 191,byte 191,byte 174,byte 112,byte 112,byte 111,byte 49,
+ byte 216,byte 216,byte 233,byte 233,byte 233,byte 250,byte 250,byte 237,
+ byte 237,byte 191,byte 174,byte 157,byte 157,byte 111,byte 94,byte 77,
+ byte 199,byte 216,byte 216,byte 249,byte 249,byte 249,byte 249,byte 203,
+ byte 203,byte 190,byte 190,byte 157,byte 140,byte 127,byte 77,byte 77,
+ byte 232,byte 232,byte 232,byte 249,byte 249,byte 249,byte 236,byte 236,
+ byte 236,byte 190,byte 173,byte 173,byte 127,byte 110,byte 110,byte 48,
+ byte 215,byte 215,byte 215,byte 215,byte 232,byte 219,byte 219,byte 219,
+ byte 219,byte 173,byte 156,byte 139,byte 139,byte 110,byte 93,byte 76,
+ byte 198,byte 248,byte 248,byte 248,byte 248,byte 252,byte 252,byte 252,
+ byte 252,byte 189,byte 189,byte 139,byte 126,byte 126,byte 76,byte 63,
+ byte 214,byte 214,byte 231,byte 231,byte 231,byte 218,byte 218,byte 218,
+ byte 218,byte 172,byte 155,byte 155,byte 109,byte 109,byte 92,byte 63,
+ byte 196,byte 213,byte 230,byte 230,byte 247,byte 200,byte 200,byte 217,
+ byte 188,byte 188,byte 171,byte 125,byte 125,byte 96,byte 78,byte 50,
+ byte 213,byte 213,byte 230,byte 247,byte 247,byte 200,byte 217,byte 217,
+ byte 188,byte 188,byte 171,byte 125,byte 125,byte 96,byte 78,byte 50,
+ byte 213,byte 230,byte 230,byte 247,byte 200,byte 200,byte 217,byte 234,
+ byte 188,byte 188,byte 188,byte 125,byte 125,byte 96,byte 78,byte 50,
+ byte 213,byte 230,byte 247,byte 247,byte 200,byte 217,byte 217,byte 234,
+ byte 188,byte 188,byte 188,byte 125,byte 113,byte 96,byte 78,byte 50,
+ byte 230,byte 230,byte 247,byte 200,byte 200,byte 217,byte 234,byte 234,
+ byte 188,byte 188,byte 188,byte 141,byte 113,byte 96,byte 78,byte 50,
+ byte 230,byte 230,byte 247,byte 200,byte 217,byte 217,byte 234,byte 251,
+ byte 188,byte 188,byte 188,byte 141,byte 113,byte 96,byte 78,byte 50,
+ byte 230,byte 247,byte 247,byte 200,byte 217,byte 234,byte 234,byte 251,
+ byte 188,byte 188,byte 175,byte 141,byte 113,byte 96,byte 78,byte 50,
+ byte 230,byte 247,byte 200,byte 200,byte 217,byte 234,byte 251,byte 251,
+ byte 221,byte 188,byte 175,byte 141,byte 113,byte 96,byte 78,byte 50,
+ byte 246,byte 246,byte 246,byte 246,byte 250,byte 250,byte 250,byte 204,
+ byte 204,byte 204,byte 191,byte 112,byte 112,byte 111,byte 49,byte 49,
+ byte 246,byte 246,byte 246,byte 233,byte 233,byte 233,byte 250,byte 250,
+ byte 204,byte 191,byte 174,byte 112,byte 112,byte 111,byte 94,byte 49,
+ byte 199,byte 199,byte 216,byte 216,byte 216,byte 233,byte 233,byte 249,
+ byte 186,byte 174,byte 157,byte 157,byte 140,byte 94,byte 77,byte 77,
+ byte 245,byte 199,byte 199,byte 249,byte 249,byte 249,byte 249,byte 249,
+ byte 190,byte 190,byte 190,byte 140,byte 127,byte 110,byte 48,byte 48,
+ byte 245,byte 215,byte 215,byte 215,byte 232,byte 232,byte 232,byte 236,
+ byte 173,byte 173,byte 156,byte 156,byte 110,byte 110,byte 93,byte 47,
+ byte 198,byte 198,byte 198,byte 198,byte 248,byte 248,byte 248,byte 202,
+ byte 189,byte 189,byte 139,byte 139,byte 126,byte 93,byte 76,byte 63,
+ byte 244,byte 231,byte 231,byte 231,byte 231,byte 248,byte 248,byte 235,
+ byte 189,byte 172,byte 172,byte 126,byte 109,byte 109,byte 63,byte 63,
+ byte 197,byte 197,byte 214,byte 214,byte 214,byte 214,byte 218,byte 218,
+ byte 155,byte 155,byte 138,byte 138,byte 92,byte 92,byte 75,byte 46,
+ byte 243,byte 196,byte 213,byte 213,byte 230,byte 247,byte 247,byte 184,
+ byte 184,byte 171,byte 154,byte 125,byte 108,byte 62,byte 62,byte 33,
+ byte 196,byte 196,byte 213,byte 230,byte 230,byte 247,byte 200,byte 184,
+ byte 171,byte 171,byte 154,byte 125,byte 108,byte 62,byte 50,byte 33,
+ byte 196,byte 213,byte 213,byte 230,byte 247,byte 247,byte 200,byte 184,
+ byte 171,byte 171,byte 171,byte 125,byte 125,byte 62,byte 50,byte 33,
+ byte 196,byte 213,byte 230,byte 230,byte 247,byte 200,byte 200,byte 217,
+ byte 171,byte 171,byte 125,byte 125,byte 125,byte 95,byte 50,byte 33,
+ byte 213,byte 213,byte 230,byte 247,byte 247,byte 200,byte 217,byte 188,
+ byte 188,byte 171,byte 125,byte 125,byte 125,byte 95,byte 50,byte 33,
+ byte 213,byte 230,byte 230,byte 247,byte 200,byte 200,byte 217,byte 188,
+ byte 188,byte 171,byte 125,byte 125,byte 96,byte 95,byte 50,byte 33,
+ byte 213,byte 230,byte 247,byte 247,byte 200,byte 217,byte 217,byte 188,
+ byte 188,byte 188,byte 125,byte 125,byte 96,byte 95,byte 50,byte 33,
+ byte 230,byte 246,byte 246,byte 246,byte 200,byte 217,byte 234,byte 204,
+ byte 204,byte 187,byte 124,byte 124,byte 96,byte 95,byte 49,byte 33,
+ byte 229,byte 246,byte 246,byte 246,byte 246,byte 233,byte 250,byte 187,
+ byte 187,byte 187,byte 124,byte 112,byte 111,byte 94,byte 49,byte 32,
+ byte 229,byte 229,byte 246,byte 216,byte 216,byte 233,byte 233,byte 187,
+ byte 187,byte 187,byte 157,byte 112,byte 111,byte 94,byte 77,byte 32,
+ byte 245,byte 199,byte 199,byte 199,byte 216,byte 216,byte 249,byte 186,
+ byte 186,byte 186,byte 140,byte 140,byte 127,byte 77,byte 48,byte 48,
+ byte 245,byte 245,byte 245,byte 215,byte 232,byte 232,byte 232,byte 186,
+ byte 186,byte 173,byte 127,byte 127,byte 110,byte 93,byte 48,byte 47,
+ byte 244,byte 244,byte 198,byte 198,byte 215,byte 215,byte 215,byte 185,
+ byte 185,byte 156,byte 139,byte 126,byte 93,byte 93,byte 76,byte 30,
+ byte 244,byte 244,byte 198,byte 198,byte 248,byte 248,byte 248,byte 185,
+ byte 185,byte 189,byte 139,byte 126,byte 109,byte 76,byte 63,byte 46,
+ byte 227,byte 214,byte 214,byte 214,byte 214,byte 214,byte 231,byte 168,
+ byte 155,byte 155,byte 155,byte 109,byte 92,byte 92,byte 46,byte 46,
+ byte 197,byte 197,byte 197,byte 197,byte 197,byte 197,byte 197,byte 201,
+ byte 138,byte 138,byte 138,byte 92,byte 75,byte 75,byte 75,byte 29,
+ byte 226,byte 243,byte 196,byte 196,byte 213,byte 230,byte 184,byte 184,
+ byte 184,byte 154,byte 137,byte 108,byte 91,byte 62,byte 62,byte 45,
+ byte 243,byte 243,byte 196,byte 213,byte 213,byte 230,byte 184,byte 184,
+ byte 184,byte 154,byte 154,byte 108,byte 91,byte 62,byte 62,byte 45,
+ byte 243,byte 196,byte 196,byte 213,byte 230,byte 230,byte 184,byte 184,
+ byte 154,byte 154,byte 125,byte 108,byte 91,byte 62,byte 62,byte 16,
+ byte 243,byte 196,byte 213,byte 213,byte 230,byte 247,byte 184,byte 184,
+ byte 154,byte 154,byte 125,byte 108,byte 108,byte 62,byte 62,byte 16,
+ byte 196,byte 196,byte 213,byte 230,byte 230,byte 247,byte 184,byte 184,
+ byte 171,byte 154,byte 125,byte 108,byte 108,byte 62,byte 33,byte 16,
+ byte 196,byte 213,byte 213,byte 230,byte 247,byte 247,byte 200,byte 184,
+ byte 171,byte 154,byte 125,byte 125,byte 108,byte 62,byte 33,byte 16,
+ byte 196,byte 213,byte 230,byte 230,byte 247,byte 200,byte 200,byte 171,
+ byte 171,byte 171,byte 125,byte 125,byte 108,byte 78,byte 33,byte 16,
+ byte 242,byte 229,byte 246,byte 246,byte 246,byte 246,byte 183,byte 187,
+ byte 187,byte 124,byte 124,byte 124,byte 107,byte 49,byte 49,byte 32,
+ byte 212,byte 229,byte 229,byte 246,byte 246,byte 246,byte 233,byte 187,
+ byte 170,byte 170,byte 124,byte 124,byte 94,byte 77,byte 32,byte 31,
+ byte 212,byte 212,byte 199,byte 199,byte 199,byte 216,byte 216,byte 186,
+ byte 170,byte 170,byte 123,byte 123,byte 77,byte 77,byte 48,byte 31,
+ byte 228,byte 245,byte 245,byte 245,byte 199,byte 199,byte 186,byte 186,
+ byte 186,byte 123,byte 123,byte 123,byte 110,byte 48,byte 48,byte 47,
+ byte 228,byte 228,byte 228,byte 245,byte 215,byte 215,byte 215,byte 169,
+ byte 169,byte 152,byte 123,byte 110,byte 93,byte 76,byte 47,byte 30,
+ byte 244,byte 244,byte 244,byte 198,byte 198,byte 198,byte 185,byte 185,
+ byte 185,byte 122,byte 122,byte 126,byte 76,byte 76,byte 63,byte 13,
+ byte 227,byte 227,byte 244,byte 244,byte 214,byte 231,byte 231,byte 168,
+ byte 168,byte 168,byte 126,byte 109,byte 92,byte 63,byte 46,byte 46,
+ byte 210,byte 210,byte 197,byte 197,byte 197,byte 197,byte 214,byte 151,
+ byte 151,byte 138,byte 92,byte 92,byte 75,byte 75,byte 29,byte 29,
+ byte 193,byte 197,byte 197,byte 197,byte 197,byte 197,byte 197,byte 134,
+ byte 138,byte 138,byte 138,byte 75,byte 75,byte 75,byte 12,byte 12,
+ byte 209,byte 226,byte 243,byte 243,byte 196,byte 213,byte 167,byte 167,
+ byte 167,byte 137,byte 121,byte 91,byte 74,byte 62,byte 45,byte 28,
+ byte 226,byte 226,byte 243,byte 196,byte 196,byte 213,byte 167,byte 167,
+ byte 167,byte 137,byte 137,byte 91,byte 74,byte 62,byte 45,byte 28,
+ byte 226,byte 243,byte 243,byte 196,byte 213,byte 213,byte 184,byte 184,
+ byte 137,byte 137,byte 137,byte 91,byte 74,byte 62,byte 45,byte 28,
+ byte 226,byte 243,byte 196,byte 196,byte 213,byte 230,byte 184,byte 184,
+ byte 154,byte 137,byte 108,byte 91,byte 74,byte 62,byte 45,byte 28,
+ byte 243,byte 243,byte 196,byte 213,byte 213,byte 230,byte 184,byte 184,
+ byte 154,byte 137,byte 108,byte 91,byte 62,byte 62,byte 45,byte 15,
+ byte 243,byte 196,byte 196,byte 213,byte 230,byte 230,byte 184,byte 184,
+ byte 154,byte 154,byte 108,byte 91,byte 62,byte 62,byte 45,byte 15,
+ byte 242,byte 242,byte 213,byte 229,byte 246,byte 246,byte 183,byte 183,
+ byte 154,byte 154,byte 124,byte 107,byte 61,byte 61,byte 61,byte 15,
+ byte 242,byte 212,byte 212,byte 229,byte 229,byte 246,byte 183,byte 183,
+ byte 170,byte 124,byte 124,byte 107,byte 61,byte 61,byte 32,byte 14,
+ byte 195,byte 195,byte 212,byte 229,byte 229,byte 199,byte 183,byte 170,
+ byte 153,byte 153,byte 107,byte 90,byte 61,byte 61,byte 31,byte 14,
+ byte 241,byte 228,byte 245,byte 245,byte 245,byte 199,byte 182,byte 153,
+ byte 153,byte 123,byte 123,byte 123,byte 60,byte 48,byte 47,byte 30,
+ byte 211,byte 228,byte 228,byte 228,byte 245,byte 245,byte 182,byte 169,
+ byte 152,byte 123,byte 123,byte 106,byte 60,byte 47,byte 30,byte 30,
+ byte 211,byte 211,byte 211,byte 244,byte 244,byte 198,byte 152,byte 152,
+ byte 135,byte 122,byte 122,byte 89,byte 76,byte 76,byte 30,byte 13,
+ byte 227,byte 227,byte 244,byte 244,byte 244,byte 198,byte 185,byte 168,
+ byte 168,byte 122,byte 122,byte 105,byte 63,byte 63,byte 46,byte 29,
+ byte 210,byte 210,byte 227,byte 227,byte 197,byte 214,byte 151,byte 151,
+ byte 151,byte 105,byte 105,byte 92,byte 75,byte 46,byte 29,byte 29,
+ byte 193,byte 193,byte 210,byte 197,byte 197,byte 197,byte 134,byte 134,
+ byte 134,byte 134,byte 75,byte 75,byte 75,byte 75,byte 12,byte 12,
+ byte 193,byte 193,byte 197,byte 197,byte 197,byte 197,byte 134,byte 134,
+ byte 134,byte 134,byte 75,byte 75,byte 75,byte 75,byte 12,byte 12,
+ byte 192,byte 209,byte 226,byte 226,byte 243,byte 180,byte 150,byte 150,
+ byte 150,byte 121,byte 104,byte 58,byte 74,byte 28,byte 11,byte 11,
+ byte 209,byte 209,byte 226,byte 243,byte 243,byte 180,byte 150,byte 150,
+ byte 121,byte 121,byte 121,byte 74,byte 74,byte 28,byte 11,byte 11,
+ byte 209,byte 226,byte 226,byte 243,byte 196,byte 180,byte 167,byte 167,
+ byte 121,byte 121,byte 121,byte 74,byte 74,byte 45,byte 28,byte 11,
+ byte 209,byte 226,byte 243,byte 243,byte 196,byte 167,byte 167,byte 167,
+ byte 137,byte 121,byte 121,byte 74,byte 74,byte 45,byte 28,byte 11,
+ byte 226,byte 226,byte 243,byte 196,byte 196,byte 167,byte 167,byte 167,
+ byte 137,byte 121,byte 91,byte 74,byte 74,byte 45,byte 28,byte 11,
+ byte 226,byte 243,byte 243,byte 196,byte 213,byte 167,byte 167,byte 167,
+ byte 137,byte 137,byte 91,byte 74,byte 62,byte 45,byte 28,byte 11,
+ byte 242,byte 242,byte 242,byte 242,byte 229,byte 183,byte 183,byte 183,
+ byte 120,byte 120,byte 107,byte 90,byte 61,byte 44,byte 27,byte 27,
+ byte 225,byte 195,byte 195,byte 212,byte 212,byte 183,byte 183,byte 183,
+ byte 153,byte 120,byte 90,byte 90,byte 61,byte 44,byte 27,byte 14,
+ byte 241,byte 195,byte 195,byte 195,byte 212,byte 182,byte 182,byte 153,
+ byte 136,byte 136,byte 90,byte 73,byte 60,byte 60,byte 27,byte 14,
+ byte 241,byte 241,byte 228,byte 228,byte 228,byte 182,byte 182,byte 182,
+ byte 136,byte 123,byte 106,byte 60,byte 60,byte 43,byte 30,byte 13,
+ byte 194,byte 211,byte 211,byte 211,byte 228,byte 165,byte 165,byte 135,
+ byte 135,byte 106,byte 89,byte 89,byte 43,byte 43,byte 13,byte 13,
+ byte 194,byte 194,byte 194,byte 244,byte 244,byte 181,byte 181,byte 135,
+ byte 135,byte 122,byte 122,byte 59,byte 59,byte 59,byte 13,byte 13,
+ byte 210,byte 210,byte 227,byte 227,byte 227,byte 181,byte 181,byte 151,
+ byte 151,byte 105,byte 105,byte 88,byte 59,byte 46,byte 29,byte 12,
+ byte 193,byte 193,byte 210,byte 210,byte 210,byte 164,byte 134,byte 134,
+ byte 134,byte 88,byte 88,byte 71,byte 75,byte 29,byte 12,byte 12,
+ byte 193,byte 193,byte 193,byte 193,byte 197,byte 134,byte 134,byte 134,
+ byte 134,byte 71,byte 71,byte 71,byte 75,byte 12,byte 12,byte 12,
+ byte 193,byte 193,byte 193,byte 193,byte 197,byte 134,byte 134,byte 134,
+ byte 134,byte 71,byte 71,byte 75,byte 75,byte 12,byte 12,byte 12,
+ byte 192,byte 192,byte 209,byte 226,byte 180,byte 180,byte 133,byte 133,
+ byte 104,byte 104,byte 104,byte 58,byte 58,byte 11,byte 11,byte 11,
+ byte 192,byte 192,byte 209,byte 226,byte 180,byte 180,byte 133,byte 133,
+ byte 104,byte 104,byte 104,byte 58,byte 58,byte 11,byte 11,byte 11,
+ byte 192,byte 209,byte 209,byte 226,byte 180,byte 180,byte 133,byte 133,
+ byte 104,byte 104,byte 104,byte 58,byte 58,byte 11,byte 11,byte 11,
+ byte 192,byte 209,byte 226,byte 226,byte 180,byte 180,byte 150,byte 150,
+ byte 121,byte 104,byte 104,byte 58,byte 58,byte 28,byte 11,byte 11,
+ byte 209,byte 209,byte 226,byte 243,byte 180,byte 180,byte 150,byte 150,
+ byte 121,byte 104,byte 58,byte 58,byte 45,byte 28,byte 11,byte 11,
+ byte 225,byte 225,byte 242,byte 242,byte 179,byte 179,byte 166,byte 120,
+ byte 120,byte 120,byte 57,byte 57,byte 44,byte 27,byte 10,byte 10,
+ byte 225,byte 225,byte 242,byte 242,byte 179,byte 166,byte 166,byte 166,
+ byte 120,byte 120,byte 90,byte 73,byte 44,byte 27,byte 10,byte 10,
+ byte 208,byte 195,byte 195,byte 195,byte 195,byte 149,byte 149,byte 136,
+ byte 136,byte 136,byte 73,byte 73,byte 27,byte 27,byte 10,byte 10,
+ byte 241,byte 241,byte 241,byte 241,byte 178,byte 182,byte 182,byte 136,
+ byte 119,byte 119,byte 73,byte 60,byte 60,byte 43,byte 26,byte 9,
+ byte 224,byte 241,byte 241,byte 211,byte 211,byte 165,byte 165,byte 165,
+ byte 119,byte 119,byte 89,byte 89,byte 43,byte 26,byte 9,byte 9,
+ byte 240,byte 194,byte 194,byte 194,byte 211,byte 181,byte 181,byte 118,
+ byte 118,byte 89,byte 72,byte 72,byte 59,byte 9,byte 9,byte 13,
+ byte 240,byte 240,byte 240,byte 227,byte 164,byte 164,byte 181,byte 118,
+ byte 118,byte 105,byte 72,byte 59,byte 42,byte 42,byte 25,byte 12,
+ byte 193,byte 193,byte 193,byte 210,byte 210,byte 164,byte 164,byte 134,
+ byte 88,byte 88,byte 71,byte 71,byte 42,byte 25,byte 12,byte 12,
+ byte 193,byte 193,byte 193,byte 193,byte 193,byte 147,byte 134,byte 134,
+ byte 71,byte 71,byte 71,byte 71,byte 25,byte 8,byte 12,byte 12,
+ byte 193,byte 193,byte 193,byte 193,byte 193,byte 130,byte 134,byte 134,
+ byte 71,byte 71,byte 71,byte 71,byte 8,byte 12,byte 12,byte 12,
+ byte 193,byte 193,byte 193,byte 193,byte 193,byte 134,byte 134,byte 134,
+ byte 71,byte 71,byte 71,byte 71,byte 8,byte 12,byte 12,byte 12,
+ byte 192,byte 192,byte 192,byte 209,byte 163,byte 163,byte 133,byte 117,
+ byte 117,byte 87,byte 87,byte 41,byte 41,byte 24,byte 11,byte 11,
+ byte 192,byte 192,byte 192,byte 209,byte 163,byte 163,byte 133,byte 117,
+ byte 117,byte 87,byte 87,byte 41,byte 41,byte 24,byte 11,byte 11,
+ byte 192,byte 192,byte 209,byte 209,byte 163,byte 163,byte 133,byte 117,
+ byte 104,byte 87,byte 58,byte 58,byte 41,byte 11,byte 11,byte 11,
+ byte 192,byte 192,byte 209,byte 226,byte 180,byte 180,byte 133,byte 117,
+ byte 104,byte 87,byte 58,byte 58,byte 41,byte 11,byte 11,byte 11,
+ byte 192,byte 209,byte 209,byte 226,byte 180,byte 180,byte 133,byte 133,
+ byte 104,byte 104,byte 58,byte 58,byte 41,byte 11,byte 11,byte 11,
+ byte 208,byte 208,byte 225,byte 225,byte 179,byte 179,byte 149,byte 116,
+ byte 103,byte 103,byte 57,byte 57,byte 40,byte 10,byte 10,byte 10,
+ byte 207,byte 208,byte 225,byte 225,byte 179,byte 132,byte 132,byte 132,
+ byte 103,byte 103,byte 57,byte 57,byte 27,byte 10,byte 10,byte 10,
+ byte 207,byte 224,byte 241,byte 241,byte 178,byte 132,byte 132,byte 119,
+ byte 119,byte 119,byte 56,byte 56,byte 10,byte 10,byte 10,byte 10,
+ byte 223,byte 224,byte 224,byte 241,byte 178,byte 165,byte 165,byte 119,
+ byte 119,byte 102,byte 56,byte 56,byte 26,byte 26,byte 9,byte 9,
+ byte 223,byte 223,byte 194,byte 194,byte 148,byte 148,byte 148,byte 102,
+ byte 102,byte 102,byte 72,byte 72,byte 26,byte 9,byte 9,byte 9,
+ byte 239,byte 240,byte 240,byte 194,byte 177,byte 164,byte 164,byte 118,
+ byte 118,byte 118,byte 72,byte 72,byte 42,byte 9,byte 9,byte 9,
+ byte 239,byte 239,byte 239,byte 210,byte 147,byte 147,byte 147,byte 101,
+ byte 101,byte 101,byte 71,byte 42,byte 25,byte 25,byte 8,byte 8,
+ byte 222,byte 193,byte 193,byte 193,byte 130,byte 130,byte 130,byte 101,
+ byte 84,byte 71,byte 71,byte 25,byte 25,byte 8,byte 8,byte 12,
+ byte 193,byte 193,byte 193,byte 193,byte 130,byte 130,byte 130,byte 130,
+ byte 71,byte 71,byte 71,byte 71,byte 8,byte 8,byte 8,byte 12,
+ byte 193,byte 193,byte 193,byte 193,byte 130,byte 130,byte 130,byte 134,
+ byte 71,byte 71,byte 71,byte 71,byte 8,byte 8,byte 12,byte 12,
+ byte 193,byte 193,byte 193,byte 193,byte 130,byte 130,byte 130,byte 134,
+ byte 71,byte 71,byte 71,byte 71,byte 8,byte 8,byte 12,byte 12,
+ byte 192,byte 192,byte 192,byte 129,byte 146,byte 146,byte 117,byte 100,
+ byte 100,byte 70,byte 70,byte 24,byte 24,byte 7,byte 7,byte 11,
+ byte 192,byte 192,byte 192,byte 146,byte 146,byte 146,byte 117,byte 117,
+ byte 117,byte 70,byte 70,byte 24,byte 24,byte 7,byte 7,byte 11,
+ byte 192,byte 192,byte 192,byte 146,byte 146,byte 146,byte 117,byte 117,
+ byte 87,byte 70,byte 70,byte 24,byte 24,byte 7,byte 11,byte 11,
+ byte 192,byte 192,byte 192,byte 146,byte 146,byte 163,byte 117,byte 117,
+ byte 87,byte 70,byte 41,byte 41,byte 24,byte 7,byte 11,byte 11,
+ byte 207,byte 207,byte 208,byte 162,byte 162,byte 162,byte 116,byte 116,
+ byte 116,byte 86,byte 57,byte 40,byte 40,byte 23,byte 10,byte 10,
+ byte 207,byte 207,byte 208,byte 162,byte 162,byte 162,byte 116,byte 116,
+ byte 86,byte 86,byte 40,byte 40,byte 23,byte 10,byte 10,byte 10,
+ byte 207,byte 207,byte 207,byte 162,byte 162,byte 132,byte 132,byte 115,
+ byte 86,byte 86,byte 40,byte 40,byte 23,byte 10,byte 10,byte 10,
+ byte 223,byte 223,byte 224,byte 178,byte 178,byte 178,byte 115,byte 115,
+ byte 102,byte 56,byte 56,byte 39,byte 39,byte 9,byte 9,byte 9,
+ byte 206,byte 223,byte 223,byte 161,byte 161,byte 148,byte 148,byte 102,
+ byte 102,byte 85,byte 39,byte 39,byte 9,byte 9,byte 9,byte 9,
+ byte 206,byte 206,byte 240,byte 177,byte 177,byte 131,byte 131,byte 118,
+ byte 85,byte 55,byte 55,byte 55,byte 9,byte 9,byte 9,byte 9,
+ byte 239,byte 239,byte 239,byte 177,byte 177,byte 131,byte 131,byte 101,
+ byte 101,byte 55,byte 55,byte 38,byte 25,byte 25,byte 8,byte 8,
+ byte 222,byte 222,byte 239,byte 160,byte 130,byte 130,byte 130,byte 84,
+ byte 84,byte 84,byte 38,byte 25,byte 8,byte 8,byte 8,byte 8,
+ byte 205,byte 222,byte 193,byte 130,byte 130,byte 130,byte 130,byte 84,
+ byte 67,byte 71,byte 71,byte 8,byte 8,byte 8,byte 8,byte 8,
+ byte 205,byte 193,byte 193,byte 130,byte 130,byte 130,byte 130,byte 67,
+ byte 67,byte 71,byte 71,byte 8,byte 8,byte 8,byte 8,byte 8,
+ byte 205,byte 193,byte 193,byte 130,byte 130,byte 130,byte 130,byte 67,
+ byte 71,byte 71,byte 71,byte 8,byte 8,byte 8,byte 8,byte 8,
+ byte 205,byte 193,byte 193,byte 130,byte 130,byte 130,byte 130,byte 67,
+ byte 71,byte 71,byte 71,byte 8,byte 8,byte 8,byte 8,byte 12,
+ byte 192,byte 192,byte 192,byte 129,byte 129,byte 129,byte 100,byte 83,
+ byte 83,byte 54,byte 54,byte 7,byte 7,byte 7,byte 7,byte 7,
+ byte 192,byte 192,byte 192,byte 129,byte 129,byte 129,byte 100,byte 100,
+ byte 83,byte 54,byte 54,byte 7,byte 7,byte 7,byte 7,byte 7,
+ byte 192,byte 192,byte 192,byte 129,byte 129,byte 129,byte 100,byte 100,
+ byte 70,byte 54,byte 54,byte 7,byte 7,byte 7,byte 7,byte 7,
+ byte 192,byte 192,byte 192,byte 129,byte 129,byte 129,byte 100,byte 100,
+ byte 70,byte 53,byte 53,byte 7,byte 7,byte 7,byte 7,byte 11,
+ byte 207,byte 207,byte 207,byte 145,byte 145,byte 145,byte 116,byte 99,
+ byte 53,byte 53,byte 23,byte 23,byte 6,byte 6,byte 6,byte 10,
+ byte 207,byte 207,byte 207,byte 145,byte 145,byte 145,byte 99,byte 99,
+ byte 69,byte 69,byte 23,byte 23,byte 6,byte 6,byte 10,byte 10,
+ byte 207,byte 207,byte 207,byte 161,byte 161,byte 115,byte 115,byte 115,
+ byte 69,byte 69,byte 39,byte 39,byte 6,byte 6,byte 10,byte 10,
+ byte 206,byte 206,byte 144,byte 144,byte 161,byte 115,byte 115,byte 115,
+ byte 85,byte 39,byte 39,byte 22,byte 22,byte 9,byte 9,byte 9,
+ byte 206,byte 206,byte 144,byte 144,byte 144,byte 131,byte 114,byte 85,
+ byte 85,byte 68,byte 22,byte 22,byte 5,byte 9,byte 9,byte 9,
+ byte 206,byte 239,byte 160,byte 160,byte 160,byte 114,byte 114,byte 114,
+ byte 68,byte 55,byte 38,byte 38,byte 38,byte 9,byte 9,byte 9,
+ byte 222,byte 222,byte 160,byte 160,byte 160,byte 114,byte 114,byte 84,
+ byte 84,byte 38,byte 38,byte 21,byte 8,byte 8,byte 8,byte 8,
+ byte 205,byte 205,byte 159,byte 159,byte 130,byte 130,byte 67,byte 67,
+ byte 67,byte 67,byte 21,byte 21,byte 8,byte 8,byte 8,byte 8,
+ byte 205,byte 205,byte 205,byte 130,byte 130,byte 130,byte 67,byte 67,
+ byte 67,byte 67,byte 21,byte 8,byte 8,byte 8,byte 8,byte 8,
+ byte 205,byte 205,byte 205,byte 130,byte 130,byte 130,byte 67,byte 67,
+ byte 67,byte 67,byte 8,byte 8,byte 8,byte 8,byte 8,byte 8,
+ byte 205,byte 205,byte 193,byte 130,byte 130,byte 130,byte 67,byte 67,
+ byte 67,byte 67,byte 8,byte 8,byte 8,byte 8,byte 8,byte 8,
+ byte 205,byte 205,byte 193,byte 130,byte 130,byte 130,byte 67,byte 67,
+ byte 67,byte 71,byte 8,byte 8,byte 8,byte 8,byte 8,byte 8,
+ byte 192,byte 192,byte 129,byte 129,byte 129,byte 129,byte 66,byte 66,
+ byte 66,byte 37,byte 37,byte 7,byte 7,byte 7,byte 7,byte 7,
+ byte 192,byte 192,byte 129,byte 129,byte 129,byte 129,byte 83,byte 83,
+ byte 54,byte 37,byte 37,byte 7,byte 7,byte 7,byte 7,byte 7,
+ byte 192,byte 192,byte 129,byte 129,byte 129,byte 83,byte 83,byte 83,
+ byte 54,byte 37,byte 37,byte 7,byte 7,byte 7,byte 7,byte 7,
+ byte 207,byte 207,byte 128,byte 128,byte 128,byte 99,byte 82,byte 82,
+ byte 53,byte 53,byte 36,byte 6,byte 6,byte 6,byte 6,byte 6,
+ byte 207,byte 207,byte 128,byte 128,byte 128,byte 82,byte 82,byte 82,
+ byte 53,byte 53,byte 6,byte 6,byte 6,byte 6,byte 6,byte 6,
+ byte 207,byte 207,byte 128,byte 128,byte 128,byte 82,byte 82,byte 82,
+ byte 69,byte 52,byte 6,byte 6,byte 6,byte 6,byte 6,byte 6,
+ byte 206,byte 206,byte 144,byte 144,byte 144,byte 98,byte 98,byte 98,
+ byte 52,byte 52,byte 22,byte 22,byte 5,byte 5,byte 5,byte 9,
+ byte 206,byte 206,byte 143,byte 143,byte 143,byte 98,byte 98,byte 98,
+ byte 68,byte 52,byte 22,byte 5,byte 5,byte 5,byte 5,byte 9,
+ byte 206,byte 206,byte 143,byte 143,byte 143,byte 114,byte 114,byte 68,
+ byte 68,byte 51,byte 5,byte 5,byte 5,byte 5,byte 9,byte 9,
+ byte 205,byte 222,byte 159,byte 159,byte 159,byte 97,byte 97,byte 97,
+ byte 51,byte 38,byte 21,byte 21,byte 21,byte 4,byte 8,byte 8,
+ byte 205,byte 205,byte 159,byte 159,byte 159,byte 97,byte 97,byte 67,
+ byte 67,byte 21,byte 21,byte 4,byte 4,byte 8,byte 8,byte 8,
+ byte 205,byte 205,byte 142,byte 142,byte 142,byte 80,byte 67,byte 67,
+ byte 67,byte 21,byte 4,byte 4,byte 4,byte 8,byte 8,byte 8,
+ byte 205,byte 205,byte 142,byte 142,byte 142,byte 130,byte 67,byte 67,
+ byte 67,byte 4,byte 4,byte 4,byte 8,byte 8,byte 8,byte 8,
+ byte 205,byte 205,byte 142,byte 142,byte 130,byte 130,byte 67,byte 67,
+ byte 67,byte 4,byte 4,byte 4,byte 8,byte 8,byte 8,byte 8,
+ byte 205,byte 205,byte 142,byte 142,byte 130,byte 130,byte 67,byte 67,
+ byte 67,byte 4,byte 4,byte 8,byte 8,byte 8,byte 8,byte 8,
+ byte 205,byte 205,byte 142,byte 130,byte 130,byte 130,byte 67,byte 67,
+ byte 67,byte 4,byte 4,byte 8,byte 8,byte 8,byte 8,byte 8,
+ byte 192,byte 129,byte 129,byte 129,byte 129,byte 66,byte 66,byte 66,
+ byte 20,byte 20,byte 20,byte 3,byte 7,byte 7,byte 7,byte 7,
+ byte 192,byte 129,byte 129,byte 129,byte 129,byte 66,byte 66,byte 66,
+ byte 37,byte 20,byte 20,byte 20,byte 7,byte 7,byte 7,byte 7,
+ byte 192,byte 129,byte 129,byte 129,byte 129,byte 66,byte 66,byte 66,
+ byte 36,byte 36,byte 20,byte 7,byte 7,byte 7,byte 7,byte 7,
+ byte 207,byte 128,byte 128,byte 128,byte 128,byte 65,byte 65,byte 65,
+ byte 36,byte 36,byte 19,byte 6,byte 6,byte 6,byte 6,byte 6,
+ byte 207,byte 128,byte 128,byte 128,byte 128,byte 65,byte 65,byte 65,
+ byte 36,byte 36,byte 19,byte 6,byte 6,byte 6,byte 6,byte 6,
+ byte 207,byte 128,byte 128,byte 128,byte 128,byte 81,byte 81,byte 52,
+ byte 52,byte 35,byte 35,byte 6,byte 6,byte 6,byte 6,byte 6,
+ byte 206,byte 143,byte 143,byte 143,byte 143,byte 81,byte 81,byte 81,
+ byte 35,byte 35,byte 5,byte 5,byte 5,byte 5,byte 5,byte 5,
+ byte 206,byte 143,byte 143,byte 143,byte 143,byte 81,byte 64,byte 64,
+ byte 51,byte 51,byte 5,byte 5,byte 5,byte 5,byte 5,byte 5,
+ byte 206,byte 143,byte 143,byte 143,byte 97,byte 97,byte 97,byte 51,
+ byte 51,byte 51,byte 5,byte 5,byte 5,byte 5,byte 5,byte 5,
+ byte 205,byte 142,byte 142,byte 142,byte 142,byte 80,byte 80,byte 80,
+ byte 34,byte 21,byte 4,byte 4,byte 4,byte 4,byte 4,byte 8,
+ byte 205,byte 142,byte 142,byte 142,byte 142,byte 80,byte 80,byte 67,
+ byte 67,byte 4,byte 4,byte 4,byte 4,byte 4,byte 4,byte 8,
+ byte 205,byte 142,byte 142,byte 142,byte 142,byte 79,byte 79,byte 67,
+ byte 67,byte 4,byte 4,byte 4,byte 4,byte 4,byte 8,byte 8,
+ byte 205,byte 142,byte 142,byte 142,byte 142,byte 79,byte 67,byte 67,
+ byte 67,byte 4,byte 4,byte 4,byte 4,byte 4,byte 8,byte 8,
+ byte 205,byte 142,byte 142,byte 142,byte 142,byte 79,byte 67,byte 67,
+ byte 67,byte 4,byte 4,byte 4,byte 4,byte 8,byte 8,byte 8,
+ byte 205,byte 142,byte 142,byte 142,byte 142,byte 67,byte 67,byte 67,
+ byte 67,byte 4,byte 4,byte 4,byte 4,byte 8,byte 8,byte 8,
+ byte 205,byte 142,byte 142,byte 142,byte 142,byte 67,byte 67,byte 67,
+ byte 67,byte 4,byte 4,byte 4,byte 8,byte 8,byte 8,byte 8,
+ byte 129,byte 129,byte 129,byte 129,byte 66,byte 66,byte 66,byte 66,
+ byte 20,byte 3,byte 3,byte 3,byte 3,byte 7,byte 7,byte 7,
+ byte 129,byte 129,byte 129,byte 129,byte 66,byte 66,byte 66,byte 66,
+ byte 20,byte 3,byte 3,byte 3,byte 3,byte 7,byte 7,byte 7,
+ byte 128,byte 128,byte 128,byte 128,byte 65,byte 65,byte 65,byte 65,
+ byte 19,byte 19,byte 2,byte 2,byte 6,byte 6,byte 6,byte 6,
+ byte 128,byte 128,byte 128,byte 128,byte 65,byte 65,byte 65,byte 65,
+ byte 19,byte 19,byte 2,byte 2,byte 6,byte 6,byte 6,byte 6,
+ byte 128,byte 128,byte 128,byte 128,byte 65,byte 65,byte 65,byte 65,
+ byte 19,byte 19,byte 2,byte 6,byte 6,byte 6,byte 6,byte 6,
+ byte 143,byte 143,byte 143,byte 143,byte 64,byte 64,byte 64,byte 35,
+ byte 35,byte 18,byte 18,byte 5,byte 5,byte 5,byte 5,byte 5,
+ byte 143,byte 143,byte 143,byte 143,byte 64,byte 64,byte 64,byte 64,
+ byte 18,byte 18,byte 18,byte 5,byte 5,byte 5,byte 5,byte 5,
+ byte 143,byte 143,byte 143,byte 143,byte 64,byte 64,byte 64,byte 34,
+ byte 34,byte 34,byte 5,byte 5,byte 5,byte 5,byte 5,byte 5,
+ byte 142,byte 142,byte 142,byte 142,byte 80,byte 80,byte 80,byte 34,
+ byte 34,byte 34,byte 4,byte 4,byte 4,byte 4,byte 4,byte 4,
+ byte 142,byte 142,byte 142,byte 142,byte 79,byte 79,byte 79,byte 34,
+ byte 17,byte 17,byte 4,byte 4,byte 4,byte 4,byte 4,byte 4,
+ byte 142,byte 142,byte 142,byte 142,byte 79,byte 79,byte 79,byte 79,
+ byte 17,byte 4,byte 4,byte 4,byte 4,byte 4,byte 4,byte 4,
+ byte 142,byte 142,byte 142,byte 142,byte 79,byte 79,byte 79,byte 79,
+ byte 4,byte 4,byte 4,byte 4,byte 4,byte 4,byte 4,byte 8,
+ byte 142,byte 142,byte 142,byte 142,byte 79,byte 79,byte 79,byte 79,
+ byte 4,byte 4,byte 4,byte 4,byte 4,byte 4,byte 4,byte 8,
+ byte 142,byte 142,byte 142,byte 142,byte 79,byte 79,byte 79,byte 67,
+ byte 4,byte 4,byte 4,byte 4,byte 4,byte 4,byte 4,byte 8,
+ byte 142,byte 142,byte 142,byte 142,byte 79,byte 79,byte 79,byte 67,
+ byte 4,byte 4,byte 4,byte 4,byte 4,byte 4,byte 8,byte 8,
+ byte 142,byte 142,byte 142,byte 142,byte 79,byte 79,byte 67,byte 67,
+ byte 4,byte 4,byte 4,byte 4,byte 4,byte 4,byte 8,byte 8,
+ byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,
+ byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,
+ byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,
+ byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,
+ byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,
+ byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,
+ byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,
+ byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,
+ byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,
+ byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,
+ byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,
+ byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,
+ byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,
+ byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,
+ byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,
+ byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,byte 1,
+ byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,
+ byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,
+ byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,
+ byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,
+ byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,
+ byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,
+ byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,
+ byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,
+ byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,
+ byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,
+ byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,
+ byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,
+ byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,
+ byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,
+ byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,
+ byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0,byte 0
+
+};
+
+rgbvmap_y := array[256] of {
+ 16rff, 16rf5, 16reb, 16re1, 16rcd, 16rc3, 16rb9, 16rb0,
+ 16r9b, 16r91, 16r87, 16r7e, 16r69, 16r5f, 16r55, 16r4c,
+ 16r47, 16ree, 16re4, 16rdb, 16rd2, 16rbf, 16rb5, 16rac,
+ 16ra3, 16r90, 16r87, 16r7e, 16r75, 16r62, 16r59, 16r50,
+ 16r4a, 16r42, 16rdd, 16rd4, 16rcc, 16rc3, 16rb1, 16ra9,
+ 16ra0, 16r98, 16r86, 16r7d, 16r75, 16r6c, 16r5b, 16r52,
+ 16r4c, 16r44, 16r3c, 16rcc, 16rc4, 16rbc, 16rb4, 16ra4,
+ 16r9c, 16r94, 16r8c, 16r7c, 16r74, 16r6c, 16r64, 16r54,
+ 16rdb, 16rd2, 16rc8, 16rb3, 16rbb, 16rb0, 16ra5, 16r81,
+ 16r83, 16r79, 16r6e, 16r4f, 16r4d, 16r42, 16r37, 16re5,
+ 16rd6, 16rcc, 16rc3, 16rba, 16ra7, 16raa, 16ra0, 16r96,
+ 16r78, 16r78, 16r6e, 16r64, 16r4a, 16r46, 16r3c, 16r32,
+ 16r2d, 16rc6, 16rbe, 16rb6, 16rad, 16r9b, 16r99, 16r90,
+ 16r87, 16r6f, 16r6b, 16r63, 16r5a, 16r45, 16r3f, 16r36,
+ 16r30, 16r28, 16rb7, 16raf, 16ra8, 16ra0, 16r8f, 16r88,
+ 16r80, 16r78, 16r67, 16r60, 16r58, 16r50, 16r3f, 16r38,
+ 16rb8, 16raf, 16r9a, 16r9e, 16r94, 16r89, 16r68, 16r67,
+ 16r77, 16r69, 16r36, 16r31, 16r31, 16r23, 16rcc, 16rc2,
+ 16rb5, 16rac, 16ra3, 16r8f, 16r90, 16r86, 16r7d, 16r61,
+ 16r5e, 16r66, 16r5a, 16r32, 16r2c, 16r2a, 16r1e, 16rbe,
+ 16rb0, 16ra8, 16r9f, 16r97, 16r85, 16r81, 16r79, 16r70,
+ 16r59, 16r54, 16r55, 16r4b, 16r2f, 16r28, 16r23, 16r19,
+ 16r14, 16ra3, 16r9b, 16r93, 16r8c, 16r7b, 16r73, 16r6b,
+ 16r64, 16r53, 16r4b, 16r44, 16r3c, 16r2b, 16r23, 16r1c,
+ 16r95, 16r80, 16r83, 16r78, 16r6d, 16r4e, 16r4b, 16r53,
+ 16r45, 16r1d, 16r15, 16r0d, 16r33, 16rb2, 16ra9, 16r9f,
+ 16r94, 16r8b, 16r77, 16r77, 16r6d, 16r63, 16r49, 16r45,
+ 16r47, 16r3b, 16r1b, 16r13, 16r0b, 16r22, 16ra6, 16r9d,
+ 16r92, 16r8a, 16r81, 16r6f, 16r6b, 16r62, 16r59, 16r44,
+ 16r3e, 16r3b, 16r31, 16r19, 16r11, 16r09, 16r11, 16r9a,
+ 16r8f, 16r87, 16r7f, 16r77, 16r67, 16r5f, 16r57, 16r4f,
+ 16r3f, 16r37, 16r2f, 16r27, 16r17, 16r0f, 16r07, 16r00
+};
+
+rgbvmap_cb := array[256] of {
+ 16r80, 16r55, 16r2b, 16r00, 16r9c, 16r71, 16r47, 16r1c,
+ 16rb8, 16r8d, 16r63, 16r38, 16rd4, 16ra9, 16r7f, 16r54,
+ 16r57, 16r80, 16r58, 16r30, 16r09, 16r9a, 16r72, 16r4b,
+ 16r23, 16rb4, 16r8c, 16r65, 16r3d, 16rce, 16ra6, 16r7f,
+ 16r7f, 16r5a, 16r80, 16r5b, 16r36, 16r11, 16r98, 16r73,
+ 16r4e, 16r2a, 16rb1, 16r8c, 16r67, 16r42, 16rc9, 16ra4,
+ 16ra1, 16r7f, 16r5d, 16r80, 16r5e, 16r3c, 16r1a, 16r96,
+ 16r74, 16r52, 16r30, 16rad, 16r8b, 16r69, 16r47, 16rc3,
+ 16r63, 16r39, 16r0e, 16raa, 16r80, 16r51, 16r22, 16rc6,
+ 16r9f, 16r70, 16r41, 16re2, 16rbd, 16r8e, 16r60, 16r8e,
+ 16r8d, 16r65, 16r3d, 16r16, 16ra8, 16r80, 16r55, 16r2b,
+ 16rc2, 16r9c, 16r71, 16r47, 16rdc, 16rb8, 16r8d, 16r63,
+ 16r66, 16r8c, 16r67, 16r42, 16r1d, 16ra5, 16r80, 16r59,
+ 16r33, 16rbd, 16r99, 16r73, 16r4d, 16rd5, 16rb2, 16r8c,
+ 16r8b, 16r69, 16r8b, 16r69, 16r47, 16r25, 16ra2, 16r80,
+ 16r5e, 16r3c, 16rb8, 16r96, 16r74, 16r52, 16rcf, 16rad,
+ 16r47, 16r1d, 16rb8, 16r8f, 16r60, 16r32, 16rd5, 16raf,
+ 16r80, 16r44, 16rf1, 16rcd, 16ra7, 16r6b, 16r9c, 16r72,
+ 16r72, 16r4b, 16r23, 16rb5, 16r8e, 16r63, 16r39, 16rcf,
+ 16raa, 16r80, 16r4d, 16re9, 16rc6, 16ra1, 16r6e, 16r9a,
+ 16r98, 16r73, 16r4e, 16r2a, 16rb1, 16r8c, 16r66, 16r40,
+ 16rca, 16ra6, 16r80, 16r55, 16re2, 16rbf, 16r9c, 16r71,
+ 16r74, 16r96, 16r74, 16r52, 16r30, 16rad, 16r8b, 16r69,
+ 16r47, 16rc4, 16ra2, 16r80, 16r5e, 16rda, 16rb8, 16r96,
+ 16r2b, 16rc7, 16r9f, 16r70, 16r42, 16re3, 16rbe, 16r94,
+ 16r58, 16rff, 16rdd, 16rbb, 16r80, 16rab, 16r80, 16r56,
+ 16r58, 16r31, 16rc2, 16r9c, 16r72, 16r47, 16rdc, 16rb8,
+ 16r91, 16r5e, 16rf7, 16rd5, 16rb3, 16r80, 16ra8, 16r80,
+ 16r80, 16r5b, 16r36, 16rbd, 16r99, 16r73, 16r4d, 16rd6,
+ 16rb3, 16r8e, 16r63, 16ree, 16rcc, 16raa, 16r80, 16ra5,
+ 16ra2, 16r80, 16r5e, 16r3c, 16rb8, 16r96, 16r74, 16r52,
+ 16rcf, 16rad, 16r8b, 16r69, 16re6, 16rc4, 16ra2, 16r80
+};
+
+rgbvmap_cr := array[256] of {
+ 16r80, 16r86, 16r8d, 16r94, 16ra3, 16raa, 16rb1, 16rb8,
+ 16rc7, 16rce, 16rd5, 16rdb, 16rea, 16rf1, 16rf8, 16rff,
+ 16rf7, 16r80, 16r86, 16r8c, 16r93, 16ra1, 16ra8, 16rae,
+ 16rb4, 16rc2, 16rc9, 16rcf, 16rd5, 16re3, 16rea, 16rf0,
+ 16re8, 16ree, 16r80, 16r86, 16r8c, 16r91, 16r9e, 16ra5,
+ 16rab, 16rb0, 16rbd, 16rc3, 16rca, 16rcf, 16rdc, 16re2,
+ 16rda, 16re0, 16re6, 16r80, 16r85, 16r8b, 16r90, 16r9c,
+ 16ra2, 16ra7, 16rad, 16rb8, 16rbe, 16rc4, 16rc9, 16rd5,
+ 16r5c, 16r63, 16r6a, 16r79, 16r80, 16r87, 16r8f, 16r9c,
+ 16ra7, 16raf, 16rb6, 16rc0, 16rce, 16rd5, 16rdd, 16r55,
+ 16r58, 16r5e, 16r64, 16r6b, 16r79, 16r80, 16r86, 16r8d,
+ 16r9a, 16ra3, 16raa, 16rb1, 16rbb, 16rc7, 16rce, 16rd5,
+ 16rcc, 16r5b, 16r61, 16r67, 16r6c, 16r79, 16r80, 16r86,
+ 16r8c, 16r98, 16ra0, 16ra6, 16rac, 16rb7, 16rc0, 16rc6,
+ 16rbe, 16rc4, 16r5e, 16r63, 16r69, 16r6e, 16r7a, 16r80,
+ 16r85, 16r8b, 16r96, 16r9c, 16ra2, 16ra7, 16rb3, 16rb8,
+ 16r38, 16r3f, 16r4e, 16r51, 16r58, 16r60, 16r72, 16r78,
+ 16r80, 16r89, 16r95, 16r9f, 16rb1, 16rbb, 16r2b, 16r31,
+ 16r37, 16r3d, 16r43, 16r51, 16r55, 16r5c, 16r63, 16r73,
+ 16r79, 16r80, 16r88, 16r94, 16r9c, 16raa, 16rb3, 16r30,
+ 16r36, 16r3c, 16r42, 16r47, 16r54, 16r59, 16r5f, 16r65,
+ 16r73, 16r79, 16r80, 16r86, 16r92, 16r99, 16ra3, 16raa,
+ 16ra2, 16r3c, 16r41, 16r47, 16r4c, 16r58, 16r5e, 16r63,
+ 16r69, 16r74, 16r7a, 16r80, 16r85, 16r91, 16r96, 16r9c,
+ 16r15, 16r24, 16r22, 16r2a, 16r31, 16r47, 16r49, 16r44,
+ 16r4e, 16r6b, 16r70, 16r76, 16r80, 16r00, 16r07, 16r0e,
+ 16r15, 16r1c, 16r2a, 16r2a, 16r31, 16r38, 16r4b, 16r4e,
+ 16r4d, 16r55, 16r6c, 16r72, 16r77, 16r80, 16r08, 16r0f,
+ 16r17, 16r1d, 16r23, 16r30, 16r33, 16r39, 16r3f, 16r4f,
+ 16r53, 16r55, 16r5c, 16r6e, 16r73, 16r79, 16r80, 16r11,
+ 16r1a, 16r1f, 16r25, 16r2a, 16r36, 16r3c, 16r41, 16r47,
+ 16r52, 16r58, 16r5e, 16r63, 16r6f, 16r74, 16r7a, 16r80
+};