-- TexPage.mesa

-- last written by Doug Wyatt,  January 10, 1980  2:43 PM

DIRECTORY
	TexDebugDefs: FROM "TexDebugDefs",
	TexDefs: FROM "TexDefs",
	TexErrorDefs: FROM "TexErrorDefs",
	TexGlueDefs: FROM "TexGlueDefs",
	TexIODefs: FROM "TexIODefs",
	TexMainDefs: FROM "TexMainDefs",
	TexNodeDefs: FROM "TexNodeDefs",
	TexOutputDefs: FROM "TexOutputDefs",
	TexPackDefs: FROM "TexPackDefs",
	TexTableDefs: FROM "TexTableDefs",
	TexTokenDefs: FROM "TexTokenDefs",
	InlineDefs: FROM "InlineDefs";

TexPage: PROGRAM
IMPORTS TexDebugDefs,TexErrorDefs,TexGlueDefs,TexIODefs,TexMainDefs,
	TexNodeDefs,TexOutputDefs,TexPackDefs,TexTableDefs,TexTokenDefs,
	InlineDefs
EXPORTS TexMainDefs =
BEGIN OPEN TexMainDefs,TexNodeDefs,TexDefs;

pagelist: NodeListPtr←InitNodeList[];
contriblist: NodeListPtr←InitNodeList[];
waitinglist: NodeListPtr←NIL;

curbreak: NodePtr;
curbadness: Penalty;
pagesize: Dimn;
pagedepthmax: Dimn;
pagetopbl: Dimn;
pageheight: Dimn;
pagedepth: Dimn;
pageflex: TexGlueDefs.FlexSums;

topmark,firstmark: TexTokenDefs.TokenListPtr←NIL;
savedpage: BoxNodePtr←NIL;
savedpageno: INTEGER←0;
outputdormant: BOOLEAN ← TRUE;
deadcycles: CARDINAL←0;


AddToPage: PUBLIC PROCEDURE[list: NodeListPtr] =
	BEGIN
	-- Now the page builder is in operation; it continues to work until
	-- the contribution list has been emptied.
	-- Note: list's listhead is destroyed by this procedure!
	AppendNodeList[contriblist,list];
	UNTIL contriblist.link=NIL
		DO
		IF EmptyPage[] THEN InitPage[]; -- initialization of page building
		Contribute[];
		ENDLOOP;
	END;

penaltyLimit: Penalty=1000; -- a penalty>=penaltyLimit is considered infinite

Contribute: PROCEDURE = --INLINE--
	BEGIN
	p: NodePtr←contriblist.link;
	-- The page builder adds the first node of the contribution list to
	-- the current page, checking to see if it is a decent place to break,
	-- and/or calling EjectPage if the current page is full.
	AddHD: PROCEDURE[h,d: Dimn] =
		BEGIN
		pageheight←pageheight+pagedepth+h;
		IF d>pagedepthmax THEN
			BEGIN pageheight←pageheight+d-pagedepthmax; d←pagedepthmax END;
		pagedepth←d;
		END;

	WITH pp:p SELECT FROM
		box =>
			BEGIN
			AddHD[pp.height, pp.depth];
			IF EmptyPage[] AND pageheight<pagetopbl THEN
				BEGIN OPEN TexGlueDefs;
				-- Put glue at top of page to adjust first baseline
				q: GluePtr←InterLineGlue[pagetopbl-pageheight, CommonGlue[zero]];
				pageheight←pagetopbl;
				StoreNode[pagelist, MakeGlueNode[q]];
				END;
			END;
		rule => AddHD[pp.height, pp.depth];
		glue =>
			BEGIN OPEN TexGlueDefs;
			q: GluePtr;
			IF EmptyPage[] THEN GOTO Delete; -- delete glue at beginning of page
			WITH pagelist.last SELECT FROM
				box,rule => IF TestPageBreak[0] THEN GOTO Eject; -- make a page
				ENDCASE;
			q←pp.g; -- pointer to glue specification
			AddHD[q.space, 0]; SumFlex[q, @pageflex];
			END;
		penalty =>
			BEGIN
			n: Penalty;
			IF EmptyPage[] THEN GOTO Delete; -- delete penalty at beginning of page
			IF (n←pp.pts)<penaltyLimit AND TestPageBreak[n] THEN GOTO Eject;
			END;
		eject =>
			BEGIN
			IF EmptyPage[] THEN GOTO Delete;
			-- Non-null page should be ejected
			-- make sure there's a place to break
			IF curbreak=NIL THEN curbreak←pagelist.last;
			[]←TestPageBreak[-200]; -- *** check this
			GOTO Eject;
			END;
		ins =>
			BEGIN OPEN TexGlueDefs;
			q: GluePtr←pp.glue;
			tempflex: FlexSums←pageflex; -- remember current pageflex and
			tempheight: Dimn←pageheight; -- pageheight, in case we must back up
			shrink: Flex;
			epsilon: Dimn=1;
			pageheight←pageheight+q.space; SumFlex[q,@pageflex]; -- add the glue
			IF pp.dir=vlist AND NOT EmptyPage[]
			 AND (shrink←DominantFlex[@pageflex[shr]]).order=regular
			 AND (pageheight-shrink.val)>(pagesize+epsilon) THEN
				BEGIN -- it won't fit
				pageflex←tempflex; pageheight←tempheight; -- restore page state
				-- add insertion to waiting list
				IF waitinglist=NIL THEN waitinglist←InitNodeList[];
				StoreNode[waitinglist, RemoveNode[contriblist]];
				END
			ELSE
				BEGIN -- it will fit (this should be the common case)
				-- add insertion to page list
				StoreNode[pagelist, RemoveNode[contriblist]];
				IF pp.dir=vlist AND TestPageBreak[0] THEN GOTO Eject;
				END;
			RETURN; -- we've taken care of this node
			END;
		mark,leader => NULL;
		ENDCASE => ERROR TexErrorDefs.Confusion;
	-- the contribution is contributed
	StoreNode[pagelist, RemoveNode[contriblist]];
	EXITS
		Delete => DsNode[RemoveNode[contriblist]];
		Eject => EjectPage;
	END;

EmptyPage: PROCEDURE RETURNS[BOOLEAN] = INLINE
	BEGIN RETURN[pagelist.link=NIL] END;

InitPage: PROCEDURE =
	BEGIN OPEN TexTableDefs;
	-- Initialization of page building
	curbreak←NIL; curbadness←maxPenalty;
	pagesize←DimnParam[vsize];
	pagedepthmax←DimnParam[maxdepth];
	pagetopbl←DimnParam[topbaseline];
	pageheight←pagedepth←0;
	TexGlueDefs.ClearFlexSums[@pageflex];
	END;

EjectPage: PROCEDURE =
	BEGIN OPEN TexTokenDefs;
	-- Now curbreak specifies the best place to break the current page.
	-- We will break it there and ship it off to the output routine.
	page: BoxNodePtr; -- the page box produced by the output routine
	-- don't try to eject an empty page
	IF EmptyPage[] THEN ERROR TexErrorDefs.Confusion;
	-- break the page at curbreak and update contriblist
	BreakPage[curbreak];
	-- update topmark and botmark
	IF topmark#NIL THEN DelRCLink[topmark];
	IF (topmark←TexPackDefs.CurMark[])#NIL THEN AddRCLink[topmark];
	savedpage←TexPackDefs.VPackage[list: pagelist,
		desiredheight: pagesize, xpand: FALSE, page: TRUE];
	pagelist←InitNodeList[];
	savedpageno←TexTableDefs.Kount[0];
	IF TexTableDefs.CurTracing[].showpages THEN
		BEGIN OPEN TexIODefs;
		UseDisplay; Ps["Completed page "]; Wn[savedpageno]; Wc['.];
	-- *** fix this
--		Ps["Completed for page "]; Wn[savedpageno]; Wc[':];
--		TraceDump[savedpage];
		END;
	outputdormant←FALSE;
	page←RunOutputRoutine[];
	outputdormant←TRUE;
	IF savedpage#NIL THEN
		BEGIN
		TexErrorDefs.Error["\output routine didn't use \page"];
		DsNodeList[savedpage]; savedpage←NIL;
		END;
	EndPageOut[page];
	END;

BreakPage: PROCEDURE[tail: NodePtr] =
	BEGIN
	-- tail points to the last node to be included in the pagelist
	-- break the page there and prune unwanted nodes at the break
	-- update pagelist, contriblist, and waitinglist

	-- first join pagelist and contriblist (there may be
	-- nodes at the head of contriblist that we want to prune)
	InsertNodeList[contriblist,pagelist];
	-- split the combined list between tail and tail.link
	pagelist←RemoveNodeList[contriblist,tail];
	-- now prune unwanted nodes at the head of contriblist
	PruneNodesAtBreak[];
	-- and put any waiting insertions at the head of contriblist
	IF waitinglist#NIL THEN
		BEGIN InsertNodeList[contriblist,waitinglist]; waitinglist←NIL END;
	END;

PruneNodesAtBreak: PROCEDURE =
	BEGIN
	t: NodePtr;
	WHILE (t←contriblist.link)#NIL
		DO
		WITH tt:t SELECT FROM
			glue,penalty => DsNode[RemoveNode[contriblist]];
			eject => BEGIN DsNode[RemoveNode[contriblist]]; EXIT END;
			ENDCASE => EXIT;
		ENDLOOP;
	RETURN;
	END;

Page: PUBLIC PROCEDURE RETURNS[page: BoxNodePtr] =
	BEGIN
	OutputOnly;
	page←savedpage; savedpage←NIL; RETURN[page];
	END;

Mark: PUBLIC PROCEDURE[t: MarkType] RETURNS[TexTokenDefs.TokenListPtr] =
	BEGIN
	OutputOnly;
	SELECT t FROM
		top => RETURN[topmark];
		bot => RETURN[TexPackDefs.CurMark[]];
		first => RETURN[firstmark];
		ENDCASE => ERROR; -- bad TopBotType
	END;

OutputOnly: PROCEDURE =
	BEGIN
	IF outputdormant THEN
		BEGIN
		TexErrorDefs.Error["This is allowed only in output routines"];
		SIGNAL Continue;
		END;
	END;

EndPageOut: PROCEDURE[page: BoxNodePtr] =
	BEGIN OPEN TexIODefs;
	IF page=NIL THEN BEGIN deadcycles←deadcycles+1; RETURN END;
	UseDisplay;
	Ws[" ["]; Wn[savedpageno];
	TexDebugDefs.ShowMem[string: "before ShipOut"];
	TexDebugDefs.ShowNodeStatistics[page,"page"];
	TexOutputDefs.ShipOut[page]; deadcycles←0;
	DsNodeList[page];
	TexDebugDefs.ShowMem[string: "after ShipOut"];
	Ws["]"];
	END;

FlushOutput: PUBLIC PROCEDURE[maxdeadcycles: CARDINAL] =
	BEGIN
	DO
	IF EmptyPage[] THEN
		BEGIN OPEN TexTableDefs;
		dummypage: BoxNodePtr;
		IF deadcycles=0 OR deadcycles>maxdeadcycles THEN RETURN;
		dummypage←NullBox[]; -- dummy page
		dummypage.height←DimnParam[vsize];
		dummypage.width←DimnParam[hsize];
		StoreNode[pagelist,dummypage];
		END;
	AddToPage[MakeNodeList[MakeEjectNode[]]];
	-- The condition EmptyPage[] implies that the waiting and contribution
	-- lists are both empty. But if NOT EmptyPage[], we need to flush out
	-- everything that is still waiting to be output.
	ENDLOOP;
	END; 

TestPageBreak: PROCEDURE[penalt: Penalty] RETURNS[BOOLEAN] =
	BEGIN OPEN TexGlueDefs;
	delta: Dimn;
	dir: FlexDir;
	flex: Flex;
	badness: Penalty;
	LONGbadness: LONG INTEGER;
	IF EmptyPage[] THEN RETURN[FALSE];
	delta←pagesize-pageheight; -- positive if stretching needed
	dir←IF delta<0 THEN shr ELSE str;
	flex←DominantFlex[@pageflex[dir]];
	badness←GluePenalty[[dir: dir, order: flex.order,
			num: ABS[delta], den: flex.val]];
	IF badness=maxPenalty AND dir=shr THEN
		BEGIN -- can't shrink this much
		IF curbreak=NIL THEN curbreak←pagelist.last; -- in this case the
			-- page will be too long, but we took the first possible break
		RETURN[TRUE];
		END;
	LONGbadness←LONG[badness]+penalt;
	IF LONGbadness>maxPenalty
		THEN badness←maxPenalty
		ELSE badness←InlineDefs.LowHalf[LONGbadness];
	IF badness<=curbadness THEN
		BEGIN curbreak←pagelist.last; curbadness←badness END;
	RETURN[FALSE];
	END;

END.