xref: /inferno-os/appl/charon/layout.b (revision 54041ca415f1fdfa1c3dcd82d524223cfa890940)
1implement Layout;
2
3include "common.m";
4include "keyboard.m";
5
6sys: Sys;
7CU: CharonUtils;
8	ByteSource, MaskedImage, CImage, ImageCache, max, min,
9	White, Black, Grey, DarkGrey, LightGrey, Blue, Navy, Red, Green, DarkRed: import CU;
10
11D: Draw;
12	Point, Rect, Font, Image, Display: import D;
13S: String;
14T: StringIntTab;
15U: Url;
16	Parsedurl: import U;
17I: Img;
18	ImageSource: import I;
19J: Script;
20E: Events;
21	Event: import E;
22G: Gui;
23	Popup: import G;
24B: Build;
25
26# B : Build, declared in layout.m so main program can use it
27	Item, ItemSource,
28	IFbrk, IFbrksp, IFnobrk, IFcleft, IFcright, IFwrap, IFhang,
29	IFrjust, IFcjust, IFsmap, IFindentshift, IFindentmask,
30	IFhangmask,
31	Voffbias,
32	ISPnull, ISPvline, ISPhspace, ISPgeneral,
33	Align, Dimen, Formfield, Option, Form,
34	Table, Tablecol, Tablerow, Tablecell,
35	Anchor, DestAnchor, Map, Area, Kidinfo, Docinfo,
36	Anone, Aleft, Acenter, Aright, Ajustify, Achar, Atop, Amiddle,
37	Abottom, Abaseline,
38	Dnone, Dpixels, Dpercent, Drelative,
39	Ftext, Fpassword, Fcheckbox, Fradio, Fsubmit, Fhidden, Fimage,
40	Freset, Ffile, Fbutton, Fselect, Ftextarea,
41	Background,
42	FntR, FntI, FntB, FntT, NumStyle,
43	Tiny, Small, Normal, Large, Verylarge, NumSize, NumFnt, DefFnt,
44	ULnone, ULunder, ULmid,
45	FRnoresize, FRnoscroll, FRhscroll, FRvscroll,
46	FRhscrollauto, FRvscrollauto
47    : import B;
48
49# font stuff
50Fontinfo : adt {
51	name:	string;
52	f:	ref Font;
53	spw:	int;			# width of a space in this font
54};
55
56fonts := array[NumFnt] of {
57	FntR*NumSize+Tiny => Fontinfo("/fonts/charon/plain.tiny.font", nil, 0),
58	FntR*NumSize+Small => ("/fonts/charon/plain.small.font", nil, 0),
59	FntR*NumSize+Normal => ("/fonts/charon/plain.normal.font", nil, 0),
60	FntR*NumSize+Large => ("/fonts/charon/plain.large.font", nil, 0),
61	FntR*NumSize+Verylarge => ("/fonts/charon/plain.vlarge.font", nil, 0),
62
63	FntI*NumSize+Tiny => ("/fonts/charon/italic.tiny.font", nil, 0),
64	FntI*NumSize+Small => ("/fonts/charon/italic.small.font", nil, 0),
65	FntI*NumSize+Normal => ("/fonts/charon/italic.normal.font", nil, 0),
66	FntI*NumSize+Large => ("/fonts/charon/italic.large.font", nil, 0),
67	FntI*NumSize+Verylarge => ("/fonts/charon/italic.vlarge.font", nil, 0),
68
69	FntB*NumSize+Tiny => ("/fonts/charon/bold.tiny.font", nil, 0),
70	FntB*NumSize+Small => ("/fonts/charon/bold.small.font", nil, 0),
71	FntB*NumSize+Normal => ("/fonts/charon/bold.normal.font", nil, 0),
72	FntB*NumSize+Large => ("/fonts/charon/bold.large.font", nil, 0),
73	FntB*NumSize+Verylarge => ("/fonts/charon/bold.vlarge.font", nil, 0),
74
75	FntT*NumSize+Tiny => ("/fonts/charon/cw.tiny.font", nil, 0),
76	FntT*NumSize+Small => ("/fonts/charon/cw.small.font", nil, 0),
77	FntT*NumSize+Normal => ("/fonts/charon/cw.normal.font", nil, 0),
78	FntT*NumSize+Large => ("/fonts/charon/cw.large.font", nil, 0),
79	FntT*NumSize+Verylarge => ("/fonts/charon/cw.vlarge.font", nil, 0)
80};
81
82# Seems better to use a slightly smaller font in Controls, to match other browsers
83CtlFnt: con (FntR*NumSize+Small);
84
85# color stuff.  have hash table mapping RGB values to D->Image for that color
86Colornode : adt {
87	rgb:	int;
88	im:	ref Image;
89	next:	ref Colornode;
90};
91
92# Source of info for page (html, image, etc.)
93Source: adt {
94	bs:	ref ByteSource;
95	redirects:	int;
96	pick {
97		Srequired or
98		Shtml =>
99			itsrc: ref ItemSource;
100		Simage =>
101			ci: ref CImage;
102			itl: list of ref Item;
103			imsrc: ref ImageSource;
104	}
105};
106
107Sources: adt {
108	main: ref Source;
109	reqd: ref Source;
110	srcs: list of ref Source;
111
112	new: fn(m : ref Source) : ref Sources;
113	add: fn(srcs: self ref Sources, s: ref Source, required: int);
114	done: fn(srcs: self ref Sources, s: ref Source);
115	waitsrc: fn(srcs : self ref Sources) : ref Source;
116};
117
118NCOLHASH : con 19;	# 19 checked for standard colors: only 1 collision
119colorhashtab := array[NCOLHASH] of ref Colornode;
120
121# No line break should happen between adjacent characters if
122# they are 'wordchars' : set in this array, or outside the array range.
123# We include certain punctuation characters that are not traditionally
124# regarded as 'word' characters.
125wordchar := array[16rA0] of {
126	'!' => byte 1,
127	'0'=>byte 1, '1'=>byte 1, '2'=>byte 1, '3'=>byte 1, '4'=>byte 1,
128	'5'=>byte 1, '6'=>byte 1, '7'=>byte 1, '8'=>byte 1, '9'=>byte 1,
129	':'=>byte 1, ';' => byte 1,
130	'?' => byte 1,
131	'A'=>byte 1, 'B'=>byte 1, 'C'=>byte 1, 'D'=>byte 1, 'E'=>byte 1, 'F'=>byte 1,
132	'G'=>byte 1, 'H'=>byte 1, 'I'=>byte 1, 'J'=>byte 1, 'K'=>byte 1, 'L'=>byte 1,
133	'M'=>byte 1, 'N'=>byte 1, 'O'=>byte 1, 'P'=>byte 1, 'Q'=>byte 1, 'R'=>byte 1,
134	'S'=>byte 1, 'T'=>byte 1, 'U'=>byte 1, 'V'=>byte 1, 'W'=>byte 1, 'X'=>byte 1,
135	'Y'=>byte 1, 'Z'=>byte 1,
136	'a'=>byte 1, 'b'=>byte 1, 'c'=>byte 1, 'd'=>byte 1, 'e'=>byte 1, 'f'=>byte 1,
137	'g'=>byte 1, 'h'=>byte 1, 'i'=>byte 1, 'j'=>byte 1, 'k'=>byte 1, 'l'=>byte 1,
138	'm'=>byte 1, 'n'=>byte 1, 'o'=>byte 1, 'p'=>byte 1, 'q'=>byte 1, 'r'=>byte 1,
139	's'=>byte 1, 't'=>byte 1, 'u'=>byte 1, 'v'=>byte 1, 'w'=>byte 1, 'x'=>byte 1,
140	'y'=>byte 1, 'z'=>byte 1,
141	'_'=>byte 1,
142	'\''=>byte 1, '"'=>byte 1, '.'=>byte 1, ','=>byte 1, '('=>byte 1, ')'=>byte 1,
143	* => byte 0
144};
145
146TABPIX: con 30;		# number of pixels in a tab
147CAPSEP: con 5;			# number of pixels separating tab from caption
148SCRBREADTH: con 14;	# scrollbar breadth (normal)
149SCRFBREADTH: con 14;	# scrollbar breadth (inside child frame or select control)
150FRMARGIN: con 0;		# default margin around frames
151RULESP: con 7;			# extra space before and after rules
152POPUPLINES: con 12;	# number of lines in popup select list
153MINSCR: con 6;			# min size in pixels of scrollbar drag widget
154SCRDELTASF: con 10000;	# fixed-point scale factor for scrollbar per-pixel step
155
156# all of the following include room for relief
157CBOXWID: con 14;		# check box width
158CBOXHT: con 12;		# check box height
159ENTVMARGIN : con 4;	# vertical margin inside entry box
160ENTHMARGIN : con 6;	# horizontal margin inside entry box
161SELMARGIN : con 4;		# margin inside select control
162BUTMARGIN: con 4;		# margin inside button control
163PBOXWID: con 10;		# progress box width
164PBOXHT: con 16;		# progress box height
165PBOXBD: con 2;		# progress box border width
166
167TABLEMAXTARGET: con 2000;	# targetwidth to get max width of table cell
168TABLEFLOATTARGET: con 1;	# targetwidth for floating tables
169
170SELBG: con 16r00FFFF;	# aqua
171
172ARPAUSE : con 500;			# autorepeat initial delay (ms)
173ARTICK : con 100;			# autorepeat tick delay (ms)
174
175display: ref D->Display;
176
177dbg := 0;
178dbgtab := 0;
179dbgev := 0;
180linespace := 0;
181lineascent := 0;
182charspace := 0;
183spspace := 0;
184ctllinespace := 0;
185ctllineascent := 0;
186ctlcharspace := 0;
187ctlspspace := 0;
188frameid := 0;
189zp := Point(0,0);
190
191init(cu: CharonUtils)
192{
193	CU = cu;
194	sys = load Sys Sys->PATH;
195	D = load Draw Draw->PATH;
196	S = load String String->PATH;
197	T = load StringIntTab StringIntTab->PATH;
198	U = load Url Url->PATH;
199	if (U != nil)
200		U->init();
201	E = cu->E;
202	G = cu->G;
203	I = cu->I;
204	J = cu->J;
205	B = cu->B;
206	display = G->display;
207
208	# make sure default and control fonts are loaded
209	getfont(DefFnt);
210	fnt := fonts[DefFnt].f;
211	linespace = fnt.height;
212	lineascent = fnt.ascent;
213	charspace = fnt.width("a");	# a kind of average char width
214	spspace = fonts[DefFnt].spw;
215	getfont(CtlFnt);
216	fnt = fonts[CtlFnt].f;
217	ctllinespace = fnt.height;
218	ctllineascent = fnt.ascent;
219	ctlcharspace = fnt.width("a");
220	ctlspspace = fonts[CtlFnt].spw;
221}
222
223stringwidth(s: string): int
224{
225	return fonts[DefFnt].f.width(s)/charspace;
226}
227
228# Use bsmain to fill frame f.
229# Return buffer containing source when done.
230layout(f: ref Frame, bsmain: ref ByteSource, linkclick: int) : array of byte
231{
232	dbg = int (CU->config).dbg['l'];
233	dbgtab = int (CU->config).dbg['t'];
234	dbgev = int (CU->config).dbg['e'];
235	if(dbgev)
236		CU->event("LAYOUT", 0);
237	sources : ref Sources;
238	hdr := bsmain.hdr;
239	auth := "";
240	url : ref Parsedurl;
241	if (bsmain.req != nil) {
242		auth = bsmain.req.auth;
243		url = bsmain.req.url;
244	}
245#	auth := bsmain.req.auth;
246	ans : array of byte = nil;
247	di := Docinfo.new();
248	if(linkclick && f.doc != nil)
249		di.referrer = f.doc.src;
250	f.reset();
251	f.doc = di;
252	di.frameid = f.id;
253	di.src = hdr.actual;
254	di.base = hdr.base;
255	di.refresh = hdr.refresh;
256	if (hdr.chset != nil)
257		di.chset = hdr.chset;
258	di.lastModified = hdr.lastModified;
259	if(J != nil)
260		J->havenewdoc(f);
261	oclipr := f.cim.clipr;
262	f.cim.clipr = f.cr;
263	if(f.framebd != 0) {
264		f.cr = f.r.inset(2);
265		drawborder(f.cim, f.cr, 2, DarkGrey);
266	}
267	fillbg(f, f.cr);
268	G->flush(f.cr);
269	f.cim.clipr = oclipr;
270	if(f.flags&FRvscroll)
271		createvscroll(f);
272	if(f.flags&FRhscroll)
273		createhscroll(f);
274	l := Lay.new(f.cr.dx(), Aleft, f.marginw, di.background);
275	f.layout = l;
276	anyanim := 0;
277	if(hdr.mtype == CU->TextHtml || hdr.mtype == CU->TextPlain) {
278		itsrc := ItemSource.new(bsmain, f, hdr.mtype);
279		sources = Sources.new(ref Source.Shtml(bsmain, 0, itsrc));
280	}
281	else {
282		# for now, must be supported image type
283		if(!I->supported(hdr.mtype)) {
284			sys->print("Need to implement something: source isn't supported image type\n");
285			return nil;
286		}
287		imsrc := I->ImageSource.new(bsmain, 0, 0);
288		ci := CImage.new(url, nil, 0, 0);
289		simage := ref Source.Simage(bsmain, 0, ci, nil, imsrc);
290		sources = Sources.new(simage);
291		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);
292		di.images = it :: nil;
293		appenditems(f, l, it);
294		simage.itl = it :: nil;
295	}
296	while ((src := sources.waitsrc()) != nil) {
297		if(dbgev)
298			CU->event("LAYOUT GETSOMETHING", 0);
299		bs := src.bs;
300		freeit := 0;
301		if(bs.err != "") {
302			if(dbg)
303				sys->print("error getting %s: %s\n", bs.req.url.tostring(), bs.err);
304			pick s := src {
305			Srequired =>
306				s.itsrc.reqddata = array [0] of byte;
307				sources.done(src);
308				CU->freebs(bs);
309				src.bs = nil;
310				continue;
311			}
312			freeit = 1;
313		}
314		else {
315			if(bs.hdr != nil && !bs.seenhdr) {
316				(use, error, challenge, newurl) := CU->hdraction(bs, 0, src.redirects);
317				if(challenge != nil) {
318					sys->print("Need to implement authorization credential dialog\n");
319					error = "Need authorization";
320					use = 0;
321				}
322				if(error != "" && dbg)
323					sys->print("subordinate error: %s\n", error);
324				if(newurl != nil) {
325					s := ref *src;
326					freeit = 1;
327					pick ps := src {
328					Shtml or Srequired =>
329						sys->print("unexpected redirect of subord\n");
330					Simage =>
331						newci := CImage.new(newurl, nil, ps.ci.width, ps.ci.height);
332						for(itl := ps.itl; itl != nil ; itl = tl itl) {
333							pick imi := hd itl {
334							Iimage =>
335								imi.ci = newci;
336							}
337						}
338						news := ref Source.Simage(nil, 0, newci, ps.itl, nil);
339						sources.add(news, 0);
340						startimreq(news, auth);
341					}
342				}
343				if(!use)
344					freeit = 1;
345			}
346			if(!freeit) {
347				pick s := src {
348				Srequired or
349				Shtml =>
350					if (tagof src == tagof Source.Srequired) {
351						s.itsrc.reqddata = bs.data;
352						sources.done(src);
353						CU->freebs(bs);
354						src.bs = nil;
355						continue;
356#						src = sources.main;
357#						CU->assert(src != nil);
358					}
359					itl := s.itsrc.getitems();
360					if(di.kidinfo != nil) {
361						if(s.itsrc.kidstk == nil) {
362							layframeset(f, di.kidinfo);
363							G->flush(f.r);
364							freeit = 1;
365						}
366					}
367					else {
368						l.background = di.background;
369						anyanim |= addsubords(sources, di, auth);
370						if(itl != nil) {
371							appenditems(f, l, itl);
372							fixframegeom(f);
373							if(dbgev)
374								CU->event("LAYOUT_DRAWALL", 0);
375							f.dirty(f.totalr);
376							drawall(f);
377						}
378					}
379					if (s.itsrc.reqdurl != nil) {
380						news := ref Source.Srequired(nil, 0, s.itsrc);
381						sources.add(news, 1);
382						rbs := CU->startreq(ref CU->ReqInfo(s.itsrc.reqdurl, CU->HGet, nil, "", ""));
383						news.bs = rbs;
384					} else {
385						if (bs.eof && bs.lim == bs.edata && s.itsrc.toks == nil)
386							freeit = 1;
387					}
388				Simage =>
389					(ret, mim) := s.imsrc.getmim();
390					# mark it done even if error
391					s.ci.complete = ret;
392					if(ret == I->Mimerror) {
393						bs.err = s.imsrc.err;
394						freeit = 1;
395					}
396					else if(ret != I->Mimnone) {
397						if(s.ci.mims == nil) {
398							s.ci.mims = array[1] of { mim };
399							s.ci.width = s.imsrc.width;
400							s.ci.height = s.imsrc.height;
401							if(ret == I->Mimdone && (CU->config).imagelvl <= CU->ImgNoAnim)
402								freeit = 1;
403						}
404						else {
405							n := len s.ci.mims;
406							if(mim != s.ci.mims[n-1]) {
407								newmims := array[n + 1] of ref MaskedImage;
408								newmims[0:] = s.ci.mims;
409								newmims[n] = mim;
410								s.ci.mims = newmims;
411								anyanim = 1;
412							}
413						}
414						if(s.ci.mims[0] == mim)
415							haveimage(f, s.ci, s.itl);
416						if(bs.eof && bs.lim == bs.edata)
417							(CU->imcache).add(s.ci);
418					}
419					if(!freeit && bs.eof && bs.lim == bs.edata)
420						freeit = 1;
421				}
422			}
423		}
424		if(freeit) {
425			if(bs == bsmain)
426				ans = bs.data[0:bs.edata];
427			CU->freebs(bs);
428			src.bs = nil;
429			sources.done(src);
430		}
431	}
432	if(anyanim && (CU->config).imagelvl > CU->ImgNoAnim)
433		spawn animproc(f);
434	if(dbgev)
435		CU->event("LAYOUT_END", 0);
436	return ans;
437}
438
439# return value is 1 if found any existing images needed animation
440addsubords(sources: ref Sources, di: ref Docinfo, auth: string) : int
441{
442	anyanim := 0;
443	if((CU->config).imagelvl == CU->ImgNone)
444		return anyanim;
445	newsims: list of ref Source.Simage = nil;
446	for(il := di.images; il != nil; il = tl il) {
447		it := hd il;
448		pick i := it {
449		Iimage =>
450			if(i.ci.mims == nil) {
451				cachedci := (CU->imcache).look(i.ci);
452				if(cachedci != nil) {
453					i.ci = cachedci;
454					if(i.imwidth == 0)
455						i.imwidth = i.ci.width;
456					if(i.imheight == 0)
457						i.imheight = i.ci.height;
458					anyanim |= (len cachedci.mims > 1);
459				}
460				else {
461				    sloop:
462					for(sl := sources.srcs; sl != nil; sl = tl sl) {
463						pick s := hd sl {
464						Simage =>
465							if(s.ci.match(i.ci)) {
466								s.itl = it :: s.itl;
467								# want all items on list to share same ci;
468								# want most-specific dimension specs
469								iciw := i.ci.width;
470								icih := i.ci.height;
471								i.ci = s.ci;
472								if(s.ci.width == 0 && s.ci.height == 0) {
473									s.ci.width = iciw;
474									s.ci.height = icih;
475								}
476								break sloop;
477							}
478						}
479					}
480					if(sl == nil) {
481						# didn't find existing Source for this image
482						s := ref Source.Simage(nil, 0, i.ci, it:: nil, nil);
483						newsims = s :: newsims;
484						sources.add(s, 0);
485					}
486				}
487			}
488		}
489	}
490	# Start requests for new newsources.
491	# di.images are in last-in-document-first order,
492	# so newsources is in first-in-document-first order (good order to load in).
493	for(sl := newsims; sl != nil; sl = tl sl)
494		startimreq(hd sl, auth);
495	return anyanim;
496}
497
498startimreq(s: ref Source.Simage, auth: string)
499{
500	if(dbgev)
501		CU->event(sys->sprint("LAYOUT STARTREQ %s", s.ci.src.tostring()), 0);
502	bs := CU->startreq(ref CU->ReqInfo(s.ci.src, CU->HGet, nil, auth, ""));
503	s.bs = bs;
504	s.imsrc = I->ImageSource.new(bs, s.ci.width, s.ci.height);
505}
506
507createvscroll(f: ref Frame)
508{
509	breadth := SCRBREADTH;
510	if(f.parent != nil)
511		breadth = SCRFBREADTH;
512	length := f.cr.dy();
513	if(f.flags&FRhscroll)
514		length -= breadth;
515	f.vscr = Control.newscroll(f, 1, length, breadth);
516	f.vscr.r = f.vscr.r.addpt(Point(f.cr.max.x-breadth, f.cr.min.y));
517	f.cr.max.x -= breadth;
518	if(f.cr.dx() <= 2*f.marginw)
519		raise "EXInternal: frame too small for layout";
520	f.vscr.draw(1);
521}
522
523createhscroll(f: ref Frame)
524{
525	breadth := SCRBREADTH;
526	if(f.parent != nil)
527		breadth = SCRFBREADTH;
528	length := f.cr.dx();
529	x := f.cr.min.x;
530	f.hscr = Control.newscroll(f, 0, length, breadth);
531	f.hscr.r = f.hscr.r.addpt(Point(x,f.cr.max.y-breadth));
532	f.cr.max.y -= breadth;
533	if(f.cr.dy() <= 2*f.marginh)
534		raise "EXInternal: frame too small for layout";
535	f.hscr.draw(1);
536}
537
538# Call after a change to f.layout or f.viewr.min to fix totalr and viewr
539# (We are to leave viewr.min unchanged, if possible, as
540# user might be scrolling).
541fixframegeom(f: ref Frame)
542{
543	l := f.layout;
544	if(dbg)
545		sys->print("fixframegeom, layout width=%d, height=%d\n", l.width, l.height);
546	crwidth := f.cr.dx();
547	crheight := f.cr.dy();
548	layw := max(l.width, crwidth);
549	layh := max(l.height, crheight);
550	f.totalr.max = Point(layw, layh);
551	crchanged := 0;
552	n := l.height+l.margin-crheight;
553	if(n > 0 && f.vscr == nil && (f.flags&FRvscrollauto)) {
554		createvscroll(f);
555		crchanged = 1;
556		crwidth = f.cr.dx();
557	}
558	if(f.viewr.min.y > n)
559		f.viewr.min.y = max(0, n);
560	n = l.width+l.margin-crwidth;
561	if(!crchanged && n > 0 && f.hscr == nil && (f.flags&FRhscrollauto)) {
562		createhscroll(f);
563		crchanged = 1;
564		crheight = f.cr.dy();
565	}
566	if(crchanged) {
567		relayout(f, l, crwidth, l.just);
568		fixframegeom(f);
569		return;
570	}
571	if(f.viewr.min.x > n)
572		f.viewr.min.x = max(0, n);
573	f.viewr.max.x = min(f.viewr.min.x+crwidth, layw);
574	f.viewr.max.y = min(f.viewr.min.y+crheight, layh);
575	if(f.vscr != nil)
576		f.vscr.scrollset(f.viewr.min.y, f.viewr.max.y, f.totalr.max.y, 0, 1);
577	if(f.hscr != nil)
578		f.hscr.scrollset(f.viewr.min.x, f.viewr.max.x, f.totalr.max.x, f.viewr.dx()/5, 1);
579}
580
581# The items its within f are Iimage items,
582# and its image, ci, now has at least a ci.mims[0], which may be partially
583# or fully filled.
584haveimage(f: ref Frame, ci: ref CImage, itl: list of ref Item)
585{
586	if(dbgev)
587		CU->event("HAVEIMAGE", 0);
588	if(dbg)
589		sys->print("\nHAVEIMAGE src=%s w=%d h=%d\n", ci.src.tostring(), ci.width, ci.height);
590	# make all base images repl'd - makes handling backgrounds much easier
591	im := ci.mims[0].im;
592	im.repl = 1;
593	im.clipr = Rect((-16rFFFFFFF, -16r3FFFFFFF), (16r3FFFFFFF, 16r3FFFFFFF));
594	dorelayout := 0;
595	for( ; itl != nil; itl = tl itl) {
596		it := hd itl;
597		pick i := it {
598		Iimage =>
599			if (!(it.state & B->IFbkg)) {
600				# If i.imwidth and i.imheight are not both 0, the HTML specified the dimens.
601				# If one of them is 0, the other is to be scaled by the same factor;
602				# we have to relay the line in that case too.
603				if(i.imwidth == 0 || i.imheight == 0) {
604					i.imwidth = ci.width;
605					i.imheight = ci.height;
606					setimagedims(i);
607					loc := f.find(zp, it);
608					# sometimes the image was added to doc image list, but
609					# never made it to layout (e.g., because html bug prevented
610					# a table from being added).
611					# also, script-created images won't have items
612					if(loc != nil) {
613						f.layout.flags |= Lchanged;
614						markchanges(loc);
615						dorelayout = 1;
616						# Floats are assumed to be premeasured, so if there
617						# are any floats in the loc list, remeasure them
618						for(k := loc.n-1; k > 0; k--) {
619							if(loc.le[k].kind == LEitem) {
620								locit := loc.le[k].item;
621								pick fit := locit {
622								Ifloat =>
623									pick xi := fit.item {
624									Iimage =>
625										fit.height = fit.item.height;
626									Itable =>
627										checktabsize(f, xi, TABLEFLOATTARGET);
628									}
629								}
630							}
631						}
632					}
633				}
634			}
635			if(dbg > 1) {
636				sys->print("\nhaveimage item: ");
637				it.print();
638			}
639		}
640	}
641	if(dorelayout) {
642		relayout(f, f.layout, f.layout.targetwidth, f.layout.just);
643		fixframegeom(f);
644	}
645	f.dirty(f.totalr);
646	drawall(f);
647	if(dbgev)
648		CU->event("HAVEIMAGE_END", 0);
649}
650# For first layout of subelements, such as table cells.
651# After this, content items will be dispersed throughout resulting lay.
652# Return index into f.sublays.
653# (This roundabout way of storing sublayouts avoids pointers to Lay
654# in Build, so that all of the layout-related stuff can be in Layout
655# where it belongs.)
656sublayout(f: ref Frame, targetwidth: int, just: byte, bg: Background, content: ref Item) : int
657{
658	if(dbg)
659		sys->print("sublayout, targetwidth=%d\n", targetwidth);
660	l := Lay.new(targetwidth, just, 0, bg);
661	if(f.sublayid >= len f.sublays) {
662		newsublays := array[len f.sublays + 30] of ref Lay;
663		newsublays[0:] = f.sublays;
664		f.sublays = newsublays;
665	}
666	id := f.sublayid;
667	f.sublays[id] = l;
668	f.sublayid++;
669	appenditems(f, l, content);
670	l.flags &= ~Lchanged;
671	if(dbg)
672		sys->print("after sublayout, width=%d\n", l.width);
673	return id;
674}
675
676# Relayout of lay, given a new target width or if something changed inside
677# or if the global justification for the layout changed.
678# Floats are hard: for now, just relay everything with floats temporarily
679# moved way down, if there are any floats.
680relayout(f: ref Frame, lay: ref Lay, targetwidth: int, just: byte)
681{
682	if(dbg)
683		sys->print("relayout, targetwidth=%d, old target=%d, changed=%d\n",
684			targetwidth, lay.targetwidth, (lay.flags&Lchanged) != byte 0);
685	changeall := (lay.targetwidth != targetwidth || lay.just != just);
686	if(!changeall && !int(lay.flags&Lchanged))
687		return;
688	if(lay.floats != nil) {
689		# move the current y positions of floats to a big value,
690		# so they don't contribute to floatw until after they've
691		# been encountered in current fixgeom
692		for(flist := lay.floats; flist != nil; flist = tl flist) {
693			ff := hd flist;
694			ff.y = 16r6fffffff;
695		}
696		changeall = 1;
697	}
698	lay.targetwidth = targetwidth;
699	lay.just = just;
700	lay.height = 0;
701	lay.width = 0;
702	if(changeall)
703		changelines(lay.start.next, lay.end);
704	fixgeom(f, lay, lay.start.next);
705	lay.flags &= ~Lchanged;
706	if(dbg)
707		sys->print("after relayout, width=%d\n", lay.width);
708}
709
710# Measure and append the items to the end of layout lay,
711# and fix the geometry.
712appenditems(f: ref Frame, lay: ref Lay, items: ref Item)
713{
714	measure(f, items);
715	if(dbg)
716		items.printlist("appenditems, after measure");
717	it := items;
718	if(it == nil)
719		return;
720	lprev := lay.end.prev;
721	l : ref Line;
722	lit := lastitem(lprev.items);
723	if(lit == nil || (it.state&IFbrk)) {
724		# start a new line after existing last line
725		l = Line.new();
726		appendline(lprev, l);
727		l.items = it;
728	}
729	else {
730		# start appending items to existing last line
731		l = lprev;
732		lit.next = it;
733	}
734	l.flags |= Lchanged;
735	while(it != nil) {
736		nexti := it.next;
737		if(nexti == nil || (nexti.state&IFbrk)) {
738			it.next = nil;
739			fixgeom(f, lay, l);
740			if(nexti == nil)
741				break;
742			# now there may be multiple lines containing the
743			# items from l, but the one after the last is lay.end
744			l = Line.new();
745			appendline(lay.end.prev, l);
746			l.flags |= Lchanged;
747			it = nexti;
748			l.items = it;
749		}
750		else
751			it = nexti;
752	}
753}
754
755# Fix up the geometry of line l and successors.
756# Assume geometry of previous line is correct.
757fixgeom(f: ref Frame, lay: ref Lay, l: ref Line)
758{
759	while(l != nil) {
760		fixlinegeom(f, lay, l);
761		mergetext(l);
762		l = l.next;
763	}
764	lay.height = max(lay.height, lay.end.pos.y);
765}
766
767mergetext(l: ref Line)
768{
769	lastit : ref Item;
770	for (it := l.items; it != nil; it = it.next) {
771		pick i := it {
772		Itext =>
773			if (lastit == nil)
774				break; #pick
775			pick pi := lastit {
776			Itext =>
777				# ignore item state flags as fixlinegeom()
778				# will have taken account of them.
779				if (pi.anchorid == i.anchorid &&
780				pi.fnt == i.fnt && pi.fg == i.fg && pi.voff == i.voff && pi.ul == i.ul) {
781					# compatible - merge
782					pi.s += i.s;
783					pi.width += i.width;
784					pi.next = i.next;
785					continue;
786				}
787			}
788		}
789		lastit = it;
790	}
791}
792
793# Fix geom for one line.
794# This may change the overall lay.width, if there is no way
795# to fit the line into the target width.
796fixlinegeom(f: ref Frame, lay: ref Lay, l: ref Line)
797{
798	lprev := l.prev;
799	y := lprev.pos.y + lprev.height;
800	it := l.items;
801	state := it.state;
802	if(dbg > 1) {
803		sys->print("\nfixlinegeom start, y=prev.y+prev.height=%d+%d=%d, changed=%d\n",
804				l.prev.pos.y, lprev.height, y, int (l.flags&Lchanged));
805		if(dbg > 2)
806			it.printlist("items");
807		else {
808			sys->print("first item: ");
809			it.print();
810		}
811	}
812	if(state&IFbrk) {
813		y = pastbrk(lay, y, state);
814		if(dbg > 1 && y != lprev.pos.y + lprev.height)
815			sys->print("after pastbrk, line y is now %d\n", y);
816	}
817	l.pos.y = y;
818	lineh := max(l.height, linespace);
819	lfloatw := floatw(y, y+lineh, lay.floats, Aleft);
820	rfloatw := floatw(y, y+lineh, lay.floats, Aright);
821	if((l.flags&Lchanged) == byte 0) {
822		# possibly adjust lay.width
823		n := (lay.width-rfloatw)-(l.pos.x-lay.margin+l.width);
824		if(n < 0)
825			lay.width += -n;
826		return;
827	}
828	hang := (state&IFhangmask)*TABPIX/10;
829	linehang := hang;
830	hangtogo := hang;
831	indent := ((state&IFindentmask)>>IFindentshift)*TABPIX;
832	just := (state&(IFcjust|IFrjust));
833	if(just == 0 && lay.just != Aleft) {
834		if(lay.just == byte Acenter)
835			just = IFcjust;
836		else if(lay.just == Aright)
837			just = IFrjust;
838	}
839	right := lay.targetwidth - lay.margin;
840	lwid := right - (lfloatw+rfloatw+indent+lay.margin);
841	if(lwid < 0) {
842		if (right - lwid > lay.width)
843			lay.width = right - lwid;
844		right += -lwid;
845		lwid = 0;
846	}
847	lwid += hang;
848	if(dbg > 1) {
849		sys->print("fixlinegeom, now y=%d, lfloatw=%d, rfloatw=%d, indent=%d, hang=%d, lwid=%d\n",
850				y, lfloatw, rfloatw, indent, hang, lwid);
851	}
852	w := 0;
853	lineh = 0;
854	linea := 0;
855	lastit: ref Item = nil;
856	nextfloats: list of ref Item.Ifloat = nil;
857	anystuff := 0;
858	eol := 0;
859	while(it != nil && !eol) {
860		if(dbg > 2) {
861			sys->print("fixlinegeom loop head, w=%d, loop item:\n", w);
862			it.print();
863		}
864		state = it.state;
865		wrapping := int (state&IFwrap);
866		if(anystuff && (state&IFbrk))
867			break;
868		checkw := 1;
869		if(hang && !(state&IFhangmask)) {
870			lwid -= hang;
871			hang = 0;
872			if(hangtogo > 0) {
873				# insert a null spacer item
874				spaceit := Item.newspacer(ISPgeneral, 0);
875				spaceit.width = hangtogo;
876				if(lastit != nil) {
877					spaceit.state = lastit.state & ~(IFbrk|IFbrksp|IFnobrk|IFcleft|IFcright);
878					lastit.next = spaceit;
879				}
880				else
881					lastit = spaceit;
882				spaceit.next = it;
883			}
884		}
885		pick i := it {
886		Ifloat =>
887			if(anystuff) {
888				# float will go after this line
889				nextfloats = i :: nextfloats;
890			}
891			else {
892				# add float beside current line, adjust widths
893				fixfloatxy(lay, y, i);
894				# TODO: only do following if y and/or height changed
895				changelines(l.next, lay.end);
896				newlfloatw := floatw(y, y+lineh, lay.floats, Aleft);
897				newrfloatw := floatw(y, y+lineh, lay.floats, Aright);
898				lwid -= (newlfloatw-lfloatw) + (newrfloatw-rfloatw);
899				if (lwid < 0) {
900					right += -lwid;
901					lwid = 0;
902				}
903				lfloatw = newlfloatw;
904				rfloatw = newrfloatw;
905			}
906			checkw = 0;
907		Itable =>
908			# When just doing layout for cell dimensions, don't
909			# want a "100%" spec to make the table really wide
910			kindspec := 0;
911			if(lay.targetwidth == TABLEMAXTARGET && i.table.width.kind() == Dpercent) {
912				kindspec = i.table.width.kindspec;
913				i.table.width = Dimen.make(Dnone, 0);
914			}
915			checktabsize(f, i, lwid-w);
916			if(kindspec != 0)
917				i.table.width.kindspec = kindspec;
918		Irule =>
919			avail := lwid-w;
920			# When just doing layout for cell dimensions, don't
921			# want a "100%" spec to make the rule really wide
922			if(lay.targetwidth == TABLEMAXTARGET)
923				avail = min(10, avail);
924			i.width = widthfromspec(i.wspec, avail);
925		Iformfield =>
926			checkffsize(f, i, i.formfield);
927		}
928		if(checkw) {
929			iw := it.width;
930			if(wrapping && w + iw > lwid) {
931				# it doesn't fit; see if it can be broken
932				takeit: int;
933				noneok := (anystuff || lfloatw != 0 || rfloatw != 0) && !(state&IFnobrk);
934				(takeit, iw) = trybreak(it, lwid-w, iw, noneok);
935				eol = 1;
936				if(!takeit) {
937					if(lastit == nil) {
938						# Nothing added because one of the float widths
939						# is nonzero, and not enough room for anything else.
940						# Move y down until there's more room and try again.
941						CU->assert(lfloatw != 0 || rfloatw != 0);
942						oldy := y;
943						y = pastbrk(lay, y, IFcleft|IFcright);
944						if(dbg > 1)
945							sys->print("moved y past %d, now y=%d\n", oldy, y);
946						CU->assert(y > oldy);	# else infinite recurse
947						# Do the move down by artificially increasing the
948						# height of the previous line
949						lprev.height += y-oldy;
950						fixlinegeom(f, lay, l);
951						return;
952					} else
953						break;
954				}
955			}
956			w += iw;
957			if(hang)
958				hangtogo -= w;
959			(lineh, linea) = lgeom(lineh, linea, it);
960			if(!anystuff) {
961				anystuff = 1;
962				# don't count an ordinary space as 'stuff' if wrapping
963				pick t := it {
964				Itext =>
965					if(wrapping && t.s == " ")
966						anystuff = 0;
967				}
968			}
969		}
970		lastit = it;
971		it = it.next;
972		if(it == nil && !eol) {
973			# perhaps next lines items can now fit on this line
974			nextl := l.next;
975			nit := nextl.items;
976			if(nextl != lay.end && !(nit.state&IFbrk)) {
977				lastit.next = nit;
978				# remove nextl
979				l.next = nextl.next;
980				l.next.prev = l;
981				it = nit;
982			}
983		}
984	}
985	# line is complete, next line will start with it (or it is nil)
986	rest := it;
987	if(lastit == nil)
988		raise "EXInternal: no items on line";
989	lastit.next = nil;
990
991	l.width = w;
992	x := lfloatw + lay.margin + indent - linehang;
993	# shift line if it begins with a space or a rule
994	pick pi := l.items {
995	Itext =>
996		if(pi.s != nil && pi.s[0] == ' ')
997			x -= fonts[pi.fnt].spw;
998	Irule =>
999		# note: build ensures that rules appear on lines
1000		# by themselves
1001		if(pi.align == Acenter)
1002			just = IFcjust;
1003		else if(pi.align == Aright)
1004			just = IFrjust;
1005	Ifloat =>
1006		if(pi.next != nil) {
1007			pick qi := pi.next {
1008			Itext =>
1009				if(qi.s != nil && qi.s[0] == ' ')
1010					x -= fonts[qi.fnt].spw;
1011			}
1012		}
1013	}
1014	xright := x+w;
1015	if (xright + rfloatw > lay.width)
1016		lay.width = xright+rfloatw;
1017	n := lay.targetwidth-(lay.margin+rfloatw+xright);
1018	if(n > 0 && just) {
1019		if(just&IFcjust)
1020			x += n/2;
1021		else
1022			x += n;
1023	}
1024	if(dbg > 1) {
1025		sys->print("line geometry fixed, (x,y)=(%d,%d), w=%d, h=%d, a=%d, lfloatw=%d, rfloatw=%d, lay.width=%d\n",
1026			x, l.pos.y, w, lineh, linea, lfloatw, rfloatw, lay.width);
1027		if(dbg > 2)
1028			l.items.printlist("final line items");
1029	}
1030	l.pos.x = x;
1031	l.height = lineh;
1032	l.ascent = linea;
1033	l.flags &= ~Lchanged;
1034
1035	if(nextfloats != nil)
1036		fixfloatsafter(lay, l, nextfloats);
1037
1038	if(rest != nil) {
1039		nextl := l.next;
1040		if(nextl == lay.end || (nextl.items.state&IFbrk)) {
1041			nextl = Line.new();
1042			appendline(l, nextl);
1043		}
1044		li := lastitem(rest);
1045		li.next = nextl.items;
1046		nextl.items = rest;
1047		nextl.flags |= Lchanged;
1048	}
1049}
1050
1051# Return y coord after y due to a break.
1052pastbrk(lay: ref Lay, y, state: int) : int
1053{
1054	nextralines := 0;
1055	if(state&IFbrksp)
1056		nextralines = 1;
1057	ynext := y;
1058	if(state&IFcleft)
1059		ynext = floatclry(lay.floats, Aleft, ynext);
1060	if(state&IFcright)
1061		ynext = max(ynext, floatclry(lay.floats, Aright, ynext));
1062	ynext += nextralines*linespace;
1063	return ynext;
1064}
1065
1066# Add line l after lprev (and before lprev's current successor)
1067appendline(lprev, l: ref Line)
1068{
1069	l.next = lprev.next;
1070	l.prev = lprev;
1071	l.next.prev = l;
1072	lprev.next = l;
1073}
1074
1075# Mark lines l up to but not including lend as changed
1076changelines(l, lend: ref Line)
1077{
1078	for( ; l != lend; l = l.next)
1079		l.flags |= Lchanged;
1080}
1081
1082# Return a ref Font for font number num = (style*NumSize + size)
1083getfont(num: int) : ref Font
1084{
1085	f := fonts[num].f;
1086	if(f == nil) {
1087		f = Font.open(display, fonts[num].name);
1088		if(f == nil) {
1089			if(num == DefFnt)
1090				raise sys->sprint("exLayout: can't open default font %s: %r", fonts[num].name);
1091			else {
1092				if(int (CU->config).dbg['w'])
1093					sys->print("warning: substituting default for font %s\n",
1094						fonts[num].name);
1095				f = fonts[DefFnt].f;
1096			}
1097		}
1098		fonts[num].f = f;
1099		fonts[num].spw = f.width(" ");
1100	}
1101	return f;
1102}
1103
1104# Set the width, height and ascent fields of all items, getting any necessary fonts.
1105# Some widths and heights depend on the available width on the line, and may be
1106# wrong until checked during fixlinegeom.
1107# Don't do tables here at all (except floating tables).
1108# Configure Controls for form fields.
1109measure(fr: ref Frame, items: ref Item)
1110{
1111	for(it := items; it != nil; it = it.next) {
1112		pick t := it {
1113		Itext =>
1114			f := getfont(t.fnt);
1115			it.width = f.width(t.s);
1116			a := f.ascent;
1117			h := f.height;
1118			if(t.voff != byte Voffbias) {
1119				a -= (int t.voff) - Voffbias;
1120				if(a > h)
1121					h = a;
1122			}
1123			it.height = h;
1124			it.ascent = a;
1125		Irule =>
1126			it.height =  t.size + 2*RULESP;
1127			it.ascent = t.size + RULESP;
1128		Iimage =>
1129			setimagedims(t);
1130		Iformfield =>
1131			c := Control.newff(fr, t.formfield);
1132			if(c != nil) {
1133				t.formfield.ctlid = fr.addcontrol(c);
1134				it.width = c.r.dx();
1135				it.height = c.r.dy();
1136				it.ascent = it.height;
1137				pick pc := c {
1138				Centry =>
1139					it.ascent = lineascent + ENTVMARGIN;
1140				Cselect =>
1141					it.ascent = lineascent + SELMARGIN;
1142				Cbutton =>
1143					if(pc.dorelief)
1144						it.ascent -= BUTMARGIN;
1145				}
1146			}
1147		Ifloat =>
1148			# Leave w at zero, so it doesn't contribute to line width in normal way
1149			# (Can find its width in t.item.width).
1150			pick i := t.item {
1151			Iimage =>
1152				setimagedims(i);
1153				it.height = t.item.height;
1154			Itable =>
1155				checktabsize(fr, i, TABLEFLOATTARGET);
1156			* =>
1157				CU->assert(0);
1158			}
1159			it.ascent = it.height;
1160		Ispacer =>
1161			case t.spkind {
1162			ISPvline =>
1163				f := getfont(t.fnt);
1164				it.height = f.height;
1165				it.ascent = f.ascent;
1166			ISPhspace =>
1167				getfont(t.fnt);
1168				it.width = fonts[t.fnt].spw;
1169			}
1170		}
1171	}
1172}
1173
1174# Set the dimensions of an image item
1175setimagedims(i: ref Item.Iimage)
1176{
1177	i.width = i.imwidth + 2*(int i.hspace + int i.border);
1178	i.height = i.imheight + 2*(int i.vspace + int i.border);
1179	i.ascent = i.height - (int i.vspace + int i.border);
1180	if((CU->config).imagelvl == CU->ImgNone && i.altrep != "") {
1181		f := fonts[DefFnt].f;
1182		i.width = max(i.width, f.width(i.altrep));
1183		i.height = max(i.height, f.height);
1184		i.ascent = f.ascent;
1185	}
1186}
1187
1188# Line geometry function:
1189# Given current line height (H) and ascent (distance from top to baseline) (A),
1190# and an item, see if that item changes height and ascent.
1191# Return (H', A'), the updated line height and ascent.
1192lgeom(H, A: int, it: ref Item) : (int, int)
1193{
1194	h := it.height;
1195	a := it.ascent;
1196	atype := Abaseline;
1197	pick i := it {
1198	Iimage =>
1199		atype = i.align;
1200	Itable =>
1201		atype = Atop;
1202	Ifloat =>
1203		return (H, A);
1204	}
1205	d := h-a;
1206	Hnew := H;
1207	Anew := A;
1208	case int atype {
1209	int Abaseline or int Abottom =>
1210		if(a > A) {
1211			Anew = a;
1212			Hnew += (Anew - A);
1213		}
1214		if(d > Hnew - Anew)
1215			Hnew = Anew + d;
1216	int Atop =>
1217		# OK to ignore what comes after in the line
1218		if(h > H)
1219			Hnew = h;
1220	int Amiddle or int Acenter =>
1221		# supposed to align middle with baseline
1222		hhalf := h/2;
1223		if(hhalf > A)
1224			Anew = hhalf;
1225		if(hhalf > H-Anew)
1226			Hnew = Anew + hhalf;
1227	}
1228	return (Hnew, Anew);
1229}
1230
1231# Try breaking item bit to make it fit in availw.
1232# If that is possible, change bit to be the part that fits
1233# and insert the rest between bit and bit.next.
1234# iw is the current width of bit.
1235# If noneok is 0, break off the minimum size word
1236# even if it exceeds availw.
1237# Return (1 if supposed to take bit, iw' = new width of bit)
1238trybreak(bit: ref Item, availw, iw, noneok: int) : (int, int)
1239{
1240	if(iw <= 0)
1241		return (1, iw);
1242	if(availw < 0) {
1243		if(noneok)
1244			return (0, iw);
1245		else
1246			availw = 0;
1247	}
1248	pick t := bit {
1249	Itext =>
1250		if(len t.s < 2)
1251			return (!noneok, iw);
1252		(s1, w1, s2, w2) := breakstring(t.s, iw, fonts[t.fnt].f, availw, noneok);
1253		if(w1 == 0)
1254			return (0, iw);
1255		itn := Item.newtext(s2, t.fnt, t.fg, int t.voff, t.ul);
1256		itn.width = w2;
1257		itn.height = t.height;
1258		itn.ascent = t.ascent;
1259		itn.anchorid = t.anchorid;
1260		itn.state = t.state & ~(IFbrk|IFbrksp|IFnobrk|IFcleft|IFcright);
1261		itn.next = t.next;
1262		t.next = itn;
1263		t.s = s1;
1264		t.width = w1;
1265		return (1, w1);
1266	}
1267	return (!noneok, iw);
1268}
1269
1270# s has width sw when drawn in fnt.
1271# Break s into s1 and s2 so that s1 fits in availw.
1272# If noneok is true, it is ok for s1 to be nil, otherwise might
1273# have to return an s1 that overflows availw somewhat.
1274# Return (s1, w1, s2, w2) where w1 and w2 are widths of s1 and s2.
1275# Assume caller has already checked that sw > availw.
1276breakstring(s: string, sw: int, fnt: ref Font, availw, noneok: int) : (string, int, string, int)
1277{
1278	slen := len s;
1279	if(slen < 2) {
1280		if(noneok)
1281			return (nil, 0, s, sw);
1282		else
1283			return (s, sw, nil, 0);
1284	}
1285
1286	# Use linear interpolation to guess break point.
1287	# We know avail < iw by conditions of trybreak call.
1288	i := slen*availw / sw - 1;
1289	if(i < 0)
1290		i = 0;
1291	i = breakpoint(s, i, -1);
1292	(ss, ww) := tryw(fnt, s, i);
1293	if(ww > availw) {
1294		while(ww > availw) {
1295			i = breakpoint(s, i-1, -1);
1296			if(i <= 0)
1297				break;
1298			(ss, ww) = tryw(fnt, s, i);
1299		}
1300	}
1301	else {
1302		oldi := i;
1303		oldss := ss;
1304		oldww := ww;
1305		while(ww < availw) {
1306			oldi = i;
1307			oldss = ss;
1308			oldww = ww;
1309			i = breakpoint(s, i+1, 1);
1310			if(i >= slen)
1311				break;
1312			(ss, ww) = tryw(fnt, s, i);
1313		}
1314		i = oldi;
1315		ss = oldss;
1316		ww = oldww;
1317	}
1318	if(i <= 0 || i >= slen) {
1319		if(noneok)
1320			return (nil, 0, s, sw);
1321		i = breakpoint(s, 1, 1);
1322		(ss,ww) = tryw(fnt, s, i);
1323	}
1324	return (ss, ww, s[i:slen], sw-ww);
1325}
1326
1327# If can break between s[i-1] and s[i], return i.
1328# Else move i in direction incr until this is true.
1329# (Might end up returning 0 or len s).
1330breakpoint(s: string, i, incr: int) : int
1331{
1332	slen := len s;
1333	ans := 0;
1334	while(i > 0 && i < slen) {
1335		ci := s[i];
1336		di := s[i-1];
1337
1338		# ASCII rules
1339		if ((ci < 16rA0 && !int wordchar[ci]) || (di < 16rA0 && !int wordchar[di])) {
1340			ans = i;
1341			break;
1342		}
1343
1344		# Treat all ideographs as breakable.
1345		# The following range includes unassigned unicode code points.
1346		# All assigned code points in the range are class ID (ideograph) as defined
1347		# by the Unicode consortium's LineBreak data.
1348		# There are many other class ID code points outside of this range.
1349		# For details on how to do unicode line breaking properly see:
1350		# Unicode Standard Annex #14 (http://www.unicode.org/unicode/reports/tr14/)
1351
1352		if ((ci >= 16r30E && ci <= 16r9FA5) || (di >= 16r30E && di <= 16r9FA5)) {
1353			ans = i;
1354			break;
1355		}
1356
1357		# consider all other characters as unbreakable
1358		i += incr;
1359	}
1360	if(i == slen)
1361		ans = slen;
1362	return ans;
1363}
1364
1365# Return (s[0:i], width of that slice in font fnt)
1366tryw(fnt: ref Font, s: string, i: int) : (string, int)
1367{
1368	if(i == 0)
1369		return ("", 0);
1370	ss := s[0:i];
1371	return (ss, fnt.width(ss));
1372}
1373
1374# Return max width of a float that overlaps [ymin, ymax) on given side.
1375# Floats are in reverse order of addition, so each float's y is <= that of
1376# preceding floats in list.  Floats from both sides are intermixed.
1377floatw(ymin, ymax: int, flist: list of ref Item.Ifloat, side: byte) : int
1378{
1379	ans := 0;
1380	for( ; flist != nil; flist = tl flist) {
1381		fl := hd flist;
1382		if(fl.side != side)
1383			continue;
1384		fymin := fl.y;
1385		fymax := fymin + fl.item.height;
1386		if((fymin <= ymin && ymin < fymax) ||
1387		   (ymin <= fymin && fymin < ymax)) {
1388			w := fl.x;
1389			if(side == Aleft)
1390				w += fl.item.width;
1391			if(ans < w)
1392				ans = w;
1393		}
1394	}
1395	return ans;
1396}
1397
1398# Float f is to be at vertical position >= y.
1399# Fix its (x,y) pos and add it to lay.floats, if not already there.
1400fixfloatxy(lay: ref Lay, y: int, f: ref Item.Ifloat)
1401{
1402	height := f.item.height;
1403	width := f.item.width;
1404	f.y = y;
1405	flist := lay.floats;
1406	if(f.infloats != byte 0) {
1407		# only take previous floats into account for width
1408		while(flist != nil) {
1409			x := hd flist;
1410			flist = tl flist;
1411			if(x == f)
1412				break;
1413		}
1414	}
1415	f.x = floatw(y, y+height, flist, f.side);
1416	endx := f.x + width + lay.margin;
1417	if (endx > lay.width)
1418		lay.width = endx;
1419	if (f.side == Aright)
1420		f.x += width;
1421	endy := f.y + height + lay.margin;
1422	if (endy > lay.height)
1423		lay.height = endy;
1424	if(f.infloats == byte 0) {
1425		lay.floats = f :: lay.floats;
1426		f.infloats = byte 1;
1427	}
1428}
1429
1430# Floats in flist are to go after line l.
1431fixfloatsafter(lay: ref Lay, l: ref Line, flist: list of ref Item.Ifloat)
1432{
1433	change := 0;
1434	y := l.pos.y + l.height;
1435	for(itl := Item.revlist(flist); itl != nil; itl = tl itl) {
1436		pick fl := hd itl {
1437		Ifloat =>
1438			oldy := fl.y;
1439			fixfloatxy(lay, y, fl);
1440			if(fl.y != oldy)
1441				change = 1;
1442			y += fl.item.height;
1443		}
1444	}
1445#	if(change)
1446# TODO only change if y and/or height changed
1447		changelines(l.next, lay.end);
1448}
1449
1450# If there's a float on given side that starts on or before y and
1451# ends after y, return ending y of that float, else return original y.
1452# Assume float list is bottom up.
1453floatclry(flist: list of ref Item.Ifloat, side: byte, y: int) : int
1454{
1455	ymax := y;
1456	for( ; flist != nil; flist = tl flist) {
1457		fl := hd flist;
1458		if(fl.side == side) {
1459			if(fl.y <= y) {
1460				flymax := fl.y + fl.item.height;
1461				if (fl.item.height == 0)
1462					# assume it will have some height later
1463					flymax++;
1464				if(flymax > ymax)
1465					ymax = flymax;
1466			}
1467		}
1468	}
1469	return ymax;
1470}
1471
1472# Do preliminaries to laying out table tab in target width linewidth,
1473# setting total height and width.
1474sizetable(f: ref Frame, tab: ref Table, availwidth: int)
1475{
1476	if(dbgtab)
1477		sys->print("sizetable %d, availwidth=%d, nrow=%d, ncol=%d, changed=%x, tab.availw=%d\n",
1478			tab.tableid, availwidth, tab.nrow, tab.ncol, int (tab.flags&Lchanged), tab.availw);
1479	if(tab.ncol == 0 || tab.nrow == 0)
1480		return;
1481	if(tab.availw == availwidth && (tab.flags&Lchanged) == byte 0)
1482		return;
1483	(hsp, vsp, pad, bd, cbd, hsep, vsep) := tableparams(tab);
1484	totw := widthfromspec(tab.width, availwidth);
1485	# reduce totw by spacing, padding, and rule widths
1486	# to leave amount left for contents
1487	totw -= (tab.ncol-1)*hsep+ 2*(hsp+bd+pad+cbd);
1488	if(totw <= 0)
1489		totw = 1;
1490	if(dbgtab)
1491		sys->print("\nsizetable %d, totw=%d, hsp=%d, vsp=%d, pad=%d, bd=%d, cbd=%d, hsep=%d, vsep=%d\n",
1492			tab.tableid, totw, hsp, vsp, pad, bd, cbd, hsep, vsep);
1493	for(cl := tab.cells; cl != nil; cl = tl cl) {
1494		c := hd cl;
1495		clay : ref Lay = nil;
1496		if(c.layid >= 0)
1497			clay = f.sublays[c.layid];
1498		if(clay == nil || (clay.flags&Lchanged) != byte 0) {
1499			c.minw = -1;
1500			tw := TABLEMAXTARGET;
1501			if(c.wspec.kind() != Dnone)
1502				tw = widthfromspec(c.wspec, totw);
1503
1504			# When finding max widths, want to lay out using Aleft alignment,
1505			# because we don't yet know final width for proper justification.
1506			# If the max widths are accepted, we'll redo those needing other justification.
1507			if(clay == nil) {
1508				if(dbg)
1509					sys->print("Initial layout for cell %d.%d\n", tab.tableid, c.cellid);
1510				c.layid = sublayout(f, tw, Aleft, c.background, c.content);
1511				clay = f.sublays[c.layid];
1512				c.content = nil;
1513			}
1514			else {
1515				if(dbg)
1516					sys->print("Relayout (for max) for cell %d.%d\n", tab.tableid, c.cellid);
1517				relayout(f, clay, tw, Aleft);
1518			}
1519			clay.flags |= Lchanged;	# for min test, below
1520			c.maxw = clay.width;
1521			if(dbgtab)
1522				sys->print("sizetable %d for cell %d max layout done, targw=%d, c.maxw=%d\n",
1523						tab.tableid, c.cellid, tw, c.maxw);
1524			if(c.wspec.kind() == Dpixels) {
1525				# Other browsers don't make the following adjustment for
1526				# percentage and relative widths
1527				if(c.maxw <= tw)
1528					c.maxw = tw;
1529				if(dbgtab)
1530					sys->print("after spec adjustment, c.maxw=%d\n", c.maxw);
1531			}
1532		}
1533	}
1534
1535	# calc max column widths
1536	colmaxw := array[tab.ncol] of { * => 0};
1537	maxw := widthcalc(tab, colmaxw, hsep, 1);
1538
1539	if(dbgtab)
1540		sys->print("sizetable %d maxw=%d, totw=%d\n", tab.tableid, maxw, totw);
1541	ci: int;
1542	if(maxw <= totw) {
1543		# trial layouts are fine,
1544		# but if table width was specified, add more space
1545		d := 0;
1546		adjust := (totw > maxw && tab.width.kind() != Dnone);
1547		for(ci = 0; ci < tab.ncol; ci++) {
1548			if (adjust) {
1549				delta := (totw-maxw);
1550				d = delta / (tab.ncol - ci);
1551				if (d <= 0) {
1552					d = delta;
1553					adjust = 0;
1554				}
1555				maxw += d;
1556			}
1557			tab.cols[ci].width = colmaxw[ci] + d;
1558		}
1559	}
1560	else {
1561		# calc min column widths and  apportion out
1562		# differences
1563		if(dbgtab)
1564			sys->print("sizetable %d, availwidth %d, need min widths too\n", tab.tableid, availwidth);
1565		for(cl = tab.cells; cl != nil; cl = tl cl) {
1566			c := hd cl;
1567			clay := f.sublays[c.layid];
1568			if(c.minw == -1 || (clay.flags&Lchanged) != byte 0) {
1569				if(dbg)
1570					sys->print("Relayout (for min) for cell %d.%d\n", tab.tableid, c.cellid);
1571				relayout(f, clay, 1, Aleft);
1572				c.minw = clay.width;
1573				if(dbgtab)
1574					sys->print("sizetable %d for cell %d min layout done, c.min=%d\n",
1575						tab.tableid, c.cellid, clay.width);
1576			}
1577		}
1578		colminw := array[tab.ncol] of { * => 0};
1579		minw := widthcalc(tab, colminw, hsep, 0);
1580		w := totw - minw;
1581		d := maxw - minw;
1582		if(dbgtab)
1583			sys->print("sizetable %d minw=%d, w=%d, d=%d\n", tab.tableid, minw, w, d);
1584		for(ci = 0; ci < tab.ncol; ci++) {
1585			wd : int;
1586			if(w < 0 || d < 0)
1587				wd = colminw[ci];
1588			else
1589				wd = colminw[ci] + (colmaxw[ci] - colminw[ci])*w/d;
1590			if(dbgtab)
1591				sys->print("sizetable %d col[%d].width = %d\n", tab.tableid, ci, wd);
1592			tab.cols[ci].width = wd;
1593		}
1594
1595		if(dbgtab)
1596			sys->print("sizetable %d, availwidth %d, doing final layouts\n", tab.tableid, availwidth);
1597	}
1598
1599	# now have col widths; set actual cell dimensions
1600	# and relayout (note: relayout will do no work if the target width
1601	# and just haven't changed from last layout)
1602	for(cl = tab.cells; cl != nil; cl = tl cl) {
1603		c := hd cl;
1604		clay := f.sublays[c.layid];
1605		wd := cellwidth(tab, c, hsep);
1606		if(dbgtab)
1607			sys->print("sizetable %d for cell %d, clay.width=%d, cellwidth=%d\n",
1608					tab.tableid, c.cellid, clay.width, wd);
1609		if(dbg)
1610			sys->print("Relayout (final) for cell %d.%d\n", tab.tableid, c.cellid);
1611		relayout(f, clay, wd, c.align.halign);
1612		if(dbgtab)
1613			sys->print("sizetable %d for cell %d, final width %d, got width %d, height %d\n",
1614					tab.tableid, c.cellid, wd, clay.width, clay.height);
1615	}
1616
1617	# set row heights and ascents
1618	# first pass: ignore cells with rowspan > 1
1619	for(ri := 0; ri < tab.nrow; ri++) {
1620		row := tab.rows[ri];
1621		h := 0;
1622		a := 0;
1623		n : int;
1624		for(rcl := row.cells; rcl != nil; rcl = tl rcl) {
1625			c := hd rcl;
1626			if(c.rowspan > 1 || c.layid < 0)
1627				continue;
1628			al := c.align.valign;
1629			if(al == Anone)
1630				al = tab.rows[c.row].align.valign;
1631			clay := f.sublays[c.layid];
1632			if(al == Abaseline) {
1633				n = c.ascent;
1634				if(n > a) {
1635					h += (n - a);
1636					a = n;
1637				}
1638				n = clay.height - c.ascent;
1639				if(n > h-a)
1640					h = a + n;
1641			}
1642			else {
1643				n = clay.height;
1644				if(n > h)
1645					h = n;
1646			}
1647		}
1648		row.height = h;
1649		row.ascent = a;
1650	}
1651	# second pass: take care of rowspan > 1
1652	# (this algorithm isn't quite right -- it might add more space
1653	# than is needed in the presence of multiple overlapping rowspans)
1654	for(cl = tab.cells; cl != nil; cl = tl cl) {
1655		c := hd cl;
1656		if(c.rowspan > 1) {
1657			spanht := 0;
1658			for(i := 0; i < c.rowspan && c.row+i < tab.nrow; i++)
1659				spanht += tab.rows[c.row+i].height;
1660			if(c.layid < 0)
1661				continue;
1662			clay := f.sublays[c.layid];
1663			ht := clay.height - (c.rowspan-1)*vsep;
1664			if(ht > spanht) {
1665				# add extra space to last spanned row
1666				i = c.row+c.rowspan-1;
1667				if(i >= tab.nrow)
1668					i = tab.nrow - 1;
1669				tab.rows[i].height += ht - spanht;
1670				if(dbgtab)
1671					sys->print("sizetable %d, row %d height %d\n", tab.tableid, i, tab.rows[i].height);
1672			}
1673		}
1674	}
1675	# get total width, heights, and col x / row y positions
1676	totw = bd + hsp + cbd + pad;
1677	for(ci = 0; ci < tab.ncol; ci++) {
1678		tab.cols[ci].pos.x = totw;
1679		if(dbgtab)
1680			sys->print("sizetable %d, col %d at x=%d\n", tab.tableid, ci, totw);
1681		totw += tab.cols[ci].width + hsep;
1682	}
1683	totw = totw - (cbd+pad) + bd;
1684	toth := bd + vsp + cbd + pad;
1685	# first time: move tab.caption items into layout
1686	if(tab.caption != nil) {
1687		# lay caption with Aleft; drawing will center it over the table width
1688		tab.caption_lay = sublayout(f, availwidth, Aleft, f.layout.background, tab.caption);
1689		caplay := f.sublays[tab.caption_lay];
1690		tab.caph = caplay.height + CAPSEP;
1691		tab.caption = nil;
1692	}
1693	else if(tab.caption_lay >= 0) {
1694		caplay := f.sublays[tab.caption_lay];
1695		if(tab.availw != availwidth || (caplay.flags&Lchanged) != byte 0) {
1696			relayout(f, caplay, availwidth, Aleft);
1697			tab.caph = caplay.height + CAPSEP;
1698		}
1699	}
1700	if(tab.caption_place == Atop)
1701		toth += tab.caph;
1702	for(ri = 0; ri < tab.nrow; ri++) {
1703		tab.rows[ri].pos.y = toth;
1704		if(dbgtab)
1705			sys->print("sizetable %d, row %d at y=%d\n", tab.tableid, ri, toth);
1706		toth += tab.rows[ri].height + vsep;
1707	}
1708	toth = toth - (cbd+pad) + bd;
1709	if(tab.caption_place == Abottom)
1710		toth += tab.caph;
1711	tab.totw = totw;
1712	tab.toth = toth;
1713	tab.availw = availwidth;
1714	tab.flags &= ~Lchanged;
1715	if(dbgtab)
1716		sys->print("\ndone sizetable %d, availwidth %d, totw=%d, toth=%d\n\n",
1717			tab.tableid, availwidth, totw, toth);
1718}
1719
1720# Calculate various table spacing parameters
1721tableparams(tab: ref Table) : (int, int, int, int, int, int, int)
1722{
1723	bd := tab.border;
1724	hsp := tab.cellspacing;
1725	vsp := hsp;
1726	pad := tab.cellpadding;
1727	if(bd != 0)
1728		cbd := 1;
1729	else
1730		cbd = 0;
1731	hsep := 2*(cbd+pad)+hsp;
1732	vsep := 2*(cbd+pad)+vsp;
1733	return (hsp, vsp, pad, bd, cbd, hsep, vsep);
1734}
1735
1736# return cell width, taking multicol spanning into account
1737cellwidth(tab: ref Table, c: ref Tablecell, hsep: int) : int
1738{
1739	if(c.colspan == 1)
1740		return tab.cols[c.col].width;
1741	wd := (c.colspan-1)*hsep;
1742	for(i := 0; i < c.colspan && c.col + i < tab.ncol; i++)
1743		wd += tab.cols[c.col + i].width;
1744	return wd;
1745}
1746
1747# return cell height, taking multirow spanning into account
1748cellheight(tab: ref Table, c: ref Tablecell, vsep: int) : int
1749{
1750	if(c.rowspan == 1)
1751		return tab.rows[c.row].height;
1752	ht := (c.rowspan-1)*vsep;
1753	for(i := 0; i < c.rowspan && c.row + i < tab.nrow; i++)
1754		ht += tab.rows[c.row + i].height;
1755	return ht;
1756}
1757
1758# Calculate the column widths w as the max of the cells
1759# maxw or minw (as domax is 1 or 0).
1760# Return the total of all w.
1761# (hseps were accounted for by the adjustment that got
1762# totw from availwidth).
1763# hsep is amount of free space available between columns
1764# where there is multicolumn spanning.
1765# This is a two-pass algorithm.  The first pass ignores
1766# cells that span multiple columns.  The second pass
1767# sees if those multispanners need still more space, and
1768# if so, apportions the space out.
1769widthcalc(tab: ref Table, w: array of int, hsep, domax: int) : int
1770{
1771	anyspan := 0;
1772	totw := 0;
1773	for(pass := 1; pass <= 2; pass++) {
1774		if(pass==2 && !anyspan)
1775			break;
1776		totw = 0;
1777		for(ci := 0; ci < tab.ncol; ci++) {
1778			for(ri := 0; ri < tab.nrow; ri++) {
1779				c := tab.grid[ri][ci];
1780				if(c == nil)
1781					continue;
1782				if(domax)
1783					cwd := c.maxw;
1784				else
1785					cwd = c.minw;
1786				if(pass == 1) {
1787					if(c.colspan > 1) {
1788						anyspan = 1;
1789						continue;
1790					}
1791					if(cwd > w[ci])
1792						w[ci] = cwd;
1793				}
1794				else {
1795					if(c.colspan == 1 || !(ci==c.col && ri==c.row))
1796						continue;
1797					curw := 0;
1798					iend := ci+c.colspan;
1799					if(iend > tab.ncol)
1800						iend = tab.ncol;
1801					for(i:=ci; i < iend; i++)
1802						curw += w[i];
1803
1804					# padding between spanned cols is free
1805					cwd -= hsep*(c.colspan-1);
1806					diff := cwd-curw;
1807					if(diff <= 0)
1808						continue;
1809					# doesn't fit: apportion diff among cols
1810					# in proportion to their current w
1811					for(i = ci; i < iend; i++) {
1812						if(curw == 0)
1813							w[i] = diff/c.colspan;
1814						else
1815							w[i] += diff*w[i]/curw;
1816					}
1817				}
1818			}
1819			totw += w[ci];
1820		}
1821	}
1822	return totw;
1823}
1824
1825layframeset(f: ref Frame, ki: ref Kidinfo)
1826{
1827	fwid := f.cr.dx();
1828	fht := f.cr.dy();
1829	if(dbg)
1830		sys->print("layframeset, configuring frame %d wide by %d high\n", fwid, fht);
1831	(nrow, rowh) := frdimens(ki.rows, fht);
1832	(ncol, colw) := frdimens(ki.cols, fwid);
1833	l := ki.kidinfos;
1834	y := f.cr.min.y;
1835	for(i := 0; i < nrow; i++) {
1836		x := f.cr.min.x;
1837		for(j := 0; j < ncol; j++) {
1838			if(l == nil)
1839				return;
1840			r := Rect(Point(x,y), Point(x+colw[j],y+rowh[i]));
1841			if(dbg)
1842				sys->print("kid gets rect (%d,%d)(%d,%d)\n", r.min.x, r.min.y, r.max.x, r.max.y);
1843			kidki := hd l;
1844			l = tl l;
1845			kidf := Frame.newkid(f, kidki, r);
1846			if(!kidki.isframeset)
1847				f.kids = kidf :: f.kids;
1848			if(kidf.framebd != 0) {
1849				kidf.cr = kidf.r.inset(2);
1850				drawborder(kidf.cim, kidf.cr, 2, DarkGrey);
1851			}
1852			if(kidki.isframeset) {
1853				layframeset(kidf, kidki);
1854				for(al := kidf.kids; al != nil; al = tl al)
1855					f.kids = (hd al) :: f.kids;
1856			}
1857			x += colw[j];
1858		}
1859		y += rowh[i];
1860	}
1861}
1862
1863# Use the dimension specs in dims to allocate total space t.
1864# Return (number of dimens, array of allocated space)
1865frdimens(dims: array of B->Dimen, t: int): (int, array of int)
1866{
1867	n := len dims;
1868	if(n == 1)
1869		return (1, array[] of {t});
1870	totpix := 0;
1871	totpcnt := 0;
1872	totrel := 0;
1873	for(i := 0; i < n; i++) {
1874		v := dims[i].spec();
1875		kind := dims[i].kind();
1876		if(v < 0) {
1877			v = 0;
1878			dims[i] = Dimen.make(kind, v);
1879		}
1880		case kind {
1881			B->Dpixels => totpix += v;
1882			B->Dpercent => totpcnt += v;
1883			B->Drelative => totrel += v;
1884			B->Dnone => totrel++;
1885		}
1886	}
1887	spix := 1.0;
1888	spcnt := 1.0;
1889	min_relu := 0;
1890	if(totrel > 0)
1891		min_relu = 30;	# allow for scrollbar (14) and a bit
1892	relu := real min_relu;
1893	tt := totpix + (t*totpcnt/100) + totrel*min_relu;
1894	# want
1895	#  t ==  totpix*spix + (totpcnt/100)*spcnt*t + totrel*relu
1896	if(tt < t) {
1897		# need to expand one of spix, spcnt, relu
1898		if(totrel == 0) {
1899			if(totpcnt != 0)
1900				# spix==1.0, relu==0, solve for spcnt
1901				spcnt = real ((t-totpix) * 100)/ real (t*totpcnt);
1902			else
1903				# relu==0, totpcnt==0, solve for spix
1904				spix = real t/ real totpix;
1905		}
1906		else
1907			# spix=1.0, spcnt=1.0, solve for relu
1908			relu += real (t-tt)/ real totrel;
1909	}
1910	else {
1911		# need to contract one or more of spix, spcnt, and have relu==min_relu
1912		totpixrel := totpix+totrel*min_relu;
1913		if(totpixrel < t) {
1914			# spix==1.0, solve for spcnt
1915			spcnt = real ((t-totpixrel) * 100)/ real (t*totpcnt);
1916		}
1917		else {
1918			# let spix==spcnt, solve
1919			trest := t - totrel*min_relu;
1920			if(trest > 0) {
1921				spcnt = real trest/real (totpix+(t*totpcnt/100));
1922			}
1923			else {
1924				spcnt = real t / real tt;
1925				relu = 0.0;
1926			}
1927			spix = spcnt;
1928		}
1929	}
1930	x := array[n] of int;
1931	tt = 0;
1932	for(i = 0; i < n-1; i++) {
1933		vr := real dims[i].spec();
1934		case dims[i].kind() {
1935			B->Dpixels => vr = vr * spix;
1936			B->Dpercent => vr = vr * real t * spcnt / 100.0;
1937			B->Drelative => vr = vr * relu;
1938			B->Dnone => vr = relu;
1939		}
1940		x[i] = int vr;
1941		tt += x[i];
1942	}
1943	x[n-1] = t - tt;
1944	return (n, x);
1945}
1946
1947# Return last item of list of items, or nil if no items
1948lastitem(it: ref Item) : ref Item
1949{
1950	ans : ref Item = it;
1951	for( ; it != nil; it = it.next)
1952		ans = it;
1953	return ans;
1954}
1955
1956# Lay out table if availw changed or tab changed
1957checktabsize(f: ref Frame, t: ref Item.Itable, availw: int)
1958{
1959	tab := t.table;
1960	if (dbgtab)
1961		sys->print("checktabsize %d, availw %d, tab.availw %d, changed %d\n", tab.tableid, availw, tab.availw, (tab.flags&Lchanged)>byte 0);
1962	if(availw != tab.availw || int (tab.flags&Lchanged)) {
1963		sizetable(f, tab, availw);
1964		t.width = tab.totw + 2*tab.border;
1965		t.height = tab.toth + 2*tab.border;
1966		t.ascent = t.height;
1967	}
1968}
1969
1970widthfromspec(wspec: Dimen, availw: int) : int
1971{
1972	w := availw;
1973	spec := wspec.spec();
1974	case wspec.kind() {
1975		Dpixels => w = spec;
1976		Dpercent => w = spec*w/100;
1977	}
1978	return w;
1979}
1980
1981# An image may have arrived for an image input field
1982checkffsize(f: ref Frame, i: ref Item, ff: ref Formfield)
1983{
1984	if(ff.ftype == Fimage && ff.image != nil) {
1985		pick imi := ff.image {
1986		Iimage =>
1987			if(imi.ci.mims != nil && ff.ctlid >= 0) {
1988				pick b := f.controls[ff.ctlid] {
1989				Cbutton =>
1990					if(b.pic == nil) {
1991						b.pic = imi.ci.mims[0].im;
1992						b.picmask = imi.ci.mims[0].mask;
1993						w := b.pic.r.dx();
1994						h := b.pic.r.dy();
1995						b.r.max.x = b.r.min.x + w;
1996						b.r.max.y = b.r.min.y + h;
1997						i.width = w;
1998						i.height = h;
1999						i.ascent = h;
2000					}
2001				}
2002			}
2003		}
2004	}
2005	else if(ff.ftype == Fselect) {
2006		opts := ff.options;
2007		if(ff.ctlid >=0) {
2008			pick c := f.controls[ff.ctlid] {
2009			Cselect =>
2010				if(len opts != len c.options) {
2011					nc := Control.newff(f, ff);
2012					f.controls[ff.ctlid] = nc;
2013					i.width = nc.r.dx();
2014					i.height = nc.r.dy();
2015					i.ascent = lineascent + SELMARGIN;
2016				}
2017			}
2018		}
2019	}
2020}
2021
2022drawall(f: ref Frame)
2023{
2024	oclipr := f.cim.clipr;
2025	origin := f.lptosp(zp);
2026	clipr := f.dirtyr.addpt(origin);
2027	f.cim.clipr = clipr;
2028	fillbg(f, clipr);
2029	if(dbg > 1)
2030		sys->print("drawall, cr=(%d,%d,%d,%d), viewr=(%d,%d,%d,%d), origin=(%d,%d)\n",
2031			f.cr.min.x, f.cr.min.y, f.cr.max.x, f.cr.max.y,
2032			f.viewr.min.x, f.viewr.min.y, f.viewr.max.x, f.viewr.max.y,
2033			origin.x, origin.y);
2034	if(f.layout != nil)
2035		drawlay(f, f.layout, origin);
2036	f.cim.clipr = oclipr;
2037	G->flush(f.cr);
2038	f.isdirty = 0;
2039}
2040
2041drawlay(f: ref Frame, lay: ref Lay, origin: Point)
2042{
2043	for(l := lay.start.next; l != lay.end; l = l.next)
2044		drawline(f, origin, l, lay);
2045}
2046
2047# Draw line l in frame f, assuming that content's (0,0)
2048# aligns with layorigin in f.cim.
2049drawline(f : ref Frame, layorigin : Point, l: ref Line, lay: ref Lay)
2050{
2051	im := f.cim;
2052	o := layorigin.add(l.pos);
2053	x := o.x;
2054	y := o.y;
2055	lr := Rect(zp, Point(l.width, l.height)).addpt(o);
2056	isdirty := f.isdirty && lr.Xrect(f.dirtyr.addpt(f.lptosp(zp)));
2057	inview := lr.Xrect(f.cr) && isdirty;
2058
2059	# note: drawimg must always be called to update
2060	# draw point of animated images
2061	for(it := l.items; it != nil; it = it.next) {
2062		pick i := it {
2063		Itext =>
2064			if (!inview || i.s == nil)
2065				break;
2066			fnt := fonts[i.fnt];
2067			width := i.width;
2068			yy := y+l.ascent - fnt.f.ascent + (int i.voff) - Voffbias;
2069			if (f.prctxt != nil) {
2070				if (yy < f.cr.min.y)
2071					continue;
2072				endy := yy + fnt.f.height;
2073				if (endy > f.cr.max.y) {
2074					# do not draw
2075					if (yy < f.prctxt.endy)
2076						f.prctxt.endy = yy;
2077					continue;
2078				}
2079			}
2080			fgi := colorimage(i.fg);
2081			im.text(Point(x, yy), fgi, zp, fnt.f, i.s);
2082			if(i.ul != ULnone) {
2083				if(i.ul == ULmid)
2084					yy += 2*i.ascent/3;
2085				else
2086					yy += i.height - 1;
2087				# don't underline leading space
2088				# have already adjusted x pos in fixlinegeom()
2089				ulx := x;
2090				ulw := width;
2091				if (i.s[0] == ' ') {
2092					ulx += fnt.spw;
2093					ulw -= fnt.spw;
2094				}
2095				if (i.s[len i.s - 1] == ' ')
2096					ulw -= fnt.spw;
2097				if (ulw < 1)
2098					continue;
2099				im.drawop(Rect(Point(ulx,yy),Point(ulx+ulw,yy+1)), fgi, nil, zp, Draw->S);
2100			}
2101		Irule =>
2102			if (!inview)
2103				break;
2104			yy := y + RULESP;
2105			im.draw(Rect(Point(x,yy),Point(x+i.width,yy+i.size)),
2106					display.black, nil, zp);
2107		Iimage =>
2108			yy := y;
2109			if(i.align == Abottom)
2110				# bottom aligns with baseline
2111				yy += l.ascent - i.imheight;
2112			else if(i.align == Amiddle)
2113				yy += l.ascent - (i.imheight/2);
2114			drawimg(f, Point(x,yy), i);
2115		Iformfield =>
2116			ff := i.formfield;
2117			if(ff.ctlid >= 0 && ff.ctlid < len f.controls) {
2118				ctl := f.controls[ff.ctlid];
2119				dims := ctl.r.max.sub(ctl.r.min);
2120				# align as text
2121				yy := y + l.ascent - i.ascent;
2122				p := Point(x,yy);
2123				ctl.r = Rect(p, p.add(dims));
2124				if (!inview)
2125					break;
2126				if (f.prctxt != nil) {
2127					if (yy < f.cr.min.y)
2128						continue;
2129					if (ctl.r.max.y > f.cr.max.y) {
2130						# do not draw
2131						if (yy < f.prctxt.endy)
2132							f.prctxt.endy = yy;
2133						continue;
2134					}
2135				}
2136				ctl.draw(0);
2137			}
2138		Itable =>
2139			# don't check inview - table can contain images
2140			drawtable(f, lay, Point(x,y), i.table);
2141			t := i.table;
2142		Ifloat =>
2143			xx := layorigin.x + lay.margin;
2144			if(i.side == Aright) {
2145				xx -= i.x;
2146#				# for main layout of frame, floats hug
2147#				# right edge of frame, not layout
2148#				# (other browsers do that)
2149#				if(f.layout == lay)
2150					xx += lay.targetwidth;
2151#				else
2152#					xx += lay.width;
2153			}
2154			else
2155				xx += i.x;
2156			pick fi := i.item {
2157			Iimage =>
2158				drawimg(f, Point(xx, layorigin.y + i.y + (int fi.border + int fi.vspace)), fi);
2159			Itable =>
2160				drawtable(f, lay, Point(xx, layorigin.y + i.y), fi.table);
2161			}
2162		}
2163		x += it.width;
2164	}
2165}
2166
2167drawimg(f: ref Frame, iorigin: Point, i: ref Item.Iimage)
2168{
2169	ci := i.ci;
2170	im := f.cim;
2171	iorigin.x += int i.hspace + int i.border;
2172	# y coord is already adjusted for border and vspace
2173	if(ci.mims != nil) {
2174		r := Rect(iorigin, iorigin.add(Point(i.imwidth,i.imheight)));
2175		inview := r.Xrect(f.cr);
2176		if(i.ctlid >= 0) {
2177			# animated
2178			c := f.controls[i.ctlid];
2179			dims := c.r.max.sub(c.r.min);
2180			c.r = Rect(iorigin, iorigin.add(dims));
2181			if (inview) {
2182				pick ac := c {
2183				Canimimage =>
2184					ac.redraw = 1;
2185					ac.bg = f.layout.background;
2186				}
2187				c.draw(0);
2188			}
2189		}
2190		else if (inview) {
2191			mim := ci.mims[0];
2192			iorigin = iorigin.add(mim.origin);
2193			im.draw(r, mim.im, mim.mask, zp);
2194		}
2195		if(inview && i.border != byte 0) {
2196			if(i.anchorid != 0)
2197				bdcol := f.doc.link;
2198			else
2199				bdcol = Black;
2200			drawborder(im, r, int i.border, bdcol);
2201		}
2202	}
2203	else if((CU->config).imagelvl == CU->ImgNone && i.altrep != "") {
2204		fnt := fonts[DefFnt].f;
2205		yy := iorigin.y+(i.imheight-fnt.height)/2;
2206		xx := iorigin.x + (i.width-fnt.width(i.altrep))/2;
2207		if(i.anchorid != 0)
2208			col := f.doc.link;
2209		else
2210			col = DarkGrey;
2211		fgi := colorimage(col);
2212		im.text(Point(xx, yy), fgi, zp, fnt, i.altrep);
2213	}
2214}
2215
2216drawtable(f : ref Frame, parentlay: ref Lay, torigin: Point, tab: ref Table)
2217{
2218	if (dbgtab)
2219		sys->print("drawtable %d\n", tab.tableid);
2220	if(tab.ncol == 0 || tab.nrow == 0)
2221		return;
2222	im := f.cim;
2223	(hsp, vsp, pad, bd, cbd, hsep, vsep) := tableparams(tab);
2224	x := torigin.x;
2225	y := torigin.y;
2226	capy := y;
2227	boxy := y;
2228	if(tab.caption_place == Abottom)
2229		capy = y+tab.toth-tab.caph+vsp;
2230	else
2231		boxy = y+tab.caph;
2232	if (tab.background.color != -1 && tab.background.color != parentlay.background.color) {
2233#	if(tab.background.image != parentlay.background.image ||
2234#	   tab.background.color != parentlay.background.color) {
2235		bgi := colorimage(tab.background.color);
2236		im.draw(((x,boxy),(x+tab.totw,boxy+tab.toth-tab.caph)),
2237			bgi, nil, zp);
2238	}
2239	if(bd != 0)
2240		drawborder(im, ((x+bd,boxy+bd),(x+tab.totw-bd,boxy+tab.toth-tab.caph-bd)),
2241			1, Black);
2242	for(cl := tab.cells; cl != nil; cl = tl cl) {
2243		c := hd cl;
2244		if (c.layid == -1 || c.layid >= len f.sublays) {
2245			# for some reason (usually scrolling)
2246			# we are drawing this cell before it has been layed out
2247			continue;
2248		}
2249		clay := f.sublays[c.layid];
2250		if(clay == nil)
2251			continue;
2252		if(c.col >= len tab.cols)
2253			continue;
2254		cx := x + tab.cols[c.col].pos.x;
2255		cy := y + tab.rows[c.row].pos.y;
2256		wd := cellwidth(tab, c, hsep);
2257		ht := cellheight(tab, c, vsep);
2258		if(c.background.image != nil && c.background.image.ci != nil && c.background.image.ci.mims != nil) {
2259			cellr := Rect((cx-pad,cy-pad),(cx+wd+pad,cy+ht+pad));
2260			ci := c.background.image.ci;
2261			bgi := ci.mims[0].im;
2262			bgmask := ci.mims[0].mask;
2263			im.draw(cellr, bgi, bgmask, bgi.r.min);
2264		} else if(c.background.color != -1 && c.background.color != tab.background.color) {
2265			bgi := colorimage(c.background.color);
2266			im.draw(((cx-pad,cy-pad),(cx+wd+pad,cy+ht+pad)),
2267				bgi, nil, zp);
2268		}
2269		if(bd != 0)
2270			drawborder(im, ((cx-pad+1,cy-pad+1),(cx+wd+pad-1,cy+ht+pad-1)),
2271				1, Black);
2272		if(c.align.valign != Atop && c.align.valign != Abaseline) {
2273			n := ht - clay.height;
2274			if(c.align.valign == Amiddle)
2275				cy += n/2;
2276			else if(c.align.valign == Abottom)
2277				cy += n;
2278		}
2279		if(dbgtab)
2280			sys->print("drawtable %d cell %d at (%d,%d)\n",
2281				tab.tableid, c.cellid, cx, cy);
2282		drawlay(f, clay, Point(cx,cy));
2283	}
2284	if(tab.caption_lay >= 0) {
2285		caplay := f.sublays[tab.caption_lay];
2286		capx := x;
2287		if(caplay.width < tab.totw)
2288			capx += (tab.totw-caplay.width) / 2;
2289		drawlay(f, caplay, Point(capx,capy));
2290	}
2291}
2292
2293# Draw border of width n just outside r, using src color
2294drawborder(im: ref Image, r: Rect, n, color: int)
2295{
2296	x := r.min.x-n;
2297	y := r.min.y - n;
2298	xr := r.max.x+n;
2299	ybi := r.max.y;
2300	src := colorimage(color);
2301	im.draw((Point(x,y),Point(xr,y+n)), src, nil, zp);				# top
2302	im.draw((Point(x,ybi),Point(xr,ybi+n)), src, nil, zp);			# bottom
2303	im.draw((Point(x,y+n),Point(x+n,ybi)), src, nil, zp);			# left
2304	im.draw((Point(xr-n,y+n),Point(xr,ybi)), src, nil, zp);			# right
2305}
2306
2307# Draw relief border just outside r, width 2 border,
2308# colors white/lightgrey/darkgrey/black
2309# to give raised relief (if raised != 0) or sunken.
2310drawrelief(im: ref Image, r: Rect, raised: int)
2311{
2312	# ((x,y),(xr,yb)) == r
2313	x := r.min.x;
2314	x1 := x-1;
2315	x2 := x-2;
2316	xr := r.max.x;
2317	xr1 := xr+1;
2318	xr2 := xr+2;
2319	y := r.min.y;
2320	y1 := y-1;
2321	y2 := y-2;
2322	yb := r.max.y;
2323	yb1 := yb+1;
2324	yb2 := yb+2;
2325
2326	# colors for top/left outside, top/left inside, bottom/right outside, bottom/right inside
2327	tlo, tli, bro, bri: ref Image;
2328	if(raised) {
2329		tlo = colorimage(Grey);
2330		tli = colorimage(White);
2331		bro = colorimage(Black);
2332		bri = colorimage(DarkGrey);
2333	}
2334	else {
2335		tlo = colorimage(DarkGrey);
2336		tli = colorimage(Black);
2337		bro = colorimage(White);
2338		bri = colorimage(Grey);
2339	}
2340
2341	im.draw((Point(x2,y2), Point(xr1,y1)), tlo, nil, zp);		# top outside
2342	im.draw((Point(x1,y1), Point(xr,y)), tli, nil, zp);			# top inside
2343	im.draw((Point(x2,y1), Point(x1,yb1)), tlo, nil, zp);		# left outside
2344	im.draw((Point(x1,y), Point(x,yb)), tli, nil, zp);			# left inside
2345	im.draw((Point(xr,y1),Point(xr1,yb)), bri, nil, zp);		# right inside
2346	im.draw((Point(xr1,y),Point(xr2,yb1)), bro, nil, zp);		# right outside
2347	im.draw((Point(x1,yb),Point(xr1,yb1)), bri, nil, zp);		# bottom inside
2348	im.draw((Point(x,yb1),Point(xr2,yb2)), bro, nil, zp);		# bottom outside
2349}
2350
2351# Fill r with color
2352drawfill(im: ref Image, r: Rect, color: int)
2353{
2354	im.draw(r, colorimage(color), nil, zp);
2355}
2356
2357# Draw string in default font at p
2358drawstring(im: ref Image, p: Point, s: string)
2359{
2360	im.text(p, colorimage(Black), zp, fonts[DefFnt].f, s);
2361}
2362
2363# Return (width, height) of string in default font
2364measurestring(s: string) : Point
2365{
2366	f := fonts[DefFnt].f;
2367	return (f.width(s), f.height);
2368}
2369
2370# Mark as "changed" everything with change flags on the loc path
2371markchanges(loc: ref Loc)
2372{
2373	lastf : ref Frame = nil;
2374	for(i := 0; i < loc.n; i++) {
2375		case loc.le[i].kind {
2376		LEframe =>
2377			lastf = loc.le[i].frame;
2378			lastf.layout.flags |= Lchanged;
2379		LEline =>
2380			loc.le[i].line.flags |= Lchanged;
2381		LEitem =>
2382			pick it := loc.le[i].item {
2383			Itable =>
2384				it.table.flags |= Lchanged;
2385			Ifloat =>
2386				# whole layout will be redone if layout changes
2387				# and there are any floats
2388				;
2389			}
2390		LEtablecell =>
2391			if(lastf == nil)
2392				raise "EXInternal: markchanges no lastf";
2393			c := loc.le[i].tcell;
2394			clay := lastf.sublays[c.layid];
2395			if(clay != nil)
2396				clay.flags |= Lchanged;
2397		}
2398	}
2399}
2400
2401# one-item cache for colorimage
2402prevrgb := -1;
2403prevrgbimage : ref Image = nil;
2404
2405colorimage(rgb: int) : ref Image
2406{
2407	if(rgb == prevrgb)
2408		return prevrgbimage;
2409	prevrgb = rgb;
2410	if(rgb == Black)
2411		prevrgbimage = display.black;
2412	else if(rgb == White)
2413		prevrgbimage = display.white;
2414	else {
2415		hv := rgb % NCOLHASH;
2416		if (hv < 0)
2417			hv = -hv;
2418		xhd := colorhashtab[hv];
2419		x := xhd;
2420		while(x != nil && x.rgb  != rgb)
2421			x = x.next;
2422		if(x == nil) {
2423#			pix := I->closest_rgbpix((rgb>>16)&255, (rgb>>8)&255, rgb&255);
2424#			im := display.color(pix);
2425			im := display.rgb((rgb>>16)&255, (rgb>>8)&255, rgb&255);
2426			if(im == nil)
2427				raise sys->sprint("exLayout: can't allocate color #%8.8ux: %r", rgb);
2428			x = ref Colornode(rgb, im, xhd);
2429			colorhashtab[hv] = x;
2430		}
2431		prevrgbimage = x.im;
2432	}
2433	return prevrgbimage;
2434}
2435
2436# Use f.background.image (if not nil) or f.background.color to fill r (in cim coord system)
2437# with background color.
2438fillbg(f: ref Frame, r: Rect)
2439{
2440	bgi: ref Image;
2441	ii := f.doc.background.image;
2442	if (ii != nil && ii.ci != nil && ii.ci.mims != nil)
2443		bgi = ii.ci.mims[0].im;
2444	if(bgi == nil)
2445		bgi = colorimage(f.doc.background.color);
2446	f.cim.drawop(r, bgi, nil, f.viewr.min, Draw->S);
2447}
2448
2449TRIup, TRIdown, TRIleft, TRIright: con iota;
2450# Assume r is a square
2451drawtriangle(im: ref Image, r: Rect, kind, style: int)
2452{
2453	drawfill(im, r, Grey);
2454	b := r.max.x - r.min.x;
2455	if(b < 4)
2456		return;
2457	b2 := b/2;
2458	bm2 := b-ReliefBd;
2459	p := array[3] of Point;
2460	col012, col20 : ref Image;
2461	d := colorimage(DarkGrey);
2462	l := colorimage(White);
2463	case kind {
2464	TRIup =>
2465		p[0] = Point(b2, ReliefBd);
2466		p[1] = Point(bm2,bm2);
2467		p[2] = Point(ReliefBd,bm2);
2468		col012 = d;
2469		col20 = l;
2470	TRIdown =>
2471		p[0] = Point(b2,bm2);
2472		p[1] = Point(ReliefBd,ReliefBd);
2473		p[2] = Point(bm2,ReliefBd);
2474		col012 = l;
2475		col20 = d;
2476	TRIleft =>
2477		p[0] = Point(bm2, ReliefBd);
2478		p[1] = Point(bm2, bm2);
2479		p[2] = Point(ReliefBd,b2);
2480		col012 = d;
2481		col20 = l;
2482	TRIright =>
2483		p[0] = Point(ReliefBd,bm2);
2484		p[1] = Point(ReliefBd,ReliefBd);
2485		p[2] = Point(bm2,b2);
2486		col012 = l;
2487		col20 = d;
2488	}
2489	if(style == ReliefSunk) {
2490		t := col012;
2491		col012 = col20;
2492		col20 = t;
2493	}
2494	for(i := 0; i < 3; i++)
2495		p[i] = p[i].add(r.min);
2496	im.fillpoly(p, ~0, colorimage(Grey), zp);
2497	im.line(p[0], p[1], 0, 0, ReliefBd/2, col012, zp);
2498	im.line(p[1], p[2], 0, 0, ReliefBd/2, col012, zp);
2499	im.line(p[2], p[0], 0, 0, ReliefBd/2, col20, zp);
2500}
2501
2502abs(a: int) : int
2503{
2504	if(a < 0)
2505		return -a;
2506	return a;
2507}
2508
2509Frame.new() : ref Frame
2510{
2511	f := ref Frame;
2512	f.parent = nil;
2513	f.cim = nil;
2514	f.r = Rect(zp, zp);
2515	f.animpid = 0;
2516	f.reset();
2517	return f;
2518}
2519
2520Frame.newkid(parent: ref Frame, ki: ref Kidinfo, r: Rect) : ref Frame
2521{
2522	f := ref Frame;
2523	f.parent = parent;
2524	f.cim = parent.cim;
2525	f.r = r;
2526	f.animpid = 0;
2527	f.reset();
2528	f.src = ki.src;
2529	f.name = ki.name;
2530	f.marginw = ki.marginw;
2531	f.marginh = ki.marginh;
2532	f.framebd = ki.framebd;
2533	f.flags = ki.flags;
2534	return f;
2535}
2536
2537# Note: f.parent, f.cim and f.r should not be reset
2538# And if f.parent is true, don't reset params set in frameset.
2539Frame.reset(f: self ref Frame)
2540{
2541	f.id = ++frameid;
2542	f.doc = nil;
2543	if(f.parent == nil) {
2544		f.src = nil;
2545		f.name = "";
2546		f.marginw = FRMARGIN;
2547		f.marginh = FRMARGIN;
2548		f.framebd = 0;
2549		f.flags = FRvscrollauto | FRhscrollauto;
2550	}
2551	f.layout = nil;
2552	f.sublays = nil;
2553	f.sublayid = 0;
2554	f.controls = nil;
2555	f.controlid = 0;
2556	f.cr = f.r;
2557	f.isdirty = 1;
2558	f.dirtyr = f.cr;
2559	f.viewr = Rect(zp, zp);
2560	f.totalr = f.viewr;
2561	f.vscr = nil;
2562	f.hscr = nil;
2563	hadkids := (f.kids != nil);
2564	f.kids = nil;
2565	if(f.animpid != 0)
2566		CU->kill(f.animpid, 0);
2567	if(J != nil && hadkids)
2568		J->frametreechanged(f);
2569	f.animpid = 0;
2570}
2571
2572Frame.dirty(f: self ref Frame, r: Draw->Rect)
2573{
2574	if (f.isdirty)
2575		f.dirtyr= f.dirtyr.combine(r);
2576	else {
2577		f.dirtyr = r;
2578		f.isdirty = 1;
2579	}
2580}
2581
2582Frame.addcontrol(f: self ref Frame, c: ref Control) : int
2583{
2584	if(len f.controls <= f.controlid) {
2585		newcontrols := array[len f.controls + 30] of ref Control;
2586		newcontrols[0:] = f.controls;
2587		f.controls = newcontrols;
2588	}
2589	f.controls[f.controlid] = c;
2590	ans := f.controlid++;
2591	return ans;
2592}
2593
2594Frame.xscroll(f: self ref Frame, kind, val: int)
2595{
2596	newx := f.viewr.min.x;
2597	case kind {
2598	CAscrollpage =>
2599		newx += val*(f.cr.dx()*8/10);
2600	CAscrollline =>
2601		newx += val*f.cr.dx()/10;
2602	CAscrolldelta =>
2603		newx += val;
2604	CAscrollabs =>
2605		newx = val;
2606	}
2607	f.scrollabs(Point(newx, f.viewr.min.y));
2608}
2609
2610# Don't actually scroll by "page" and "line",
2611# But rather, 80% and 10%, which give more
2612# context in the first case, and more motion
2613# in the second.
2614Frame.yscroll(f: self ref Frame, kind, val: int)
2615{
2616	newy := f.viewr.min.y;
2617	case kind {
2618	CAscrollpage =>
2619		newy += val*(f.cr.dy()*8/10);
2620	CAscrollline =>
2621		newy += val*f.cr.dy()/20;
2622	CAscrolldelta =>
2623		newy += val;
2624	CAscrollabs =>
2625		newy = val;
2626	}
2627	f.scrollabs(Point(f.viewr.min.x, newy));
2628}
2629
2630Frame.scrollrel(f : self ref Frame, p : Point)
2631{
2632	(x, y) := p;
2633	x += f.viewr.min.x;
2634	y += f.viewr.min.y;
2635	f.scrollabs(f.viewr.min.add(p));
2636}
2637
2638Frame.scrollabs(f : self ref Frame, p : Point)
2639{
2640	(x, y) := p;
2641	lay := f.layout;
2642	margin := 0;
2643	if (lay != nil)
2644		margin = lay.margin;
2645	x = max(0, min(x, f.totalr.max.x));
2646	y = max(0, min(y, f.totalr.max.y + margin - f.cr.dy()));
2647	(oldx, oldy) := f.viewr.min;
2648	if (oldx != x || oldy != y) {
2649		f.viewr.min = (x, y);
2650		fixframegeom(f);
2651		# blit scroll
2652		dx := f.viewr.min.x - oldx;
2653		dy := f.viewr.min.y - oldy;
2654		origin := f.lptosp(zp);
2655		destr := f.viewr.addpt(origin);
2656		srcpt := destr.min.add((dx, dy));
2657		oclipr := f.cim.clipr;
2658		f.cim.clipr = f.cr;
2659		f.cim.drawop(destr, f.cim, nil, srcpt, Draw->S);
2660		if (dx > 0)
2661			f.dirty(Rect((f.viewr.max.x - dx, f.viewr.min.y), f.viewr.max));
2662		else if (dx < 0)
2663			f.dirty(Rect(f.viewr.min, (f.viewr.min.x - dx, f.viewr.max.y)));
2664
2665		if (dy > 0)
2666			f.dirty(Rect((f.viewr.min.x, f.viewr.max.y-dy), f.viewr.max));
2667		else if (dy < 0)
2668			f.dirty(Rect(f.viewr.min, (f.viewr.max.x, f.viewr.min.y-dy)));
2669#f.cim.draw(destr, display.white, nil, zp);
2670		drawall(f);
2671		f.cim.clipr = oclipr;
2672	}
2673}
2674
2675# Convert layout coords (where (0,0) is top left of layout)
2676# to screen coords (i.e., coord system of mouse, f.cr, etc.)
2677Frame.sptolp(f: self ref Frame, sp: Point) : Point
2678{
2679	return f.viewr.min.add(sp.sub(f.cr.min));
2680}
2681
2682# Reverse translation of sptolp
2683Frame.lptosp(f: self ref Frame, lp: Point) : Point
2684{
2685	return lp.add(f.cr.min.sub(f.viewr.min));
2686}
2687
2688# Return Loc of Item or Scrollbar containing p (p in screen coords)
2689# or item it, if that is not nil.
2690Frame.find(f: self ref Frame, p: Point, it: ref Item) : ref Loc
2691{
2692	return framefind(Loc.new(), f, p, it);
2693}
2694
2695# Find it (if non-nil) or place where p is (known to be inside f's layout).
2696framefind(loc: ref Loc, f: ref Frame, p: Point, it: ref Item) : ref Loc
2697{
2698	loc.add(LEframe, f.r.min);
2699	loc.le[loc.n-1].frame = f;
2700	if(it == nil) {
2701		if(f.vscr != nil && p.in(f.vscr.r)) {
2702			loc.add(LEcontrol, f.vscr.r.min);
2703			loc.le[loc.n-1].control = f.vscr;
2704			loc.pos = p.sub(f.vscr.r.min);
2705			return loc;
2706		}
2707		if(f.hscr != nil && p.in(f.hscr.r)) {
2708			loc.add(LEcontrol, f.hscr.r.min);
2709			loc.le[loc.n-1].control = f.hscr;
2710			loc.pos = p.sub(f.hscr.r.min);
2711			return loc;
2712		}
2713	}
2714	if(it != nil || p.in(f.cr)) {
2715		lay := f.layout;
2716		if(f.kids != nil) {
2717			for(fl := f.kids; fl != nil; fl = tl fl) {
2718				kf := hd fl;
2719				try := framefind(loc, kf, p, it);
2720				if(try != nil)
2721					return try;
2722			}
2723		}
2724		else if(lay != nil)
2725			return layfind(loc, f, lay, f.lptosp(zp), p, it);
2726	}
2727	return nil;
2728}
2729
2730# Find it (if non-nil) or place where p is (known to be inside f's layout).
2731# p (in screen coords), lay offset by origin also in screen coords
2732layfind(loc: ref Loc, f: ref Frame, lay: ref Lay, origin, p: Point, it: ref Item) : ref Loc
2733{
2734	for(flist := lay.floats; flist != nil; flist = tl flist) {
2735		fl := hd flist;
2736		fymin := fl.y+origin.y;
2737		fymax := fymin + fl.item.height;
2738		inside := 0;
2739		xx : int;
2740		if(it != nil || (fymin <= p.y && p.y < fymax)) {
2741			xx = origin.x + lay.margin;
2742			if(fl.side == Aright) {
2743				xx -= fl.x;
2744				xx += lay.targetwidth;
2745#				if(lay == f.layout)
2746#					xx = origin.x + (f.cr.dx() - lay.margin) - fl.x;
2747##					xx += f.cr.dx() - fl.x;
2748#				else
2749#					xx += lay.width - fl.x;
2750			}
2751			else
2752				xx += fl.x;
2753			if(p.x >= xx && p.x < xx+fl.item.width)
2754					inside = 1;
2755		}
2756		fp := Point(xx,fymin);
2757		match := 0;
2758		if(it != nil) {
2759			pick fi := fl.item {
2760			Itable =>
2761				loc.add(LEitem, fp);
2762				loc.le[loc.n-1].item = fl;
2763				loc.pos = p.sub(fp);
2764				lloc := tablefind(loc, f, fi, fp, p, it);
2765				if(lloc != nil)
2766					return lloc;
2767			Iimage =>
2768				match = (it == fl || it == fl.item);
2769			}
2770		}
2771		if(match || inside) {
2772			loc.add(LEitem, fp);
2773			loc.le[loc.n-1].item = fl;
2774			loc.pos = p.sub(fp);
2775			if(it == fl.item) {
2776				loc.add(LEitem, fp);
2777				loc.le[loc.n-1].item = fl.item;
2778			}
2779			if(inside) {
2780				pick fi := fl.item {
2781				Itable =>
2782					loc = tablefind(loc, f, fi, fp, p, it);
2783				}
2784			}
2785			return loc;
2786		}
2787	}
2788	for(l :=lay.start; l != nil; l = l.next) {
2789		o := origin.add(l.pos);
2790		if(it != nil || (o.y <= p.y && p.y < o.y+l.height)) {
2791			lloc := linefind(loc, f, l, o, p, it);
2792			if(lloc != nil)
2793				return lloc;
2794			if(it == nil && o.y + l.height >= p.y)
2795				break;
2796		}
2797	}
2798	return nil;
2799}
2800
2801# p (in screen coords), line at o, also in screen coords
2802linefind(loc: ref Loc, f: ref Frame, l: ref Line, o, p: Point, it: ref Item) : ref Loc
2803{
2804	loc.add(LEline, o);
2805	loc.le[loc.n-1].line = l;
2806	x := o.x;
2807	y := o.y;
2808	inside := 0;
2809	for(i := l.items; i != nil; i = i.next) {
2810		if(it != nil || (x <= p.x && p.x < x+i.width)) {
2811			yy := y;
2812			h := 0;
2813			pick pi := i {
2814			Itext =>
2815				fnt := fonts[pi.fnt].f;
2816				yy += l.ascent - fnt.ascent + (int pi.voff) - Voffbias;
2817				h = fnt.height;
2818			Irule =>
2819				h = pi.size;
2820			Iimage =>
2821				yy = y;
2822				if(pi.align == Abottom)
2823					yy += l.ascent - pi.imheight;
2824				else if(pi.align == Amiddle)
2825					yy += l.ascent - (pi.imheight/2);
2826				h = pi.imheight;
2827			Iformfield =>
2828				h = pi.height;
2829				yy += l.ascent - pi.ascent;
2830				if(it != nil) {
2831					if(it == pi.formfield.image) {
2832						loc.add(LEitem, Point(x,yy));
2833						loc.le[loc.n-1].item = i;
2834						loc.add(LEitem, Point(x,yy));
2835						loc.le[loc.n-1].item = it;
2836						loc.pos = zp;	# doesn't matter, its an 'it' test
2837						return loc;
2838					}
2839				}
2840				else if(yy < p.y && p.y < yy+h && pi.formfield.ctlid >= 0) {
2841					loc.add(LEcontrol, Point(x,yy));
2842					loc.le[loc.n-1].control = f.controls[pi.formfield.ctlid];
2843					loc.pos = p.sub(Point(x,yy));
2844					return loc;
2845				}
2846			Itable =>
2847				lloc := tablefind(loc, f, pi, Point(x,y), p, it);
2848				if(lloc != nil)
2849					return lloc;
2850				# else leave h==0 so p test will fail
2851
2852			# floats were handled separately. nulls can be picked by 'it' test
2853			# leave h==0, so p test will fail
2854			}
2855			if(it == i || (it == nil && yy <= p.y && p.y < yy+h)) {
2856				loc.add(LEitem, Point(x,yy));
2857				loc.le[loc.n-1].item = i;
2858				loc.pos = p.sub(Point(x,yy));
2859				return loc;
2860			}
2861			if(it == nil)
2862				return nil;
2863		}
2864		x += i.width;
2865		if(it == nil && x >= p.x)
2866			break;
2867	}
2868	loc.n--;
2869	return nil;
2870}
2871
2872tablefind(loc: ref Loc, f: ref Frame, ti: ref Item.Itable, torigin: Point, p: Point, it: ref Item) : ref Loc
2873{
2874	loc.add(LEitem, torigin);
2875	loc.le[loc.n-1].item = ti;
2876	t := ti.table;
2877	(hsp, vsp, pad, bd, cbd, hsep, vsep) := tableparams(t);
2878	if(t.caption_lay >= 0) {
2879		caplay := f.sublays[t.caption_lay];
2880		capy := torigin.y;
2881		if(t.caption_place == Abottom)
2882			capy += t.toth-t.caph+vsp;
2883		lloc := layfind(loc, f, caplay, Point(torigin.x,capy), p, it);
2884		if(lloc != nil)
2885			return lloc;
2886	}
2887	for(cl := t.cells; cl != nil; cl = tl cl) {
2888		c := hd cl;
2889		if(c.layid == -1 || c.layid >= len f.sublays)
2890			continue;
2891		clay := f.sublays[c.layid];
2892		if(clay == nil)
2893			continue;
2894		cx := torigin.x + t.cols[c.col].pos.x;
2895		cy := torigin.y + t.rows[c.row].pos.y;
2896		wd := cellwidth(t, c, hsep);
2897		ht := cellheight(t, c, vsep);
2898		if(it == nil && !p.in(Rect(Point(cx,cy),Point(cx+wd,cy+ht))))
2899			continue;
2900		if(c.align.valign != Atop && c.align.valign != Abaseline) {
2901			n := ht - clay.height;
2902			if(c.align.valign == Amiddle)
2903				cy += n/2;
2904			else if(c.align.valign == Abottom)
2905				cy += n;
2906		}
2907		loc.add(LEtablecell, Point(cx,cy));
2908		loc.le[loc.n-1].tcell = c;
2909		lloc := layfind(loc, f, clay, Point(cx,cy), p, it);
2910		if(lloc != nil)
2911			return lloc;
2912		loc.n--;
2913		if(it == nil)
2914			return nil;
2915	}
2916	loc.n--;
2917	return nil;
2918}
2919
2920# (called from jscript)
2921# 'it' is an Iimage item in frame f whose image is to be switched
2922# to come from the src URL.
2923#
2924# For now, assume this is called only after the entire build process
2925# has finished.  Also, only handle the case where the image has
2926# been preloaded and is in the cache now.  This isn't right (BUG), but will
2927# cover most of the cases of extant image swapping, and besides,
2928# image swapping is mostly cosmetic anyway.
2929#
2930# For now, pay no attention to scaling issues or animation issues.
2931Frame.swapimage(f: self ref Frame, im: ref Item.Iimage, src: string)
2932{
2933	u := U->parse(src);
2934	if(u.scheme == "")
2935		return;
2936	u = U->mkabs(u, f.doc.base);
2937	# width=height=0 finds u if in cache
2938	newci := CImage.new(u, nil, 0, 0);
2939	cachedci := (CU->imcache).look(newci);
2940	if(cachedci == nil || cachedci.mims == nil)
2941		return;
2942	im.ci = cachedci;
2943
2944	# we're assuming image will have same dimensions
2945	# as one that is replaced, so no relayout is needed;
2946	# otherwise need to call haveimage() instead of drawall()
2947	# Netscape scales replacement image to size of replaced image
2948
2949	f.dirty(f.totalr);
2950	drawall(f);
2951}
2952
2953Frame.focus(f : self ref Frame, focus, raisex : int)
2954{
2955	di := f.doc;
2956	if (di == nil || (CU->config).doscripts == 0)
2957		return;
2958	if (di.evmask && raisex) {
2959		kind := E->SEonfocus;
2960		if (!focus)
2961			kind = E->SEonblur;
2962		if(di.evmask & kind)
2963			se := ref E->ScriptEvent(kind, f.id, -1, -1, -1, -1, -1, -1, 0, nil, nil, 0);
2964	}
2965}
2966
2967Control.newff(f: ref Frame, ff: ref B->Formfield) : ref Control
2968{
2969	ans : ref Control = nil;
2970	case ff.ftype {
2971	Ftext or Fpassword or Ftextarea =>
2972		nh := ff.size;
2973		nv := 1;
2974		linewrap := 0;
2975		if(ff.ftype == Ftextarea) {
2976			nh = ff.cols;
2977			nv = ff.rows;
2978			linewrap = 1;
2979		}
2980		ans = Control.newentry(f, nh, nv, linewrap);
2981		if(ff.ftype == Fpassword)
2982			ans.flags |= CFsecure;
2983		ans.entryset(ff.value);
2984	Fcheckbox or Fradio =>
2985		ans = Control.newcheckbox(f, ff.ftype==Fradio);
2986		if((ff.flags&B->FFchecked) != byte 0)
2987			ans.flags |= CFactive;
2988	Fsubmit or Fimage or Freset or Fbutton =>
2989		if(ff.image == nil)
2990			ans = Control.newbutton(f, nil, nil, ff.value, nil, 0, 1);
2991		else {
2992			pick i := ff.image {
2993			Iimage =>
2994				pic, picmask : ref Image;
2995				if(i.ci.mims != nil) {
2996					pic = i.ci.mims[0].im;
2997					picmask = i.ci.mims[0].mask;
2998				}
2999				lab := "";
3000				if((CU->config).imagelvl == CU->ImgNone) {
3001					lab = i.altrep;
3002					i = nil;
3003				}
3004				ans = Control.newbutton(f, pic, picmask, lab, i, 0, 0);
3005			}
3006		}
3007	Fselect =>
3008		n := len ff.options;
3009		if(n > 0) {
3010			ao := array[n] of Option;
3011			l := ff.options;
3012			for(i := 0; i < n; i++) {
3013				o := hd l;
3014				# these are copied, so selected can be used for current state
3015				ao[i] = *o;
3016				l = tl l;
3017			}
3018			nvis := ff.size;
3019			ans = Control.newselect(f, nvis, ao);
3020		}
3021	Ffile =>
3022		if(dbg)
3023			sys->print("warning: unimplemented file form field\n");
3024	}
3025	if(ans != nil)
3026		ans.ff = ff;
3027	return ans;
3028}
3029
3030Control.newscroll(f: ref Frame, isvert, length, breadth: int) : ref Control
3031{
3032	# need room for at least two squares and 2 borders of size 2
3033	if(length < 12) {
3034		breadth = 0;
3035		length = 0;
3036	}
3037	else if(breadth*2 + 4 > length)
3038		breadth = (length - 4) / 2;
3039	maxpt : Point;
3040	flags := CFenabled;
3041	if(isvert) {
3042		maxpt = Point(breadth, length);
3043		flags |= CFscrvert;
3044	}
3045	else
3046		maxpt = Point(length, breadth);
3047	return ref Control.Cscrollbar(f, nil, Rect(zp,maxpt), flags, nil, 0, 0, 1, 0, nil, (0, 0));
3048}
3049
3050Control.newentry(f: ref Frame, nh, nv, linewrap: int) : ref Control
3051{
3052	w := ctlcharspace*nh + 2*ENTHMARGIN;
3053	h := ctllinespace*nv + 2*ENTVMARGIN;
3054	scr : ref Control;
3055	if (linewrap) {
3056		scr = Control.newscroll(f, 1, h-4, SCRFBREADTH);
3057		scr.r.addpt(Point(w,0));
3058		w += SCRFBREADTH;
3059	}
3060	ans := ref Control.Centry(f, nil, Rect(zp,Point(w,h)), CFenabled, nil, scr, "", (0, 0), 0, linewrap, 0);
3061	if (scr != nil) {
3062		pick pscr := scr {
3063		Cscrollbar =>
3064			pscr.ctl = ans;
3065		}
3066		scr.scrollset(0, 1, 1, 0, 0);
3067	}
3068	return ans;
3069}
3070
3071Control.newbutton(f: ref Frame, pic, picmask: ref Image, lab: string, it: ref Item.Iimage, candisable, dorelief: int) : ref Control
3072{
3073	dpic, dpicmask: ref Image;
3074	w := 0;
3075	h := 0;
3076	if(pic != nil) {
3077		w = pic.r.dx();
3078		h = pic.r.dy();
3079	}
3080	else if(it != nil) {
3081		w = it.imwidth;
3082		h = it.imheight;
3083	}
3084	else {
3085		w = fonts[CtlFnt].f.width(lab);
3086		h = ctllinespace;
3087	}
3088	if(dorelief) {
3089		# form image buttons are shown without margins in other browsers
3090		w += 2*BUTMARGIN;
3091		h += 2*BUTMARGIN;
3092	}
3093	r := Rect(zp, Point(w,h));
3094	if(candisable && pic != nil) {
3095		# make "greyed out" image:
3096		#	- convert pic to monochrome (ones where pic is non-white)
3097		#	- draw pic in White, then DarkGrey shifted (-1,-1) and use
3098		#	    union of those two areas as mask
3099		dpicmask = display.newimage(pic.r, Draw->GREY1, 0, D->White);
3100		dpic = display.newimage(pic.r, pic.chans, 0, D->White);
3101		dpic.draw(dpic.r, colorimage(White), pic, zp);
3102		dpicmask.draw(dpicmask.r, display.black, pic, zp);
3103		dpic.draw(dpic.r.addpt(Point(-1,-1)), colorimage(DarkGrey), pic, zp);
3104		dpicmask.draw(dpicmask.r.addpt(Point(-1,-1)), display.black, pic, zp);
3105	}
3106	b := ref Control.Cbutton(f, nil, r, CFenabled, nil, pic, picmask, dpic, dpicmask, lab, dorelief);
3107	return b;
3108}
3109
3110Control.newcheckbox(f: ref Frame, isradio: int) : ref Control
3111{
3112	return ref Control.Ccheckbox(f, nil, Rect((0,0),(CBOXWID,CBOXHT)), CFenabled, nil, isradio);
3113}
3114
3115Control.newselect(f: ref Frame, nvis: int, options: array of B->Option) : ref Control
3116{
3117	nvis = min(5, len options);
3118	if (nvis < 1)
3119		nvis = 1;
3120	fnt := fonts[CtlFnt].f;
3121	w := 0;
3122	first := -1;
3123	for(i := 0; i < len options; i++) {
3124		if (first == -1 && options[i].selected)
3125			first = i;
3126		w = max(w, fnt.width(options[i].display));
3127	}
3128	if (first == -1)
3129		first = 0;
3130	if (len options -nvis > 0 && len options - nvis < first)
3131		first = len options - nvis;
3132	w += 2*SELMARGIN;
3133	h := ctllinespace*nvis + 2*SELMARGIN;
3134	scr: ref Control;
3135	if (nvis > 1 && nvis < len options) {
3136		scr = Control.newscroll(f, 1, h, SCRFBREADTH);
3137		scr.r.addpt(Point(w,0));
3138	}
3139	if (nvis < len options)
3140		w += SCRFBREADTH;
3141	ans := ref Control.Cselect(f, nil, Rect(zp, Point(w,h)), CFenabled, nil, nil, scr, nvis, first, options);
3142	if(scr != nil) {
3143		pick pscr := scr {
3144		Cscrollbar =>
3145			pscr.ctl = ans;
3146		}
3147		scr.scrollset(first, first+nvis, len options, len options, 0);
3148	}
3149	return ans;
3150}
3151
3152Control.newlistbox(f: ref Frame, nrow, ncol: int, options: array of B->Option) : ref Control
3153{
3154	fnt := fonts[CtlFnt].f;
3155	w := charspace*ncol + 2*SELMARGIN;
3156	h := fnt.height*nrow + 2*SELMARGIN;
3157
3158	vscr: ref Control = nil;
3159	#if(nrow < len options) {
3160		vscr = Control.newscroll(f, 1, (h-4)+SCRFBREADTH, SCRFBREADTH);
3161		vscr.r.addpt(Point(w-SCRFBREADTH,0));
3162		w += SCRFBREADTH;
3163	#}
3164
3165	maxw := 0;
3166	for(i := 0; i < len options; i++)
3167		maxw = max(maxw, fnt.width(options[i].display));
3168
3169	hscr: ref Control = nil;
3170	#if(w < maxw) {
3171		# allow for border (inset(2))
3172		hscr = Control.newscroll(f, 0, (w-4)-SCRFBREADTH, SCRFBREADTH);
3173		hscr.r.addpt(Point(0, h-SCRBREADTH));
3174		h += SCRFBREADTH;
3175	#}
3176
3177	ans := ref Control.Clistbox(f, nil, Rect(zp, Point(w,h)), CFenabled, nil, hscr, vscr, nrow, 0, 0, maxw/charspace, options, nil);
3178	if(vscr != nil) {
3179		pick pscr := vscr {
3180		Cscrollbar =>
3181			pscr.ctl = ans;
3182		}
3183		vscr.scrollset(0, nrow, len options, len options, 0);
3184	}
3185	if(hscr != nil) {
3186		pick pscr := hscr {
3187		Cscrollbar =>
3188			pscr.ctl = ans;
3189		}
3190		hscr.scrollset(0, w-SCRFBREADTH, maxw, 0, 0);
3191	}
3192	return ans;
3193}
3194
3195Control.newanimimage(f: ref Frame, cim: ref CU->CImage, bg: Background) : ref Control
3196{
3197	return ref Control.Canimimage(f, nil, Rect((0,0),(cim.width,cim.height)), 0, nil, cim, 0, 0, big 0, bg);
3198}
3199
3200Control.newlabel(f: ref Frame, s: string) : ref Control
3201{
3202	w := fonts[DefFnt].f.width(s);
3203	h := ctllinespace + 2*ENTVMARGIN;	# give it same height as an entry box
3204	return ref Control.Clabel(f, nil, Rect(zp,Point(w,h)), 0, nil, s);
3205}
3206
3207Control.disable(c: self ref Control)
3208{
3209	if(c.flags & CFenabled) {
3210		win := c.f.cim;
3211		c.flags &= ~CFenabled;
3212		if(c.f.cim != nil)
3213			c.draw(1);
3214	}
3215}
3216
3217Control.enable(c: self ref Control)
3218{
3219	if(!(c.flags & CFenabled)) {
3220		c.flags |= CFenabled;
3221		if(c.f.cim != nil)
3222			c.draw(1);
3223	}
3224}
3225
3226changeevent(c: ref Control)
3227{
3228	onchange := 0;
3229	pick pc := c {
3230	Centry =>
3231		onchange = pc.onchange;
3232		pc.onchange = 0;
3233# this code reproduced Navigator 2 bug
3234# changes to Select Formfield selection only resulted in onchange event upon
3235# loss of focus.  Now handled by domouse() code so event can be raised
3236# immediately
3237#	Cselect =>
3238#		onchange = pc.onchange;
3239#		pc.onchange = 0;
3240	}
3241	if(onchange && (c.ff.evmask & E->SEonchange)) {
3242		se := ref E->ScriptEvent(E->SEonchange, c.f.id, c.ff.form.formid, c.ff.fieldid, -1, -1, -1, -1, 1, nil, nil, 0);
3243		J->jevchan <-= se;
3244	}
3245}
3246
3247blurfocusevent(c: ref Control, kind, raisex: int)
3248{
3249	if((CU->config).doscripts && c.ff != nil && c.ff.evmask) {
3250		if(kind == E->SEonblur)
3251			changeevent(c);
3252		if (!raisex || !(c.ff.evmask & kind))
3253			return;
3254		se := ref E->ScriptEvent(kind, c.f.id, c.ff.form.formid, c.ff.fieldid, -1, -1, -1, -1, 0, nil, nil, 0);
3255		J->jevchan <-= se;
3256	}
3257}
3258
3259Control.losefocus(c: self ref Control, raisex: int)
3260{
3261	if(c.flags & CFhasfocus) {
3262		c.flags &= ~CFhasfocus;
3263		if(c.f.cim != nil) {
3264			blurfocusevent(c, E->SEonblur, raisex);
3265			c.draw(1);
3266		}
3267	}
3268}
3269
3270Control.gainfocus(c: self ref Control, raisex: int)
3271{
3272	if(!(c.flags & CFhasfocus)) {
3273		c.flags |= CFhasfocus;
3274		if(c.f.cim != nil) {
3275			blurfocusevent(c, E->SEonfocus, raisex);
3276			c.draw(1);
3277		}
3278		G->clientfocus();
3279	}
3280}
3281
3282Control.scrollset(c: self ref Control, v1, v2, vmax, nsteps, draw: int)
3283{
3284	pick sc := c {
3285	Cscrollbar =>
3286		if(v1 < 0)
3287			v1 = 0;
3288		if(v2 > vmax)
3289			v2 = vmax;
3290		if(v1 > v2)
3291			v1 = v2;
3292		if(v1 == 0 && v2 == vmax) {
3293			sc.mindelta = 1;
3294			sc.deltaval = 0;
3295			sc.top = 0;
3296			sc.bot = 0;
3297		}
3298		else {
3299			length, breadth: int;
3300			if(sc.flags&CFscrvert) {
3301				length = sc.r.max.y - sc.r.min.y;
3302				breadth = sc.r.max.x - sc.r.min.x;
3303			}
3304			else {
3305				length = sc.r.max.x - sc.r.min.x;
3306				breadth = sc.r.max.y - sc.r.min.y;
3307			}
3308			l := length - (2*breadth + MINSCR);
3309			if(l <= 0)
3310				l = 1;
3311			if(l < 0)
3312				raise "EXInternal: negative scrollbar trough";
3313			sc.top = l*v1/vmax;
3314			sc.bot = l*(vmax-v2)/vmax;
3315			if (nsteps == 0)
3316				sc.mindelta = 1;
3317			else
3318				sc.mindelta = max(1, length/nsteps);
3319			sc.deltaval = max(1, vmax/(l/sc.mindelta))*SCRDELTASF;
3320		}
3321		if(sc.f.cim != nil && draw)
3322			sc.draw(1);
3323	}
3324}
3325
3326SPECMASK : con 16rf000;
3327CTRLMASK : con 16r1f;
3328DEL : con 16r7f;
3329TAB : con '\t';
3330CR: con '\n';
3331
3332Control.dokey(ctl: self ref Control, keychar: int) : int
3333{
3334	if(!(ctl.flags&CFenabled))
3335		return CAnone;
3336	ans := CAnone;
3337	pick c := ctl {
3338	Centry =>
3339		olds := c.s;
3340		slen := len c.s;
3341		(sels, sele) := normalsel(c.sel);
3342		modified := 0;
3343		(osels, osele) := (sels, sele);
3344		case keychar {
3345			('a' & CTRLMASK) or Keyboard->Home =>
3346				(sels, sele) = (0, 0);
3347			('e' & CTRLMASK) or Keyboard->End =>
3348				(sels, sele) = (slen, slen);
3349			'f' & CTRLMASK or Keyboard->Right =>
3350				if(sele < slen)
3351					(sels, sele) = (sele+1, sele+1);
3352			'b' & CTRLMASK or Keyboard->Left =>
3353				if(sels > 0)
3354					(sels, sele) = (sels-1, sels-1);
3355			Keyboard->Up =>
3356				if (c.linewrap)
3357					sels = sele = entryupdown(c, sels, -1);
3358			Keyboard->Down =>
3359				if (c.linewrap)
3360					sels = sele = entryupdown(c, sele, 1);
3361			'u' & CTRLMASK =>
3362				entrydelrange(c, 0, slen);
3363				modified = 1;
3364				(sels, sele) = c.sel;
3365			'c' & CTRLMASK =>
3366				entrysetsnarf(c);
3367			'v' & CTRLMASK =>
3368				entryinsertsnarf(c);
3369				modified = 1;
3370				(sels, sele) = c.sel;
3371			'h' & CTRLMASK or DEL=>
3372				if (sels != sele) {
3373					entrydelrange(c, sels, sele);
3374					modified = 1;
3375				} else if(sels > 0) {
3376					entrydelrange(c, sels-1, sels);
3377					modified = 1;
3378				}
3379				(sels, sele) = c.sel;
3380			Keyboard->Del =>
3381				if (sels != sele) {
3382					entrydelrange(c, sels, sele);
3383					modified = 1;
3384				} else if(sels < len c.s) {
3385					entrydelrange(c, sels, sels+1);
3386					modified = 1;
3387				}
3388				(sels, sele) = c.sel;
3389			TAB =>
3390				ans = CAtabkey;
3391			* =>
3392				if ((keychar & SPECMASK) == Keyboard->Spec)
3393					# ignore all other special keys
3394					break;
3395				if(keychar == CR) {
3396					if(c.linewrap)
3397						keychar = '\n';
3398					else
3399						ans = CAreturnkey;
3400				}
3401				if(keychar > CTRLMASK || (keychar == '\n' && c.linewrap)) {
3402					if (sels != sele) {
3403						entrydelrange(c, sels, sele);
3404						(sels, sele) = c.sel;
3405					}
3406					slen = len c.s;
3407					c.s[slen] = 0;	# expand string by 1 char
3408					for(k := slen; k > sels; k--)
3409						c.s[k] = c.s[k-1];
3410					c.s[sels] = keychar;
3411					(sels, sele) = (sels+1, sels+1);
3412					modified = 1;
3413				}
3414		}
3415		c.sel = (sels, sele);
3416		if(osels != sels || osele != sele || modified) {
3417			entryscroll(c);
3418			c.draw(1);
3419		}
3420		if (c.s != olds)
3421			c.onchange = 1;
3422	}
3423	return ans;
3424}
3425
3426Control.domouse(ctl: self ref Control, p: Point, mtype: int, oldgrab : ref Control) : (int, ref Control)
3427{
3428	up := (mtype == E->Mlbuttonup || mtype == E->Mldrop);
3429	down := (mtype == E->Mlbuttondown);
3430	drag := (mtype == E->Mldrag);
3431	hold := (mtype == E->Mhold);
3432	move := (mtype == E->Mmove);
3433
3434	# any button actions stop auto-repeat
3435	# it's up to the individual controls to re-instate it
3436	if (!move)
3437		E->autorepeat(nil, 0, 0);
3438
3439	if(!(ctl.flags&CFenabled))
3440		return (CAnone, nil);
3441	ans := CAnone;
3442	changed := 0;
3443	newgrab : ref Control;
3444	grabbed := oldgrab != nil;
3445	pick c := ctl {
3446	Cbutton =>
3447		if(down) {
3448			c.flags |= CFactive;
3449			newgrab = c;
3450			changed = 1;
3451		}
3452		else if(move && c.ff == nil) {
3453			ans = CAflyover;
3454		}
3455		else if (drag && grabbed) {
3456			newgrab = c;
3457			active := 0;
3458			if (p.in(c.r))
3459				active = CFactive;
3460			if ((c.flags & CFactive) != active)
3461				changed = 1;
3462			c.flags = (c.flags & ~CFactive) | active;
3463		}
3464		else if(up) {
3465			if (c.flags & CFactive)
3466				ans = CAbuttonpush;
3467			c.flags &= ~CFactive;
3468			changed = 1;
3469		}
3470	Centry =>
3471		if(c.scr != nil && !grabbed && p.x >= c.r.max.x-SCRFBREADTH) {
3472			pick scr := c.scr {
3473			Cscrollbar =>
3474				return scr.domouse(p, mtype, oldgrab);
3475			}
3476		}
3477		(sels, sele) := c.sel;
3478		if(mtype == E->Mlbuttonup && grabbed) {
3479			if (sels != sele)
3480				ans = CAselected;
3481		}
3482		if(down || (drag && grabbed)) {
3483			newgrab = c;
3484			x := c.r.min.x+ENTHMARGIN;
3485			fnt := fonts[CtlFnt].f;
3486			s := c.s;
3487			if(c.flags&CFsecure) {
3488				for(i := 0; i < len s; i++)
3489					s[i] = '*';
3490			}
3491			(osels, osele) := c.sel;
3492			s1 := " ";
3493			i := 0;
3494			iend := len s - 1;
3495			if(c.linewrap) {
3496				(lines, linestarts, topline, cursline) := entrywrapcalc(c);
3497				if(len lines > 1) {
3498					lineno := topline + (p.y - (c.r.min.y+ENTVMARGIN)) / ctllinespace;
3499					lineno = min(lineno, len lines -1);
3500					lineno = max(lineno, 0);
3501
3502					i = linestarts[lineno];
3503					iend = i + len lines[lineno] -1;
3504				}
3505			} else
3506				x -= fnt.width(s[:c.left]);
3507			for(; i <= iend; i++) {
3508				s1[0] = s[i];
3509				cx := fnt.width(s1);
3510				if(p.x < x + cx)
3511					break;
3512				x += cx;
3513			}
3514			sele = i;
3515
3516			if (down)
3517				sels = sele;
3518			c.sel = (sels, sele);
3519
3520			if (sels != osels || sele != osele) {
3521				changed = 1;
3522				entryscroll(c);
3523				if (p.x < c.r.min.x + ENTHMARGIN || p.x > c.r.max.x - ENTHMARGIN
3524				|| p.y < c.r.min.y + ENTVMARGIN || p.y > c.r.max.y - ENTVMARGIN) {
3525					E->autorepeat(ref (Event.Emouse)(p, mtype), ARTICK, ARTICK);
3526				}
3527			}
3528
3529			if(!(c.flags&CFhasfocus))
3530				ans = CAkeyfocus;
3531		}
3532	Ccheckbox=>
3533		if(up) {
3534			if(c.isradio) {
3535				if(!(c.flags&CFactive)) {
3536					c.flags |= CFactive;
3537					changed = 1;
3538					ans = CAbuttonpush;
3539					# turn off other radio button
3540					frm := c.ff.form;
3541					for(lf := frm.fields; lf != nil; lf = tl lf) {
3542						ff := hd lf;
3543						if(ff == c.ff)
3544							continue;
3545						if(ff.ftype == Fradio && ff.name==c.ff.name && ff.ctlid >= 0) {
3546							d := c.f.controls[ff.ctlid];
3547							if(d.flags&CFactive) {
3548								d.flags &= ~CFactive;
3549								d.draw(1);
3550								break;		# at most one other should be on
3551							}
3552						}
3553					}
3554				}
3555			}
3556			else {
3557				c.flags ^= CFactive;
3558				changed = 1;
3559			}
3560		}
3561	Cselect =>
3562		if (c.nvis == 1 && up && c.popup == nil && c.r.contains(p))
3563			return (CAdopopup, nil);
3564		if(c.scr != nil && (grabbed || p.x >= c.r.max.x-SCRFBREADTH)) {
3565			pick scr := c.scr {
3566			Cscrollbar =>
3567				(a, grab) := scr.domouse(p, mtype, oldgrab);
3568				if (grab != nil)
3569					grab = c;
3570				return (a, grab);
3571			}
3572			return (ans, nil);
3573		}
3574		n := (p.y - (c.r.min.y+SELMARGIN))/ctllinespace + c.first;
3575		if (n >= c.first && n < c.first+c.nvis) {
3576			if ((c.ff.flags&B->FFmultiple) != byte 0) {
3577				if (down) {
3578					c.options[n].selected ^= 1;
3579					changed = 1;
3580				}
3581			} else if (up || drag) {
3582				changed = c.options[n].selected == 0;
3583				c.options[n].selected = 1;
3584				for(i := 0; i < len c.options; i++) {
3585					if(i != n)
3586						c.options[i].selected = 0;
3587				}
3588			}
3589		}
3590		if (up) {
3591			if (c.popup != nil)
3592				ans = CAdonepopup;
3593			else
3594				ans = CAchanged;
3595		}
3596	Clistbox =>
3597		if(c.vscr != nil && (c.grab == c.vscr || (!grabbed && p.x >= c.r.max.x-SCRFBREADTH))) {
3598			c.grab = nil;
3599			pick vscr := c.vscr {
3600			Cscrollbar =>
3601				(a, grab) := vscr.domouse(p, mtype, oldgrab);
3602				if (grab != nil) {
3603					c.grab = c.vscr;
3604					grab = c;
3605				}
3606				return (a, grab);
3607			}
3608		}
3609		else if(c.hscr != nil && (c.grab == c.hscr || (!grabbed && p.y >= c.r.max.y-SCRFBREADTH))) {
3610			c.grab = nil;
3611			pick hscr := c.hscr {
3612			Cscrollbar =>
3613				(a, grab) := hscr.domouse(p, mtype, oldgrab);
3614				if (grab != nil) {
3615					c.grab = c.hscr;
3616					grab = c;
3617				}
3618				return (a, grab);
3619			}
3620		}
3621		else if(up) {
3622			fnt := fonts[CtlFnt].f;
3623			n := (p.y - (c.r.min.y+SELMARGIN))/fnt.height + c.first;
3624			if(n >= 0 && n < len c.options) {
3625				c.options[n].selected ^= 1;
3626				# turn off other selections
3627				for(i := 0; i < len c.options; i++) {
3628					if(i != n)
3629						c.options[i].selected = 0;
3630				}
3631				ans = CAchanged;
3632				changed = 1;
3633			}
3634		}
3635	Cscrollbar =>
3636		val := 0;
3637		v, vmin, vmax, b: int;
3638		if(c.flags&CFscrvert) {
3639			v = p.y;
3640			vmin = c.r.min.y;
3641			vmax = c.r.max.y;
3642			b = c.r.dx();
3643		}
3644		else {
3645			v = p.x;
3646			vmin = c.r.min.x;
3647			vmax = c.r.max.x;
3648			b = c.r.dy();
3649		}
3650		vsltop := vmin+b+c.top;
3651		vslbot := vmax-b-c.bot;
3652		actflags := 0;
3653		oldactflags := c.flags&CFscrallact;
3654
3655		if ((down || drag) && !up && !hold)
3656			newgrab = c;
3657
3658		if (down) {
3659			newgrab = c;
3660			holdval := 0;
3661			repeat := 1;
3662			if (v >= vsltop && v < vslbot) {
3663				holdval = v - vsltop;
3664				actflags = CFactive;
3665				repeat = 0;
3666			}
3667			if(v < vmin+b) {
3668				holdval = -1;
3669				actflags = CFscracta1;
3670			}
3671			else if(v < vsltop) {
3672				holdval = -1;
3673				actflags = CFscracttr1;
3674			}
3675			else if(v >= vmax-b) {
3676				holdval = 1;
3677				actflags = CFscracta2;
3678			}
3679			else if(v >= vslbot) {
3680				holdval = 1;
3681				actflags = CFscracttr2;
3682			}
3683			c.holdstate = (actflags, holdval);
3684			if (repeat) {
3685				E->autorepeat(ref (Event.Emouse)(p, E->Mhold), ARPAUSE, ARTICK);
3686			}
3687		}
3688		if (drag) {
3689			(actflags, val) = c.holdstate;
3690			if (actflags == CFactive) {
3691				# dragging main scroll widget (relative to top of drag block)
3692				val = (v - vsltop) - val;
3693				if(abs(val) >= c.mindelta) {
3694					ans = CAscrolldelta;
3695					val = (c.deltaval * (val / c.mindelta))/SCRDELTASF;
3696				}
3697			} else {
3698				E->autorepeat(ref (Event.Emouse)(p, E->Mhold), ARTICK, ARTICK);
3699			}
3700		}
3701		if (up || hold) {
3702			# set the action according to the hold state
3703			# Note: main widget (case CFactive) handled by drag
3704			act := 0;
3705			(act, val) = c.holdstate;
3706			case act {
3707			CFscracta1 or
3708			CFscracta2 =>
3709				ans = CAscrollline;
3710			CFscracttr1 or
3711			CFscracttr2 =>
3712				ans = CAscrollpage;
3713			}
3714			if (up) {
3715				c.holdstate = (0, 0);
3716			} else { # hold
3717				(actflags, nil) = c.holdstate;
3718				if (ans != CAnone) {
3719					E->autorepeat(ref (Event.Emouse)(p, E->Mhold), ARTICK, ARTICK);
3720					newgrab = c;
3721				}
3722			}
3723		}
3724		c.flags = (c.flags & ~CFscrallact) | actflags;
3725		if(ans != CAnone) {
3726			ftoscroll := c.f;
3727			if(c.ctl != nil) {
3728				pick cff := c.ctl {
3729				Centry =>
3730					ny := (cff.r.dy() - 2 * ENTVMARGIN) / ctllinespace;
3731					(nil, linestarts, topline, nil) := entrywrapcalc(cff);
3732					nlines := len linestarts;
3733					case ans {
3734					CAscrollpage =>
3735						topline += val*ny;
3736					CAscrollline =>
3737						topline += val;
3738					CAscrolldelta =>
3739#						# insufficient for large number of lines
3740						topline += val;
3741#						if(val > 0)
3742#							topline++;
3743#						else
3744#							topline--;
3745					}
3746					if (topline+ny >= nlines)
3747						topline = (nlines-1) - ny;
3748					if (topline < 0)
3749						topline = 0;
3750					cff.left = linestarts[topline];
3751					c.scrollset(topline, topline+ny, nlines - 1, nlines, 1);
3752					cff.draw(1);
3753					return (ans, newgrab);
3754				Cselect =>
3755					newfirst := cff.first;
3756					case ans {
3757					CAscrollpage =>
3758						newfirst += val*cff.nvis;
3759					CAscrollline =>
3760						newfirst += val;
3761					CAscrolldelta =>
3762#						# insufficient for very long select lists
3763						newfirst += val;
3764#						if(val > 0)
3765#							newfirst++;
3766#						else
3767#							newfirst--;
3768					}
3769					newfirst = max(0, min(newfirst, len cff.options - cff.nvis));
3770					cff.first = newfirst;
3771					nopt := len cff.options;
3772					c.scrollset(newfirst, newfirst+cff.nvis, nopt, nopt, 0);
3773					cff.draw(1);
3774					return (ans, newgrab);
3775				Clistbox =>
3776					if(c.flags&CFscrvert) {
3777						newfirst := cff.first;
3778						case ans {
3779						CAscrollpage =>
3780							newfirst += val*cff.nvis;
3781						CAscrollline =>
3782							newfirst += val;
3783						CAscrolldelta =>
3784							newfirst += val;
3785#							if(val > 0)
3786#								newfirst++;
3787#							else
3788#								newfirst--;
3789						}
3790						newfirst = max(0, min(newfirst, len cff.options - cff.nvis));
3791						cff.first = newfirst;
3792						c.scrollset(newfirst, newfirst+cff.nvis, len cff.options, 0, 1);
3793						# TODO: need redraw only vscr and content
3794					}
3795					else {
3796						hw := cff.maxcol;
3797						w := (c.r.max.x - c.r.min.x - SCRFBREADTH)/charspace;
3798						newstart := cff.start;
3799						case ans {
3800						CAscrollpage =>
3801								newstart += val*hw;
3802						CAscrollline =>
3803								newstart += val;
3804						CAscrolldelta =>
3805							if(val > 0)
3806								newstart++;
3807							else
3808								newstart--;
3809						}
3810						if(hw < w)
3811							newstart = 0;
3812						else
3813							newstart = max(0, min(newstart, hw - w));
3814						cff.start = newstart;
3815						c.scrollset(newstart, w+newstart, hw, 0, 1);
3816						# TODO: need redraw only hscr and content
3817					}
3818					cff.draw(1);
3819					return (ans, newgrab);
3820				}
3821			}
3822			else {
3823				if(c.flags&CFscrvert)
3824					c.f.yscroll(ans, val);
3825				else
3826					c.f.xscroll(ans, val);
3827			}
3828			changed = 1;
3829		}
3830		else if(actflags != oldactflags) {
3831			changed = 1;
3832		}
3833	}
3834	if(changed)
3835		ctl.draw(1);
3836	return (ans, newgrab);
3837}
3838
3839# returns a new popup control
3840Control.dopopup(ctl: self ref Control): ref Control
3841{
3842	sel : ref Control.Cselect;
3843	pick c := ctl {
3844	Cselect =>
3845		if (c.nvis > 1)
3846			return nil;
3847		sel = c;
3848	* =>
3849		return nil;
3850	}
3851
3852	w := sel.r.dx();
3853	nopt := len sel.options;
3854	nvis := min(nopt, POPUPLINES);
3855	first := sel.first;
3856	if (first + nvis > nopt)
3857		first = nopt - nvis;
3858	h := ctllinespace*nvis + 2*SELMARGIN;
3859	r := Rect(sel.r.min, sel.r.min.add(Point(w, h)));
3860	popup := G->getpopup(r);
3861	if (popup == nil)
3862		return nil;
3863	scr : ref Control;
3864	if (nvis < nopt) {
3865		scr = Control.newscroll(sel.f, 1, h, SCRFBREADTH);
3866		scr.r.addpt(Point(w,0));
3867	}
3868	newsel := ref Control.Cselect(sel.f, sel.ff, r, sel.flags, popup, sel, scr, nvis, first, sel.options);
3869	if(scr != nil) {
3870		pick pscr := scr {
3871		Cscrollbar =>
3872			pscr.ctl = newsel;
3873		}
3874		scr.popup = popup;
3875		scr.scrollset(first, first+nvis, nopt, nopt, 0);
3876	}
3877	newsel.draw(1);
3878	return newsel;
3879}
3880
3881# returns original control for which this was a popup
3882Control.donepopup(ctl: self ref Control): ref Control
3883{
3884	owner: ref Control;
3885	pick c := ctl {
3886	Cselect =>
3887		if (c.owner == nil)
3888			return nil;
3889		owner = c.owner;
3890	* =>
3891		return nil;
3892	}
3893	G->cancelpopup();
3894	pick c := owner {
3895	Cselect =>
3896		for (first := 0; first < len c.options; first++)
3897			if (c.options[first].selected)
3898				break;
3899		if (first == len c.options)
3900			first = 0;
3901		c.first = first;
3902	}
3903	owner.draw(1);
3904	return owner;
3905}
3906
3907
3908Control.reset(ctl: self ref Control)
3909{
3910	pick c := ctl {
3911	Cbutton =>
3912		c.flags &= ~CFactive;
3913	Centry =>
3914		c.s = "";
3915		c.sel = (0, 0);
3916		c.left = 0;
3917		if(c.ff != nil && c.ff.value != "")
3918			c.s = c.ff.value;
3919		if (c.scr != nil)
3920			c.scr.scrollset(0, 1, 1, 0, 0);
3921	Ccheckbox=>
3922		c.flags &= ~CFactive;
3923		if(c.ff != nil && (c.ff.flags&B->FFchecked) != byte 0)
3924			c.flags |= CFactive;
3925	Cselect =>
3926		nopt := len c.options;
3927		if(c.ff != nil) {
3928			l := c.ff.options;
3929			for(i := 0; i < nopt; i++) {
3930				o := hd l;
3931				c.options[i].selected = o.selected;
3932				l = tl l;
3933			}
3934		}
3935		c.first = 0;
3936		if(c.scr != nil) {
3937			c.scr.scrollset(0, c.nvis, nopt, nopt, 0);
3938		}
3939	Clistbox =>
3940		c.first = 0;
3941		nopt := len c.options;
3942		if(c.vscr != nil) {
3943			c.vscr.scrollset(0, c.nvis, nopt, nopt, 0);
3944		}
3945		hw := 0;
3946		for(i := 0; i < len c.options; i++)
3947			hw = max(hw, fonts[DefFnt].f.width(c.options[i].display));
3948		if(c.hscr != nil) {
3949			c.hscr.scrollset(0, c.r.max.x, hw, 0, 0);
3950		}
3951	Canimimage =>
3952		c.cur = 0;
3953	}
3954	ctl.draw(0);
3955}
3956
3957Control.draw(ctl: self ref Control, flush: int)
3958{
3959	win := ctl.f.cim;
3960	if (ctl.popup != nil)
3961		win = ctl.popup.image;
3962	if (win == nil)
3963		return;
3964	oclipr := win.clipr;
3965	clipr := oclipr;
3966	any: int;
3967	if (ctl.popup == nil) {
3968		(clipr, any) = ctl.r.clip(ctl.f.cr);
3969		if(!any && ctl != ctl.f.vscr && ctl != ctl.f.hscr)
3970			return;
3971		win.clipr = clipr;
3972	}
3973	pick c := ctl {
3974	Cbutton =>
3975		if(c.ff != nil && c.ff.image != nil && c.pic == nil) {
3976			# check to see if image arrived
3977			# (dimensions will have been set by checkffsize, if needed;
3978			# this code is only for when the HTML specified the dimensions)
3979			pick imi := c.ff.image {
3980			Iimage =>
3981				if(imi.ci.mims != nil) {
3982					c.pic = imi.ci.mims[0].im;
3983					c.picmask = imi.ci.mims[0].mask;
3984				}
3985			}
3986		}
3987		if(c.dorelief || c.pic == nil)
3988			win.draw(c.r, colorimage(Grey), nil, zp);
3989		if(c.pic != nil) {
3990			p, m: ref Image;
3991			if(c.flags & CFenabled) {
3992				p = c.pic;
3993				m = c.picmask;
3994			}
3995			else {
3996				p = c.dpic;
3997				m = c.dpicmask;
3998			}
3999			w := p.r.dx();
4000			h := p.r.dy();
4001			x := c.r.min.x + (c.r.dx() - w) / 2;
4002			y := c.r.min.y + (c.r.dy() - h) / 2;
4003			if((c.flags & CFactive) && c.dorelief) {
4004				x++;
4005				y++;
4006			}
4007			win.draw(Rect((x,y),(x+w,y+h)), p, m, zp);
4008		}
4009		else if(c.label != "") {
4010			p := c.r.min.add(Point(BUTMARGIN, BUTMARGIN));
4011			if(c.flags & CFactive)
4012				p = p.add(Point(1,1));
4013			win.text(p, colorimage(Black), zp, fonts[CtlFnt].f, c.label);
4014		}
4015		if(c.dorelief) {
4016			relief := ReliefRaised;
4017			if(c.flags & CFactive)
4018				relief = ReliefSunk;
4019			drawrelief(win, c.r.inset(2), relief);
4020		}
4021	Centry =>
4022		win.draw(c.r, colorimage(White), nil, zp);
4023		insetr := c.r.inset(2);
4024		drawrelief(win,insetr, ReliefSunk);
4025		eclipr := c.r;
4026		eclipr.min.x += ENTHMARGIN;
4027		eclipr.max.x -= ENTHMARGIN;
4028		eclipr.min.y += ENTVMARGIN;
4029		eclipr.max.y -= ENTVMARGIN;
4030#		if (c.scr != nil)
4031#			eclipr.max.x -= SCRFBREADTH;
4032		(eclipr, any) = clipr.clip(eclipr);
4033		win.clipr = eclipr;
4034		p := c.r.min.add(Point(ENTHMARGIN,ENTVMARGIN));
4035		s := c.s;
4036		fnt := fonts[CtlFnt].f;
4037		if(c.left > 0)
4038			s = s[c.left:];
4039		if(c.flags&CFsecure) {
4040			for(i := 0; i < len s; i++)
4041				s[i] = '*';
4042		}
4043
4044		(sels, sele) := normalsel(c.sel);
4045		(sels, sele) = (sels-c.left, sele-c.left);
4046
4047		lines : array of string;
4048		linestarts : array of int;
4049		textw := c.r.dx()-2*ENTHMARGIN;
4050		if (c.scr != nil) {
4051			textw -= SCRFBREADTH;
4052			c.scr.r = c.scr.r.subpt(c.scr.r.min);
4053			c.scr.r = c.scr.r.addpt(Point(insetr.max.x-SCRFBREADTH,insetr.min.y));
4054			c.scr.draw(0);
4055		}
4056		if (c.linewrap)
4057			(lines, linestarts) = wrapstring(fnt, s, textw);
4058		else
4059			(lines, linestarts) = (array [] of {s}, array [] of {0});
4060
4061		q := p;
4062		black := colorimage(Black);
4063		white := colorimage(White);
4064		navy := colorimage(Navy);
4065		nlines := len lines;
4066		for (n := 0; n < nlines; n++) {
4067			segs : list of (int, int, int);
4068			# only show cursor or selection if we have focus
4069			if (c.flags & CFhasfocus)
4070				segs = selsegs(len lines[n], sels-linestarts[n], sele-linestarts[n]);
4071			else
4072				segs = (0, len lines[n], 0) :: nil;
4073			for (; segs != nil; segs = tl segs) {
4074				(ss, se, sel) := hd segs;
4075				txt := lines[n][ss:se];
4076				w := fnt.width(txt);
4077				txtcol : ref Image;
4078				if (!sel)
4079					txtcol = black;
4080				else {
4081					txtcol = white;
4082					bgcol := navy;
4083					if (n < nlines-1 && sele >= linestarts[n+1])
4084						w = (p.x-q.x) + textw;
4085					selr := Rect((q.x, q.y-1), (q.x+w, q.y+ctllinespace+1));
4086					if (selr.dx() == 0) {
4087						# empty selection - assume cursor
4088						bgcol = black;
4089						selr.max.x = selr.min.x + 2;
4090					}
4091					win.draw(selr, bgcol, nil, zp);
4092				}
4093				if (se > ss)
4094					win.text(q, txtcol, zp, fnt, txt);
4095				q.x += w;
4096			}
4097			q = (p.x, q.y + ctllinespace);
4098		}
4099	Ccheckbox=>
4100		win.draw(c.r, colorimage(White), nil, zp);
4101		if(c.isradio) {
4102			a := CBOXHT/2;
4103			a1 := a-1;
4104			cen := Point(c.r.min.x+a,c.r.min.y+a);
4105			win.ellipse(cen, a1, a1, 1, colorimage(DarkGrey), zp);
4106			win.arc(cen, a, a, 0, colorimage(Black), zp, 45, 180);
4107			win.arc(cen, a, a, 0, colorimage(Grey), zp, 225, 180);
4108			if(c.flags&CFactive)
4109				win.fillellipse(cen, 2, 2, colorimage(Black), zp);
4110		}
4111		else {
4112			ir := c.r.inset(2);
4113			ir.min.x += CBOXWID-CBOXHT;
4114			ir.max.x -= CBOXWID-CBOXHT;
4115			drawrelief(win, ir, ReliefSunk);
4116			if(c.flags&CFactive) {
4117				p1 := Point(ir.min.x, ir.min.y);
4118				p2 := Point(ir.max.x, ir.max.y);
4119				p3 := Point(ir.max.x, ir.min.y);
4120				p4 := Point(ir.min.x, ir.max.y);
4121				win.line(p1, p2, D->Endsquare, D->Endsquare, 0, colorimage(Black), zp);
4122				win.line(p3, p4, D->Endsquare, D->Endsquare, 0, colorimage(Black), zp);
4123			}
4124		}
4125	Cselect =>
4126		black := colorimage(Black);
4127		white := colorimage(White);
4128		navy := colorimage(Navy);
4129		win.draw(c.r, white, nil, zp);
4130		drawrelief(win, c.r.inset(2), ReliefSunk);
4131		ir := c.r.inset(SELMARGIN);
4132		p := ir.min;
4133		fnt := fonts[CtlFnt].f;
4134		drawsel := c.nvis > 1;
4135		for(i := c.first; i < len c.options && i < c.first+c.nvis; i++) {
4136			if(drawsel && c.options[i].selected) {
4137				maxx := ir.max.x;
4138				if (c.scr != nil)
4139					maxx -= SCRFBREADTH;
4140				r := Rect((p.x-SELMARGIN,p.y),(maxx,p.y+ctllinespace));
4141				win.draw(r, navy, nil, zp);
4142				win.text(p, white, zp, fnt, c.options[i].display);
4143			}
4144			else {
4145				win.text(p, black, zp, fnt, c.options[i].display);
4146			}
4147			p.y += ctllinespace;
4148		}
4149		if (c.nvis == 1 && len c.options > 1) {
4150			# drop down select list - draw marker (must be same width as scroll bar)
4151			r := Rect((ir.max.x - SCRFBREADTH, ir.min.y), ir.max);
4152			drawtriangle(win, r, TRIdown, ReliefRaised);
4153		}
4154		if(c.scr != nil) {
4155			c.scr.r = Rect((ir.max.x - SCRFBREADTH, ir.min.y), ir.max);
4156			c.scr.draw(0);
4157		}
4158	Clistbox =>
4159		black := colorimage(Black);
4160		white := colorimage(White);
4161		navy := colorimage(Navy);
4162		win.draw(c.r, white, nil, zp);
4163		insetr := c.r.inset(2);
4164		#drawrelief(win, c.r.inset(2), ReliefSunk);
4165		ir := c.r.inset(SELMARGIN);
4166		p := ir.min;
4167		fnt := fonts[CtlFnt].f;
4168		for(i := c.first; i < len c.options && i < c.first+c.nvis; i++) {
4169			txt := "";
4170			if (c.start < len c.options[i].display)
4171				txt = c.options[i].display[c.start:];
4172			if(c.options[i].selected) {
4173				r := Rect((p.x-SELMARGIN,p.y),(c.r.max.x-SCRFBREADTH,p.y+fnt.height));
4174				win.draw(r, navy, nil, zp);
4175				win.text(p, white, zp, fnt, txt);
4176			}
4177			else {
4178 				win.text(p, black, zp, fnt, txt);
4179			}
4180			p.y +=fnt.height;
4181		}
4182		if(c.vscr != nil) {
4183			c.vscr.r = c.vscr.r.subpt(c.vscr.r.min);
4184			c.vscr.r = c.vscr.r.addpt(Point(insetr.max.x-SCRFBREADTH,insetr.min.y));
4185			c.vscr.draw(0);
4186 		}
4187 		if(c.hscr != nil) {
4188			c.hscr.r = c.hscr.r.subpt(c.hscr.r.min);
4189			c.hscr.r = c.hscr.r.addpt(Point(insetr.min.x, insetr.max.y-SCRFBREADTH));
4190 			c.hscr.draw(0);
4191		}
4192		drawrelief(win, insetr, ReliefSunk);
4193
4194	Cscrollbar =>
4195		# Scrollbar components: arrow 1 (a1), trough 1 (t1), slider (s), trough 2 (t2), arrow 2 (a2)
4196		x := c.r.min.x;
4197		y := c.r.min.y;
4198		ra1, rt1, rs, rt2, ra2: Rect;
4199		b, l, a1kind, a2kind: int;
4200		if(c.flags&CFscrvert) {
4201			l = c.r.max.y - c.r.min.y;
4202			b = c.r.max.x - c.r.min.x;
4203			xr := x+b;
4204			yt1 := y+b;
4205			ys := yt1+c.top;
4206			yb := y+l;
4207			ya2 := yb-b;
4208			yt2 := ya2-c.bot;
4209			ra1 = Rect(Point(x,y),Point(xr,yt1));
4210			rt1 = Rect(Point(x,yt1),Point(xr,ys));
4211			rs = Rect(Point(x,ys),Point(xr,yt2));
4212			rt2 = Rect(Point(x,yt2),Point(xr,ya2));
4213			ra2 = Rect(Point(x,ya2),Point(xr,yb));
4214			a1kind = TRIup;
4215			a2kind = TRIdown;
4216		}
4217		else {
4218			l = c.r.max.x - c.r.min.x;
4219			b = c.r.max.y - c.r.min.y;
4220			yb := y+b;
4221			xt1 := x+b;
4222			xs := xt1+c.top;
4223			xr := x+l;
4224			xa2 := xr-b;
4225			xt2 := xa2-c.bot;
4226			ra1 = Rect(Point(x,y),Point(xt1,yb));
4227			rt1 = Rect(Point(xt1,y),Point(xs,yb));
4228			rs = Rect(Point(xs,y),Point(xt2,yb));
4229			rt2 = Rect(Point(xt2,y),Point(xa2,yb));
4230			ra2 = Rect(Point(xa2,y),Point(xr,yb));
4231			a1kind = TRIleft;
4232			a2kind = TRIright;
4233		}
4234		a1relief := ReliefRaised;
4235		if(c.flags&CFscracta1)
4236			a1relief = ReliefSunk;
4237		a2relief := ReliefRaised;
4238		if(c.flags&CFscracta2)
4239			a2relief = ReliefSunk;
4240		drawtriangle(win, ra1, a1kind, a1relief);
4241		drawtriangle(win, ra2, a2kind, a2relief);
4242		drawfill(win, rt1, Grey);
4243		rs = rs.inset(2);
4244		drawfill(win, rs, Grey);
4245		rsrelief := ReliefRaised;
4246		if(c.flags&CFactive)
4247			rsrelief = ReliefSunk;
4248		drawrelief(win, rs, rsrelief);
4249		drawfill(win, rt2, Grey);
4250	Canimimage =>
4251		i := c.cur;
4252		if(c.redraw)
4253			i = 0;
4254		else if(i > 0) {
4255			iprev := i-1;
4256			if(c.cim.mims[iprev].bgcolor != -1) {
4257				i = iprev;
4258				# get i back to before all "reset to previous"
4259				# images (which will be skipped in following
4260				# image drawing loop)
4261				while(i > 0 && c.cim.mims[i].bgcolor == -2)
4262					i--;
4263			}
4264		}
4265		bgi := colorimage(c.bg.color);
4266		if(c.bg.image != nil && c.bg.image.ci != nil && len c.bg.image.ci.mims > 0)
4267			bgi = c.bg.image.ci.mims[0].im;
4268		for( ; i <= c.cur; i++) {
4269			mim := c.cim.mims[i];
4270			if(i > 0 && i < c.cur && mim.bgcolor == -2)
4271				continue;
4272			p := c.r.min.add(mim.origin);
4273			r := mim.im.r;
4274			r = Rect(p, p.add(Point(r.dx(), r.dy())));
4275
4276			# IE takes "clear-to-background" disposal method to mean
4277			# clear to background of HTML page, ignoring any background
4278			# color specified in the GIF.
4279			# IE clears to background before frame 0
4280			if(i == 0)
4281				win.draw(c.r, bgi, nil, zp);
4282
4283			if(i != c.cur && mim.bgcolor >= 0)
4284				win.draw(r, bgi, nil, zp);
4285			else
4286				win.draw(r, mim.im, mim.mask, zp);
4287		}
4288	Clabel =>
4289		p := c.r.min.add(Point(0,ENTVMARGIN));
4290		win.text(p, colorimage(Black), zp, fonts[DefFnt].f, c.s);
4291	}
4292	if(flush) {
4293		if (ctl.popup != nil)
4294			ctl.popup.flush(ctl.r);
4295		else
4296			G->flush(ctl.r);
4297	}
4298	win.clipr = oclipr;
4299}
4300
4301# Break s up into substrings that fit in width availw
4302# when printing with font fnt.
4303# The second returned array contains the indexes into the original
4304# string where the corresponding line starts (which might not be simply
4305# the sum of the preceding lines because of cr/lf's in the original string
4306# which are omitted from the lines array.
4307# Empty lines (ending in cr) get put into the array as empty strings.
4308# The start indices array has an entry for the phantom next line, to avoid
4309# the need for special cases in the rest of the code.
4310wrapstring(fnt: ref Font, s: string, availw: int) : (array of string, array of int)
4311{
4312	sl : list of (string, int) = nil;
4313	sw := fnt.width(s);
4314	n := 0;
4315	k := 0;	# index into original s where current s starts
4316	origlen := len s;
4317	done := 0;
4318	while(!done) {
4319		kincr := 0;
4320		s1, s2: string;
4321		if(s == "") {
4322			s1 = s;
4323			done = 1;
4324		}
4325		else {
4326			# if any newlines in s1, it's a forced break
4327			# (and newlines aren't to appear in result)
4328			(s1, s2) = S->splitl(s, "\n");
4329			if(s2 != nil && fnt.width(s1) <= availw) {
4330				s = s2[1:];
4331				sw = fnt.width(s);
4332				kincr = (len s1) + 1;
4333			}
4334			else if(sw <= availw) {
4335				s1 = s;
4336				done = 1;
4337			}
4338			else {
4339				(s1, nil, s, sw) = breakstring(s, sw, fnt, availw, 0);
4340				kincr = len s1;
4341				if(s == "")
4342					done = 1;
4343			}
4344		}
4345		sl = (s1, k) :: sl;
4346		k += kincr;
4347		n++;
4348	}
4349	# reverse sl back to original order
4350	lines := array[n] of string;
4351	linestarts := array[n+1] of int;
4352	linestarts[n] = origlen;
4353	while(sl != nil) {
4354		(ss, nn) := hd sl;
4355		lines[--n] = ss;
4356		linestarts[n] = nn;
4357		sl = tl sl;
4358	}
4359	return (lines, linestarts);
4360}
4361
4362normalsel(sel : (int, int)) : (int, int)
4363{
4364	(s, e) := sel;
4365	if (s > e)
4366		(e, s) = sel;
4367	return (s, e);
4368}
4369
4370selsegs(n, s, e : int) : list of (int, int, int)
4371{
4372	if (e < 0 || s > n)
4373		# selection is not in 0..n
4374		return (0, n, 0) :: nil;
4375
4376	if (e > n) {
4377		# second half of string is selected
4378		if (s <= 0)
4379			return (0, n, 1) :: nil;
4380		return (0, s, 0) :: (s, n, 1) :: nil;
4381	}
4382
4383	if (s < 0) {
4384		# first half of string is selected
4385		if (e >= n)
4386			return (0, n, 1) :: nil;
4387		return (0, e, 1) :: (e, n, 0) :: nil;
4388	}
4389	# middle section of string is selected
4390	return (0, s, 0) :: (s, e, 1) :: (e, n, 0) :: nil;
4391}
4392
4393# Figure out in which area of scrollbar, if any, p lies.
4394# Then use p and mtype from mouse event to return desired action.
4395Control.entryset(c: self ref Control, s: string)
4396{
4397	pick e := c {
4398	Centry =>
4399		e.s = s;
4400		e.sel = (0, 0);
4401		e.left = 0;
4402		# calculate scroll bar settings
4403		if (e.linewrap && e.scr != nil) {
4404			(lines, nil, nil, nil) := entrywrapcalc(e);
4405			nlines := len lines;
4406			ny := (e.r.dy() - 2 * ENTVMARGIN)/ctllinespace;
4407			e.scr.scrollset(0, ny, (nlines - 1), nlines, 0);
4408		}
4409	}
4410}
4411
4412entryupdown(e: ref Control.Centry, cur : int, delta : int) : int
4413{
4414	e.sel = (cur, cur);
4415	(lines, linestarts, topline, cursline) := entrywrapcalc(e);
4416	newl := cursline + delta;
4417	if (newl < 0 || newl >= len lines)
4418		return cur;
4419
4420	fnt := fonts[CtlFnt].f;
4421	x := cur - linestarts[cursline];
4422	w := fnt.width(lines[cursline][0:x]);
4423	l := lines[newl];
4424	if (len l == 0)
4425		return linestarts[newl];
4426	prevw := fnt.width(l);
4427	curw := prevw;
4428	for (ix := len l - 1; ix > 0 ; ix--) {
4429		prevw = curw;
4430		curw = fnt.width(l[:ix]);
4431		if (curw < w)
4432			break;
4433	}
4434	# decide on closest (curw <= w <= prevw)
4435	if (prevw-w <= w - curw)
4436		# closer to rhs
4437		ix++;
4438	return linestarts[newl]+ix;
4439}
4440
4441# delete given range of characters, and redraw
4442entrydelrange(e: ref Control.Centry, istart, iend: int)
4443{
4444	n := iend - istart;
4445	(sels, sele) := normalsel(e.sel);
4446	if(n > 0) {
4447		e.s = e.s[0:istart] + e.s[iend:];
4448
4449		if(sels > istart) {
4450			if(sels < iend)
4451				sels = istart;
4452			else
4453				sels -= n;
4454		}
4455		if (sele > istart) {
4456			if (sele < iend)
4457				sele = istart;
4458			else
4459				sele -= n;
4460		}
4461
4462		if(e.left > istart)
4463			e.left = max(istart-1, 0);
4464		e.sel = (sels, sele);
4465		entryscroll(e);
4466	}
4467}
4468
4469snarf : string;
4470entrysetsnarf(e: ref Control.Centry)
4471{
4472	if (e.s == nil)
4473		return;
4474	s := e.s;
4475	(sels, sele) := normalsel(e.sel);
4476	if (sels != sele)
4477		s = e.s[sels:sele];
4478
4479	f := sys->open("/chan/snarf", sys->OWRITE);
4480	if (f == nil)
4481		snarf = s;
4482	else {
4483		data := array of byte s;
4484		sys->write(f, data, len data);
4485	}
4486}
4487
4488entryinsertsnarf(e: ref Control.Centry)
4489{
4490	f := sys->open("/chan/snarf", sys->OREAD);
4491	if(f != nil) {
4492		buf := array[sys->ATOMICIO] of byte;
4493		n := sys->read(f, buf, len buf);
4494		if(n > 0) {
4495			# trim a trailing newline, as a service...
4496			if(buf[n-1] == byte '\n')
4497				n--;
4498		}
4499		snarf = "";
4500		if (n > 0)
4501			snarf = string buf[:n];
4502	}
4503
4504	if (snarf != nil) {
4505		(sels, sele) := normalsel(e.sel);
4506		if (sels != sele) {
4507			entrydelrange(e, sels, sele);
4508			(sels, sele) = e.sel;
4509		}
4510		lhs, rhs : string;
4511		if (sels > 0)
4512			lhs = e.s[:sels];
4513		if (sels < len e.s)
4514			rhs  = e.s[sels:];
4515		e.entryset(lhs + snarf + rhs);
4516		e.sel = (len lhs, len lhs + len snarf);
4517	}
4518}
4519
4520# make sure can see cursor and following char or two
4521entryscroll(e: ref Control.Centry)
4522{
4523	s := e.s;
4524	slen := len s;
4525	if(e.flags&CFsecure) {
4526		for(i := 0; i < slen; i++)
4527			s[i] = '*';
4528	}
4529	if(e.linewrap) {
4530		# For multiple line entries, c.left is the char
4531		# at the beginning of the topmost visible line,
4532		# and we just want to scroll to make sure that
4533		# the line with the cursor is visible
4534		(lines, linestarts, topline, cursline) := entrywrapcalc(e);
4535		vislines := (e.r.dy()-2*ENTVMARGIN) / ctllinespace;
4536		nlines := len linestarts;
4537		if(cursline < topline)
4538			topline = cursline;
4539		else {
4540			if(cursline >= topline+vislines)
4541				topline = cursline-vislines+1;
4542			if (topline + vislines >= nlines)
4543				topline = max(0, (nlines-1) - vislines);
4544		}
4545		e.left = linestarts[topline];
4546		if (e.scr != nil)
4547			e.scr.scrollset(topline, topline+vislines, nlines-1, nlines, 1);
4548	}
4549	else {
4550		(nil, sele) := e.sel;
4551		# sele is always the drag point
4552		if(sele < e.left)
4553			e.left = sele;
4554		else if(sele > e.left) {
4555			fnt := fonts[CtlFnt].f;
4556			wantw := e.r.dx() -2*ENTHMARGIN; # - 2*ctlspspace;
4557			while(e.left < sele-1) {
4558				w := fnt.width(e.s[e.left:sele]);
4559				if(w < wantw)
4560					break;
4561				e.left++;
4562			}
4563		}
4564	}
4565}
4566
4567# Given e, a Centry with line wrapping,
4568# return (wrapped lines, line start indices, line# of top displayed line, line# containing cursor).
4569entrywrapcalc(e: ref Control.Centry) : (array of string, array of int, int, int)
4570{
4571	s := e.s;
4572	if(e.flags&CFsecure) {
4573		for(i := 0; i < len s; i++)
4574			s[i] = '*';
4575	}
4576	(nil, sele) := e.sel;
4577	textw := e.r.dx()-2*ENTHMARGIN;
4578	if (e.scr != nil)
4579		textw -= SCRFBREADTH;
4580	(lines, linestarts) := wrapstring(fonts[CtlFnt].f, s, textw);
4581	topline := 0;
4582	cursline := 0;
4583	for(i := 0; i < len lines; i++) {
4584		s = lines[i];
4585		i1 := linestarts[i];
4586		i2 := linestarts[i+1];
4587		if(e.left >= i1 && e.left < i2)
4588			topline = i;
4589		if(sele >= i1 && sele < i2)
4590			cursline = i;
4591	}
4592	if(sele == linestarts[len lines])
4593		cursline = len lines - 1;
4594	return (lines, linestarts, topline, cursline);
4595}
4596
4597Lay.new(targwidth: int, just: byte, margin: int, bg: Background) : ref Lay
4598{
4599	ans := ref Lay(Line.new(), Line.new(),
4600			targwidth, 0, 0, margin, nil, bg, just, byte 0);
4601	if(ans.targetwidth < 0)
4602		ans.targetwidth = 0;
4603	ans.start.pos = Point(margin, margin);
4604	ans.start.next = ans.end;
4605	ans.end.prev = ans.start;
4606	# dummy item at end so ans.end will have correct y coord
4607	it := Item.newspacer(ISPnull, 0);
4608	it.state = IFbrk|IFcleft|IFcright;
4609	ans.end.items = it;
4610	return ans;
4611}
4612
4613Line.new() : ref Line
4614{
4615	return ref Line(
4616			nil, nil, nil,	# items, next, prev
4617			zp,		# pos
4618			0, 0, 0,	# width, height, ascent
4619			byte 0);	# flags
4620}
4621
4622Loc.new() : ref Loc
4623{
4624	return ref Loc(array[10] of Locelem, 0, zp);	# le, n, pos
4625}
4626
4627Loc.add(loc: self ref Loc, kind: int, pos: Point)
4628{
4629	if(loc.n == len loc.le) {
4630		newa := array[len loc.le + 10] of Locelem;
4631		newa[0:] = loc.le;
4632		loc.le = newa;
4633	}
4634	loc.le[loc.n].kind = kind;
4635	loc.le[loc.n].pos = pos;
4636	loc.n++;
4637}
4638
4639# return last frame in loc's path
4640Loc.lastframe(loc: self ref Loc) : ref Frame
4641{
4642	if (loc == nil)
4643		return nil;
4644	for(i := loc.n-1; i >=0; i--)
4645		if(loc.le[i].kind == LEframe)
4646			return loc.le[i].frame;
4647	return nil;
4648}
4649
4650Loc.print(loc: self ref Loc, msg: string)
4651{
4652	sys->print("%s: Loc with %d components, pos=(%d,%d)\n", msg, loc.n, loc.pos.x, loc.pos.y);
4653	for(i := 0; i < loc.n; i++) {
4654		case loc.le[i].kind {
4655		LEframe =>
4656			sys->print("frame %x\n",  loc.le[i].frame);
4657		LEline =>
4658			sys->print("line %x\n", loc.le[i].line);
4659		LEitem =>
4660			sys->print("item: %x", loc.le[i].item);
4661			loc.le[i].item.print();
4662		LEtablecell =>
4663			sys->print("tablecell: %x, cellid=%d\n", loc.le[i].tcell, loc.le[i].tcell.cellid);
4664		LEcontrol =>
4665			sys->print("control %x\n", loc.le[i].control);
4666		}
4667	}
4668}
4669
4670Sources.new(m : ref Source) : ref Sources
4671{
4672	srcs := ref Sources;
4673	srcs.main = m;
4674	return srcs;
4675}
4676
4677Sources.add(srcs: self ref Sources, s: ref Source, required: int)
4678{
4679	if (required) {
4680		CU->assert(srcs.reqd == nil);
4681		srcs.reqd = s;
4682	} else
4683		srcs.srcs = s :: srcs.srcs;
4684}
4685
4686Sources.done(srcs: self ref Sources, s: ref Source)
4687{
4688	if (s == srcs.main) {
4689		if (srcs.reqd != nil) {
4690			sys->print("FREEING MAIN WHEN REQD != nil\n");
4691			if (s.bs == nil)
4692				sys->print("s.bs == nil\n");
4693			else
4694				sys->print("main.eof = %d main.lim = %d, main.edata = %d\n", s.bs.eof, s.bs.lim, s.bs.edata);
4695		}
4696		srcs.main = nil;
4697	}
4698	else if (s == srcs.reqd)
4699		srcs.reqd = nil;
4700	else {
4701		new : list of ref Source;
4702		for (old := srcs.srcs; old != nil; old = tl old) {
4703			src := hd old;
4704			if (src == s)
4705				continue;
4706			new = src :: new;
4707		}
4708		srcs.srcs = new;
4709	}
4710}
4711
4712Sources.waitsrc(srcs: self ref Sources) : ref Source
4713{
4714	if (srcs == nil)
4715		return nil;
4716
4717	bsl : list of ref ByteSource;
4718
4719	if (srcs.reqd == nil && srcs.main != nil) {
4720		pick s := srcs.main {
4721		Shtml =>
4722			if (s.itsrc.toks != nil || s.itsrc.reqddata != nil)
4723				return s;
4724		}
4725	}
4726
4727	# always check for subordinates
4728	for (sl := srcs.srcs; sl != nil; sl = tl sl)
4729		bsl = (hd sl).bs :: bsl;
4730	# reqd is taken in preference to main source as main
4731	# cannot be processed until we have the whole of reqd
4732	if (srcs.reqd != nil)
4733		bsl = srcs.reqd.bs :: bsl;
4734	else if (srcs.main != nil)
4735		bsl = srcs.main.bs :: bsl;
4736	if (bsl == nil)
4737		return nil;
4738	bs : ref ByteSource;
4739	for (;;) {
4740		bs = CU->waitreq(bsl);
4741		if (srcs.reqd == nil || srcs.reqd.bs != bs)
4742			break;
4743		# only interested in reqd if we have got it all
4744		if (bs.err != "" || bs.eof)
4745			return srcs.reqd;
4746	}
4747	if (srcs.main != nil && srcs.main.bs == bs)
4748		return srcs.main;
4749	found : ref Source;
4750	for(sl = srcs.srcs; sl != nil; sl = tl sl) {
4751		s := hd sl;
4752		if(s.bs == bs) {
4753			found = s;
4754			break;
4755		}
4756	}
4757	CU->assert(found != nil);
4758	return found;
4759}
4760
4761# spawned to animate images in frame f
4762animproc(f: ref Frame)
4763{
4764	f.animpid = sys->pctl(0, nil);
4765	aits : list of ref Item = nil;
4766	# let del be millisecs to sleep before next frame change
4767	del := 10000000;
4768	d : int;
4769	for(il := f.doc.images; il != nil; il = tl il) {
4770		it := hd il;
4771		pick i := it {
4772		Iimage =>
4773			ms := i.ci.mims;
4774			if(len ms > 1) {
4775				loc := f.find(zp, it);
4776				if(loc == nil) {
4777					# could be background, I suppose -- don't animate it
4778					if(dbg)
4779						sys->print("couldn't find item for animated image\n");
4780					continue;
4781				}
4782				p := loc.le[loc.n-1].pos;
4783				p.x += int i.hspace + int i.border;
4784				# BUG: should get background from least enclosing layout
4785				ctl := Control.newanimimage(f, i.ci, f.layout.background);
4786				ctl.r = ctl.r.addpt(p);
4787				i.ctlid = f.addcontrol(ctl);
4788				d = ms[0].delay;
4789				if(dbg)
4790					sys->print("added anim ctl %d for image %s, initial delay %d\n",
4791						i.ctlid, i.ci.src.tostring(), d);
4792				aits = it :: aits;
4793				if(d < del)
4794					del = d;
4795			}
4796		}
4797	}
4798	if(aits == nil)
4799		return;
4800	tot := big 0;
4801	for(;;) {
4802		sys->sleep(del);
4803		tot = tot + big del;
4804		newdel := 10000000;
4805		for(al := aits; al != nil; al = tl al) {
4806			it := hd al;
4807			pick i := hd al {
4808			Iimage =>
4809				ms := i.ci.mims;
4810				pick c := f.controls[i.ctlid] {
4811				Canimimage =>
4812					m := ms[c.cur];
4813					d = m.delay;
4814					if(d > 0)
4815						d -= int (tot - c.ts);
4816					if(d == 0) {
4817						# advance to next frame and show it
4818						c.cur++;
4819						if(c.cur == len ms)
4820							c.cur = 0;
4821						d = ms[c.cur].delay;
4822						c.ts = tot;
4823						c.draw(1);
4824					}
4825					if(d < newdel)
4826						newdel = d;
4827				}
4828			}
4829		}
4830		del = newdel;
4831	}
4832}
4833