summaryrefslogtreecommitdiff
path: root/appl/wm/brutus/table.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/wm/brutus/table.b')
-rw-r--r--appl/wm/brutus/table.b1478
1 files changed, 1478 insertions, 0 deletions
diff --git a/appl/wm/brutus/table.b b/appl/wm/brutus/table.b
new file mode 100644
index 00000000..24740a31
--- /dev/null
+++ b/appl/wm/brutus/table.b
@@ -0,0 +1,1478 @@
+implement Brutusext;
+
+# <Extension table tablefile>
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Point, Font, Rect: import draw;
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "bufio.m";
+
+include "string.m";
+ S: String;
+
+include "html.m";
+ html: HTML;
+ Lex, Attr, RBRA, Data, Ttable, Tcaption, Tcol, Ttr, Ttd: import html;
+
+include "brutus.m";
+ Size6, Size8, Size10, Size12, Size16, NSIZE,
+ Roman, Italic, Bold, Type, NFONT, NFONTTAG,
+ Example, List, Listelem, Heading, Nofill, Author, Title,
+ DefFont, DefSize, TitleFont, TitleSize, HeadingFont, HeadingSize: import Brutus;
+
+include "brutusext.m";
+
+Name: con "Table";
+
+# alignment types
+Anone, Aleft, Acenter, Aright, Ajustify, Atop, Amiddle, Abottom, Abaseline: con iota;
+
+# A cell has a number of Lines, each of which has a number of Items.
+# Each Item is a string in one font.
+Item: adt
+{
+ itemid: int; # canvas text item id
+ s: string;
+ fontnum: int; # (style*NumSizes + size)
+ pos: Point; # nw corner of text item, relative to line origin
+ width: int; # of s, in pixels, when displayed in font
+ line: cyclic ref Line; # containing line
+ prev: cyclic ref Item;
+ next: cyclic ref Item;
+};
+
+Line: adt
+{
+ items: cyclic ref Item;
+ pos: Point; # nw corner of Line relative to containing cell;
+ height: int;
+ ascent: int;
+ width: int;
+ cell: cyclic ref Tablecell; # containing cell
+ next: cyclic ref Line;
+};
+
+Align: adt
+{
+ halign: int;
+ valign: int;
+};
+
+Tablecell: adt
+{
+ cellid: int;
+ content: array of ref Lex;
+ lines: cyclic ref Line;
+ rowspan: int;
+ colspan: int;
+ nowrap: int;
+ align: Align;
+ width: int;
+ height: int;
+ ascent: int;
+ row: int;
+ col: int;
+ pos: Point; # nw corner of cell, in canvas coords
+};
+
+Tablegcell: adt
+{
+ cell: ref Tablecell;
+ drawnhere: int;
+};
+
+Tablerow: adt
+{
+ cells: list of ref Tablecell;
+ height: int;
+ ascent: int;
+ align: Align;
+ pos: Point;
+ rule: int; # width of rule below row, if > 0
+ ruleids: list of int; # canvas ids of lines used to draw rule
+};
+
+Tablecol: adt
+{
+ width: int;
+ align: Align;
+ pos: Point;
+ rule: int; # width of rule to right of col, if > 0
+ ruleids: list of int; # canvas ids of lines used to draw rule
+};
+
+Table: adt
+{
+ nrow: int;
+ ncol: int;
+ ncell: int;
+ width: int;
+ height: int;
+ capcell: ref Tablecell;
+ border: int;
+ brectid: int;
+ cols: array of ref Tablecol;
+ rows: array of ref Tablerow;
+ cells: list of ref Tablecell;
+ grid: array of array of ref Tablegcell;
+ colw: array of int;
+ rowh: array of int;
+};
+
+# Font stuff
+
+DefaultFnum: con (DefFont*NSIZE + Size10);
+
+fontnames := array[NFONTTAG] of {
+ "/fonts/lucidasans/unicode.6.font",
+ "/fonts/lucidasans/unicode.7.font",
+ "/fonts/lucidasans/unicode.8.font",
+ "/fonts/lucidasans/unicode.10.font",
+ "/fonts/lucidasans/unicode.13.font",
+ "/fonts/lucidasans/italiclatin1.6.font",
+ "/fonts/lucidasans/italiclatin1.7.font",
+ "/fonts/lucidasans/italiclatin1.8.font",
+ "/fonts/lucidasans/italiclatin1.10.font",
+ "/fonts/lucidasans/italiclatin1.13.font",
+ "/fonts/lucidasans/boldlatin1.6.font",
+ "/fonts/lucidasans/boldlatin1.7.font",
+ "/fonts/lucidasans/boldlatin1.8.font",
+ "/fonts/lucidasans/boldlatin1.10.font",
+ "/fonts/lucidasans/boldlatin1.13.font",
+ "/fonts/lucidasans/typelatin1.6.font",
+ "/fonts/lucidasans/typelatin1.7.font",
+ "/fonts/pelm/latin1.9.font",
+ "/fonts/pelm/ascii.12.font",
+ "/fonts/pelm/ascii.16.font"
+};
+
+fontrefs := array[NFONTTAG] of ref Font;
+fontused := array[NFONTTAG] of { DefaultFnum => 1, * => 0};
+
+# TABHPAD, TABVPAD are extra space between columns, rows
+TABHPAD: con 10;
+TABVPAD: con 4;
+
+tab: ref Table;
+top: ref Tk->Toplevel;
+display: ref Draw->Display;
+canv: string;
+
+init(asys: Sys, adraw: Draw, nil: Bufio, atk: Tk, aw: Tkclient)
+{
+ sys = asys;
+ draw = adraw;
+ tk = atk;
+ tkclient = aw;
+ html = load HTML HTML->PATH;
+ S = load String String->PATH;
+}
+
+create(parent: string, t: ref Tk->Toplevel, name, args: string): string
+{
+ if(html == nil)
+ return "can't load HTML module";
+ top = t;
+ display = t.image.display;
+ canv = name;
+ err := tk->cmd(t, "canvas " + canv);
+ if(len err > 0 && err[0] == '!')
+ return err_ret(err);
+
+ spec: array of ref Lex;
+ (spec, err) = getspec(parent, args);
+ if(err != "")
+ return err_ret(err);
+
+ err = parsetab(spec);
+ if(err != "")
+ return err_ret(err);
+
+ err = build();
+ if(err != "")
+ return err_ret(err);
+ return "";
+}
+
+err_ret(s: string) : string
+{
+ return Name + ": " + s;
+}
+
+getspec(parent, args: string) : (array of ref Lex, string)
+{
+ (n, argl) := sys->tokenize(args, " ");
+ if(n != 1)
+ return (nil, "usage: " + Name + " file");
+ (filebytes, err) := readfile(fullname(parent, hd argl));
+ if(err != "")
+ return (nil, err);
+ return(html->lex(filebytes, HTML->UTF8, 1), "");
+}
+
+readfile(path: string): (array of byte, string)
+{
+ fd := sys->open(path, sys->OREAD);
+ if(fd == nil)
+ return (nil, sys->sprint("can't open %s, the error was: %r", path));
+ (ok, d) := sys->fstat(fd);
+ if(ok < 0)
+ return (nil, sys->sprint("can't stat %s, the error was: %r", path));
+ if(d.mode & Sys->DMDIR)
+ return (nil, sys->sprint("%s is a directory", path));
+
+ l := int d.length;
+ buf := array[l] of byte;
+ tot := 0;
+ while(tot < l) {
+ need := l - tot;
+ n := sys->read(fd, buf[tot:], need);
+ if(n <= 0)
+ return (nil, sys->sprint("error reading %s, the error was: %r", path));
+ tot += n;
+ }
+ return (buf, "");
+}
+
+# Use HTML 3.2 table spec as external representation
+# (But no th cells, width specs; and extra "rule" attribute
+# for col and tr meaning that a rule of given width is to
+# follow the given column or row).
+# DTD elements:
+# table: - O (caption?, col*, tr*)
+# caption: - - (%text+)
+# col: - O empty
+# tr: - O td*
+# td: - O (%body.content)
+parsetab(toks: array of ref Lex) : string
+{
+ tabletlex := toks[0];
+ n := len toks;
+ (tlex, i) := nexttok(toks, n, 0);
+
+ # caption
+ capcell: ref Tablecell = nil;
+ if(tlex != nil && tlex.tag == Tcaption) {
+ for(j := i+1; j < n; j++) {
+ tlex = toks[j];
+ if(tlex.tag == Tcaption + RBRA)
+ break;
+ }
+ if(j >= n)
+ return syntax_err(tlex, j);
+ if(j > i+1) {
+ captoks := toks[i+1:j];
+ (caplines, e) := lexes2lines(captoks);
+ if(e != nil)
+ return e;
+ # we ignore caption now
+# capcell = ref Tablecell(0, captoks, caplines, 1, 1, 1, Align(Anone, Anone),
+# 0, 0, 0, 0, 0, Point(0,0));
+ }
+ (tlex, i) = nexttok(toks, n, j);
+ }
+
+ # col*
+ cols: list of ref Tablecol = nil;
+ while(tlex != nil && tlex.tag == Tcol) {
+ col := makecol(tlex);
+ if(col.align.halign == Anone)
+ col.align.halign = Aleft;
+ cols = col :: cols;
+ (tlex, i) = nexttok(toks, n, i);
+ }
+ cols = revcols(cols);
+
+ body : list of ref Tablerow = nil;
+ cells : list of ref Tablecell = nil;
+ cellid := 0;
+ rows: list of ref Tablerow = nil;
+
+ # tr*
+ while(tlex != nil && tlex.tag == Ttr) {
+ currow := ref Tablerow(nil, 0, 0, makealign(tlex), Point(0,0), makelinew(tlex, "rule"), nil);
+ rows = currow :: rows;
+
+ # td*
+ (tlex, i) = nexttok(toks, n, i);
+ while(tlex != nil && tlex.tag == Ttd) {
+ rowspan := 1;
+ (rsfnd, rs) := html->attrvalue(tlex.attr, "rowspan");
+ if(rsfnd && rs != "")
+ rowspan = int rs;
+ colspan := 1;
+ (csfnd, cs) := html->attrvalue(tlex.attr, "colspan");
+ if(csfnd && cs != "")
+ colspan = int cs;
+ nowrap := 0;
+ (nwfnd, nil) := html->attrvalue(tlex.attr, "nowrap");
+ if(nwfnd)
+ nowrap = 1;
+ align := makealign(tlex);
+ for(j := i+1; j < n; j++) {
+ tlex = toks[j];
+ tg := tlex.tag;
+ if(tg == Ttd + RBRA || tg == Ttd || tg == Ttr + RBRA || tg == Ttr)
+ break;
+ }
+ if(j == n)
+ tlex = nil;
+ content: array of ref Lex = nil;
+ if(j > i+1)
+ content = toks[i+1:j];
+ (lines, err) := lexes2lines(content);
+ if(err != "")
+ return err;
+ curcell := ref Tablecell(cellid, content, lines, rowspan, colspan, nowrap, align, 0, 0, 0, 0, 0, Point(0,0));
+ currow.cells = curcell :: currow.cells;
+ cells = curcell :: cells;
+ cellid++;
+ if(tlex != nil && tlex.tag == Ttd + RBRA)
+ (tlex, i) = nexttok(toks, n, j);
+ else
+ i = j;
+ }
+ if(tlex != nil && tlex.tag == Ttr + RBRA)
+ (tlex, i) = nexttok(toks, n, i);
+ }
+ if(tlex == nil || tlex.tag != Ttable + RBRA)
+ return syntax_err(tlex, i);
+
+ # now reverse all the lists that were built in reverse order
+ # and calculate nrow, ncol
+
+ rows = revrowl(rows);
+ nrow := len rows;
+ rowa := array[nrow] of ref Tablerow;
+ ncol := 0;
+ r := 0;
+ for(rl := rows; rl != nil; rl = tl rl) {
+ row := hd rl;
+ rowa[r++] = row;
+ rcols := 0;
+ cl := row.cells;
+ 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;
+ }
+ cells = revcelll(cells);
+
+ cola := array[ncol] of ref Tablecol;
+ for(c := 0; c < ncol; c++) {
+ if(cols != nil) {
+ cola[c] = hd cols;
+ cols = tl cols;
+ }
+ else
+ cola[c] = ref Tablecol(0, Align(Anone, Anone), Point(0,0), 0, nil);
+ }
+
+ if(tabletlex.tag != Ttable)
+ return syntax_err(tabletlex, 0);
+ border := makelinew(tabletlex, "border");
+ tab = ref Table(nrow, ncol, cellid, 0, 0, capcell, border, 0, cola, rowa, cells, nil, nil, nil);
+
+ return "";
+}
+
+syntax_err(tlex: ref Lex, i: int) : string
+{
+ if(tlex == nil)
+ return "syntax error in table: premature end";
+ else
+ return "syntax error in table at token " + string i + ": " + html->lex2string(tlex);
+}
+
+# next token after toks[i], skipping whitespace
+nexttok(toks: array of ref Lex, ntoks, i: int) : (ref Lex, int)
+{
+ i++;
+ if(i >= ntoks)
+ return (nil, i);
+ t := toks[i];
+ while(t.tag == Data) {
+ if(S->drop(t.text, " \t\n\r") != "")
+ break;
+ i++;
+ if(i >= ntoks)
+ return (nil, i);
+ t = toks[i];
+ }
+# sys->print("nexttok returning (%s,%d)\n", html->lex2string(t), i);
+ return(t, i);
+}
+
+makecol(tlex: ref Lex) : ref Tablecol
+{
+ return ref Tablecol(0, makealign(tlex), Point(0,0), makelinew(tlex, "rule"), nil);
+}
+
+makelinew(tlex: ref Lex, aname: string) : int
+{
+ ans := 0;
+ (fnd, val) := html->attrvalue(tlex.attr, aname);
+ if(fnd) {
+ if(val == "")
+ ans = 1;
+ else
+ ans = int val;
+ }
+ return ans;
+}
+
+makealign(tlex: ref Lex) : Align
+{
+ (nil,h) := html->attrvalue(tlex.attr, "align");
+ (nil,v) := html->attrvalue(tlex.attr, "valign");
+ hal := align_val(h, Anone);
+ val := align_val(v, Anone);
+ return Align(hal, val);
+}
+
+align_val(sal: string, dflt: int) : int
+{
+ ans := dflt;
+ case sal {
+ "left" => ans = Aleft;
+ "center" => ans = Acenter;
+ "right" => ans = Aright;
+ "justify" => ans = Ajustify;
+ "top" => ans = Atop;
+ "middle" => ans = Amiddle;
+ "bottom" => ans = Abottom;
+ "baseline" => ans = Abaseline;
+ }
+ return ans;
+}
+
+revcols(l : list of ref Tablecol) : list of ref Tablecol
+{
+ ans : list of ref Tablecol = nil;
+ while(l != nil) {
+ ans = hd l :: ans;
+ l = tl l;
+ }
+ return ans;
+}
+
+revrowl(l : list of ref Tablerow) : list of ref Tablerow
+{
+ ans : list of ref Tablerow = nil;
+ while(l != nil) {
+ ans = hd l :: ans;
+ l = tl l;
+ }
+ return ans;
+}
+
+revcelll(l : list of ref Tablecell) : list of ref Tablecell
+{
+ ans : list of ref Tablecell = nil;
+ while(l != nil) {
+ ans = hd l :: ans;
+ l = tl l;
+ }
+ return ans;
+}
+
+revintl(l : list of int) : list of int
+{
+ ans : list of int = nil;
+ while(l != nil) {
+ ans = hd l :: ans;
+ l = tl l;
+ }
+ return ans;
+}
+
+# toks should contain only Font (i.e., size) and style changes, along with text.
+lexes2lines(toks: array of ref Lex) : (ref Line, string)
+{
+ n := len toks;
+ (tlex, i) := nexttok(toks, n, -1);
+ ans: ref Line = nil;
+ if(tlex == nil)
+ return(ans, "");
+ curline : ref Line = nil;
+ curitem : ref Item = nil;
+ stylestk := DefFont :: nil;
+ sizestk := DefSize :: nil;
+ f := DefaultFnum;
+ fontstk:= f :: nil;
+ for(;;) {
+ if(i >= n)
+ break;
+ tlex = toks[i++];
+ case tlex.tag {
+ Data =>
+ text := tlex.text;
+ while(text != "") {
+ if(curline == nil) {
+ curline = ref Line(nil, Point(0,0), 0, 0, 0, nil, nil);
+ ans = curline;
+ }
+ s : string;
+ (s, text) = S->splitl(text, "\n");
+ if(s != "") {
+ f = hd fontstk;
+ it := ref Item(0, s, f, Point(0,0), 0, curline, curitem, nil);
+ if(curitem == nil)
+ curline.items = it;
+ else
+ curitem.next = it;
+ curitem = it;
+ }
+ if(text != "") {
+ text = text[1:];
+ curline.next = ref Line(nil, Point(0,0), 0, 0, 0, nil, nil);
+ curline = curline.next;
+ curitem = nil;
+ }
+ }
+ HTML->Tfont =>
+ (fnd, ssize) := html->attrvalue(tlex.attr, "size");
+ if(fnd && len ssize > 0) {
+ # HTML size 3 == our Size10
+ sz := (int ssize) + (Size10 - 3);
+ if(sz < 0 || sz >= NSIZE)
+ return (nil, "bad font size " + ssize);
+ sizestk = sz :: sizestk;
+ fontstk = fnum(hd stylestk, sz) :: fontstk;
+ }
+ else
+ return (nil, "bad font command: no size");
+ HTML->Tfont + RBRA =>
+ fontstk = tl fontstk;
+ sizestk = tl sizestk;
+ if(sizestk == nil)
+ return (nil, "unmatched </FONT>");
+ HTML->Tb =>
+ stylestk = Bold :: stylestk;
+ fontstk = fnum(Bold, hd sizestk) :: fontstk;
+ HTML->Ti =>
+ stylestk = Italic :: stylestk;
+ fontstk = fnum(Italic, hd sizestk) :: fontstk;
+ HTML->Ttt =>
+ stylestk = Type :: stylestk;
+ fontstk = fnum(Type, hd sizestk) :: fontstk;
+ HTML->Tb + RBRA or HTML->Ti + RBRA or HTML->Ttt + RBRA =>
+ fontstk = tl fontstk;
+ stylestk = tl stylestk;
+ if(stylestk == nil)
+ return (nil, "unmatched </B>, </I>, or </TT>");
+ }
+ }
+ return (ans, "");
+}
+
+fnum(fstyle, fsize: int) : int
+{
+ ans := fstyle*NSIZE + fsize;
+ fontused[ans] = 1;
+ return ans;
+}
+
+loadfonts() : string
+{
+ for(i := 0; i < NFONTTAG; i++) {
+ if(fontused[i] && fontrefs[i] == nil) {
+ fname := fontnames[i];
+ f := Font.open(display, fname);
+ if(f == nil)
+ return sys->sprint("can't open font %s: %r", fname);
+ fontrefs[i] = f;
+ }
+ }
+ return "";
+}
+
+# Find where each cell goes in nrow x ncol grid
+setgrid()
+{
+ gcells := array[tab.nrow] of { * => array[tab.ncol] of { * => ref Tablegcell(nil, 1)} };
+
+ # 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[tab.ncol] of { * => 0};
+ rowspancell := array[tab.ncol] of ref Tablecell;
+
+ ri := 0;
+ ci := 0;
+ for(ri = 0; ri < tab.nrow; ri++) {
+ row := tab.rows[ri];
+ cl := row.cells;
+ for(ci = 0; ci < tab.ncol; ) {
+ if(rowspancnt[ci] > 0) {
+ gcells[ri][ci].cell = rowspancell[ci];
+ gcells[ri][ci].drawnhere = 0;
+ rowspancnt[ci]--;
+ ci++;
+ }
+ else {
+ if(cl == nil) {
+ ci++;
+ continue;
+ }
+ c := hd cl;
+ cl = tl cl;
+ cspan := c.colspan;
+ if(cspan == 0) {
+ cspan = tab.ncol - ci;
+ c.colspan = cspan;
+ }
+ rspan := c.rowspan;
+ if(rspan == 0) {
+ rspan = tab.nrow - ri;
+ c.rowspan = rspan;
+ }
+ c.row = ri;
+ c.col = ci;
+ for(i := 0; i < cspan && ci < tab.ncol; i++) {
+ gcells[ri][ci].cell = c;
+ if(i > 0)
+ gcells[ri][ci].drawnhere = 0;
+ if(rspan > 1) {
+ rowspancnt[ci] = rspan-1;
+ rowspancell[ci] = c;
+ }
+ ci++;
+ }
+ }
+ }
+ }
+ tab.grid = gcells;
+}
+
+build() : string
+{
+ ri, ci: int;
+
+# sys->print("\n\ninitial table\n"); printtable();
+ if(tab.ncol == 0 || tab.nrow == 0)
+ return "";
+
+ setgrid();
+
+ err := loadfonts();
+ if(err != "")
+ return err;
+
+ for(cl := tab.cells; cl != nil; cl = tl cl)
+ cell_geom(hd cl);
+
+ for(ci = 0; ci < tab.ncol; ci++)
+ col_geom(ci);
+
+ for(ri = 0; ri < tab.nrow; ri++)
+ row_geom(ri);
+
+ caption_geom();
+
+ table_geom();
+# sys->print("\n\ntable after geometry set\n"); printtable();
+
+ h := tab.height;
+ w := tab.width;
+ if(tab.capcell != nil) {
+ h += tab.capcell.height;
+ if(tab.capcell.width > w)
+ w = tab.capcell.width;
+ }
+
+ err = tk->cmd(top, canv + " configure -width " + string w
+ + " -height " + string h);
+ if(len err > 0 && err[0] == '!')
+ return err;
+ err = create_cells();
+ if(err != "")
+ return err;
+ err = create_border();
+ if(err != "")
+ return err;
+ err = create_rules();
+ if(err != "")
+ return err;
+ err = create_caption();
+ if(err != "")
+ return err;
+ tk->cmd(top, "update");
+
+ return "";
+}
+
+create_cells() : string
+{
+ for(cl := tab.cells; cl != nil; cl = tl cl) {
+ c := hd cl;
+ cpos := c.pos;
+ for(l := c.lines; l != nil; l = l.next) {
+ lpos := l.pos;
+ for(it := l.items; it != nil; it = it.next) {
+ ipos := it.pos;
+ pos := ipos.add(lpos.add(cpos));
+ fnt := fontrefs[it.fontnum];
+ v := tk->cmd(top, canv + " create text " + string pos.x + " "
+ + string pos.y + " -anchor nw -font " + fnt.name
+ + " -text '" + it.s);
+ if(len v > 0 && v[0] == '!')
+ return v;
+ it.itemid = int v;
+ }
+ }
+ }
+ return "";
+}
+
+create_border() : string
+{
+ bd := tab.border;
+ if(bd > 0) {
+ x1 := string (bd / 2);
+ y1 := x1;
+ x2 := string (tab.width - bd/2 -1);
+ y2 := string (tab.height - bd/2 -1);
+ v := tk->cmd(top, canv + " create rectangle "
+ + x1 + " " + y1 + " " + x2 + " " + y2 + " -width " + string bd);
+ if(len v > 0 && v[0] == '!')
+ return v;
+ tab.brectid = int v;
+ }
+ return "";
+}
+
+create_rules() : string
+{
+ ci, ri, i: int;
+ err : string;
+ c : ref Tablecell;
+ for(ci = 0; ci < tab.ncol; ci++) {
+ col := tab.cols[ci];
+ rw := col.rule;
+ if(rw > 0) {
+ x := col.pos.x + col.width + TABHPAD/2 - rw/2;
+ ids: list of int = nil;
+ startri := 0;
+ for(ri = 0; ri < tab.nrow; ri++) {
+ c = tab.grid[ri][ci].cell;
+ if(c.col+c.colspan-1 > ci) {
+ # rule would cross a spanning cell at this column
+ if(ri > startri) {
+ (err, i) = create_col_rule(startri, ri-1, x, rw);
+ if(err != "")
+ return err;
+ ids = i :: ids;
+ }
+ startri = ri+1;
+ }
+ }
+ if(ri > startri)
+ (err, i) = create_col_rule(startri, ri-1, x, rw);
+ ids = i :: ids;
+ col.ruleids = revintl(ids);
+ }
+ }
+ for(ri = 0; ri < tab.nrow; ri++) {
+ row := tab.rows[ri];
+ rw := row.rule;
+ if(rw > 0) {
+ y := row.pos.y + row.height + TABVPAD/2 - rw/2;
+ ids: list of int = nil;
+ startci := 0;
+ for(ci = 0; ci < tab.ncol; ci++) {
+ c = tab.grid[ri][ci].cell;
+ if(c.row+c.rowspan-1 > ri) {
+ # rule would cross a spanning cell at this row
+ if(ci > startci) {
+ (err, i) = create_row_rule(startci, ci-1, y, rw);
+ if(err != "")
+ return err;
+ ids = i :: ids;
+ }
+ startci = ci+1;
+ }
+ }
+ if(ci > startci)
+ (err, i) = create_row_rule(startci, ci-1, y, rw);
+ ids = i :: ids;
+ row.ruleids = revintl(ids);
+ }
+ }
+ return "";
+}
+
+create_col_rule(topri, botri, x, rw: int) : (string, int)
+{
+ y1, y2: int;
+ if(topri == 0)
+ y1 = 0;
+ else
+ y1 = tab.rows[topri].pos.y - TABVPAD/2;
+ if(botri == tab.nrow-1)
+ y2 = tab.height;
+ else
+ y2 = tab.rows[botri].pos.y + tab.rows[botri].height + TABVPAD/2;
+ sx := string x;
+ v := tk->cmd(top, canv + " create line " + sx + " "
+ + string y1 + " " + sx + " " + string y2 + " -width " + string rw);
+ if(len v > 0 && v[0] == '!')
+ return (v, 0);
+ return ("", int v);
+}
+
+create_row_rule(leftci, rightci, y, rw: int) : (string, int)
+{
+ x1, x2: int;
+ if(leftci == 0)
+ x1 = 0;
+ else
+ x1 = tab.cols[leftci].pos.x - TABHPAD/2;
+ if(rightci == tab.ncol-1)
+ x2 = tab.width;
+ else
+ x2 = tab.cols[rightci].pos.x + tab.cols[rightci].width + TABHPAD/2;
+ sy := string y;
+ v := tk->cmd(top, canv + " create line " + string x1 + " "
+ + sy + " " + string x2 + " " + sy + " -width " + string rw);
+ if(len v > 0 && v[0] == '!')
+ return (v, 0);
+ return ("", int v);
+}
+
+create_caption() : string
+{
+ if(tab.capcell == nil)
+ return "";
+ cpos := Point(0, tab.height + 2*TABVPAD);
+ for(l := tab.capcell.lines; l != nil; l = l.next) {
+ lpos := l.pos;
+ for(it := l.items; it != nil; it = it.next) {
+ ipos := it.pos;
+ pos := ipos.add(lpos.add(cpos));
+ fnt := fontrefs[it.fontnum];
+ v := tk->cmd(top, canv + " create text " + string pos.x + " "
+ + string pos.y + " -anchor nw -font " + fnt.name
+ + " -text '" + it.s);
+ if(len v > 0 && v[0] == '!')
+ return v;
+ it.itemid = int v;
+ }
+ }
+ return "";
+}
+
+# Assuming row and col geoms correct, set row, col, and cell origins
+table_geom()
+{
+ row: ref Tablerow;
+ col: ref Tablecol;
+ orig := Point(0,0);
+ bd := tab.border;
+ if(bd > 0)
+ orig = orig.add(Point(TABHPAD+bd, TABVPAD+bd));
+ o := orig;
+ for(ci := 0; ci < tab.ncol; ci++) {
+ col = tab.cols[ci];
+ col.pos = o;
+ o.x += col.width + col.rule;
+ if(ci < tab.ncol-1)
+ o.x += TABHPAD;
+ }
+ if(bd > 0)
+ o.x += TABHPAD + bd;
+ tab.width = o.x;
+
+ o = orig;
+ for(ri := 0; ri < tab.nrow; ri++) {
+ row = tab.rows[ri];
+ row.pos = o;
+ o.y += row.height + row.rule;
+ if(ri < tab.nrow-1)
+ o.y += TABVPAD;
+ }
+ if(bd > 0)
+ o.y += TABVPAD + bd;
+ tab.height = o.y;
+
+ if(tab.capcell != nil) {
+ tabw := tab.width;
+ if(tab.capcell.width > tabw)
+ tabw = tab.capcell.width;
+ for(l := tab.capcell.lines; l != nil; l = l.next)
+ l.pos.x += (tabw - l.width)/2;
+ }
+
+ for(cl := tab.cells; cl != nil; cl = tl cl) {
+ c := hd cl;
+ row = tab.rows[c.row];
+ col = tab.cols[c.col];
+ x := col.pos.x;
+ y := row.pos.y;
+ w := spanned_col_width(c.col, c.col+c.colspan-1);
+ case (cellhalign(c)) {
+ Aright =>
+ x += w - c.width;
+ Acenter =>
+ x += (w - c.width) / 2;
+ }
+ h := spanned_row_height(c.row, c.row+c.rowspan-1);
+ case (cellvalign(c)) {
+ Abottom =>
+ y += h - c.height;
+ Anone or Amiddle =>
+ y += (h - c.height) / 2;
+ Abaseline =>
+ y += row.ascent - c.ascent;
+ }
+ c.pos = Point(x,y);
+ }
+}
+
+spanned_col_width(firstci, lastci: int) : int
+{
+ firstcol := tab.cols[firstci];
+ if(firstci == lastci)
+ return firstcol.width;
+ lastcol := tab.cols[lastci];
+ return (lastcol.pos.x + lastcol.width - firstcol.pos.x);
+}
+
+spanned_row_height(firstri, lastri: int) : int
+{
+ firstrow := tab.rows[firstri];
+ if(firstri == lastri)
+ return firstrow.height;
+ lastrow := tab.rows[lastri];
+ return (lastrow.pos.y + lastrow.height - firstrow.pos.y);
+}
+
+# Assuming cell geoms are correct, set col widths.
+# This code is sloppy for spanned columns;
+# it will allocate too much space for them because
+# inter-column pad is ignored, and it may make
+# narrow columns wider than they have to be.
+col_geom(ci: int)
+{
+ col := tab.cols[ci];
+ col.width = 0;
+ for(ri := 0; ri < tab.nrow; ri++) {
+ c := tab.grid[ri][ci].cell;
+ if(c == nil)
+ continue;
+ cwd := c.width / c.colspan;
+ if(cwd > col.width)
+ col.width = cwd;
+ }
+}
+
+# Assuming cell geoms are correct, set row heights
+row_geom(ri: int)
+{
+ row := tab.rows[ri];
+ # find rows's global height and ascent
+ h := 0;
+ a := 0;
+ n : int;
+ for(cl := row.cells; cl != nil; cl = tl cl) {
+ c := hd cl;
+ al := cellvalign(c);
+ if(al == Abaseline) {
+ n = c.ascent;
+ if(n > a) {
+ h += (n - a);
+ a = n;
+ }
+ n = c.height - c.ascent;
+ if(n > h-a)
+ h = a + n;
+ }
+ else {
+ n = c.height;
+ if(n > h)
+ h = n;
+ }
+ }
+ row.height = h;
+ row.ascent = a;
+}
+
+cell_geom(c: ref Tablecell)
+{
+ width := 0;
+ o := Point(0,0);
+ for(l := c.lines; l != nil; l = l.next) {
+ line_geom(l, o);
+ o.y += l.height;
+ if(l.width > width)
+ width = l.width;
+ }
+ c.width = width;
+ c.height = o.y;
+ if(c.lines != nil)
+ c.ascent = c.lines.ascent;
+ else
+ c.ascent = 0;
+
+ al := cellhalign(c);
+ if(al == Acenter || al == Aright) {
+ for(l = c.lines; l != nil; l = l.next) {
+ xdelta := c.width - l.width;
+ if(al == Acenter)
+ xdelta /= 2;
+ l.pos.x += xdelta;
+ }
+ }
+}
+
+caption_geom()
+{
+ if(tab.capcell != nil) {
+ o := Point(0,TABVPAD);
+ width := 0;
+ for(l := tab.capcell.lines; l != nil; l = l.next) {
+ line_geom(l, o);
+ o.y += l.height;
+ if(l.width > width)
+ width = l.width;
+ }
+ tab.capcell.width = width;
+ tab.capcell.height = o.y + 4*TABVPAD;
+ }
+}
+
+line_geom(l: ref Line, o: Point)
+{
+ # find line's global height and ascent
+ h := 0;
+ a := 0;
+ for(it := l.items; it != nil; it = it.next) {
+ fnt := fontrefs[it.fontnum];
+ n := fnt.ascent;
+ if(n > a) {
+ h += (n - a);
+ a = n;
+ }
+ n = fnt.height - fnt.ascent;
+ if(n > h-a)
+ h = a + n;
+ }
+ l.height = h;
+ l.ascent = a;
+ # set positions
+ l.pos = o;
+ for(it = l.items; it != nil; it = it.next) {
+ fnt := fontrefs[it.fontnum];
+ it.width = fnt.width(it.s);
+ it.pos.x = o.x;
+ o.x += it.width;
+ it.pos.y = a - fnt.ascent;
+ }
+ l.width = o.x;
+}
+
+cellhalign(c: ref Tablecell) : int
+{
+ a := c.align.halign;
+ if(a == Anone)
+ a = tab.cols[c.col].align.halign;
+ return a;
+}
+
+cellvalign(c: ref Tablecell) : int
+{
+ a := c.align.valign;
+ if(a == Anone)
+ a = tab.rows[c.row].align.valign;
+ return a;
+}
+
+# table debugging
+printtable()
+{
+ if(tab == nil) {
+ sys->print("no table\n");
+ return;
+ }
+ sys->print("Table %d rows, %d cols width %d height %d\n",
+ tab.nrow, tab.ncol, tab.width, tab.height);
+ if(tab.capcell != nil)
+ sys->print(" caption: "); printlexes(tab.capcell.content, " ");
+ sys->print(" cols:\n"); printcols(tab.cols);
+ sys->print(" rows:\n"); printrows(tab.rows);
+}
+
+align2string(al: int) : string
+{
+ s := "";
+ case al {
+ Anone => s = "none";
+ Aleft => s = "left";
+ Acenter => s = "center";
+ Aright => s = "right";
+ Ajustify => s = "justify";
+ Atop => s = "top";
+ Amiddle => s = "middle";
+ Abottom => s = "bottom";
+ Abaseline => s = "baseline";
+ }
+ return s;
+}
+
+printcols(cols: array of ref Tablecol)
+{
+ n := len cols;
+ for(i := 0 ; i < n; i++) {
+ c := cols[i];
+ sys->print(" width %d align = %s,%s pos (%d,%d) rule %d\n", c.width,
+ align2string(c.align.halign), align2string(c.align.valign), c.pos.x, c.pos.y, c.rule);
+ }
+}
+
+printrows(rows: array of ref Tablerow)
+{
+ n := len rows;
+ for(i := 0; i < n; i++) {
+ tr := rows[i];
+ sys->print(" row height %d ascent %d align=%s,%s pos (%d,%d) rule %d\n", tr.height, tr.ascent,
+ align2string(tr.align.halign), align2string(tr.align.valign), tr.pos.x, tr.pos.y, tr.rule);
+ for(cl := tr.cells; cl != nil; cl = tl cl) {
+ c := hd cl;
+ sys->print(" cell %d width %d height %d ascent %d align=%s,%s\n",
+ c.cellid, c.width, c.height, c.ascent,
+ align2string(c.align.halign), align2string(c.align.valign));
+ sys->print(" pos (%d,%d) rowspan=%d colspan=%d nowrap=%d\n",
+ c.pos.x, c.pos.y, c.rowspan, c.colspan, c.nowrap);
+ printlexes(c.content, " ");
+ printlines(c.lines);
+ }
+ }
+}
+
+printlexes(lexes: array of ref Lex, indent: string)
+{
+ for(i := 0; i < len lexes; i++)
+ sys->print("%s%s\n", indent, html->lex2string(lexes[i]));
+}
+
+printlines(l: ref Line)
+{
+ if(l == nil)
+ return;
+ sys->print("lines: \n");
+ while(l != nil) {
+ sys->print(" Line: pos (%d,%d), height %d ascent %d\n", l.pos.x, l.pos.y, l.height, l.ascent);
+ printitems(l.items);
+ l = l.next;
+ }
+}
+
+printitems(i: ref Item)
+{
+ while(i != nil) {
+ sys->print(" '%s' id %d fontnum %d w %d, pos (%d,%d)\n", i.s, i.itemid, i.fontnum,
+ i.width, i.pos.x, i.pos.y);
+ i = i.next;
+ }
+}
+
+printgrid(g: array of array of ref Tablegcell)
+{
+ nr := len g;
+ nc := len g[0];
+ for(r := 0; r < nr; r++) {
+ for(c := 0; c < nc; c++) {
+ x := g[r][c];
+ cell := x.cell;
+ suf := " ";
+ if(x.drawnhere == 0)
+ suf = "*";
+ if(cell == nil)
+ sys->print(" %s", suf);
+ else
+ sys->print("%5d%s", cell.cellid, suf);
+ }
+ sys->print("\n");
+ }
+}
+
+# Return (table in correct format, error string)
+cook(parent: string, fmt: int, args: string) : (ref Celem, string)
+{
+ (spec, err) := getspec(parent, args);
+ if(err != "")
+ return (nil, err);
+ if(fmt == FHtml)
+ return cookhtml(spec);
+ else
+ return cooklatex(spec);
+}
+
+# Return (table as latex, error string)
+# BUG: cells spanning multiple rows not handled correctly
+# (all their contents go in the first row of span, though hrules properly broken)
+cooklatex(spec: array of ref Lex) : (ref Celem, string)
+{
+ s : string;
+ ci, ri: int;
+ err := parsetab(spec);
+ if(err != "")
+ return (nil, err_ret(err));
+
+ setgrid();
+
+ ans := ref Celem(SGML, "", nil, nil, nil, nil);
+ cur : ref Celem = nil;
+ cur = add(ans, cur, specialce("\\begin{tabular}[t]{" + lcolspec() + "}\n"));
+ if(tab.border) {
+ if(tab.border == 1)
+ s = "\\hline\n";
+ else
+ s = "\\hline\\hline\n";
+ cur = add(ans, cur, specialce(s));
+ }
+ for(ri = 0; ri < tab.nrow; ri++) {
+ row := tab.rows[ri];
+ ci = 0;
+ anyrowspan := 0;
+ for(cl := row.cells; cl != nil; cl = tl cl) {
+ c := hd cl;
+ while(ci < c.col) {
+ cur = add(ans, cur, specialce("&"));
+ ci++;
+ }
+ mcol := 0;
+ if(c.colspan > 1) {
+ cur = add(ans, cur, specialce("\\multicolumn{" + string c.colspan + "}{" +
+ lnthcolspec(ci, ci+c.colspan-1, c.align.halign) + "}{"));
+ mcol = 1;
+ }
+ else if(c.align.halign != Anone) {
+ cur = add(ans, cur, specialce("\\multicolumn{1}{" +
+ lnthcolspec(ci, ci, c.align.halign) + "}{"));
+ mcol = 1;
+ }
+ if(c.rowspan > 1)
+ anyrowspan = 1;
+ cur = addlconvlines(ans, cur, c);
+ if(mcol) {
+ cur = add(ans, cur, specialce("}"));
+ ci += c.colspan-1;
+ }
+ }
+ while(ci++ < tab.ncol-1)
+ cur = add(ans, cur, specialce("&"));
+ if(ri < tab.nrow-1 || row.rule > 0 || tab.border > 0)
+ cur = add(ans, cur, specialce("\\\\\n"));
+ if(row.rule) {
+ if(anyrowspan) {
+ startci := 0;
+ for(ci = 0; ci < tab.ncol; ci++) {
+ c := tab.grid[ri][ci].cell;
+ if(c.row+c.rowspan-1 > ri) {
+ # rule would cross a spanning cell at this row
+ if(ci > startci)
+ cur = add(ans, cur, specialce("\\cline{" +
+ string (startci+1) + "-" + string ci + "}"));
+ startci = ci+1;
+ }
+ }
+ if(ci > startci)
+ cur = add(ans, cur, specialce("\\cline{" +
+ string (startci+1) + "-" + string ci + "}"));
+ }
+ else
+ cur = add(ans, cur, specialce("\\hline\n"));
+ }
+ }
+ if(tab.border) {
+ if(tab.border == 1)
+ s = "\\hline\n";
+ else
+ s = "\\hline\\hline\n";
+ cur = add(ans, cur, specialce(s));
+ }
+ cur = add(ans, cur, specialce("\\end{tabular}\n"));
+
+ if(ans != nil)
+ ans = ans.contents;
+ return (ans, "");
+}
+
+lcolspec() : string
+{
+ ans := "";
+ for(ci := 0; ci < tab.ncol; ci++)
+ ans += lnthcolspec(ci, ci, Anone);
+ return ans;
+}
+
+lnthcolspec(ci, cie, al: int) : string
+{
+ ans := "";
+ if(ci == 0) {
+ if(tab.border == 1)
+ ans = "|";
+ else if(tab.border > 1)
+ ans = "||";
+ }
+ col := tab.cols[ci];
+ if(al == Anone)
+ al = col.align.halign;
+ case al {
+ Acenter =>
+ ans += "c";
+ Aright =>
+ ans += "r";
+ * =>
+ ans += "l";
+ }
+ if(ci == cie) {
+ if(col.rule == 1)
+ ans += "|";
+ else if(col.rule > 1)
+ ans += "||";
+ }
+ if(cie == tab.ncol - 1) {
+ if(tab.border == 1)
+ ans += "|";
+ else if(tab.border > 1)
+ ans += "||";
+ }
+ return ans;
+}
+
+addlconvlines(par, tail: ref Celem, c: ref Tablecell) : ref Celem
+{
+ line := c.lines;
+ if(line == nil)
+ return tail;
+ multiline := 0;
+ if(line.next != nil) {
+ multiline = 1;
+ val := "";
+ case cellvalign(c) {
+ Abaseline or Atop => val = "[t]";
+ Abottom => val = "[b]";
+ }
+ hal := "l";
+ case cellhalign(c) {
+ Aright => hal = "r";
+ Acenter => hal = "c";
+ }
+ # The @{}'s in the colspec eliminate extra space before and after result
+ tail = add(par, tail, specialce("\\begin{tabular}" + val + "{@{}" + hal + "@{}}\n"));
+ }
+ while(line != nil) {
+ for(it := line.items; it != nil; it = it.next) {
+ fnum := it.fontnum;
+ f := fnum / NSIZE;
+ sz := fnum % NSIZE;
+ grouped := 0;
+ if((f != DefFont || sz != DefSize) && (it.prev!=nil || it.next!=nil)) {
+ tail = add(par, tail, specialce("{"));
+ grouped = 1;
+ }
+ if(f != DefFont) {
+ fcmd := "";
+ case f {
+ Roman => fcmd = "\\rmfamily ";
+ Italic => fcmd = "\\itshape ";
+ Bold => fcmd = "\\bfseries ";
+ Type => fcmd = "\\ttfamily ";
+ }
+ tail = add(par, tail, specialce(fcmd));
+ }
+ if(sz != DefSize) {
+ szcmd := "";
+ case sz {
+ Size6 => szcmd = "\\footnotesize ";
+ Size8 => szcmd = "\\small ";
+ Size10 => szcmd = "\\normalsize ";
+ Size12 => szcmd = "\\large ";
+ Size16 => szcmd = "\\Large ";
+ }
+ tail = add(par, tail, specialce(szcmd));
+ }
+ tail = add(par, tail, textce(it.s));
+ if(grouped)
+ tail = add(par, tail, specialce("}"));
+ }
+ ln := line.next;
+ if(multiline && ln != nil)
+ tail = add(par, tail, specialce("\\\\\n"));
+ line = line.next;
+ }
+ if(multiline)
+ tail = add(par, tail, specialce("\\end{tabular}\n"));
+ return tail;
+}
+
+# Return (table as html, error string)
+cookhtml(spec: array of ref Lex) : (ref Celem, string)
+{
+ n := len spec;
+ ans := ref Celem(SGML, "", nil, nil, nil, nil);
+ cur : ref Celem = nil;
+ for(i := 0; i < n; i++) {
+ tok := spec[i];
+ if(tok.tag == Data)
+ cur = add(ans, cur, textce(tok.text));
+ else {
+ s := html->lex2string(spec[i]);
+ cur = add(ans, cur, specialce(s));
+ }
+ }
+ if(ans != nil)
+ ans = ans.contents;
+ return (ans, "");
+}
+
+textce(s: string) : ref Celem
+{
+ return ref Celem(Text, s, nil, nil, nil, nil);
+}
+
+specialce(s: string) : ref Celem
+{
+ return ref Celem(Special, s, nil, nil, nil, nil);
+}
+
+add(par, tail: ref Celem, e: ref Celem) : ref Celem
+{
+ if(tail == nil) {
+ par.contents = e;
+ e.parent = par;
+ }
+ else
+ tail.next = e;
+ e.prev = tail;
+ return e;
+}
+
+fullname(parent, file: string): string
+{
+ if(len parent==0 || (len file>0 && (file[0]=='/' || file[0]=='#')))
+ return file;
+
+ for(i:=len parent-1; i>=0; i--)
+ if(parent[i] == '/')
+ return parent[0:i+1] + file;
+ return file;
+}