diff options
Diffstat (limited to 'appl/charon')
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 (&) 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 (­ / &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 +}; |
